GameplayKit Tutorial: Artificial Intelligence

In this tutorial, you’ll learn how to implement artificial intelligence (AI) in a SpriteKit game using GameplayKit and Swift. By Ryan Ackermann.

4.5 (4) · 1 Review

Save for later
Share
You are currently viewing page 2 of 2 of this article. Click here to view the first page.

Setting up the Strategist

This is where everything comes together! You’ll use the model you defined to drive the strategist.

Open Strategist.swift and replace the contents of the struct with the following:

struct Strategist {
  
  // 1
  private let strategist: GKMinmaxStrategist = {
    let strategist = GKMinmaxStrategist()
    
    strategist.maxLookAheadDepth = 5
    strategist.randomSource = GKARC4RandomSource()
    
    return strategist
  }()
  
  // 2
  var board: Board {
    didSet {
      strategist.gameModel = board
    }
  }
  
  // 3
  var bestCoordinate: CGPoint? {
    if let move = strategist.bestMove(for: board.currentPlayer) as? Move {
      return move.coordinate
    }
    
    return nil
  }
  
}

Inside of this struct:

  1. You instantiate a GKMinmaxStrategist with a maxLookAheadDepth of 5. The look ahead depth is the constraint you give a strategist to limit the number of future moves it can simulate. You also provide a random source to be the deciding factor when the strategist selects multiple moves as the best move.
  2. You keep a reference to the game model you defined and supply that to the strategist.
  3. The best coordinate is a CGPoint representing the strategist’s best move. The bestMove(for:) method will return nil if the player is in an invalid state or nonexistent.

Now only one thing remains: adding the strategist to the game.

Switch to the GameScene.swift file located inside the Game folder and add the following to the properties section near the top:

var strategist: Strategist!

With this property you keep a reference to the strategist used by the game.

Next, add following inside didMove(to:) towards the bottom, above resetGame():

strategist = Strategist(board: board)

Here you initialize the strategist with the model driving the game.

Next, add the following at the bottom of resetGame():

strategist.board = board

This ensures that every time you reset the game you also provide the strategist with a fresh game model.

Scroll down to the Touches section and add this new method right above:

fileprivate func processAIMove() {
  // 1
  DispatchQueue.global().async { [unowned self] in
    // 2
    let strategistTime = CFAbsoluteTimeGetCurrent()
    guard let bestCoordinate = self.strategist.bestCoordinate else {
      return
    }
    // 3
    let delta = CFAbsoluteTimeGetCurrent() - strategistTime
    
    let aiTimeCeiling = 0.75
    // 4
    let delay = max(delta, aiTimeCeiling)
    
    // 5
    DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
      self.updateBoard(with: Int(bestCoordinate.x), y: Int(bestCoordinate.y))
    }
  }
}

In this function you do the following:

  1. Create a dispatch queue to process the AI’s move since it could take a while.
  2. Record the starting time before the strategist decides on the best move.
  3. Calculate the time it took for the strategist to make its decision.
  4. Create a delay based on a constant so the AI will appear to take a bit of time to make a decision. On modern hardware, the AI might return a decision almost immediately so the delay gives the AI a human-like pause.
  5. Finally, you update the board on the main queue to reflect the new move.

Now you only have to call this method when it’s the AI’s turn. To do this, add the following to the bottom of updateGame():

if board.currentPlayer.value == .brain {
  processAIMove()
}

Build and run. Now the game has a mind of its own!

The Brain is Alive

If you play the game fast enough, you’ll soon notice that you can make the move for the AI. That’s not good.

To prevent this, add the following at the beginning of handleTouchEnd(_:with:):

guard board.currentPlayer.value == .zombie else {
  return
}

Build and run. Now the game will work as expected. Great job!

Game Complete

Where To Go From Here?

You can find the final project for this tutorial here.

Now you’re able to take this new knowledge you learned from this GameplayKit tutorial and add AI to your own games!

To further your knowledge of GameplayKit, check out Apple’s developer videos covering GameplayKit and its advances over time.

As an extra challenge, I encourage you to try and build more intelligence into the AI. Try adding detection of the opponent being one move away from winning and give a blocking move a higher rank. You’d only need to extend the Board.swift class and the enum in Move.swift.

We’d love you see what you can come up with. Join the discussion below to comment, ask questions or share your ideas!