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
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 |
|
}
|
|
|