nvzqz / Sage

A cross-platform chess library for Swift
Apache License 2.0
373 stars 43 forks source link

Create a new game from a FEN string #7

Closed cesarvarela closed 8 years ago

cesarvarela commented 8 years ago

HI!, thanks for the previous issue!

Seems like you can create boards and positions, but I'm not seeing how to create a Game from it.

nvzqz commented 8 years ago

Making a Game from a Board or Position doesn't guarantee that the game would be a valid one. The execute(move:) and execute(uncheckedMove:) methods make the assumption that the game is in a valid state. For example, it assumes that rooks haven't moved if the appropriate castling rights are available.

Something close to this that I want to implement is creating a Game from a valid PGN.

cesarvarela commented 8 years ago

Mmm not sure I follow.

Let's say I play a game until X position, generate a FEN string from there, store it, and continue playing another time from that same FEN string. Are you saying the FEN string does not have enough information to continue playing a valid game from a specific position?

nvzqz commented 8 years ago

If you're handling things like that, then yes you do know that the game is a valid one. You're guaranteeing it is because it originated from a game. However, putting in just any FEN string could lead to an invalid game state or a legally unreachable position.

I'm not saying this isn't possible. An initializer from a FEN string would just need a validator that checks for things such as there's only one king per side and that castling rights correspond to king and rook squares.

This would be a great feature to have because it would allow for deserialization of stored file data into Game instances.

cesarvarela commented 8 years ago

I've added this initializer and seems be working:

    /// Creates a new chess game from a FEN string
    ///
    /// - parameter whitePlayer: The game's white player. Default is a nameless human.
    /// - parameter blackPlayer: The game's black player. Default is a nameless human.
    /// - parameter fen: FEN string to start from
    public init(whitePlayer: Player = Player(),
                blackPlayer: Player = Player(),
                fen: String) {

        let position = Game.Position(fen: fen)! // asume the fen string is valid

        self._moveHistory = []
        self._undoHistory = []
        self.board = position.board
        self.playerTurn = position.playerTurn
        self.castlingRights = position.castlingRights
        self.whitePlayer = whitePlayer
        self.blackPlayer = blackPlayer
        self.variant = .standard
        self.attackersToKing = position.board.attackersToKing(for: position.playerTurn)
        self.halfmoves = position.halfmoves
    }

It's good enough for my needs since I'm not saving moves history (at the moment)

(Let me know if I'm missing something)

nvzqz commented 8 years ago

It would be a lot safer to pass a Position as a parameter than assuming fen is valid. I'll add this functionality soon. Thank you

cesarvarela commented 8 years ago

FYI: With that change, and starting from a FEN string, everything seems to work, except the en passant rule, since it's calculated using _moveHistory and it's empty there.

I've tried a quick hack but couldn't get it to work, I'll wait for you to implement it.

You can use this FEN to test it: rnbqkbnr/pp2pppp/8/2ppP3/8/8/PPPP1PPP/RNBQKBNR w KQkq d6 0 3

nvzqz commented 8 years ago

Done: c5550d16df1ffc8d50ca18a22d8dca1a51c363fc + 892c2a9aa54f0688f75f39fee396140260df4faa + be761674c7d70bc8ad6a71b9bf4fb1f2b7edfb1a

The en passant target is added to the game's history in order to have a target when undoing even if the game was originally started with a Position.