GKMinmaxStrategist doesn't return any moves

455 views Asked by At

I have the following code in my main.swift:

let strategist = GKMinmaxStrategist()
strategist.gameModel = position
strategist.maxLookAheadDepth = 1
strategist.randomSource = nil

let move = strategist.bestMoveForActivePlayer()

...where position is an instance of my GKGameModel subclass Position. After this code is run, move is nil. bestMoveForPlayer(position.activePlayer!) also results in nil (but position.activePlayer! results in a Player object).

However,

let moves = position.gameModelUpdatesForPlayer(position.activePlayer!)!

results in a non-empty array of possible moves. From Apple's documentation (about bestMoveForPlayer(_:)):

Returns nil if the player is invalid, the player is not a part of the game model, or the player has no valid moves available.

As far as I know, none of this is the case, but the function still returns nil. What could be going on here?

If it can be of any help, here's my implementation of the GKGameModel protocol:

var players: [GKGameModelPlayer]? = [Player.whitePlayer, Player.blackPlayer]
var activePlayer: GKGameModelPlayer? {
    return playerToMove
}

func setGameModel(gameModel: GKGameModel) {
    let position = gameModel as! Position
    pieces = position.pieces
    ply = position.ply
    reloadLegalMoves()
}

func gameModelUpdatesForPlayer(thePlayer: GKGameModelPlayer) -> [GKGameModelUpdate]? {
    let player = thePlayer as! Player
    let moves = legalMoves(ofPlayer: player)
    return moves.count > 0 ? moves : nil
}

func applyGameModelUpdate(gameModelUpdate: GKGameModelUpdate) {
    let move = gameModelUpdate as! Move
    playMove(move)
}

func unapplyGameModelUpdate(gameModelUpdate: GKGameModelUpdate) {
    let move = gameModelUpdate as! Move
    undoMove(move)
}

func scoreForPlayer(thePlayer: GKGameModelPlayer) -> Int {
    let player = thePlayer as! Player
    var score = 0
    for (_, piece) in pieces {
        score += piece.player == player ? 1 : -1
    }
    return score
}

func isLossForPlayer(thePlayer: GKGameModelPlayer) -> Bool {
    let player = thePlayer as! Player
    return legalMoves(ofPlayer: player).count == 0
}

func isWinForPlayer(thePlayer: GKGameModelPlayer) -> Bool {
    let player = thePlayer as! Player
    return isLossForPlayer(player.opponent)
}

func copyWithZone(zone: NSZone) -> AnyObject {
    let copy = Position(withPieces: pieces.map({ $0.1 }), playerToMove: playerToMove)
    copy.setGameModel(self)
    return copy
}

If there's any other code I should show, let me know.

3

There are 3 answers

0
PPh On

simple checklist:

  1. GKMinmaxStrategist's .bestMove(for:) is called
  2. GKMinmaxStrategist's .gameModel is set
  3. GKGameModel's isWin(for:) does not return true before move
  4. GKGameModel's isLoss(for:) does not return true before move
  5. GKGameModel's gameModelUpdates(for:) does not return nil all the time
  6. GKGameModel's score(for:) is implemented
0
Maciej Konieczny On

I had the same problem. Turns out, .activePlayer has to return one of the instances returned by .players. It's not enough to return a new instance with matching .playerId.

2
Hugo Alonso On

You need to change the activePlayer after apply or unapply a move. In your case that would be playerToMove.

The player whose turn it is to perform an update to the game model. GKMinmaxStrategist assumes that the next call to the applyGameModelUpdate: method will perform a move on behalf of this player.

and, of course:

Function applyGameModelUpdate Applies a GKGameModelUpdate to the game model, potentially resulting in a new activePlayer. GKMinmaxStrategist will call this method on a copy of the primary game model to speculate about possible future moves and their effects. It is assumed that calling this method performs a move on behalf of the player identified by the activePlayer property.

func applyGameModelUpdate(gameModelUpdate: GKGameModelUpdate) {
    let move = gameModelUpdate as! Move
    playMove(move)

    //Here change the current Player
    let player = playerToMove as! Player
    playerToMove = player.opponent
}

The same goes for your unapplyGameModelUpdate implementation.

Also, keep special attention to your setGameModelimplementation as it should copy All data in your model. This includes activePlayer

Sets the data of another game model. All data should be copied over, and should not maintain any pointers to the copied game state. This is used by the GKMinmaxStrategist to process permutations of the game without needing to apply potentially destructive updates to the primary game model.