GKMinmaxStrategist doesn't return any moves
Asked Answered
D

3

16

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.

Darnell answered 5/1, 2016 at 16:44 Comment(11)
Have you tried incrementing maxLookAheadDepth?Masque
I have, but it doesn't make a difference.Darnell
I have a theory, could you please add your playMove() implementation to see if my theory is correct?Masque
Hi @TimVermeulen did you managed to found the issue?Masque
@HugoAlonso Nope. :/Darnell
Can you upload a sample project with the same problem to a repo in github or bitbucket so I can check it out?Masque
@TimVermeulen Hello, did you find the problem? I'm have the same issue.Gershwin
@DimaDeplov I didn't, unfortunately. I just rolled my own. I didn't really want to use classes, anyways.Darnell
@TimVermeulen actually, I found the problem. Maybe somebody will find it useful. I had the same issue, e.g. I have possible moves but best move is nil. My problem was related to isWin method. If your this method return true for your player, the game is over and there is no best move. My implementation was related to isLoss like "return !isLoss". Now I got it, that it is wrong. I advice anybody who have such issue check isWin.Gershwin
@TimVermeulen oh, didn't look to your code too deep, now I see you have same mistake in your code! This is a problem:)Gershwin
@DimaDeplov could you elaborate on the issues with the code? I have tried all of your previous suggestions but none of these methods are even being called!Attestation
M
4

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.

Masque answered 12/1, 2016 at 20:24 Comment(2)
My playMove(_:) function does change the playerToMove (or more specifically, it increments the ply variable that is used inside the getter of playerToMove).Darnell
I don't know why I hadn't tried this before, but I set breakpoints inside my eight functions that I showed in my question, and bestMoveForActivePlayer() doesn't call a single one of them. It only calls the getter of activePlayer, twice (it's the first player both times).Darnell
S
0

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.

Siddra answered 11/9, 2016 at 15:42 Comment(0)
E
0

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
Enriqueenriqueta answered 5/7, 2021 at 17:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.