Tic Tac Toe: Setting up the Visual Game Board

My goal in 2018 was to post to this blog at least once a week, but life happens. I got sick and I had some obligations for others that prevented me from reaching my goal. Sometimes you have a bad week, but you do your best and keep trying. So in the spirit of that, here is my post for the week.

Now that I have most of my game logic somewhat hammered out, I am going to put it on the back burner and focus on my user interface elements. I am choosing to think of each element as an independent piece rather than focusing on how everything is going to work together. I am hoping if I get each piece working properly, by the time I need to bring them together as an actual game I won’t have to worry too much about their implementation details.

One big question I have with SpriteKit over UIKit is placing elements on a screen. In UIKit we use auto layout to apply rules to the elements to tell them what size they are and how they exist within relation to one another. I know SpriteKit has an Interface Builder-esque scene builder, but I am a masochist and want to do my layout in code.

Game Board Layout

My intention for the game is to only be able to play it in portrait mode and not landscape. I want to think of this layout algebraically to try and get around the lack of auto layout. (Note: It may be possible that auto layout coexists with SpriteKit, but I am stubborn and want to pretend it doesn’t as a thought exercise and to prove a point. Don’t judge me.)

Since the phone is always going to be taller than it is wide, the width is my limiting factor here. I want my board to take up a square amount of space that spans from one side of the screen to the other. I also want the board to be centered in the screen with some space at the top or bottom which will include labels and such at a later state of the game.

Game Layers

So looking at my mock up, I have a board layer that is square and varies based on the width of the screen. Within that board layer, I have nine subviews representing the tic tac toe tiles. Those tiles exist relative to one another within the board layer. So I want to create two separate layers. I also want the tiles in my tile layer to exist relative to that layer and basically know nothing about the larger screen they exist within.

The board layer should be relatively straightforward. We know the length and width of the layer will be the same and that they are both constrained by the width of the screen.

First I need to create the sprite node that will represent the board:

// View Properties
let boardLayer = SKSpriteNode()

To create the view, we simply need to figure out the width of the screen. Next, we want to make the anchor point of the board layer the middle rather than one of the edges. Finally, we need to set the position of the anchor to the middle of the screen.

override init(size: CGSize) {
	super.init(size: size)
		
	anchorPoint = CGPoint(x: 0.5, y: 0.5)
		
	let background = SKSpriteNode(color: UIColor.white, size: size)
	addChild(background)

	let boardSize = size.width
	boardLayer.anchorPoint = CGPoint.zero
	boardLayer.position = CGPoint(x: -(boardSize / 2.0), y: -(boardSize / 2.0))
	boardLayer.size = CGSize(width: boardSize, height: boardSize)
	boardLayer.color = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.2)
	addChild(boardLayer)
}
	
required init?(coder aDecoder: NSCoder) {
	super.init(coder: aDecoder)
}

If you try to build and run this… nothing happens.

I got super confused and did a bunch of color debugging and changing the anchor points and sizes, but nothing was happening. After some digging and breakpointing, I realized that the game scene was never actually loading and that I needed to make some changes to the GameViewController class.

Game View Controller

I assumed that since this was an Apple template that it would just work, but either it doesn’t or I accidentally changed something to prevent it from doing so. I have been referring to a few SpriteKit tutorials on Ray Wenderlich’s site and in the ones I have seen, you need to declare a game scene property and explicitly let it, so that is what I did.

var scene: GameScene!

override func viewDidLoad() {
	super.viewDidLoad()
		
	let skView = view as! SKView
	skView.isMultipleTouchEnabled = false
		
	scene = GameScene(size: skView.bounds.size)
	scene.scaleMode = .aspectFill
		
	skView.presentScene(scene)
			
	skView.ignoresSiblingOrder = true
	skView.showsFPS = true
	skView.showsNodeCount = true
}

This now loads the game scene and you get a nice boring white background with a grey square. Later in this process I will be adding nice graphics, but this works for now. Next, we need to populate the game board with some tiles.

Game Tiles

Since I don’t know how wide my screen is going to be at any point in time, I don’t want to set hard values for the size of my tic tac toe tiles. I want to set their size proportionally as a percentage of a whole rather than hoping that 150 pixels will work well on all screens and adjusting the spaces between them.

The board is a grid of nine tiles. The tiles are all the same size and they form a square. The tiles are closer to one another than they are to the border of the game board. I need to determine how large they need to be and also how to place them within this space.

To determine their width/height, I am going to create an algebraic equation. I know that I need to fit three tiles into the board’s width, so I will be dividing the width by three. However, I also need to account for the spaces between each tile and padding at the edge. So, to determine how large the tiles are I need to do the following calculation:

tileWidth = (boardWidth - (outerPadding * 2) - (innerPadding * 2)) / 3

We now know how to calculate the size of each tile, but we need to actually create each one and make sure we give each one an identifier so that when the player taps the middle square the program knows how to identify it.

Tile Sprites

Earlier in this project I created an array of tile states. This was the game model. We are now working on the game view, so we need analogous view objects that correlate to the model objects.

We will need nine sprite objects that represent a tile in our array of tile state. Each sprite needs to be given a size (which we calculated above), a position, and an identifier. The position is determined relative to the other tiles and relative to the board view. The identifier is the way we will be able to track which sprite has been tapped while the game is being played.

SpriteKit Coordinate System

