Basic implementation of bitboard, board and move

This commit is contained in:
baccenfutter 2020-05-08 22:53:19 +02:00
parent 07adc3474a
commit c21a117a23
Signed by: baccenfutter
GPG Key ID: 9EF0A3998363DBC9
7 changed files with 988 additions and 0 deletions

82
pkg/board/bitboard.go Normal file
View File

@ -0,0 +1,82 @@
package board
import (
"strconv"
)
// Bitboard is defined as
type Bitboard uint64
// NewBitboard returns an initialized Bitboard.
func NewBitboard(m map[Square]bool) Bitboard {
s := ""
for sq := 0; sq < 64; sq++ {
if m[Square(sq)] {
s += "1"
} else {
s += "0"
}
}
bb, err := strconv.ParseUint(s, 2, 64)
if err != nil {
panic(err)
}
return Bitboard(bb)
}
// Map returns a map[Square]bool representation of this Bitboard.
func (bb Bitboard) Map() map[Square]bool {
m := map[Square]bool{}
for sq := 0; sq < 64; sq++ {
if bb&Square(sq).Bitboard() > 0 {
m[Square(sq)] = true
}
}
return m
}
// Squares returns a []Square of all bits in this Bitboard.
func (bb Bitboard) Squares() []Square {
squares := []Square{}
for sq := 0; sq < 64; sq++ {
if bb&Square(sq).Bitboard() > 0 {
squares = append(squares, Square(sq))
}
}
return squares
}
// Occupied returns true if the given Square is occupied.
func (bb Bitboard) Occupied(sq Square) bool {
return (uint64(bb) >> uint64(63-sq) & 1) == 1
}
// Draw returns visual representation of the bitboard useful for debugging.
func (bb Bitboard) 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++ {
sq := NewSquare(File(f), Rank(r))
if bb.Occupied(sq) {
s += "1"
} else {
s += "0"
}
s += " "
}
s += "\n"
}
return s
}
// Flip toggles the bit at the given square.
func (bb *Bitboard) Flip(sq Square) {
if !sq.IsValid() {
return
}
oldBB := *bb
newBB := oldBB ^ sq.Bitboard()
*bb = newBB
}

303
pkg/board/board.go Normal file
View File

@ -0,0 +1,303 @@
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
}

73
pkg/board/move.go Normal file
View File

@ -0,0 +1,73 @@
package board
import (
"fmt"
"strings"
)
// MoveProp is defined as
// <piece><disambiguity><captures><square><promote><check|mate>
type MoveProp uint16
// Available MoveProps
const (
QueenSideCastle MoveProp = 1 << iota
KingSideCastle
Capture
EnPassant
Check
Mate
inCheck
)
// Move is defined as
type Move struct {
Piece PieceType
FromRank *Rank
FromFile *File
To Square
PromoteTo PieceType
Props MoveProp
}
func (m Move) String() string {
out := ""
if m.HasProp(KingSideCastle) {
out += "O-O"
} else if m.HasProp(QueenSideCastle) {
out += "O-O-O"
} else {
if m.Piece != NoPieceType && m.Piece != Pawn {
out += strings.ToUpper(m.Piece.String())
}
if m.FromFile != nil {
out += m.FromFile.String()
}
if m.FromRank != nil {
out += m.FromRank.String()
}
if m.HasProp(Capture) {
out += "x"
}
out += m.To.String()
if m.PromoteTo != NoPieceType {
out += fmt.Sprintf("=%s", m.PromoteTo.String())
}
}
if m.HasProp(Check) {
out += "+"
} else if m.HasProp(Mate) {
out += "#"
}
return out
}
// AddProp adds the given MoveProp to this move.
func (m *Move) AddProp(prop MoveProp) {
m.Props = m.Props | prop
}
// HasProp returns true if this move has the given MoveProp.
func (m Move) HasProp(prop MoveProp) bool {
return m.Props&prop > 0
}

205
pkg/board/pieces.go Normal file
View File

