In my last post, I explained my logic/thinking behind 2 of the 3 files that make up Tic-Tac-Toe V1: Main.mxml
and GamePiece.mxml
. Click here to play the game (right click to view/download the source). In this post, I’ll breakdown the remaining piece.
GameBoard.mxml
This piece is the real workhorse of the game. It houses not only the board where the pieces are laid out, but also the game logic itself.
Was that the best decision?
Probably not. If I wanted to swap out the game rules but keep the same pieces, I couldn’t do that. It’s not so common with Tic-Tac-Toe, but think of a card game. One deck of 52 cards can play an almost infinite number of games. Would it make much sense to put the Solitaire logic right inside the CardDeck
class file? Nope, it surely wouldn’t. I was in a hurry though so I did.
Let’s start off with the states. You’ll notice there’s only 2 states:
- The Base State – Here I have the the lines of the board drawn out. You’ll see that I went the cheap route regarding the grid of the game board. No fancy drawing APIs and no background image. Just four rules (2 vertical and 2 horizontal) make up the grid. You can see that I also manually lay out the 9 instances of the
GamePiece
class, giving them descriptiveid
values such as “TopRight”, “BottomLeft”, etc. I’ll explain why later. - “BlankSlate” – For this state, I pretty much remove the game pieces and the grid that “houses” them. In their place, I put a label up that gives the title of the game and a button that resets the board back to the base state.
Why is the BlankSlate not blank?
It was initially, hence the name. Then I thought, “Well, I need to tell the user what the name of the game is and let them start it.” So I added those items to the blankSlate
state. I should have either renamed the state or created a new state based on blankSlate
.
<mx:Canvas ...other code... currentState="blankSlate">
By default, I set the currentState
of the GameBoard
class to blankSlate
in the root Canvas tag.
<mx:Button x="134.5" y="216" label="Start Game" click="reset()"/>
If we take a look at the button we add in the blankState
, we see that I call the reset
function when clicked. Let’s take a look at that function.
public function reset():void
{
resetPieces();
lastPiece = "O";
winnerFound = false;
currentState = "";
}
It calls a function nameed resetPieces()
. That function just calls reset()
on the individual pieces to make sure they’re ready to be played. It preps the board by setting the properties back to values that we’ll see used later. It also sets the currentState
back to default, so that we get the board and pieces back on the stage.
<ns1:GamePiece x="4" y="4" id="TopLeft" click="pieceChosen(TopLeft)">
</ns1:GamePiece>
If you look at the pieces, you’ll see that there’s a click event handler. It calls the pieceChosen
function. Let’s take a look at that function.
public function pieceChosen(piece:GamePiece):void
In the declaration, we see that it expects a GamePiece
to be passed into it. Therefore, each piece passes in a reference to itself.
NOTE: We can’t use the this
keyword to pass in a reference to the GamePiece
instance. This is because in the MXML, the this
refers to the containing class (in this case, GameBoard
) and not the tag that has the handler code. Confusing? Try this. Look at the click
handler. You give it a function and that function is not in the GamePiece
class, but rather the parent class. Same thing with the this
keyord.
Let’s get back to the pieceChosen
function.
var message:String = new String();
We setup a message
variable to hold a message for the end. This will be used to tell the players who won or if there was a tie.
if (!piece.used && !winnerFound)
Our first statement checks to make sure the piece is playable and that no one has won the game yet.
if (lastPiece == "O")
{
piece.currentState = "X";
lastPiece = "X";
} else
{
piece.currentState = "O";
lastPiece = "O";
}
piece.used = true;
It sets the piece to the appropriate state based on the last piece played and resets the lastPiece
value. After that, it marks the piece
as being used
.
if (checkForWinner())
Next, it checks for a winner. This function is probably the ugliest of the code base for two reasons:
- It’s horribly hardcoded. You must know every possible winning combination and hard code it.
- It’s terribly inefficient. It checks every possible combination in the same order each time, even if pieces that aren’t part of the winning combination weren’t played this turn.
Let’s take a look at a little snippet from the checkForWinner
function.
if (TopLeft.currentState == lastPiece && MiddleLeft.currentState == lastPiece && BottomLeft.currentState == lastPiece)
As you can see, whether any of the -Left piece got played just now doesn’t really matter. It’s gonna check if those 3 pieces magically got turned on and won the game.
The logic inside the if statements isn’t so bad except the first line.
TopLeft.currentState = MiddleLeft.currentState = BottomLeft.currentState = lastPiece + "Winner";
Surely, there’s a function (let’s call it checkCombination
) that I can extract from that logic. Because if I change the name of the winning state, I’ll have to go through and modify it in 8 places. Regardless, this works for changing the winning pieces to their proper winning state.
winnerFound = true;
return true;
The two lines above our nice, but again we can detect the winnerFound
value in the checkCombination
function I propose above. We can then have that new function return our true/false value for us. (I’ll do that in the next iteration.)
If the checkForWinner
function comes back true, than we’re done.
message = "Player " + lastPiece + " won. ";
We set the message
to the proper winner in preparation for the announcement.
What if no winner was found though, should we just let the next player go?
At first, that’s what I did. Then it dawned on me, “What if that’s the last playable piece? It’s a cats game and I gotta let the players know.” Therefore, I created the checkForTie()
function. It checks for one thing only: Are all the pieces used?
if (TopLeft.used == true && MiddleLeft.used == true && BottomLeft.used == true && TopMiddle.used == true && MiddleMiddle.used == true && BottomMiddle.used == true && TopRight.used == true && MiddleRight.used == true && BottomRight.used == true)
Once again, not efficient and surely not pretty, but it gets the job done. I need to rewrite that so that it’s a nice iterative loop that inspects each piece programmatically vs via a hardcoded list. It then returns true
or false
depending on if all the pieces are used or not.
Back in the pieceChosen
function, we either continue playing the game or set the message to tie.
Alert.show( message + "Would you like to play again?", "Game Over", 3, this, playAgainHandler);
Lastly, if we haven’t moved on to the next turn, we pop up an alert. Here we display the message and ask if they want to play again. The playAgainHandler
takes care of their choice.
reset();
if (event.detail==Alert.NO)
{
currentState = "blankSlate";
}
We reset
the board in either case. Then if they don’t want to play anymore, we return the board to the blankSlate
state.
That’s it! There’s the entire code for version 1 of my Tic-Tac-Toe game. It’s time to create Version 2! Stay tuned! It’ll mainly be optimizations to the code base vs a new look and feel. The latter won’t likely come until version 3.