In order to place objects in space, it’s important to understand SpriteKit’s anchor points and coordinate system. We know how to calculate the size and shape of our tiles, but we also need to know how to explain to Xcode where we want each tile to be placed.

Back when I learned algebra, I became familiar with Cartesian coordinates:

Generic Cartesian coordinate space

It’s the idea that you have an X and a Y axis that intersect at a center point, or origin. Every point in the coordinate plane can be expressed in relation to that center point. Any point that is above the X axis is thought of as positive and anything below the X axis is thought of as negative. Anything to the right of the Y axis is also positive and anything to the left is negative.

In SpriteKit, the center point/origin of the screen is oriented at the bottom left corner:

SpriteKit’s default coordinate orientation

What that means is that every point you look at is effectively going be positive. In this scenario, if you wanted to place an element at the bottom of the screen, offset by twenty pixels, you would set the y-position to 20. If you wanted to place it at the top, offset by twenty pixels, you would set the y-position to screen.size.height - 20.

Alternately, you could place your anchor point at the top right corner of the screen:

Inverted SpriteKit anchor point. Everything on the screen is negative in relation to the anchor point.

This would work the opposite of the default. Instead of thinking of everything positively, you think of things negatively. If you wanted to place an element at the bottom of the screen, offset by twenty pixels, you would set the y-position to -screen.size.height + 20. If you wanted to place it at the top, offset by twenty pixels, you would set the y-position to -20.

For my purposes, I care about building my application from the center out. I have no idea how tall the screen is that my app will appear on, so I need to build everything from the middle out. This means my anchor point will need to split the difference between SpriteKit’s default anchor point and the inverted one:

Center anchor point. Half the coordinates will be positive in relation to the anchor and half will be negative.

This makes placement slightly tricky. Some of my coordinates exist in positive coordinate space and some exist in negative space. Anything to the left of the center of the screen is negative while anything to the right is positive.

Embedded Nodes and Relative Positioning

I have no idea how tall the iPhone displaying my game is going to be. For the last few years Apple has tried to get people to think of screens proportionally rather than absolutely. I can’t position my tiles in relation to the height of the iPhone because that is variable. I went to a bunch of trouble to create a square area at the center of the screen that adjusts its size based on the height of the iPhone. I know this will always be square and I am basing the size and position of my tiles relative to that object.

SpriteKit utilizes a node graph. What this means is that you can add a sprite node as a child of either the scene or of another sprite. The tic tac toe board will have nine sprites that I want to control as one object and place within their own relative context.

Anchor points greatly impact positioning of embedded objects.

Tile Code

First I need variables for the padding around my tiles. I use this in a few places and if I don’t like the way it looks I only want to change the value in one place.

let outerBorderPadding:CGFloat = 30.0
let betweenTilesPadding:CGFloat = 15.0

I am going to use these to calculate the size of my tiles:

let padding = (outerBorderPadding * 2) + (betweenTilesPadding * 2)
let tileSize = (boardSize - CGFloat(padding)) / 3

Next I need to place and name my tiles. I had a creepy convoluted rats nest of code because I was trying to avoid rows and columns, but then I talked through this with The Boyfriend and we came up with something less busted:

var tileCounter = 0

for yIndex in 0..<3 {
	for xIndex in 0..<3 {
		let sprite = SKSpriteNode()
		sprite.color = UIColor.red
		sprite.size = CGSize(width: tileSize, height: tileSize)
		sprite.name = "Tile\(tileCounter)"
				
		let xPosition = CGFloat(outerBorderPadding) + CGFloat(xIndex) * (tileSize + betweenTilesPadding)
		let yPosition = CGFloat(outerBorderPadding) + CGFloat(2 - yIndex) * (tileSize + betweenTilesPadding)

		sprite.anchorPoint = CGPoint.zero
		sprite.position = CGPoint(x: xPosition, y: yPosition)
		boardLayer.addChild(sprite)

		tileCounter += 1
	}
}

There is a tile counter outside of the nested loops so that I can ensure that my tile name correlates to the index it represents in the data model.

We have nested loops for the rows and columns. I was concerned I would need to create persistent objects to keep track of the row and column of each square. That is a valid design pattern that would utilize if I was doing something more complex, but with only nine objects I had hoped to just keep things simple.

For each tile, I need to label the tile so I know which one we are dealing with and I also need to place it properly in relation to the ones around it. Later I will add logic for the squares to have graphics associated with selection and deselection, but for now I simply want to establish that my layout works properly.

In the nested loops, I am creating a tile for a row and column. There are a few ways this could have been placed. In this implementation, I kept the default SpriteKit coordinate system for these sprites, which means the origin is in the lower left corner. We need to account for that in our positioning of the sprites. I am counting the indices of the tiles starting in the top left while the origin is in the bottom left. This doesn’t affect the x-position, but it does affect the y-position. This is why I’m subtracting two from the y-index. The sprite is added to the board and the tile counter is incremented.

Final output of the board.

Conclusion

The current state of the project is available here.

For me, the challenge of figuring out how to place elements programmatically is an incredibly important problem. My aim with a lot of my game development is to do work in board/card games. Since the screens I am working with are variable and the number of elements are variable, it is tremendously important to me to figure out how to emulate auto layout in SpriteKit.

The game logic is relatively simple compared to the amount of programming that goes into the UI. I feel like when people think about programming a game, the thing they focus on is the mechanics and not the layout. Graphics are the most intensive part of game development and there are a lot of problems to solve that you don’t think about because you just kind of expect things to work.