Now that we do manage
field selection correctly,
we should finally implement our logic
for determining a winner.
And for this, let's quickly clarify
when you win this game.
Player one, if he or she has three items,
so three times his or her symbol next to each other,
for example, in a row,
in this pattern here,
player one would have won
because we have three Xs in a row.
The same would be true if you have
three items in a column.
But you also win if you have
three items next to each other diagonally.
And of course also from bottom left to top right.
So therefore we'll have to check for all these conditions.
And of course, we'll do that with help
of our little gameData array full of arrays,
which replicates the game field in JavaScript.
Now in game.js, I want to add a brand new function.
Let's say here, after startNewGame,
before I have switchPlayer and selectGameField,
which I will name checkForGameOver.
The exact function position
doesn't matter, by the way.
You could also move it to the end.
And actually, I will move it here
so that we have a logical flow.
So checkForGameOver should do what its name implies.
Whenever we call this function,
I want to check if one of our two players won.
And for this, we can now simply have a look
at the different combinations in our gameData array
that will make a winner.
And for that, we can start quite simple here.
We could check if gameData zero zero,
so the top left field,
is equal to one,
so player one owns the top left field.
And if gameData zero one,
which is the second item in the first row,
this here, that is zero, one,
if that is equal to one.
And if gameData zero two,
which is the last item here,
so this item, that's zero, two
because we access the first array
and then the third item,
if that is equal to one.
And with this if-check, we would know
that player one owns the entire first row.
So the entire first row
was selected by player one.
And therefore, we know that player one
did win in this case because player one
has three items in a row.
And in that case, we could say
that checkForGameOver should maybe return
either a string that says "Player one won"
or we just return the ID of the player who won,
in this case one.
And player two would have the ID two.
But if we do it like this,
if we try to write if-conditions
for all the possible combinations
that we could have,
then we are going to write a lot of code.
And we especially have a lot of unnecessary complexity here,
which we don't need.
For that, let me show you how we would write
the same condition for player two.
Now I'm checking whether player two
owns that first row.
So it's the same logic as here just with player two.
And then I return player two therefore.
Now that is some duplicate code here,
and it's only one of many chunks of code
that we have to write if we follow this pattern.
And it's also particularly unnecessary
to write it like this,
because the ID of the winner
is already stored in gameData.
So checking for the different IDs
in these two code blocks,
just to return the ID here like this then,
is unnecessary.
Instead, our first improvement here
would be that we don't check
whether this first field belongs to player one
and the second field belongs to player one,
to then return player one's ID.
But if we instead simply check
if the first field is owned by the same player
as the second field,
by saying gameData zero zero
is equal to gameData zero one.
And then I want to check if gameData zero one,
so that second field in the first row,
is equal to gameData zero two,
so the last field in the first row.
If I check it like this,
I don't know whether it belongs to player one or two,
but I know that all the fields in the first row
are owned by the same player
because they all have the same ID in it.
That's what I'm checking
by checking for equality here.
And then, since I'm interested in the ID of the winner,
I can simply return gameData zero zero,
because in gameData, in this array full of arrays,
we are storing player IDs.
Keep that in mind.
That's what we do in selectField.
I insert my player IDs into gameData.
So if I know that the entire first row
is owned by the same player,
I can return either field's data in that first row,
and I do return that player ID.
And that's a smarter way for checking
whether the entire row is owned by the same player,
to then return the ID of that player,
since that player will be the winner.
So this is an improvement.
But still this logic has a flaw here.
By just checking whether all the fields
in the first row are equal or not,
I am actually returning zero
as a winner here
because initially they are all equal,
they are all zero.
But zero should mean that the field
is not occupied at all.
So I don't just want to check for equality
in all these fields here.
I also want to make sure that the field values
are greater than zero
so that they are one or two
for our two different players.
And this can be achieved by, for example,
checking whether gameData zero zero
is greater than zero,
and then checking for all these equalities.
With that, we ensure that the overall condition here
is only true if all fields are equal
and they are not zero,
so they are one or two
or any other value higher than zero.
But in this application, in this game here,
we, of course, will only have one or two as our values.
But with this, if I reformat this
to make it a bit easier to read,
we now have a check here
that will ensure that we find out
whether the entire first row
is occupied by the same player.
And then we return the ID of that player.
So now with this logic,
we are checking our first row for equality.
So we check whether the first row
is owned by player one or two.
Now of course we have three rows, right,
here in our gameData in our code.
And of course also on the real game board,
we have three rows.
So of course we want to check all those rows.
And what we could do for this
is we could copy this if-statement
and now just increment
the first index that we access here
for the row by one to one.
And also do that here.
With that, we are accessing the second item
inside of gameData,
which simply is this second array in gameData.
And then we still go through all the values
in that second array.
That doesn't change.
So this logic here stays the same.
We still access the different fields
in that inner array,
but instead of checking the first inner array,
as we did up there,
we now check the second inner array,
which is our second row.
And then of course, if that is true,
if we make it into this if-block,
we want to return the player ID
stored in the first item in the second row.
Now that's how we could do it.
And of course, should not be a surprise,
we can repeat this for the last row
as well, like that.
But, if I zoom out here,
do you notice something in this code here?
In the end it's always the same code.
The only thing that differs
is the index that we access here at the beginning.
So the inner array that we access.
That differs.
Since that's the case,
this is a perfect scenario for a loop,
for a regular for-loop, to be precise,
because we can simply loop through the values
zero, one and two,
and then repeat this code
with dynamic access to different indexes
based on the current iteration.
Hence, I will actually remove these two code blocks
now that I showed you how we could do that.
And instead write a for-loop.
But now not a loop where I loop
through multiple items in an array,
but instead a regular for-loop
where I create a helper variable,
typically named i,
which starts at a value of zero.
Where I then loop as long as i is smaller than three
or is smaller and equal than two.
But here I'll go for a smaller three.
And then I want to increment i by one
after every iteration.
And you learned about this kind of for-loop
in one of the previous sections as well.
And then we can move this if-check
into our loop here and reformat,
and now just change the hard-coded zero here
in all these places for the i variable,
which will be different for every iteration
so that we dynamically access
different rows in our gameData.
That is what we can do here, like that.
And with that, we're still checking all rows
and we still determine whether one of these rows
then belongs to one and the same player,
but we do this with less code.
And that's our finished logic
for checking the rows
for equality.
So whether one of our three rows
belongs to the same player.
Now we also have to check the columns.
And for this, we can simply grab this for-loop,
copy and paste it,
and just adjust it a little bit
in our if-check condition here,
because generally it's the same logic.
Now we just want to check our three columns
instead of our three rows,
but still we have to loop through three items,
but we now access different values in here,
to be precise.
Now, with that,
I don't want to change my first index here.
Instead, that should always be zero,
like that.
But it's the second index that should change now.
So it's the second index,
which I'll now turn into a dynamic index here.
Like this.
And here we now should use gameData one,
gameData two.
Now what's the idea behind this logic?
What's happening here?
Well, here, in this place,
I'm always diving into the first row
because with gameData zero
I simply access the first item here
in my gameData array.
So I access this first inner array.
Then, because I have i here as a dynamic index,
this will be a value of zero, one or two,
depending on the iteration.
And hence, for this first row, which we access here,
I will access the different items in that row.
Now with that, I go through all the items
in the first row,
and then I compare that
with this code to all the items
in the second and third row,
because here we dive into the second row,
and here we dive into the third row.
And then we still access
all the different columns in there
since we still use the dynamic i index here.
And with that logic,
we basically just have the opposite
of what we're doing here.
Here, we are going through all the rows
and we keep the columns fixed.
Here, we instead go for all the columns
and we keep the rows fixed
because the row data here
is hard-coded and not dynamic.
The column data is, that's our i here,
the second index that we access.
And therefore with this logic here
we are checking the columns for equality.
Now, with that, we should be able
to determine whether we have a winner or not,
if a row or a column
is owned by the same player.
So if you have a row or a column
full of the same symbol.
But we also need to check our two diagonals,
top left to bottom right,
and bottom left to top right.
And for this, I'll add two additional if-checks,
where in the first if-check,
I use my gameData
and I simply check the top left field,
which has the indexes zero and zero,
and I check if it's equal
to the field in the middle,
which has the indexes one, one.
With one, one we access this second row,
so the second element in our main array,
and then with the second one,
we access the second item in that inner array,
and therefore the overall middle item
in our matrix here,
which we have in the end.
And therefore, I check if the top left field
is equal to the middle field,
and I then want to check if that middle field
is also equal to the bottom right field,
which is our first diagonal.
So here that is two, two,
because of course with indexes two and two,
I am going into the third row here,
and then there the third element,
so the bottom right element.
With that, I am checking this diagonal.
And of course, I also still want to check
that the value is not just equal,
but also greater than zero.
That's still important.
And therefore, this is now our check
for diagonal top left to bottom right.
And here, I then also want to return
the winner ID, which for example,
would be stored in gameData zero zero.
And now we can repeat that logic
and just adjust our indexes a little bit
by copying this,
to check for the diagonal
from the bottom left to top right.
So the other diagonal.
Here, bottom left is the third row
with index two, and then the first item,
and we need that here as well.
Then we still want to check the middle item, of course.
And then I want to check if the middle item
is equal to the top right item,
which is in the first row, hence index zero,
and then the last item there.
And then we can, for example,
return gameData two zero.
With that, we're checking the bottom left to top right.
And now we're checking all the different cases
that we need here to determine who won this game.
Now to verify whether that works,
and if we need to add more to it,
or if we have an error in there,
we should now call checkForGameOver,
and hence we'll then get back the ID of the winner,
or we don't get back anything
in case we have no winner.
If we make it into neither of these if-checks,
then we never return.
So in that case, we don't have a winner.
We could return zero in this case here
as a default value at the end,
to signal that we don't have a winner yet.
And we can then use this winner ID
to show a message to the user
or anything like that.
Actually, we do have our game over part here,
which we eventually want to show.
But before we show that, let's quickly verify
whether it works by just logging the result
to the console for now.
For this here in selectGameField,
I want to call checkForGameOver
because whenever a field was selected,
I want to check if we now have a winner,
because whenever we have a new field selected,
we could potentially have a winner.
So therefore here, before I switch my player,
I will call checkForGameOver
and I will get back my winner ID here.
Zero if we have no winner,
one if player one won,
and two if player two won.
And I will just console log the winner ID here.
Soon we will do more, but for the moment that's it.
With all of that, if we go back and reload,
open the console, let's test this.
If I make player one with the X
own the entire first row,
indeed here I get one.
Before I always got zero
as long as we didn't have a winner.
Now that the first row is occupied,
I get one.
Let's reload and also check
for the first column here.
And we also get one
as soon as the first column is occupied
by winner one.
Now I also want to be super safe
and check the other combinations.
Middle column works.
And let's now check the last column here.
That also works.
We get one as soon as it's selected.
Let's also check the other rows.
Middle row works.
Last row works.
And let's check the diagonals.
So here I'm going for top left to bottom right.
Seems to work.
I get one as soon as it's selected.
And bottom left to top right also works.
Now of course it will also work if player two wins.
Let's say we make player two own the middle column,
then we get two as a result
as soon as that's the case,
since the ID of the winner then is two.
And that should also work for the diagonal, of course.
Yeah.
So that's looking good.
But there is one case that we're not handling
and that's a draw.
Let's say we actually
do end up in a draw here.
And for that, let me just make sure
that I don't mess up here.
Yeah, like this.
In this case we have no winner.
And hence we only get zeros here.
We never get any our winner ID
because we don't have a winner.
Now, of course, we also want to handle this case
that the game is over
because no more fields can be selected,
but we don't have a winner.
And there are various ways of handling this case.
The easiest approach probably
is to keep track of the current round.
And whenever we select a field,
we increment this round by one.
And as soon as we reached round nine,
and that round is over
because we have nine fields in total,
then we know if no winner was detected
up to this point, we have a draw.
For this in our code, back in app.js,
we can add a new variable,
which we could name currentRound.
And initially that's one, of course,
because we start in round one.
In game.js, we can now use this currentRound variable,
which we just added.
In checkForGameOver,
here, after going through all the other if-checks,
I also want to check if currentRound
is equal to nine,
which means we exhausted all our rounds.
And in this case,
if we are here at the end of checkForGameOver,
which we only are if we have no other winner,
we know since we are in round nine,
there won't be a winner.
There is a draw.
And in that case, we could return minus one
as a value for a draw.
So we return zero if we still have rounds left,
and we simply don't have a winner yet,
but the game is also not over yet.
And we return minus one, if we have no winner,
but the game is over because we exhausted all rounds,
and then minus one signals that we'll have a draw.
So that's one way of handling this.
Now, of course, for this to work,
we also need to change currentRound
because it's one initially,
but it never changes at the moment.
And that means that whenever we switched a player
right before we do that, or after we did that,
we also want to set currentRound equal
to currentRound plus one.
And of course we can also shorten this
to just currentRound plus plus,
as you learned.
Both works.
And with that, we're changing the currentRound
after every iteration.
And now we should also handle that draw case.
So now with that, if I, again, check this
and I'll try not to win here,
so let me quickly do that.
Yeah.
Then I get minus one in the draw case.
So now we're also handling this
and therefore now with that,
we are now checking whether the game is over
or not successfully,
and we are determining the correct winner
or a draw.
And hence we got this logic in place
and therefore we are now almost done
with this demo website
and with this game here.
Now that we have a way of finding a winner,
we now just also want to make sure
that we output some information
about this winner on this screen
so that we show this game over article here,
and then tell the user who won here,
and that we then also handle
this Start New Game button
in case we had a game before
so that we then reset the game appropriately.
That's what we'll need to do next.