At this point, our game is fully usable. Users can add X
s and O
s and can see for themselves who has won or not. However, this is a computer game now and it should be programmed to determine who the winner is algorithmically.
Breaking down the problem of tic-tac-toe, one will realize that there are only 8 winning conditions in the game. We can create a handful of functions that will checkForWin
when a square is clicked. Once we have determined the winner, we can update the UI to announce that to the users.
Kyle Shevlin: [0:00] Our board accepts values, but it does not yet determine when a winner has taken place in our tic-tac-toe game. We need to create a way to checkForWin conditions on our grid every time a click is done.
[0:13] The way we're going to do this is we're going to insert some guard statements here. To do this, we need to understand a few things about our tic-tac-toe board. I'm going to come up here and I'm going to find a good spot to create some room.
[0:30] What we need to understand is that a win in tic-tac-toe occurs when any row contains the same value, any column contains the same value, or the diagonals contain all the same values.
[0:44] The simplest way to do this is to combine a simple checkThree function that will verify that a row has all the same values with a function that will operate on every row, every column, and the two diagonals.
[0:58] We'll start by making a checkThree function. The arguments to checkThree are just a, b, and c. They can be generic. In tic-tac-toe, specifically, we want to make sure that if any of them are null, we return false. There's no way a win can take place in those if there's nothing in them. If (! A || ! B || ! C) return false. That's a start of our checkThree.
[1:26] The second part of our checkThree is simply determining if a = b and b = c, then we have a winning condition. We have our checkThree function. Now what we can do is we can create our checkForWin function. This is going to receive our grid, but I'm going to make a slight change to it pretty soon.
[1:47] What we want to do is we want to call checkThree on all of the possible row, column, and diagonal combinations. How do I easily get each of these cells to make those combinations?
[2:02] I think the easiest way to do this, if I can array to structure these to look like a compass. Northwest, north, northeast, west, center, east, southwest, south, southeast. I'm just going to write that as is. You might recall that our grid is actually two dimensional, technically, this won't work yet.
[2:23] What I really want to do is I want to be able to pass a flattened grid in here. This will be useful for checking for draws later as well. In order to flatten the grid, we need a way to take our two-dimensional array and turn it into a single-dimensional array. If you're using ES2019, you can use Array.prototype.flat() to do this.
[2:46] If you're an environment that doesn't have that, you can use this simple trick. I will create a function called flatten that receives an array, and it will call reduce on that array, and we'll get the accumulator and the current item.
[2:59] What we'll do is we'll return a new array with the previous array spread into it and we'll spread any items in the current item. We'll start off with an empty array. We can call this on any array and pass that in. We'll call flatten on our grid before we pass it in a checkForWin and we'll trust that.
[3:19] Now from here, we need to make the eight checkThree() combinations. I will do that now. I'll add checkThree() and each of these will be an OR, because if any of these are true, we have a win. I will repeat this eight times and we'll pass in our combinations. We'll start with the rows, and then we'll do columns, and we'll do our two diagonals.
[3:45] Now, we can take our checkForWin function, and we can insert it here. We'll make our flatGrid, and if (checkForWin(flatGrid)), if we have a win, what we're going to do is return the nextState as it is. We don't want to update the turn but we do want to indicate somehow that our game is in a win state.
[4:09] I'm going to add a status key to our state and I will call this, 'success.' Having done that I need to come up to our InitialState and add status. I would simply call the game, inProgress, if it's not a successful game. Then I'm going to update our UI to make use of our status as well.
[4:30] I'm going to destructure status and turn off of our state. Then inside of here, I'm actually going to do a few things. Let's tell them whose turn it is. We can do that by simply doing, Next turn is {turn}. Then in the middle of all this, I'm going to add status = 'success,' I want you to return, {turn} won! Or return null.
[5:04] To style this up a little bit, I'm going to use Flexbox. I'm going to use flex, and I'm going to use space justifyContent, and space-between. Just to shrink this up a bit, we'll use display inline-block on the parent as well. Now that we have those in place, we can quickly see if it works. We'll have some x's. Looks like X won. We'll reset and we'll make sure O wins this time. Perfect.