Building Games with Flex: Tic-Tac-Toe V1 Code Explained Pt 2

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.mxmlClick here to play the game (right click to view/download the source).  In this post, I’ll breakdown the remaining piece.

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:

  1. 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 descriptive id values such as “TopRight”, “BottomLeft”, etc.  I’ll explain why later.
  2. “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
     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)">

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:

  1. It’s horribly hardcoded.  You must know every possible winning combination and hard code it.
  2. 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. 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.

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.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s