ref: 03c4b270665987e034d6589d595e4e6b638a02ab
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 }