libchess/pkg/board/board.go

304 lines
7.1 KiB
Go
Raw Permalink Normal View History

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
}