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.

221 lines
5.8 KiB

package chess
import (
"crypto/md5"
"fmt"
"strconv"
"strings"
)
// Side represents a side of the board.
type Side int
const (
// KingSide is the right side of the board from white's perspective.
KingSide Side = iota + 1
// QueenSide is the left side of the board from white's perspective.
QueenSide
)
// CastleRights holds the state of both sides castling abilities.
type CastleRights string
// CanCastle returns true if the given color and side combination
// can castle, otherwise returns false.
func (cr CastleRights) CanCastle(c Color, side Side) bool {
char := "k"
if side == QueenSide {
char = "q"
}
if c == White {
char = strings.ToUpper(char)
}
return strings.Contains(string(cr), char)
}
// String implements the fmt.Stringer interface and returns
// a FEN compatible string. Ex. KQq
func (cr CastleRights) String() string {
return string(cr)
}
// Position represents the state of the game without reguard
// to its outcome. Position is translatable to FEN notation.
type Position struct {
board *Board
turn Color
castleRights CastleRights
enPassantSquare Square
halfMoveClock int
moveCount int
inCheck bool
validMoves []*Move
}
// Update returns a new position resulting from the given move.
// The move itself isn't validated, if validation is needed use
// Game's Move method. This method is more performant for bots that
// rely on the ValidMoves because it skips redundant validation.
func (pos *Position) Update(m *Move) *Position {
moveCount := pos.moveCount
if pos.turn == Black {
moveCount++
}
cr := pos.CastleRights()
ncr := pos.updateCastleRights(m)
p := pos.board.Piece(m.s1)
halfMove := pos.halfMoveClock
if p.Type() == Pawn || m.HasTag(Capture) || cr != ncr {
halfMove = 0
} else {
halfMove++
}
b := pos.board.copy()
b.update(m)
return &Position{
board: b,
turn: pos.turn.Other(),
castleRights: ncr,
enPassantSquare: pos.updateEnPassantSquare(m),
halfMoveClock: halfMove,
moveCount: moveCount,
inCheck: m.HasTag(Check),
}
}
// ValidMoves returns a list of valid moves for the position.
func (pos *Position) ValidMoves() []*Move {
if pos.validMoves != nil {
return append([]*Move(nil), pos.validMoves...)
}
pos.validMoves = engine{}.CalcMoves(pos, false)
return append([]*Move(nil), pos.validMoves...)
}
// Status returns the position's status as one of the outcome methods.
// Possible returns values include Checkmate, Stalemate, and NoMethod.
func (pos *Position) Status() Method {
return engine{}.Status(pos)
}
// Board returns the position's board.
func (pos *Position) Board() *Board {
return pos.board
}
// Turn returns the color to move next.
func (pos *Position) Turn() Color {
return pos.turn
}
// CastleRights returns the castling rights of the position.
func (pos *Position) CastleRights() CastleRights {
return pos.castleRights
}
// String implements the fmt.Stringer interface and returns a
// string with the FEN format: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
func (pos *Position) String() string {
b := pos.board.String()
t := pos.turn.String()
c := pos.castleRights.String()
sq := "-"
if pos.enPassantSquare != NoSquare {
sq = pos.enPassantSquare.String()
}
return fmt.Sprintf("%s %s %s %s %d %d", b, t, c, sq, pos.halfMoveClock, pos.moveCount)
}
// Hash returns a unique hash of the position
func (pos *Position) Hash() [16]byte {
sq := "-"
if pos.enPassantSquare != NoSquare {
sq = pos.enPassantSquare.String()
}
s := pos.turn.String() + ":" + pos.castleRights.String() + ":" + sq
for _, p := range allPieces {
bb := pos.board.bbForPiece(p)
s += ":" + strconv.FormatUint(uint64(bb), 16)
}
return md5.Sum([]byte(s))
}
// MarshalText implements the encoding.TextMarshaler interface and
// encodes the position's FEN.
func (pos *Position) MarshalText() (text []byte, err error) {
return []byte(pos.String()), nil
}
// UnmarshalText implements the encoding.TextUnarshaler interface and
// assumes the data is in the FEN format.
func (pos *Position) UnmarshalText(text []byte) error {
cp, err := decodeFEN(string(text))
if err != nil {
return err
}
pos.board = cp.board
pos.turn = cp.turn
pos.castleRights = cp.castleRights
pos.enPassantSquare = cp.enPassantSquare
pos.halfMoveClock = cp.halfMoveClock
pos.moveCount = cp.moveCount
pos.inCheck = isInCheck(cp)
return nil
}
func (pos *Position) copy() *Position {
return &Position{
board: pos.board.copy(),
turn: pos.turn,
castleRights: pos.castleRights,
enPassantSquare: pos.enPassantSquare,
halfMoveClock: pos.halfMoveClock,
moveCount: pos.moveCount,
inCheck: pos.inCheck,
}
}
func (pos *Position) updateCastleRights(m *Move) CastleRights {
cr := string(pos.castleRights)
p := pos.board.Piece(m.s1)
if p == WhiteKing || m.s1 == H1 || m.s2 == H1 {
cr = strings.Replace(cr, "K", "", -1)
}
if p == WhiteKing || m.s1 == A1 || m.s2 == A1 {
cr = strings.Replace(cr, "Q", "", -1)
}
if p == BlackKing || m.s1 == H8 || m.s2 == H8 {
cr = strings.Replace(cr, "k", "", -1)
}
if p == BlackKing || m.s1 == A8 || m.s2 == A8 {
cr = strings.Replace(cr, "q", "", -1)
}
if cr == "" {
cr = "-"
}
return CastleRights(cr)
}
func (pos *Position) updateEnPassantSquare(m *Move) Square {
p := pos.board.Piece(m.s1)
if p.Type() != Pawn {
return NoSquare
}
if pos.turn == White &&
(bbForSquare(m.s1)&bbRank2) != 0 &&
(bbForSquare(m.s2)&bbRank4) != 0 {
return Square(m.s2 - 8)
} else if pos.turn == Black &&
(bbForSquare(m.s1)&bbRank7) != 0 &&
(bbForSquare(m.s2)&bbRank5) != 0 {
return Square(m.s2 + 8)
}
return NoSquare
}
func (pos *Position) samePosition(pos2 *Position) bool {
return pos.board.String() == pos2.board.String() &&
pos.turn == pos2.turn &&
pos.castleRights.String() == pos2.castleRights.String() &&
pos.enPassantSquare == pos2.enPassantSquare
}