Tic Tac Toe in Swift with a UICollectionView

I’m starting to ramp up how much work I put into using Swift to eventually switch over to using it completely over Objective-C now that Apple has released version 1.0 with iOS 8.

One of my first projects was to make a simple Tic Tac Toe game in Swift. What started out as something relatively simple, as Tic Tac Toe isn’t the most complex game, turned into something much more involved.

Utilizing standard UIKit components and implementing a MinMax algorithm to make the AI unbeatable, I set out to make a barebones, but fully functional, game of Tic Tac Toe in Swift.

Without going into too much nitty gritty detail here on my website, I’d implore you to just browse through the source code over on GitHub. I’ll lay out some of the more important parts which I enjoyed making.

UICollectionView as the Tic Tac Toe Board

Instead of manually laying out 9 buttons or interaction enabled views, I instead went with what could be considered overkill and created the board using a 3×3 grid UICollectionView. I chose to do this for several reasons:

  1. Subclassing the UICollectionViewFlowLayout is neither difficult nor scary and getting familiar with the UICollectionViewLayout protocol is pretty important
  2. The UICollectionViewFlowLayout allows you to make a grid with even spacing pretty darn easily and pretty much straight out of the box. Just make sure you have 3 sections with 3 rows each.
  3. Built in item selecting meant I could hook that into my board pretty easily:
    func collectionView(collectionView: UICollectionView!, didSelectItemAtIndexPath indexPath: NSIndexPath) {
        if manager.placedPiece(indexPath.row, y: indexPath.section) {
            setPlayersLabel()
        }
    }
  4. Automatically scales any any resolutions to work on both the iPhone and iPad as well as easy to customize just like any other standard UIKit control.

Keeping all Tic Tac Toe logic in the GameManager

This meant all of my display logic was handled in my UIViewController and any sort of item placing was handled in the GameManager class. One particular good example was the item placing that I showed above in #3. The if manager.placedPiece(indexPath.row, y: indexPath.section attempts to place a piece at the provided indexPath coordinates for the current player. If it’s successful it’ll return true and tell the UIViewController to change its display of whose current turn it is.

This way the UIViewController doesn’t really ever need to tell the GameManager what to do; however, the GameManager can notify the UIViewController when it should update something accordingly, like in the actual placedPiece method:

func placedPiece(x : Int, y : Int) -> Bool {
    let piece = board.pieceAt(x, y: y)
    if piece.isOpen {
        if whoseTurnIsIt == 1 {
            piece?.playerOwner = player1
        }
        else {
            piece?.playerOwner = player2
        }
        viewController.collectionView.reloadItemsAtIndexPaths([NSIndexPath(forRow: x, inSection: y)])
        nextTurn()
        return true
    }
    
    return false
}

The code is pretty readable, but the last part where we reload the UICollectionView’s cell at the location given means we update the UI to display the newly placed piece if need be, instead of just reloadData-ing the entire UICollectionView. Sure – performance wise a 3×3 grid isn’t going to kill anything but this is a matter of principles, right?

The Unbeatable AI

This was a requirement that I seriously didn’t think would take that long but ended up being a lot more involved than I thought. I started out by doing my best to handle all of the cases by hand. I was able to make the AI unbeatable EXCEPT for cases when I, the user, would create “Forks” (Turns where in the next turn I’d have two ways to possibly win). Here’s the logic for the AI’s turn:

  1. Check if the middle piece is open. If it is, place it, otherwise choose a corner and place it only if it’s the AI’s first turn.
  2. Check if the AI has two pieces in a row and can place a third, and if so place it there to win (horizontal, vertical and diagonal)
  3. Check if the player has two pieces in a row and can place a third, and if so place it there to block the player from winning (horizontal, vertical and diagonal)
  4. Check for a fork – This was what I was missing and will explain in a bit
  5. Place it in the center if it’s open
  6. Place it in any free corner
  7. Place it in any free side piece

For the longest time, I couldn’t figure out how to do #4. Searching Google suggested I create a MinMax algorithm for the AI to determine which move gives it the best chances to win. I fortunately found an example of how to do this in Java, to which I then converted over to Swift.

MinMax in Swift

I’m not going to try to reexplain how the MinMax algorithm works in this instance as the link provided will do a much better job at it; however, if the score that it returned to my when checking for any forks the AI should make met a positive score, the AI would place an item there. This made the AI unbeatable!

unbeatable tic tac toe AI

Wrapping up

In the end, I was pretty happy with how this turned out! Now, if I was a good developer I’d go ahead and write some tests to make sure my logic was sound… but we can save that for another day.

 

Leave a Reply

Your email address will not be published. Required fields are marked *