New! Unlimited audio, video & web asset downloads! Unlimited audio, video & web assets! From \$16.50/m

# Creating a Game with Bonjour - Game Logic

Difficulty:IntermediateLength:LongLanguages:

In the previous articles, we predominantly focused on the network aspect of the game. In this final installment, it is time to zoom in on the game itself. We will implement the game and leverage the foundation we laid in the previous articles to create a multiplayer game.

## Introduction

In this article, we will discuss two topics, (1) creating the game and (2) leveraging the foundation that we created in the previous articles. In the previous article, a bug has found its way into our project. I must admit that it took me quite some time to discover this nasty, little creature. Don't worry, though. We will squash this bug as we go and I will show you exactly where it is causing havoc. Even though I could have updated the previous article to get rid of the bug, I prefer to show you how to find and fix the bug as it will help understand how the CocoaAsyncSocket library works. We have quite a bit of work ahead of us so let's get started.

## 1. Updating the User Interface

Let me start this article by briefly talking about the game, Four in a Row. If you haven't heard of Four in a Row, then I suggest you pay Wikipedia a visit. By the way, Four in a Row is known by many names, such as as Connect Four, Find Four, and Plot Four. The concept is simple. We have a board or grid with seven columns each containing six cells. The user can tap a column to add a disc to that column. Each time a player adds a disc to a column, we invoke a method to check if the player has won the game, that is, four discs in a row. Rows can be horizontal, vertical, or diagonal.

This implies that we need to keep track of quite a few variables. To keep track of the state of the game, we create a data structure, an array of arrays, mirroring the board or grid of cells. Each array in the array represents a column. Whenever a player adds a disc to a column, we update the data structure that backs the game and check whether the player has won the game.

I am not an expert in game development and the approach we use in this project is not the only solution to implement Four in a Row. It probably isn't the most efficient implementation either. However, by using well known Objective-C patterns and sticking to Foundation classes, most of you should be able to keep pace without much difficulty.

While exploring Four in a Row, I stumbled upon a Stack Overflow answer that outlines an algorithm for Four in a Row using bitboards. This is a very efficient and fast solution so if you are serious about board games, such as tic-tac-toe or chess, then I recommend exploring bitboards in more detail.

As I said, we will be using an array of arrays as the data structure of the game. The board itself will be a simple view with 42 subviews or board cells. Each subview or board cell corresponds to a position in the data structure. Because we need an easy way to keep a reference to each board cell, we manage a second data structure, another array of arrays, to store a reference to each board cell. This makes it easy to update the board view, but it also has some other benefits that will become evident a bit later in this tutorial.

### Step 1: Adding the Board View

Let's start by creating the board view. Open MTViewController.xib, add a `UIView` instance to the view controller's view and set its dimensions to 280 points by 240 points (figure 1). Modify the constraints of the view in such a way that the board view has a fixed width and height. The board view should also be horizontally and vertically centered in the view controller's view. Autolayout makes this a breeze.

Create an outlet in MTViewController.h for the board view and name it `boardView`. In Interface Builder, connect the outlet to the board view. We will add the board view's subviews programmatically.

### Step 2: Adding a Replay Button

When the game ends, we want to give the player the opportunity to start a new game. Add a new button to the view controller's view and give it a title of Replay (figure 2). Create an outlet, `replayButton`, for the button in MTViewController.h and an action named `replay:` in MTViewController.m. Connect the outlet and action to the replay button in Interface Builder (figure 2).

### Step 3: Adding a State Label

The player of the game should be informed about the state of the game. Whose turn is it? Who has won the game? We add a label to the view controller's view and update it whenever the game state changes. Revisit MTViewController.xib and add a label (`UILabel`) to the view controller's view (figure 3). Create an outlet for the label in the view controller's header file, name it `gameStateLabel`, and connect it to the label in Interface Builder (figure 3).

## 2. Laying Out the Board

### Step 1: Creating the Board Cell Class

As I mentioned earlier, the board view contains 42 subviews or board cells. We will create a `UIView` subclass to make each board cell a bit smarter and easier to use. Create a `UIView` subclass and name it `MTBoardCell` (figure 4). The `MTBoardCell` class has one property, `cellType` of type `MTBoardCellType`, which is declared at the top of the header file.

In the designated initializer, we set `cellType` to `MTBoardCellTypeEmpty` to mark the board cell as empty. In the implementation file of the class, we also override the setter of `cellType`. In `setCellType:`, we update the view by invoking `updateView`, a helper method in which we update the view's background color.

### Step 2: Setting Up a Game

To set up a new game, we invoke the main view controller's `resetGame` method. We will invoke `resetGame` in various places in our project. One of those places is the view controller's `viewDidLoad` method. Because I prefer to keep the `viewDidLoad` method concise, I generally move the view's setup logic to a separate `setupView` helper method which is invoked in `viewDidLoad`. In `setupView`, we also hide all the view's subviews with the exception of the host and join button.

Before we can implement `resetGame`, we need to create the data structure that stores the game state and the data structure that stores the references to the board cells of the board view. Add a class extension at the top of MTViewController.h and create two properties, `board` (`NSArray`) and `matrix` (`NSMutableArray`). We also import the header file of `MTBoardCell` and define to constants, `kMTMatrixWidth` and `kMTMatrixHeight`, that store the dimensions of the board.

The implementation of `resetGame` isn't rocket science as you can see below. Because `resetGame` will also be invoked when a player taps the replay button, the implementation starts with hiding the replay button. We calculate the size of a board cell, create a mutable array for each column of the board, and add six board cells to each column. This array of arrays is stored in the class's `board` property as an immutable array. The class's `matrix` property is very similar. It also stores an array of arrays. The main differences are that (1) the columns contain no objects when the game is reset and (2) each column is an instance of `NSMutableArray`.

### Step 1: Adding a Gesture Recognizer

Adding interaction to the game is as simple as adding a tap gesture recognizer to the board view in the view controller's `setupView` method. Each time a player taps the board view, the `addDiscToColumn:` message is sent to our `MTViewController` instance.

Before we implement `addDiscToColumn:`, we need to take a detour and talk about the game state. The `MTViewController` class needs to keep track of the state of the game. By game state, I don't refer to the data structures (`board` and `matrix`) that we created earlier. I simply mean a property that keeps tracks of whose turn it is and whether a player has won the game. To make things easier, it is a good idea to declare a custom type for the game state. Because we will use this custom type in various places in our project, it is best to declare it in a separate file, MTConstants.h, and add an import statement for MTConstants.h to the project's precompiled header file.

Create a new `NSObject` subclass named `MTConstants` (figure 5), delete the implementation file (MTConstants.m), and clear the contents of MTConstants.h. In MTConstants.h, we define `MTGameState` as shown below.

Add an import statement for MTConstants.h to the project's precompiled header file so that its contents are available throughout the project.

In `MTConstants.h`, we declare the various states of the game. In a more complex game, this might not be the best strategy or you may need to add additional states. For this project, this approach will suffice. Because Four in a Row is a turn based game, most of the game is spent in the `MTGameStateMyTurn` and `MTGameStateYourTurn` states, that is, it is either your turn or your opponent's turn to add a disc to the board. The last two states are used when the game has ended with one of the players as the winner of the game.

With `MTGameState` defined in `MTConstants.h`, it is time to declare the `gameState` property in the `MTViewController` class extension that we created earlier. As you might have guessed, the `gameState` property is of type `MTGameState`.

It is time to implement the `addDiscToColumn:` method. The implementation of `addDiscToColumn:` shown below is incomplete as you can see by the comments in its implementation. We will complete its implementation as we go. The main element to focus on at this point is the method's flow. We start by checking if the game has already been won by one of the players. If it has, then there is no need to add any more discs to the board. The second check we make is whether the player can add a disc, that is, is it the player's turn to add a disc to the board. If this isn't the case, then we show an alert view informing the player that it's not their turn.

The interesting part of `addDiscToColumn:` is what happens if the game hasn't ended and the player is allowed to add a disc to the board. We calculate which column the player has tapped by invoking `columnForPoint:` and pass the location in the board view that the player has tapped. The `column` variable is then passed as an argument to `addDiscToColumn:withType:`. The second parameter of this method is the cell type, which is `MTBoardCellTypeMine` in this case.

The `columnForPoint:` method is nothing more than a simple calculation to infer the column based on the coordinates of `point`.

In `addDiscToColumn:withType:`, we update the game state by updating the view controller's `matrix` property. We then fetch a reference to the corresponding board cell, stored in the view controller's `board` property, and set its cell type to `cellType`. Because we overrode the `setCellType:` method in `MTBoardCell`, the board cell's background color will be updated automatically.

Before testing the game, we need to amend the `startGameWithSocket:` and `endGame` methods. In these methods, we update the view controller's view based on the state of the game. Run two instances of the application and test the game in its current state.

## 4. Improving Interaction

At the moment, there are no limits to the number of discs a player can add and the actions of player A are not visible to player B, and vice versa. Let's fix that.

### Step 1: Limiting Interaction

To limit interaction, we need to update the view controller's `gameState` property at the appropriate time. The interaction with the board is already limited by the value of `gameState` in `addDiscToColumn:`, but this isn't very useful if we don't update the `gameState` property.

First of all, we need to decide who's turn it is when a new game starts. We could do something fancy like a coin toss, but let's keep it simple and let the player hosting the game make the first move. This is easy enough. We simply update the `gameState` property in the `controller:didHostGameOnSocket:` and `controller:didJoinGameOnSocket:` delegate methods. The result is that only the player hosting the game can add a disc to the board.

The second change we need to make is update the game state whenever the player makes a valid move. We do this in `addDiscToColumn:` as shown below. Each time a player adds a disc to the board, the game state is set to `MTGameStateYourTurn`, which means that the player cannot add any more discs to the board as long as the game state isn't updated. Before we continue, test the application one more time to see the result of our changes.

Even though we establish a connection when a new game is started, thus far, we haven't done much with that connection. The class that is in charge of the connection is `MTGameController`, which we created in the previous article. Open MTGameController.h and declare an instance method named `addDiscToColumn:`. The view controller will invoke this method to inform the game controller that the other player needs to be updated about the changed game state. This is also a good moment to expand the `MTGameControllerDelegate` protocol. When the game controller receives an update, it needs to notify its delegate, the main view controller, about the update because the main view controller is in charge of updating the board view. Take a look at the updated header file of the `MTGameController` class.

The `addDiscToColumn:` method is very easy to implement thanks to the groundwork we did in the previous articles. I have updated the header file of the `MTPacket` class by adding `MTPacketTypeDidAddDisc` to the enumeration of packet types. Even though we declared the `action` property in the `MTPacket` class, we won't be needing it in this project.

The `parseBody:` method also needs to be updated. In its current implementation, all we do is log the packet's data to the console. In the updated implementation, we check the packet's type and notify the delegate that the opponent added a disc to a column if the packet's type is equal to `MTPacketTypeDidAddDisc`.

Implement the new delegate method of the `MTGameControllerDelegate` protocol in the `MTViewController` class as shown below. We invoke `addDiscToColumn:withType:` and pass the column and cell type (`MTBoardCellTypeYours`). The view controller's `gameState` property is also updated to ensure that the player can add a new disc to the board.

Last but not least, we need to invoke the `addDiscToColumn:` method of the `MTGameController` class in the view controller's `addDiscToColumn:` method. This is the last piece of the puzzle.

Run two instances of the application and test the game one more time. Did you run into a problem? It is time to squash that bug that I told you about earlier in this article. The bug is located in the `MTJoinGameViewController` class. In the `socket:didConnectToHost:port:` method of the `GCDAsyncSocketDelegate` protocol, we notify the delegate of the `MTJoinGameViewController` class and pass it a reference to the socket. We stop browsing for new services and dismiss the join game view controller.

By dismissing the join game view controller, we implicitly get rid of the join game view controller as it is no longer needed. This means that the class's `dealloc` method is invoked when the object is released. The current implementation of the `dealloc` method is shown below.

In the `dealloc` method of the `MTJoinGameViewController` class, we clean everything up. However, because this socket is managed by the game controller, we shouldn't set the delegate to `nil` and neither should we set the delegate queue to `NULL`. The game controller is instantiated before the `dealloc` method is invoked, which means that the delegate of the game controller's socket is (re)set to `nil` when the join game view controller is deallocated. In other words, even though the game controller has a reference to the socket, the socket's delegate is set to `nil` and this renders the socket unusable to us. The solution is as simple as removing the last few lines of the `dealloc` method in which we set the socket's delegate to `nil` and the socket's delegate queue to `NULL`. Run the application one more time to see if we have successfully fixed that nasty bug.