@ -0,0 +1,205 @@
package board
import (
"fmt"
"strings"
"code.c-base.org/gochess/libchess/pkg/runes"
)
// PieceType defines the type of a piece
type PieceType uint8
// The following PieceTypes exist
const (
NoPieceType PieceType = iota
King
Queen
Rook
Bishop
Knight
Pawn
)
func (pt PieceType) String() string {
switch pt {
case King:
return "k"
case Queen:
return "q"
case Rook:
return "r"
case Bishop:
return "b"
case Knight:
return "n"
case Pawn:
return "p"
default:
return "-"
}
}
// PromotableTo returns true if a pawn can be promoted to the given PieceType.
func (pt PieceType) PromotableTo() bool {
switch pt {
case Queen, Rook, Bishop, Knight:
return true
}
return false
}
// PieceTypes returns a slice of all piece types.
func PieceTypes() [6]PieceType {
return [6]PieceType{King, Queen, Rook, Bishop, Knight, Pawn}
}
// Piece is defined as
type Piece uint8
// The following Pieces exist:
const (
NoPiece Piece = iota
// White
WhiteKing
WhiteQueen
WhiteRook
WhiteBishop
WhiteKnight
WhitePawn
// Black
BlackKing
BlackQueen
BlackRook
BlackBishop
BlackKnight
BlackPawn
)
// AllPieces returns a []Piece containing with all available Pieces.
func AllPieces() []Piece {
return []Piece{
WhiteKing, WhiteQueen, WhiteRook, WhiteBishop, WhiteKnight, WhitePawn,
BlackKing, BlackQueen, BlackRook, BlackBishop, BlackKnight, BlackPawn,
}
}
// NewPiece returns an initialized Piece based on the given PieceType and Color.
func NewPiece(t PieceType, c Color) Piece {
for _, p := range AllPieces() {
if p.Color() == c && p.Type() == t {
return p
}
}
return NoPiece
}
// NewPieceByFEN returns an initialized piece based on the given FEN letter representation.
func NewPieceByFEN(c rune) Piece {
switch c {
case 'K':
return WhiteKing
case 'Q':
return WhiteQueen
case 'R':
return WhiteRook
case 'B':
return WhiteBishop
case 'N':
return WhiteKnight
case 'P':
return WhitePawn
case 'k':
return BlackKing
case 'q':
return BlackQueen
case 'r':
return BlackRook
case 'b':
return BlackBishop
case 'n':
return BlackKnight
case 'p':
return BlackPawn
case '1':
return NoPiece
default:
panic(fmt.Errorf("unable to parse piece: %s", string(c)))
}
}
func (p Piece) String() string {
s := p.Type().String()
if p.Color() == White {
s = strings.ToUpper(s)
}
return s
}
// Rune returns the UTF-8 char for this piece.
func (p Piece) Rune() string {
var r rune
switch p.Color() {
case White:
switch p.Type() {
case King:
r = runes.WhiteKing
case Queen:
r = runes.WhiteQueen
case Rook:
r = runes.WhiteRook
case Bishop:
r = runes.WhiteBishop
case Knight:
r = runes.WhiteKnight
case Pawn:
r = runes.WhitePawn
}
case Black:
switch p.Type() {
case King:
r = runes.BlackKing
case Queen:
r = runes.BackQueen
case Rook:
r = runes.BlackRook
case Bishop:
r = runes.BlackBishop
case Knight:
r = runes.BlackKnight
case Pawn:
r = runes.BlackPawn
}
}
return string(r)
}
// Type returns the type of the piece.
func (p Piece) Type() PieceType {
switch p {
case WhiteKing, BlackKing:
return King
case WhiteQueen, BlackQueen:
return Queen
case WhiteRook, BlackRook:
return Rook
case WhiteBishop, BlackBishop:
return Bishop
case WhiteKnight, BlackKnight:
return Knight
case WhitePawn, BlackPawn:
return Pawn
}
return NoPieceType
}
// Color returns the color of the piece.
func (p Piece) Color() Color {
switch p {
case WhiteKing, WhiteQueen, WhiteRook, WhiteBishop, WhiteKnight, WhitePawn:
return White
case BlackKing, BlackQueen, BlackRook, BlackBishop, BlackKnight, BlackPawn:
return Black
}
return NoColor
}

268
pkg/board/squares.go Normal file
View File

