341
Remember that it’s the cloned layout array that we need to examine right now
because the layout property in state hasn’t been updated yet, so that’s why it gets passed
to anyMovesLeft(). Once there, the first task is to get a list of all free tiles. To do this, we
need to iterate through the entire layout – and remember it’s a three-dimensional array.
Thus, we have nested loops here – and for each, call the canTileBeSelected() function.
Now, if a
tile is determined to be free, and it’s a wildcard, then we automatically
know that the board has not dead-ended because a wildcard can match any other tile
type. And, because we start with an even number of tiles, and they only ever get removed
in pairs, that means that there must be at least two tiles left. Therefore, there is at least
one move left for sure and we can short-circuit this function by returning “yes” right
then and there.
Otherwise, the tile is pushed into the selectableTiles array for further scrutiny.
That scrutiny begins with a simple check:
if (numTiles === 0) {
return "cleared";
}
Obviously,
if there are no tiles at all, then there can be no more moves left! This case
means that the player just cleared the board, so we let the caller know with the return
value “cleared”. By the way, this should make it apparent now why this function can’t
return a simple boolean true or false: because there are actually three outcomes! There
could be a move left, there could be no moves left due to a dead end, or there could be
no moves left due to the board being cleared. The caller needs
to differentiate those
outcomes, so a string is returned instead.
At this point, we know that there are still selectable tiles, and we know that there are
no wildcards (i.e., no
selectable wildcards – there still could be some unselectable ones).
Therefore, the next trick is to see if there are any matches.
Your initial thought here might be that you would need to iterate over this array and
compare each element to every other element to find at least one match. And certainly,
that would work. But there is a more efficient approach:
const counts: number[] = [];
for (let i: number = 0; i < selectableTiles.length; i++) {
if (counts[selectableTiles[i]] === undefined) {
counts[selectableTiles[i]] = 1;
Chapter 11 time for fun: BattleJong, the Client
342
} else {
counts[selectableTiles[i]]++;
}
}
First, you count how many times each tile type occurs. This uses an associative array,
meaning that the tile type becomes the key of an array element. With that done, the
check to see if there are any matches left becomes very simple:
for (let i: number = 0; i < counts.length; i++) {
if (counts[i] >= 2) {
return "yes";
}
}
That’s it! If any element of the array has a value
greater than or equal to two, then
that means there is a match left to be made because, remember, we already checked that
these tiles are selectable.
If there are no such values found in the array, then the final possible return value is
returned:
return "no";
So, jumping back to the code in tileClick(), now that we know if there are any
moves left, we can act accordingly:
switch (anyMovesLeft) {
case "no":
gameState = "deadEnd";
this.state.socketComm.send(`done_${this.state.pid}`);
break;
case "cleared":
scores.player += 100;
gameState = "cleared";
this.state.socketComm.send(`match_${this.state.pid}_100`);
this.state.socketComm.send(`done_${this.state.pid}`);
break;
}
Chapter 11 time for fun: BattleJong, the Client
343
A return from anyMovesLeft() of “no” indicates a dead-ended board, so gameState is
transitioned
to that state, and the server is notified that this player is done. A return value
of “cleared” indicates the board was cleared, in which case we give them a point bonus,
in addition to transitioning gameState and telling the server that they are finished. Note
that we must send
two messages here because the server needs to know about the point
bonus too. There’s no special message for that, though; we simply tell the server that
another match occurred via the “match” message. It doesn’t
matter that one technically
hasn’t occurred. The server only needs the number of points to add here, so we can force
that message to do double duty.
Now, all that logic was for dealing with a pair of tiles being selected, but what
happens if this tile click event was the second tile of an unmatched pair? Well, that’s
where the else branch of the opening if statement comes in:
} else {
layout[selectedTiles[0].layer][selectedTiles[0].row]
[selectedTiles[0].column] = layout[selectedTiles[0].layer]
[selectedTiles[0].row]
[selectedTiles[0].column] - 1000;
layout[selectedTiles[1].layer][selectedTiles[1].row]
[selectedTiles[1].column] = layout[selectedTiles[1].layer]
[selectedTiles[1].row]
[selectedTiles[1].column] - 1000;
}
In this situation, all we need to do is revert the tile value of the two tiles to their
101–142 range, and we’re done.
Regardless of whether we just handled a matched pair or not,
both tiles would be
either cleared or de-selected now, so they need to be removed from selectedTiles:
selectedTiles = [ ];
Only one thing remains to be done, but it is absolutely key:
this.setState({
gameState : gameState,
layout : layout,
selectedTiles : selectedTiles,
Chapter 11 time for fun: BattleJong, the Client
344
scores : scores,
timeSinceLastMatch : timeSinceLastMatch
});
None of the code in tileClick() to this point will have altered state, but that needs
to occur lest nothing change on the screen! So, a quick call to setState() takes care of it.
I didn’t want to introduce any sort of conditional
updates here either; I figured it was
easier just to update everything that could have changed, whether it actually did or not.
And with that, our journey through BattleJong is complete!
Do'stlaringiz bilan baham: