Tic Tac Toe: Touches and Responses

Last time I set up the tic tac toe board with a set of nine tiles that will represent the board. This week, my goal is to make each tile responsive to touch so that we can connect our game logic to it.

I need to do the following steps:

  • Set up a touch observer to look for touches
  • Determine if the touch is located on a sprite representation of a tile
  • If so, which tile?
  • Figure out how that tile correlates to my array of game logic tiles
  • Change the state on the correlating game logic tile
  • Update the UI
  • Check for a win or a draw
  • Either end or continue the game

I have found that, for me personally, breaking tasks down into smaller chunks makes it easier to stay focused and get something done. Instead of worrying or freaking out about how I am going to set up multiple game scenes, which I intend to do later, I am focused on this one small chunk of the program. I am further dissecting this chunk into even smaller chunks so I don’t get confused about how I am going to get all of these things to work together.

Connecting the Model Game Tiles to the View Game Tiles

The first problem I would like to solve before I go any further is to figure out how to connect my model tiles to my view tiles. The view tiles are the objects that the player sees and can respond and detect touches, but they only know about what they look like and what is touching them. They don’t know that a certain color or image or state means anything outside of their own context. That is the responsibility of my model tiles. Those tiles know what state they are in and they provide data to my functions that determine if they are part of a win or not. I need some way to allow the model tiles to talk to the view tiles so they can both do their jobs.

There are a few ways to do this. I could have created a bunch of methods and structures to check the row and column of my board, but I only have nine objects to track. SpriteKit has a method of checking sprites by name, so I chose instead to create a single array of sprites and to give each a unique name that includes their index so that they can be found and tracked based on an identifier.

I created an array of model tiles that represent the state of the board. I am using their indices and positions in my game logic. I created a similar array to hold all of my sprite tiles. When I generated the sprite tiles, I generated them in the same order as I did for the model tiles and named them accordingly. The first tile in the sprite array should be the uppermost left tile, which correlates to the index and first tile in the model array. So the indices of each should line up. Keep that in mind as we walk through the rest of the steps.

I realized after I wrote this section when I was trying to get the next section to work that I don’t actually populate either of my arrays at this point in my explanation. I add the sprites to the board layer but I forget to add them to the array I created to hold them.

I added the following code within the nested loop in the initializer:

boardLayer.addChild(sprite)
tiles.append(sprite)

I had also added a method to populate the model array that I commented out because I wasn’t using it yet. It is this one:


board = resetBoard()

This is a good example of ways things can go wrong when you’re working with computers. You may intend to do something but unless you explicitly set it, then it won’t happen and you become frustrated. This is actually kind of true with people too…

Detecting Touches

UIKit has built in gesture recognition. One such gesture is touching. I generally haven’t used this because I use other elements, like buttons, that implement it automatically. But it’s useful for SpriteKit games, especially ones of a platform nature where you don’t necessarily need to hit a specific object in order to get a character to move on the screen. In fact, that would kind of defeat the purpose of having a touch screen over a controller of some kind.

One thing you have to remember (that I forgot and had to correct) is that you have to enable user interaction explicitly when you initialize the scene. You do this by putting this line of code somewhere after the call to super:

isUserInteractionEnabled = true

There are several methods for touch, but the default one is touchesBegan. This method kind of lurks around observing the screen waiting for a touch to happen. Once one does, it implements whatever game logic needs to be executed to respond to the touch.

The method doesn’t just bring in a single touch. It brings in an array of UITouch objects. So keep that in mind when you are using touchesBegan that it could potentially have many touches and not just one.

Here is my initial boilerplate touchesBegan method:

override func touchesBegan(_ touches: Set, with event: UIEvent?) {
	if let touch = touches.first {
		let location = touch.location(in: self)
		let tappedNodes = nodes(at: location)
	}
}

At this point I started adding a bunch of loops and conditional logic to get my code to work. It didn’t. I got confused and frustrated. I went to The Boyfriend to ask for help debugging my code. No, I am not sexist Computer Engineer Barbie. The Boyfriend has 30 years of programming experience to my five.

We debugged the array issue I mentioned above and came up with this much nicer method for checking for touches in our tiles:

override func touchesBegan(_ touches: Set, with event: UIEvent?) {
	guard let touch = touches.first else {return}
	let location = touch.location(in: self)
	let rawNodes = nodes(at: location)
	let tappedNodes = rawNodes.filter{return $0.name != nil}
		
	guard let node = tappedNodes.first as? SKSpriteNode else {
            return
        }
	node.color = UIColor.black
	board[tiles.index(of: node)!] = .playerA
}

First we are ensuring that we actually have a touch. If there isn’t an eligible touch, we want to break out of the method. Next, we need to store the location of the touch. This location is used to get an array of all nodes that exist at that location. One thing I had forgotten when putting the board together was that the background and the game board are also SKSpriteNode objects. This meant that when I touched the screen three nodes were added to the raw nodes. Since we only care about the one we named, we need to remove those other sprites from the array. We do that by checking for a name. Since the board and the background are not named, they are removed from the array. Lastly, we’re checking to make sure that any node that is left in the array can be cast to SKSpriteNode. If not, we break out of the method early.

Now that we have ensured that the only node we can possibly have at this point are SKSpriteNodes, we can now set properties on the node such as the color. We can also use the node to find its index within the array of sprite kit tiles. This directly correlates to the index within the model of the tile state that needs to be updated. Voila!

Can touch a tile and switch it from red to black

Updating the UI

Right now this isn’t particularly interesting. I can tap on a tile once and it changes from red to black. I can’t switch back and forth between players. Yet.

I modified the bottom of the touchesBegan() method to the following:

if board[tiles.index(of: node)!] == .notSelected {		
	switch currentPlayer {
	case .playerA:
		node.color = UIColor.black
		board[tiles.index(of: node)!] = .playerA
	case .playerB:
		node.color = UIColor.red
		board[tiles.index(of: node)!] = .playerB
	case .notPlaying:
		node.color = UIColor.white
		board[tiles.index(of: node)!] = .notSelected
	}
}

I changed the default unselected color of the tiles to white to make the different players a little more dynamic. I want to make sure that if a tile has already been selected that it can’t be selected again. I check the index of the correlating tile in the model to see if it’s been selected or not. If the current player is Player A, the tile switches to black. If the current player is Player B, it switches to red. To ensure an exhaustive switch statement I simply leave the node color white, but this should never be called.

I noticed that the first time I tested this that the first tile I touched didn’t do anything. I realized that the first time I touched the board I was still in the .NotPlaying state. I had commented out the switchPlayer call at the end of the initializer, so I just had to delete the comment and we are nearly ready to go.

Can switch between players now

Checking for Win and Ending the Game

Finally, after the move has been completed, we need to check for whether or not someone won the game. I set this logic up before, so all we have to do is call our game over method:

if let isWin = checkForWin(currentPlayer: currentPlayer, board: board) {
	switch isWin {
	case .playerAWin:
		print("Player A wins")
	case .playerBWin:
		print("Player B wins")
	case .draw:
		print("No one wins. Everyone gets a participation trophy")
	}		
}

currentPlayer = switchPlayer(currentPlayer: currentPlayer)

I thought about trying to get an alert view going for these win cases, but then I would have to remove them later when we have better UI, so for the time being I am not concerned with how to tell the player that the game is over. I am simply printing the result to the console and making it so no one can play anymore because I am mean.

Have fun reading the tiny print on the console!! :p

If isWin falls through because the game is not yet over, we need to switch players, which is the last bit that we do at the end of this method.

Conclusions

My project so far can be seen here.

This was far easier to set up that I originally anticipated. All of the work I did over the last few weeks to separate the model from the view really paid off here. I could focus on simply checking what state the game is in and update the UI accordingly.

Most of the SpriteKit games I have done tutorials for are ones that are running continuously and thus have all of their logic in a game loop. Turn based applications are similar except you only update the game once someone has made a move. As someone who is primarily interested in turn based games, this is something for me to keep in mind.

I have broken this game out into three more chunks that need to be done before I feel like I have completed this game adequately:

  • Graphics, Labels, and Sounds: I have an art tile set that I want to apply to the game so it looks like a real game and not a minimalist painting. I want to update labels that say what the current state of the game is. Lastly, at a minimum, I want some kind of background music.
  • Implementing an AI: This game isn’t very interesting without someone or something to play against. I want to look into how to implement some kind of AI for this game, be it using GKStrategist or me just rolling my own AI.
  • Multiple Scenes and Variations: I want an actual main menu screen to explicitly start the game. I would like a game over screen to take you back to the main menu. I also have some ideas for variations on tic tac toe for the player to be able to choose because the base game is pretty boring. I want those options to be selectable in the main menu.

So just three more blog posts left on this project! Then it’s on to actually attempting something that people might actually want to play. Stay tuned!