package board import ( "fmt" "strconv" "strings" ) // CastleRights is a helper for maintaining castling rights. type CastleRights struct { WhiteKingSide bool WhiteQueenSide bool BlackKingSide bool BlackQueenSide bool } // NewCastleRights returns an initialized CastleRights from the given string. func NewCastleRights(s string) (*CastleRights, error) { cr := &CastleRights{} for _, c := range s { switch c { case 'K': cr.WhiteKingSide = true case 'Q': cr.WhiteQueenSide = true case 'k': cr.BlackKingSide = true case 'q': cr.BlackQueenSide = true default: return nil, fmt.Errorf("invalid castle-rights: %s", s) } } return cr, nil } func (cr CastleRights) String() string { var out string if cr.WhiteKingSide { out += WhiteKing.String() } if cr.WhiteQueenSide { out += WhiteQueen.String() } if cr.BlackKingSide { out += BlackKing.String() } if cr.BlackQueenSide { out += BlackQueen.String() } return out } // Board represents a chess board. type Board struct { // White WhiteKing Bitboard WhiteQueen Bitboard WhiteBishop Bitboard WhiteKnight Bitboard WhiteRook Bitboard WhitePawn Bitboard // Black BlackKing Bitboard BlackQueen Bitboard BlackBishop Bitboard BlackKnight Bitboard BlackRook Bitboard BlackPawn Bitboard // Convenience WhitePieces Bitboard BlackPieces Bitboard FreeSquares Bitboard Turn Color CastleRights CastleRights EnPassent *Square HalfMove uint64 FullMove uint64 } // NewBoard returns an initialized *Board. func NewBoard(pieces map[Square]Piece, turn Color, cr CastleRights, ep *Square, halfMove, fullMove uint64) (*Board, error) { board := new(Board) board.WhiteKing = NewBitboard(nil) board.WhiteQueen = NewBitboard(nil) board.WhiteBishop = NewBitboard(nil) board.WhiteKnight = NewBitboard(nil) board.WhiteRook = NewBitboard(nil) board.WhitePawn = NewBitboard(nil) board.BlackKing = NewBitboard(nil) board.BlackQueen = NewBitboard(nil) board.BlackBishop = NewBitboard(nil) board.BlackKnight = NewBitboard(nil) board.BlackRook = NewBitboard(nil) board.BlackPawn = NewBitboard(nil) board.Turn = turn board.CastleRights = cr board.EnPassent = ep board.HalfMove = halfMove board.FullMove = fullMove for square, piece := range pieces { switch piece { case WhiteKing: board.WhiteKing = board.WhiteKing | NewBitboard(map[Square]bool{square: true}) case WhiteQueen: board.WhiteQueen = board.WhiteQueen | NewBitboard(map[Square]bool{square: true}) case WhiteBishop: board.WhiteBishop = board.WhiteBishop | NewBitboard(map[Square]bool{square: true}) case WhiteKnight: board.WhiteKnight = board.WhiteKnight | NewBitboard(map[Square]bool{square: true}) case WhiteRook: board.WhiteRook = board.WhiteRook | NewBitboard(map[Square]bool{square: true}) case WhitePawn: board.WhitePawn = board.WhitePawn | NewBitboard(map[Square]bool{square: true}) case BlackKing: board.BlackKing = board.BlackKing | NewBitboard(map[Square]bool{square: true}) case BlackQueen: board.BlackQueen = board.BlackQueen | NewBitboard(map[Square]bool{square: true}) case BlackBishop: board.BlackBishop = board.BlackBishop | NewBitboard(map[Square]bool{square: true}) case BlackKnight: board.BlackKnight = board.BlackKnight | NewBitboard(map[Square]bool{square: true}) case BlackRook: board.BlackRook = board.BlackRook | NewBitboard(map[Square]bool{square: true}) case BlackPawn: board.BlackPawn = board.BlackPawn | NewBitboard(map[Square]bool{square: true}) default: panic("invalid piece") } } board.UpdateConvenienceBitboards() return board, nil } // UpdateConvenienceBitboards updates the convenience bitboards. func (b *Board) UpdateConvenienceBitboards() { b.WhitePieces = b.WhiteKing | b.WhiteQueen | b.WhiteBishop | b.WhiteKnight | b.WhiteRook | b.WhitePawn b.BlackPieces = b.BlackKing | b.BlackQueen | b.BlackBishop | b.BlackKnight | b.BlackRook | b.BlackPawn b.FreeSquares = ^(b.WhitePieces | b.BlackPieces) } // IsOccupied returns true if the given field is occupied. func (b Board) IsOccupied(sq Square) bool { return sq.Bitboard()&b.FreeSquares == 0 } // Piece returns the piece on the given field. func (b Board) Piece(sq Square) Piece { contains := func(bb Bitboard, sq Square) bool { for _, square := range bb.Squares() { if square == sq { return true } } return false } if contains(b.WhiteKing, sq) { return WhiteKing } if contains(b.WhiteQueen, sq) { return WhiteQueen } if contains(b.WhiteBishop, sq) { return WhiteBishop } if contains(b.WhiteKnight, sq) { return WhiteKnight } if contains(b.WhiteRook, sq) { return WhiteRook } if contains(b.WhitePawn, sq) { return WhitePawn } if contains(b.BlackKing, sq) { return BlackKing } if contains(b.BlackQueen, sq) { return BlackQueen } if contains(b.BlackBishop, sq) { return BlackBishop } if contains(b.BlackKnight, sq) { return BlackKnight } if contains(b.BlackRook, sq) { return BlackRook } if contains(b.BlackPawn, sq) { return BlackPawn } return NoPiece } func (b Board) String() string { fen := "" for r := 7; r >= 0; r-- { for f := 0; f < 8; f++ { sq := NewSquare(File(f), Rank(r)) p := b.Piece(sq) if p != NoPiece { fen += p.String() } else { fen += "1" } } if r != 0 { fen += "/" } } for i := 8; i > 1; i-- { repeatStr := strings.Repeat("1", i) countStr := strconv.Itoa(i) fen = strings.Replace(fen, repeatStr, countStr, -1) } ep := "-" if b.EnPassent != nil { ep = b.EnPassent.String() } fen += fmt.Sprintf( " %s %s %s %d %d", b.Turn, b.CastleRights, ep, b.HalfMove, b.FullMove, ) return fen } // Draw prints an ASCII art of this board to STDOUT. func (b Board) Draw() string { s := "\n A B C D E F G H\n" for r := 7; r >= 0; r-- { s += Rank(r).String() s += " " for f := 0; f < 8; f++ { p := b.Piece(NewSquare(File(f), Rank(r))) if p == NoPiece { s += "." } else { s += p.Rune() } s += " " } s += "\n" } return s } // UpdatePiece moves the a piece on the board from the given src to dest square. func (b *Board) UpdatePiece(src, dest Square) error { if !b.IsOccupied(src) { return fmt.Errorf("can not move piece on vacant square: %s", src) } if b.IsOccupied(dest) { return fmt.Errorf("can not move to occupied square: %s", dest) } switch b.Piece(src) { case WhiteKing: b.WhiteKing.Flip(src) b.WhiteKing.Flip(dest) case WhiteQueen: b.WhiteQueen.Flip(src) b.WhiteQueen.Flip(dest) case WhiteRook: b.WhiteRook.Flip(src) b.WhiteRook.Flip(dest) case WhiteBishop: b.WhiteBishop.Flip(src) b.WhiteBishop.Flip(dest) case WhiteKnight: b.WhiteKnight.Flip(src) b.WhiteKnight.Flip(dest) case WhitePawn: b.WhitePawn.Flip(src) b.WhitePawn.Flip(dest) case BlackKing: b.BlackKing.Flip(src) b.BlackKing.Flip(dest) case BlackQueen: b.BlackQueen.Flip(src) b.BlackQueen.Flip(dest) case BlackRook: b.BlackRook.Flip(src) b.BlackRook.Flip(dest) case BlackBishop: b.BlackBishop.Flip(src) b.BlackBishop.Flip(dest) case BlackKnight: b.BlackKnight.Flip(src) b.BlackKnight.Flip(dest) case BlackPawn: b.BlackPawn.Flip(src) b.BlackPawn.Flip(dest) default: panic("") } b.UpdateConvenienceBitboards() return nil }