package chess import ( "crypto/md5" "fmt" "strconv" "strings" ) // Side represents a side of the board. type Side int const ( // KingSide is the right side of the board from white's perspective. KingSide Side = iota + 1 // QueenSide is the left side of the board from white's perspective. QueenSide ) // CastleRights holds the state of both sides castling abilities. type CastleRights string // CanCastle returns true if the given color and side combination // can castle, otherwise returns false. func (cr CastleRights) CanCastle(c Color, side Side) bool { char := "k" if side == QueenSide { char = "q" } if c == White { char = strings.ToUpper(char) } return strings.Contains(string(cr), char) } // String implements the fmt.Stringer interface and returns // a FEN compatible string. Ex. KQq func (cr CastleRights) String() string { return string(cr) } // Position represents the state of the game without reguard // to its outcome. Position is translatable to FEN notation. type Position struct { board *Board turn Color castleRights CastleRights enPassantSquare Square halfMoveClock int moveCount int inCheck bool validMoves []*Move } // Update returns a new position resulting from the given move. // The move itself isn't validated, if validation is needed use // Game's Move method. This method is more performant for bots that // rely on the ValidMoves because it skips redundant validation. func (pos *Position) Update(m *Move) *Position { moveCount := pos.moveCount if pos.turn == Black { moveCount++ } cr := pos.CastleRights() ncr := pos.updateCastleRights(m) p := pos.board.Piece(m.s1) halfMove := pos.halfMoveClock if p.Type() == Pawn || m.HasTag(Capture) || cr != ncr { halfMove = 0 } else { halfMove++ } b := pos.board.copy() b.update(m) return &Position{ board: b, turn: pos.turn.Other(), castleRights: ncr, enPassantSquare: pos.updateEnPassantSquare(m), halfMoveClock: halfMove, moveCount: moveCount, inCheck: m.HasTag(Check), } } // ValidMoves returns a list of valid moves for the position. func (pos *Position) ValidMoves() []*Move { if pos.validMoves != nil { return append([]*Move(nil), pos.validMoves...) } pos.validMoves = engine{}.CalcMoves(pos, false) return append([]*Move(nil), pos.validMoves...) } // Status returns the position's status as one of the outcome methods. // Possible returns values include Checkmate, Stalemate, and NoMethod. func (pos *Position) Status() Method { return engine{}.Status(pos) } // Board returns the position's board. func (pos *Position) Board() *Board { return pos.board } // Turn returns the color to move next. func (pos *Position) Turn() Color { return pos.turn } // CastleRights returns the castling rights of the position. func (pos *Position) CastleRights() CastleRights { return pos.castleRights } // String implements the fmt.Stringer interface and returns a // string with the FEN format: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 func (pos *Position) String() string { b := pos.board.String() t := pos.turn.String() c := pos.castleRights.String() sq := "-" if pos.enPassantSquare != NoSquare { sq = pos.enPassantSquare.String() } return fmt.Sprintf("%s %s %s %s %d %d", b, t, c, sq, pos.halfMoveClock, pos.moveCount) } // Hash returns a unique hash of the position func (pos *Position) Hash() [16]byte { sq := "-" if pos.enPassantSquare != NoSquare { sq = pos.enPassantSquare.String() } s := pos.turn.String() + ":" + pos.castleRights.String() + ":" + sq for _, p := range allPieces { bb := pos.board.bbForPiece(p) s += ":" + strconv.FormatUint(uint64(bb), 16) } return md5.Sum([]byte(s)) } // MarshalText implements the encoding.TextMarshaler interface and // encodes the position's FEN. func (pos *Position) MarshalText() (text []byte, err error) { return []byte(pos.String()), nil } // UnmarshalText implements the encoding.TextUnarshaler interface and // assumes the data is in the FEN format. func (pos *Position) UnmarshalText(text []byte) error { cp, err := decodeFEN(string(text)) if err != nil { return err } pos.board = cp.board pos.turn = cp.turn pos.castleRights = cp.castleRights pos.enPassantSquare = cp.enPassantSquare pos.halfMoveClock = cp.halfMoveClock pos.moveCount = cp.moveCount pos.inCheck = isInCheck(cp) return nil } func (pos *Position) copy() *Position { return &Position{ board: pos.board.copy(), turn: pos.turn, castleRights: pos.castleRights, enPassantSquare: pos.enPassantSquare, halfMoveClock: pos.halfMoveClock, moveCount: pos.moveCount, inCheck: pos.inCheck, } } func (pos *Position) updateCastleRights(m *Move) CastleRights { cr := string(pos.castleRights) p := pos.board.Piece(m.s1) if p == WhiteKing || m.s1 == H1 || m.s2 == H1 { cr = strings.Replace(cr, "K", "", -1) } if p == WhiteKing || m.s1 == A1 || m.s2 == A1 { cr = strings.Replace(cr, "Q", "", -1) } if p == BlackKing || m.s1 == H8 || m.s2 == H8 { cr = strings.Replace(cr, "k", "", -1) } if p == BlackKing || m.s1 == A8 || m.s2 == A8 { cr = strings.Replace(cr, "q", "", -1) } if cr == "" { cr = "-" } return CastleRights(cr) } func (pos *Position) updateEnPassantSquare(m *Move) Square { p := pos.board.Piece(m.s1) if p.Type() != Pawn { return NoSquare } if pos.turn == White && (bbForSquare(m.s1)&bbRank2) != 0 && (bbForSquare(m.s2)&bbRank4) != 0 { return Square(m.s2 - 8) } else if pos.turn == Black && (bbForSquare(m.s1)&bbRank7) != 0 && (bbForSquare(m.s2)&bbRank5) != 0 { return Square(m.s2 + 8) } return NoSquare } func (pos *Position) samePosition(pos2 *Position) bool { return pos.board.String() == pos2.board.String() && pos.turn == pos2.turn && pos.castleRights.String() == pos2.castleRights.String() && pos.enPassantSquare == pos2.enPassantSquare }