@ -0,0 +1,268 @@
package board
// File represents a file of squares on the chess board.
type File int8
// The following files exist:
const (
FileA File = iota
FileB
FileC
FileD
FileE
FileF
FileG
FileH
)
// FileChars contains the available file letters a-h.
const FileChars = "abcdefgh"
func (f File) String() string {
return FileChars[f : f+1]
}
// Next returns the next File.
func (f File) Next() File {
return File(f + 1)
}
// Previous returns the previous File.
func (f File) Previous() File {
return File(f - 1)
}
// Shift returns the theoretical File after shifting by the given int.
func (f File) Shift(i int) File {
return File(int(f) + i)
}
// IsValid returns true if this file is valid.
func (f File) IsValid() bool {
return FileA <= f && f <= FileH
}
// Rank represents a rank of squares on the chess board.
type Rank int8
// The following ranks exist:
const (
Rank1 Rank = iota
Rank2
Rank3
Rank4
Rank5
Rank6
Rank7
Rank8
)
// RankChars contains all rank chars
const RankChars = "12345678"
func (r Rank) String() string {
return RankChars[r : r+1]
}
// Next returns the next Rank.
func (r Rank) Next() Rank {
return Rank(r + 1)
}
// Previous returns the previous Rank.
func (r Rank) Previous() Rank {
return Rank(r - 1)
}
// Shift returns the theoretical Rank after shifting the given int.
func (r Rank) Shift(i int) Rank {
return Rank(int(r) + i)
}
// IsValid returns true if this is a valid rank.
func (r Rank) IsValid() bool {
return Rank1 <= r && r <= Rank8
}
// Color is defined as
type Color uint8
// Two colors exist
const (
NoColor Color = iota
White
Black
)
func (c Color) String() string {
switch c {
case White:
return "w"
case Black:
return "b"
}
return "-"
}
// Name returns the name of the color.
func (c Color) Name() string {
switch c {
case White:
return "White"
case Black:
return "Black"
}
return "NoColor"
}
// Other returns the opposite color.
func (c Color) Other() Color {
switch c {
case White:
return Black
case Black:
return White
}
return NoColor
}
// Side is defined as
type Side uint8
// The sides of the board are .
const (
QueenSide Side = iota
KingSide
)
// Square represents a square on the chess board.
type Square int8
// The following Squares exist
const (
NoSquare Square = iota - 1
A1
B1
C1
D1
E1
F1
G1
H1
A2
B2
C2
D2
E2
F2
G2
H2
A3
B3
C3
D3
E3
F3
G3
H3
A4
B4
C4
D4
E4
F4
G4
H4
A5
B5
C5
D5
E5
F5
G5
H5
A6
B6
C6
D6
E6
F6
G6
H6
A7
B7
C7
D7
E7
F7
G7
H7
A8
B8
C8
D8
E8
F8
G8
H8
)
// StrToSquareMap maps all Squares to the string representation.
var StrToSquareMap = map[string]Square{
"a1": A1, "a2": A2, "a3": A3, "a4": A4, "a5": A5, "a6": A6, "a7": A7, "a8": A8,
"b1": B1, "b2": B2, "b3": B3, "b4": B4, "b5": B5, "b6": B6, "b7": B7, "b8": B8,
"c1": C1, "c2": C2, "c3": C3, "c4": C4, "c5": C5, "c6": C6, "c7": C7, "c8": C8,
"d1": D1, "d2": D2, "d3": D3, "d4": D4, "d5": D5, "d6": D6, "d7": D7, "d8": D8,
"e1": E1, "e2": E2, "e3": E3, "e4": E4, "e5": E5, "e6": E6, "e7": E7, "e8": E8,
"f1": F1, "f2": F2, "f3": F3, "f4": F4, "f5": F5, "f6": F6, "f7": F7, "f8": F8,
"g1": G1, "g2": G2, "g3": G3, "g4": G4, "g5": G5, "g6": G6, "g7": G7, "g8": G8,
"h1": H1, "h2": H2, "h3": H3, "h4": H4, "h5": H5, "h6": H6, "h7": H7, "h8": H8,
}
// NewSquare returns an initialized Square.
func NewSquare(f File, r Rank) Square {
return Square((int(r) * 8) + int(f))
}
// Color returns the color of this Square.
func (sq Square) Color() Color {
if ((sq / 8) % 2) == (sq % 2) {
return Black
}
return White
}
// File returns the File this Square is in.
func (sq Square) File() File {
return File(uint(sq) % 8)
}
// Rank returns the Rank the Square is in.
func (sq Square) Rank() Rank {
return Rank(uint(sq) / 8)
}
// Side returns the side of board this square is on.
func (sq Square) Side() Side {
if sq.File() <= FileA && sq.File() <= FileD {
return QueenSide
}
return KingSide
}
func (sq Square) String() string {
if sq == NoSquare {
return "-"
}
return sq.File().String() + sq.Rank().String()
}
// IsValid returns true if this is a valid square.
func (sq Square) IsValid() bool {
return int(A1) <= int(sq) && int(sq) <= int(H8)
}
// Bitboard returns the Bitboard for this square.
func (sq Square) Bitboard() Bitboard {
return Bitboard(uint64(1) << (uint8(63) - uint8(sq)))
}

38
pkg/game/game.go Normal file
View File

@ -0,0 +1,38 @@
package game
import (
"fmt"
"code.c-base.org/gochess/libchess/pkg/board"
"code.c-base.org/gochess/libchess/pkg/fen"
)
// Tag serves as data-container for game tags.
type Tag struct {
Key string
Value string
}
// Game represents a game of chess.
type Game struct {
Tags []Tag
Board *board.Board
Moves []*board.Move
}
// NewGame returns an initialized game.
func NewGame() *Game {
b, err := fen.Import(fen.DefaultSetup)
if err != nil {
panic(err)
}
return &Game{
Tags: []Tag{},
Board: b,
Moves: []*board.Move{},
}
}
func (g Game) String() string {
return fmt.Sprintf("<Game(Tags: %q, Moves: %q)>", g.Tags, g.Moves)
}

19
pkg/runes/runes.go Normal file
View File

@ -0,0 +1,19 @@
package runes
// The relevant unicode runes for chess are:
const (
WhiteKing = '♔'
WhiteQueen = '♕'
WhiteRook = '♖'
WhiteBishop = '♗'
WhiteKnight = '♘'
WhitePawn = '♙'
BlackKing = '♚'
BackQueen = '♛'
BlackRook = '♜'
BlackBishop = '♝'
BlackKnight = '♞'
BlackPawn = '♟'
Check = '†'
Mate = '‡'
)