ref: e82dcee1a3d54f14c93a19f8fe21965ea72c56ce
dir: /board.go/
package main
import (
"fmt"
"time"
"math"
"sync"
)
// errors
type InvalidMove struct {}
func (e InvalidMove) Error() string {
return fmt.Sprintf("invalid move")
}
type IllegalMove struct {}
func (e IllegalMove) Error() string {
return fmt.Sprintf("illegal move")
}
// Chess pieces
type Piece uint
const (
King = iota
Queen
Rook
Bishop
Knight
Pawn
NoPiece // denotes the lack of a taken piece
AnyPiece // for internal use (i.e checks, etc)
)
// Piece color
type Color bool
const (
White = false
Black = true
)
// Colored piece
type PieceColored struct {
Piece Piece
Color Color
}
func (p *PieceColored) ToPretty() rune {
switch (p.Color) {
case White:
switch (p.Piece) {
case King:
return '♔'
case Queen:
return '♕'
case Rook:
return '♖'
case Bishop:
return '♗'
case Knight:
return '♘'
case Pawn:
return '♙'
default:
return ' '
}
case Black:
switch (p.Piece) {
case King:
return '♚'
case Queen:
return '♛'
case Rook:
return '♜'
case Bishop:
return '♝'
case Knight:
return '♞'
case Pawn:
return '♟'
default:
return ' '
}
}
return ' '
}
// Helper functions
func Abs(i int) int {
return int(math.Abs(float64(i)))
}
// Board
type Board struct {
Board [8][8]*PieceColored
Turn Color
PlayerTime int64
LastTick int64
SecondsWhite int64
SecondsBlack int64
History []string
mu sync.Mutex
}
func NewBoard() Board {
return NewBoardDuration(-1)
}
func NewBoardDuration(duration int64) Board {
board := [8][8]*PieceColored {}
for i := 0; i < 8; i++ {
board[1][i] = &PieceColored {
Piece: Pawn,
Color: Black,
}
board[6][i] = &PieceColored {
Piece: Pawn,
Color: White,
}
}
for i, piece := range []Piece { Rook, Knight, Bishop } {
board[0][i] = &PieceColored {
Piece: piece,
Color: Black,
}
board[7][i] = &PieceColored {
Piece: piece,
Color: White,
}
board[0][7 - i] = &PieceColored {
Piece: piece,
Color: Black,
}
board[7][7 - i] = &PieceColored {
Piece: piece,
Color: White,
}
}
board[0][3] = &PieceColored {
Piece: King,
Color: Black,
}
board[0][4] = &PieceColored {
Piece: Queen,
Color: Black,
}
board[7][3] = &PieceColored {
Piece: King,
Color: White,
}
board[7][4] = &PieceColored {
Piece: Queen,
Color: White,
}
return Board {
Board: board,
Turn: White,
PlayerTime: duration,
LastTick: time.Now().Unix(),
SecondsWhite: 0,
SecondsBlack: 0,
mu: sync.Mutex {},
}
}
func (b *Board) Clone() *Board {
newBoard := NewBoardDuration(b.PlayerTime)
newBoard.Board = b.Board
newBoard.Turn = b.Turn
newBoard.PlayerTime = b.PlayerTime
newBoard.LastTick = b.LastTick
newBoard.SecondsWhite = b.SecondsWhite
newBoard.SecondsBlack = b.SecondsBlack
newBoard.History = b.History
newBoard.mu = sync.Mutex {}
return &newBoard
}
func (b *Board) Reset() {
newBoard := NewBoardDuration(b.PlayerTime)
b.mu.Lock()
b.Board = newBoard.Board
b.Turn = newBoard.Turn
b.PlayerTime = newBoard.PlayerTime
b.LastTick = newBoard.LastTick
b.SecondsWhite = newBoard.SecondsWhite
b.SecondsBlack = newBoard.SecondsBlack
b.History = newBoard.History
b.mu = sync.Mutex {}
}
func (b *Board) getKing() Coordinate {
var kingCoordinates Coordinate
for i := 0; i < 8; i++ {
for j := 0; j < 8; j++ {
if b.Board[i][j] != nil {
piece := b.Board[i][j]
p := piece.Piece
c := piece.Color
if c == b.Turn {
if p == King {
kingCoordinates = Coordinate {
X: i,
Y: j,
}
}
}
}
}
}
return kingCoordinates
}
func (b *Board) IsCheck() bool {
kingCoordinates := b.getKing()
for i := 0; i < 8; i++ {
for j := 0; j < 8; j++ {
if b.Board[i][j] == nil {
continue
}
if i == kingCoordinates.X && j == kingCoordinates.Y {
continue
}
piece := b.Board[i][j]
if piece.Color == b.Turn {
continue
}
checkMove := Move {
Tp: Capture,
Piece: piece.Piece,
Takes: AnyPiece,
From: &Coordinate {
X: i,
Y: j,
},
To: &kingCoordinates,
}
err := b.ValidateMove(&checkMove)
if err == nil {
fmt.Printf("is check\n")
return true
}
}
}
return false
}
func (b *Board) IsCheckmate() bool {
if !b.IsCheck() {
return false
}
for i := 0; i < 8; i++ {
for j := 0; j < 8; j++ {
if b.Board[i][j] == nil {
continue
}
piece := b.Board[i][j]
if piece.Color != b.Turn {
continue
}
for k := 0; k < 8; k++ {
for l := 0; l < 8; l++ {
uncheckMove := Move {
Tp: Capture,
Piece: piece.Piece,
Takes: AnyPiece,
From: &Coordinate {
X: i,
Y: j,
},
To: &Coordinate {
X: k,
Y: l,
},
}
err := b.ValidateMove(&uncheckMove)
if err == nil {
fmt.Printf("is checkmate?\n")
if !b.IsCheck() {
return false
}
}
}
}
}
}
return true
}
func (b *Board) FindSingleMove(piece Piece, to *Coordinate) *Coordinate {
var from *Coordinate
for i := 0; i < 8; i++ {
for j := 0; j < 8; j++ {
testedMove := Move {
Tp: GenericMove,
Piece: piece,
Takes: AnyPiece,
From: &Coordinate {
X: i,
Y: j,
},
To: to,
}
if b.ValidateMove(&testedMove) == nil {
if from != nil {
return nil
}
from = testedMove.From
}
}
}
return from
}
func (b *Board) ChangeTurn() {
b.UpdateTime()
b.CheckTime()
b.mu.Lock()
defer b.mu.Unlock()
switch (b.Turn) {
case White:
b.Turn = Black
case Black:
b.Turn = White
}
}
func (b *Board) UpdateTime() {
b.mu.Lock()
defer b.mu.Unlock()
currentTime := time.Now().Unix()
switch (b.Turn) {
case White:
b.SecondsWhite = b.SecondsWhite + (currentTime - b.LastTick)
case Black:
b.SecondsBlack = b.SecondsBlack + (currentTime - b.LastTick)
}
b.LastTick = currentTime
}
// TODO implement time-based win/lose
func (b *Board) CheckTime() (bool, Color) {
if b.PlayerTime < 0 {
return false, false
}
if b.SecondsWhite > b.PlayerTime {
return true, Black
}
if b.SecondsBlack > b.PlayerTime {
return true, White
}
return false, false
}
func (b *Board) ValidateMove(m *Move) error {
piece := b.Board[m.From.Y][m.From.X]
if piece == nil {
return InvalidMove {}
}
p := piece.Piece
c := piece.Color
if (m.To.X > 8) || (m.To.Y > 8) {
return InvalidMove {}
}
// check piece validity
if p != m.Piece {
return InvalidMove {}
}
// check color validity
if b.Turn != c {
return IllegalMove {}
}
// check promotion sanity
if m.Tp == Promotion {
if p != Pawn {
return InvalidMove {}
}
}
var canTake = true
// check validity
// TODO implement castling
// TODO implement promotion
// TODO implement en passant
Validity:
switch (p) {
case King:
if b.Board[m.To.Y][m.To.X] != nil {
return InvalidMove {}
}
if (Abs(m.From.X - m.From.Y) > 1) || (Abs(m.To.X - m.To.Y) > 1) {
return InvalidMove {}
}
break Validity
case Queen:
if m.From.X == m.To.X {
for i := m.From.Y; i < m.To.Y; i++ {
if b.Board[i + 1][m.From.X] != nil {
return InvalidMove {}
}
}
break Validity
}
if m.From.Y == m.To.Y {
for i := m.From.X; i < m.To.X; i++ {
if b.Board[m.From.Y][i + 1] != nil {
return InvalidMove {}
}
}
break Validity
}
if Abs(m.From.X - m.To.X) == Abs(m.From.Y - m.To.Y) {
var dirX = 1
var dirY = 1
if m.From.X > m.To.X {
dirX = -1
}
if m.From.Y > m.To.Y {
dirY = -1
}
for i := 1; i < Abs(m.From.X - m.To.X); i++ {
if b.Board[m.From.Y + i * dirY][m.From.X + i * dirX] != nil {
return InvalidMove {}
}
}
break Validity
}
return InvalidMove {}
case Rook:
if m.From.X == m.To.X {
for i := m.From.Y; i < m.To.Y; i++ {
if b.Board[i + 1][m.From.X] != nil {
return InvalidMove {}
}
}
break Validity
}
if m.From.Y == m.To.Y {
for i := m.From.X; i < m.To.X; i++ {
if b.Board[m.From.Y][i + 1] != nil {
return InvalidMove {}
}
}
break Validity
}
return InvalidMove {}
case Bishop:
if Abs(m.From.X - m.To.X) != Abs(m.From.Y - m.To.Y) {
return InvalidMove {}
}
var dirX = 1
var dirY = 1
if m.From.X > m.To.X {
dirX = -1
}
if m.From.Y > m.To.Y {
dirY = -1
}
for i := 1; i < Abs(m.From.X - m.To.X); i++ {
if b.Board[m.From.Y + i * dirY][m.From.X + i * dirX] != nil {
return InvalidMove {}
}
}
case Knight:
var valid = false
valid = valid || (((m.From.X - m.To.X) == -1) && ((m.From.Y - m.To.Y) == -2))
valid = valid || (((m.From.X - m.To.X) == -2) && ((m.From.Y - m.To.Y) == -1))
valid = valid || (((m.From.X - m.To.X) == -1) && ((m.From.Y - m.To.Y) == 2))
valid = valid || (((m.From.X - m.To.X) == -2) && ((m.From.Y - m.To.Y) == 1))
valid = valid || (((m.From.X - m.To.X) == 1) && ((m.From.Y - m.To.Y) == 2))
valid = valid || (((m.From.X - m.To.X) == 2) && ((m.From.Y - m.To.Y) == 1))
valid = valid || (((m.From.X - m.To.X) == 1) && ((m.From.Y - m.To.Y) == -2))
valid = valid || (((m.From.X - m.To.X) == 2) && ((m.From.Y - m.To.Y) == -1))
if !valid {
return InvalidMove {}
}
case Pawn:
var maxSquares = 1
var direction = 1
if b.Turn == White && m.From.Y == 6 {
maxSquares = 2
}
if b.Turn == Black && m.From.Y == 1 {
maxSquares = 2
}
if b.Turn == White {
direction = -1
}
if b.Turn == Black {
direction = 1
}
// TODO en passant
if Abs(m.From.X - m.To.X) > 1 {
return InvalidMove {}
}
if Abs(m.From.X - m.To.X) == 1 {
if Abs(m.From.Y - m.To.Y) > 1 {
return InvalidMove {}
}
if b.Board[m.To.Y][m.To.X] == nil {
return InvalidMove {}
}
} else {
if b.Board[m.To.Y][m.To.X] != nil {
return InvalidMove {}
}
}
disp := (m.To.Y - m.From.Y) * direction
if disp > maxSquares {
return InvalidMove {}
}
if m.Tp == Promotion {
if b.Turn == White && m.To.Y != 0 {
return InvalidMove {}
}
if b.Turn == Black && m.To.Y != 7 {
return InvalidMove {}
}
}
if b.Turn == White && m.To.Y == 0 {
if m.Tp != Promotion {
return InvalidMove {}
}
}
if b.Turn == Black && m.To.Y == 7 {
if m.Tp != Promotion {
return InvalidMove {}
}
}
}
// Check that taken piece has the right color
takenPiece := b.Board[m.To.Y][m.To.X]
if takenPiece != nil {
if !canTake {
return IllegalMove {}
}
if takenPiece.Color == b.Turn {
return IllegalMove {}
}
if takenPiece.Piece != m.Takes && m.Takes != AnyPiece {
return IllegalMove {}
}
} else {
if m.Takes != NoPiece && m.Takes != AnyPiece {
return IllegalMove {}
}
}
// Simulate
var n = b.Clone()
n.Board[m.From.Y][m.From.X] = nil
n.Board[m.To.Y][m.To.X] = &PieceColored {
Piece: m.Piece,
Color: b.Turn,
}
valid := n.IsCheck() || n.IsCheckmate()
if valid {
return IllegalMove {}
}
return nil
}
func (b *Board) MovePiece(m *Move) error {
b.mu.Lock()
// Validate move
var err = b.ValidateMove(m)
if err != nil {
b.mu.Unlock()
return err
}
// Replace board and change turn
var n = b
n.Board[m.From.Y][m.From.X] = nil
replacingPiece := &PieceColored {
Piece: m.Piece,
Color: b.Turn,
}
if m.Tp == Promotion {
replacingPiece.Piece = m.PromotionPiece
}
n.Board[m.To.Y][m.To.X] = replacingPiece
b.Board = n.Board
b.History = append(b.History, m.Notation)
b.mu.Unlock()
b.ChangeTurn()
return nil
}