## 5. Winning the Game

In its current state, it is not possible to win a game because we haven't implemented an algorithm that checks if one of the players has four of its own discs in a row. I have created a `hasPlayerOfTypeWon:` method for this purpose. It takes one argument of type `MTPlayerType` and checks the board if the player of the passed type has won the game. The `MTPlayerType` type is defined in `MTConstants.h`. Even though we could pass `0` for player A and `1` for player B, our code becomes much more readable (and maintainable) by declaring a custom type.

As you might expect, `hasPlayerOfTypeWon:` returns a boolean value. I won't discuss its implementation in detail because it is quite lengthy and not that difficult. The gist of it is that we check all possible winning combinations. It searches for horizontal, vertical, and diagonal matches. This is certainly not the best way to check for matches, but it is a method that I am sure most of you can understand without much difficulty. At the end of the `hasPlayerOfTypeWon:` method, we also update the view controller's `gameState` property if appropriate.

The `hasPlayerOfTypeWon:` method is invoked in two places in the `MTViewController` class. The first place is in the `addDiscToColumn:` method. After the player has added a disc to the board, we check if the player has won the game by passing `MTPlayerMe` as the argument of `hasPlayerOfTypeWon:`.

If the player did win the game, we invoke `showWinner`, which we will implement shortly. Notice that we also invoke the `showWinner` method at the beginning of the `addDiscToColumn:` method if the user taps the board view when the game has already ended.

The `hasPlayerOfTypeWon:` method is also invoked in the `controller:didAddDiscToColumn:` method of the `MTGameControllerDelegate` protocol. Take a look at its updated implementation below. If the player's opponent has won the game, we also invoke the `showWinner` method.

In the `showWinner` method, we update the view by displaying the replay button and showing an alert view that tells the player about the winner of the game.

## 6. Filling the Gaps

There are two pieces of functionality that I'd like to add before wrapping up this project, (1) updating the game state label whenever the game state changes and (2) enabling the replay button. Both are easy to implement.

### Step 1: Updating the Game State Label

To update the game state label, we need to update the view whenever the `gameState` property changes. We could use KVO (Key Value Observing) for this, but I prefer to simply override the setter of the `gameState` property. Whenever the value of `_gameState` changes, we invoke `updateView`, another helper method.

The `updateView` method is, just like `setupView`, a helper method. In `updateView`, we update the `text` property of `gameStateLabel`.

### Step 2: Enabling the Replay Button

To enable the replay button, we should start by implementing the `replay:` action. This action is invoked when the player taps the replay button, which appears when the game ends. We do three things in `replay:`, (1) invoke `resetGame` to reset the game, (2) update the game state to `MTGameStateMyTurn`, and send the game controller a message of `startNewGame`. This means that the player initiating the new game can make the first move.

We need to implement the `startNewGame` method on the `MTGameController` class and extend the `MTGameControllerDelegate` protocol. Open the header file of the `MTGameController` class and declare the `startNewGame` method and the new delegate method of the `MTGameControllerDelegate` protocol.

Again, thanks to the foundation we laid in the previous article, the `startNewGame` method is short and simple. To make all this work, we need to revisit the `MTPacket` class and update the `MTPacketType` enumeration.

In the `parseBody:` method of the `MTGameController` class, we send the delegate a message of `controllerDidStartNewGame:` if the packet's type is equal to `MTPacketTypeStartNewGame`.

The last bit of work that we need to do is implementing the `controllerDidStartNewGame:` delegate method in the `MTViewController` class. We invoke `resetGame`, as we did in the `replay:` action, and update the `gameState` property.

Run two instances of the application and play the game with a friend to see if everything works as it should.

## Conclusion

Even though we now have a playable game, I think you agree that it still needs some tweaking and polishing. The design is pretty basic and a few animations would be nice too. However, the goal of this project has been achieved, creating a multiplayer game using Bonjour and the CocoaAsyncSocket library. You should now have a basic understanding of Bonjour and the CocoaAsyncSocket library and know what each can do for you.