You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

424 lines
11 KiB

package chess
import (
"bytes"
"encoding/binary"
"errors"
"strconv"
"strings"
)
// A Board represents a chess board and its relationship between squares and pieces.
type Board struct {
bbWhiteKing bitboard
bbWhiteQueen bitboard
bbWhiteRook bitboard
bbWhiteBishop bitboard
bbWhiteKnight bitboard
bbWhitePawn bitboard
bbBlackKing bitboard
bbBlackQueen bitboard
bbBlackRook bitboard
bbBlackBishop bitboard
bbBlackKnight bitboard
bbBlackPawn bitboard
whiteSqs bitboard
blackSqs bitboard
emptySqs bitboard
whiteKingSq Square
blackKingSq Square
}
// NewBoard returns a board from a square to piece mapping.
func NewBoard(m map[Square]Piece) *Board {
b := &Board{}
for _, p1 := range allPieces {
bm := map[Square]bool{}
for sq, p2 := range m {
if p1 == p2 {
bm[sq] = true
}
}
bb := newBitboard(bm)
b.setBBForPiece(p1, bb)
}
b.calcConvienceBBs(nil)
return b
}
// SquareMap returns a mapping of squares to pieces. A square is only added to the map if it is occupied.
func (b *Board) SquareMap() map[Square]Piece {
m := map[Square]Piece{}
for sq := 0; sq < numOfSquaresInBoard; sq++ {
p := b.Piece(Square(sq))
if p != NoPiece {
m[Square(sq)] = p
}
}
return m
}
// Rotate rotates the board 90 degrees clockwise.
func (b *Board) Rotate() *Board {
return b.Flip(UpDown).Transpose()
}
// FlipDirection is the direction for the Board.Flip method
type FlipDirection int
const (
// UpDown flips the board's rank values
UpDown FlipDirection = iota
// LeftRight flips the board's file values
LeftRight
)
// Flip flips the board over the vertical or hoizontal
// center line.
func (b *Board) Flip(fd FlipDirection) *Board {
m := map[Square]Piece{}
for sq := 0; sq < numOfSquaresInBoard; sq++ {
var mv Square
switch fd {
case UpDown:
file := Square(sq).File()
rank := Rank(7 - Square(sq).Rank())
mv = getSquare(file, rank)
case LeftRight:
file := File(7 - Square(sq).File())
rank := Square(sq).Rank()
mv = getSquare(file, rank)
}
m[mv] = b.Piece(Square(sq))
}
return NewBoard(m)
}
// Transpose flips the board over the A8 to H1 diagonal.
func (b *Board) Transpose() *Board {
m := map[Square]Piece{}
for sq := 0; sq < numOfSquaresInBoard; sq++ {
file := File(7 - Square(sq).Rank())
rank := Rank(7 - Square(sq).File())
mv := getSquare(file, rank)
m[mv] = b.Piece(Square(sq))
}
return NewBoard(m)
}
// Draw returns visual representation of the board useful for debugging.
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()
for f := 0; f < numOfSquaresInRow; f++ {
p := b.Piece(getSquare(File(f), Rank(r)))
if p == NoPiece {
s += "-"
} else {
s += p.String()
}
s += " "
}
s += "\n"
}
return s
}
// String implements the fmt.Stringer interface and returns
// a string in the FEN board format: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR
func (b *Board) String() string {
fen := ""
for r := 7; r >= 0; r-- {
for f := 0; f < numOfSquaresInRow; f++ {
sq := getSquare(File(f), Rank(r))
p := b.Piece(sq)
if p != NoPiece {
fen += p.getFENChar()
} 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)
}
return fen
}
// Piece returns the piece for the given square.
func (b *Board) Piece(sq Square) Piece {
for _, p := range allPieces {
bb := b.bbForPiece(p)
if bb.Occupied(sq) {
return p
}
}
return NoPiece
}
// MarshalText implements the encoding.TextMarshaler interface and returns
// a string in the FEN board format: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR
func (b *Board) MarshalText() (text []byte, err error) {
return []byte(b.String()), nil
}
// UnmarshalText implements the encoding.TextUnarshaler interface and takes
// a string in the FEN board format: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR
func (b *Board) UnmarshalText(text []byte) error {
cp, err := fenBoard(string(text))
if err != nil {
return err
}
*b = *cp
return nil
}
// MarshalBinary implements the encoding.BinaryMarshaler interface and returns
// the bitboard representations as a array of bytes. Bitboads are encoded
// in the following order: WhiteKing, WhiteQueen, WhiteRook, WhiteBishop, WhiteKnight
// WhitePawn, BlackKing, BlackQueen, BlackRook, BlackBishop, BlackKnight, BlackPawn
func (b *Board) MarshalBinary() (data []byte, err error) {
bbs := []bitboard{b.bbWhiteKing, b.bbWhiteQueen, b.bbWhiteRook, b.bbWhiteBishop, b.bbWhiteKnight, b.bbWhitePawn,
b.bbBlackKing, b.bbBlackQueen, b.bbBlackRook, b.bbBlackBishop, b.bbBlackKnight, b.bbBlackPawn}
buf := new(bytes.Buffer)
err = binary.Write(buf, binary.BigEndian, bbs)
return buf.Bytes(), err
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface and parses
// the bitboard representations as a array of bytes. Bitboads are decoded
// in the following order: WhiteKing, WhiteQueen, WhiteRook, WhiteBishop, WhiteKnight
// WhitePawn, BlackKing, BlackQueen, BlackRook, BlackBishop, BlackKnight, BlackPawn
func (b *Board) UnmarshalBinary(data []byte) error {
if len(data) != 96 {
return errors.New("chess: invalid number of bytes for board unmarshal binary")
}
b.bbWhiteKing = bitboard(binary.BigEndian.Uint64(data[:8]))
b.bbWhiteQueen = bitboard(binary.BigEndian.Uint64(data[8:16]))
b.bbWhiteRook = bitboard(binary.BigEndian.Uint64(data[16:24]))
b.bbWhiteBishop = bitboard(binary.BigEndian.Uint64(data[24:32]))
b.bbWhiteKnight = bitboard(binary.BigEndian.Uint64(data[32:40]))
b.bbWhitePawn = bitboard(binary.BigEndian.Uint64(data[40:48]))
b.bbBlackKing = bitboard(binary.BigEndian.Uint64(data[48:56]))
b.bbBlackQueen = bitboard(binary.BigEndian.Uint64(data[56:64]))
b.bbBlackRook = bitboard(binary.BigEndian.Uint64(data[64:72]))
b.bbBlackBishop = bitboard(binary.BigEndian.Uint64(data[72:80]))
b.bbBlackKnight = bitboard(binary.BigEndian.Uint64(data[80:88]))
b.bbBlackPawn = bitboard(binary.BigEndian.Uint64(data[88:96]))
b.calcConvienceBBs(nil)
return nil
}
func (b *Board) update(m *Move) {
p1 := b.Piece(m.s1)
s1BB := bbForSquare(m.s1)
s2BB := bbForSquare(m.s2)
// move s1 piece to s2
for _, p := range allPieces {
bb := b.bbForPiece(p)
// remove what was at s2
b.setBBForPiece(p, bb & ^s2BB)
// move what was at s1 to s2
if bb.Occupied(m.s1) {
bb = b.bbForPiece(p)
b.setBBForPiece(p, (bb & ^s1BB)|s2BB)
}
}
// check promotion
if m.promo != NoPieceType {
newPiece := getPiece(m.promo, p1.Color())
// remove pawn
bbPawn := b.bbForPiece(p1)
b.setBBForPiece(p1, bbPawn & ^s2BB)
// add promo piece
bbPromo := b.bbForPiece(newPiece)
b.setBBForPiece(newPiece, bbPromo|s2BB)
}
// remove captured en passant piece
if m.HasTag(EnPassant) {
if p1.Color() == White {
b.bbBlackPawn = ^(bbForSquare(m.s2) << 8) & b.bbBlackPawn
} else {
b.bbWhitePawn = ^(bbForSquare(m.s2) >> 8) & b.bbWhitePawn
}
}
// move rook for castle
if p1.Color() == White && m.HasTag(KingSideCastle) {
b.bbWhiteRook = (b.bbWhiteRook & ^bbForSquare(H1) | bbForSquare(F1))
} else if p1.Color() == White && m.HasTag(QueenSideCastle) {
b.bbWhiteRook = (b.bbWhiteRook & ^bbForSquare(A1)) | bbForSquare(D1)
} else if p1.Color() == Black && m.HasTag(KingSideCastle) {
b.bbBlackRook = (b.bbBlackRook & ^bbForSquare(H8) | bbForSquare(F8))
} else if p1.Color() == Black && m.HasTag(QueenSideCastle) {
b.bbBlackRook = (b.bbBlackRook & ^bbForSquare(A8)) | bbForSquare(D8)
}
b.calcConvienceBBs(m)
}
func (b *Board) calcConvienceBBs(m *Move) {
whiteSqs := b.bbWhiteKing | b.bbWhiteQueen | b.bbWhiteRook | b.bbWhiteBishop | b.bbWhiteKnight | b.bbWhitePawn
blackSqs := b.bbBlackKing | b.bbBlackQueen | b.bbBlackRook | b.bbBlackBishop | b.bbBlackKnight | b.bbBlackPawn
emptySqs := ^(whiteSqs | blackSqs)
b.whiteSqs = whiteSqs
b.blackSqs = blackSqs
b.emptySqs = emptySqs
if m == nil {
b.whiteKingSq = NoSquare
b.blackKingSq = NoSquare
for sq := 0; sq < numOfSquaresInBoard; sq++ {
sqr := Square(sq)
if b.bbWhiteKing.Occupied(sqr) {
b.whiteKingSq = sqr
} else if b.bbBlackKing.Occupied(sqr) {
b.blackKingSq = sqr
}
}
} else if m.s1 == b.whiteKingSq {
b.whiteKingSq = m.s2
} else if m.s1 == b.blackKingSq {
b.blackKingSq = m.s2
}
}
func (b *Board) copy() *Board {
return &Board{
whiteSqs: b.whiteSqs,
blackSqs: b.blackSqs,
emptySqs: b.emptySqs,
whiteKingSq: b.whiteKingSq,
blackKingSq: b.blackKingSq,
bbWhiteKing: b.bbWhiteKing,
bbWhiteQueen: b.bbWhiteQueen,
bbWhiteRook: b.bbWhiteRook,
bbWhiteBishop: b.bbWhiteBishop,
bbWhiteKnight: b.bbWhiteKnight,
bbWhitePawn: b.bbWhitePawn,
bbBlackKing: b.bbBlackKing,
bbBlackQueen: b.bbBlackQueen,
bbBlackRook: b.bbBlackRook,
bbBlackBishop: b.bbBlackBishop,
bbBlackKnight: b.bbBlackKnight,
bbBlackPawn: b.bbBlackPawn,
}
}
func (b *Board) isOccupied(sq Square) bool {
return !b.emptySqs.Occupied(sq)
}
func (b *Board) hasSufficientMaterial() bool {
// queen, rook, or pawn exist
if (b.bbWhiteQueen | b.bbWhiteRook | b.bbWhitePawn |
b.bbBlackQueen | b.bbBlackRook | b.bbBlackPawn) > 0 {
return true
}
// if king is missing then it is a test
if b.bbWhiteKing == 0 || b.bbBlackKing == 0 {
return true
}
count := map[PieceType]int{}
pieceMap := b.SquareMap()
for _, p := range pieceMap {
count[p.Type()]++
}
// king versus king
if count[Bishop] == 0 && count[Knight] == 0 {
return false
}
// king and bishop versus king
if count[Bishop] == 1 && count[Knight] == 0 {
return false
}
// king and knight versus king
if count[Bishop] == 0 && count[Knight] == 1 {
return false
}
// king and bishop(s) versus king and bishop(s) with the bishops on the same colour.
if count[Knight] == 0 {
whiteCount := 0
blackCount := 0
for sq, p := range pieceMap {
if p.Type() == Bishop {
switch sq.color() {
case White:
whiteCount++
case Black:
blackCount++
}
}
}
if whiteCount == 0 || blackCount == 0 {
return false
}
}
return true
}
func (b *Board) bbForPiece(p Piece) bitboard {
switch p {
case WhiteKing:
return b.bbWhiteKing
case WhiteQueen:
return b.bbWhiteQueen
case WhiteRook:
return b.bbWhiteRook
case WhiteBishop:
return b.bbWhiteBishop
case WhiteKnight:
return b.bbWhiteKnight
case WhitePawn:
return b.bbWhitePawn
case BlackKing:
return b.bbBlackKing
case BlackQueen:
return b.bbBlackQueen
case BlackRook:
return b.bbBlackRook
case BlackBishop:
return b.bbBlackBishop
case BlackKnight:
return b.bbBlackKnight
case BlackPawn:
return b.bbBlackPawn
}
return bitboard(0)
}
func (b *Board) setBBForPiece(p Piece, bb bitboard) {
switch p {
case WhiteKing:
b.bbWhiteKing = bb
case WhiteQueen:
b.bbWhiteQueen = bb
case WhiteRook:
b.bbWhiteRook = bb
case WhiteBishop:
b.bbWhiteBishop = bb
case WhiteKnight:
b.bbWhiteKnight = bb
case WhitePawn:
b.bbWhitePawn = bb
case BlackKing:
b.bbBlackKing = bb
case BlackQueen:
b.bbBlackQueen = bb
case BlackRook:
b.bbBlackRook = bb
case BlackBishop:
b.bbBlackBishop = bb
case BlackKnight:
b.bbBlackKnight = bb
case BlackPawn:
b.bbBlackPawn = bb
default:
panic("invalid piece")
}
}