Don’t feel that you have failed a class! FreeCodeCamp is very different from a traditional class in that you really are learning on your own and coding projects from scratch.
I would suggest that you start over with a clean slate. If your code is well organized and you have your variables set up and know what to do, you can accomplish this much more easily.
Here is what I did. First, I figured out how to represent a game state as a string of numbers. The numbers corresponded to the moves the players made. I represented the squares on the board with a magic square:
2 7 6
9 5 1
4 3 8
which makes it easier to test for a won game (more on that later). So, if X goes first, then X picking the top left corner, O picking the center, and then X picking the top right corner, the state of the game would be represented by “256”. If X picks the center left and then O picks the bottom right, the state of the game would be “98”. If nobody has moved, the state is “”.
Now on to the actual evaluations. An evaluation is an evaluation of the position for the player that just moved. The evaluation represents the outcome with best play from both sides from here on. If the position is an eventual forced win (or an actual win right there), the evaluation is positive: 10 minus the total number of turns for the eventual win. If a win cannot be forced and the opponent cannot force a win, the evaluation is 0. If a win cannot be forced but the opponent can force a win, the evaluation is negative: -10 plus the total number of turns for the eventual loss. We worry about the number of turns because we want the computer to force a win as quickly as possible, if it can in fact force a win.
So, the evaluation of “2651” is -5 because the second player just allowed the first player to win by playing 8, which is a win in 5 moves. Remember that the evaluation is for the player that had just moved.
To calculate an evaluation, first see if the position is an immediate win or draw for the player that just moved. One can check for a win by seeing if any triplet of numbers for the player who just moved add up to 15. This is because of the magic square property of the number labeling. One can check for a draw if the string has length 9 i.e. all moves have been played. If there is no immediate win or draw, then we use recursion.
This is where minimax comes in. We want to take our string i.e. “45” and try every possibility for the next move: “451”, “452”, “453”, “456”, “457”, “458”, “459”. We evaluate recursively all of these positions. Then, we select the minimum of the negatives of these evaluations. We select the minimum of the negatives because that would be the best move for player 1, and so player 2, who just played 5 in “45”, has to take into account player 1’s best possible response. A win for player 1 is a loss for player 2, and vice-versa.
Now that positional evaluation is taken care of, the last requirement is to be able to make moves. This means turning a string “3” into a string “35”. I.e. if player 1 moved by selecting number 3, then the computer (player 2) should respond by selecting number 5. In my program, a move is chosen at random amongst the possible moves that have the negative of the same positional evaluation as the previous position, so that the computer ensures it’s making one of the best possible choices. This is a nice feature, so that the computer doesn’t play the same way every time, yet still plays perfectly.
My codepen with just the tic-tac-toe engine is here. It shouldn’t require many lines of code: mine took about 60, and generally speaking, it shouldn’t take more than 100…
Let me know if you have any questions.