shithub: chessfs

ref: e82dcee1a3d54f14c93a19f8fe21965ea72c56ce
dir: /board.go/

View raw version
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
}