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.
224 lines
5.1 KiB
224 lines
5.1 KiB
package chess |
|
|
|
import ( |
|
"fmt" |
|
"strings" |
|
) |
|
|
|
// Encoder is the interface implemented by objects that can |
|
// encode a move into a string given the position. It is not |
|
// the encoders responsibility to validate the move. |
|
type Encoder interface { |
|
Encode(pos *Position, m *Move) string |
|
} |
|
|
|
// Decoder is the interface implemented by objects that can |
|
// decode a string into a move given the position. It is not |
|
// the decoders responsibility to validate the move. An error |
|
// is returned if the string could not be decoded. |
|
type Decoder interface { |
|
Decode(pos *Position, s string) (*Move, error) |
|
} |
|
|
|
// Notation is the interface implemented by objects that can |
|
// encode and decode moves. |
|
type Notation interface { |
|
Encoder |
|
Decoder |
|
} |
|
|
|
// LongAlgebraicNotation is a more computer friendly alternative to algebraic |
|
// notation. This notation uses the same format as the UCI (Universal Chess |
|
// Interface). Examples: e2e4, e7e5, e1g1 (white short castling), e7e8q (for promotion) |
|
type LongAlgebraicNotation struct{} |
|
|
|
// String implements the fmt.Stringer interface and returns |
|
// the notation's name. |
|
func (_ LongAlgebraicNotation) String() string { |
|
return "Long Algebraic Notation" |
|
} |
|
|
|
// Encode implements the Encoder interface. |
|
func (_ LongAlgebraicNotation) Encode(pos *Position, m *Move) string { |
|
return m.S1().String() + m.S2().String() + m.Promo().String() |
|
} |
|
|
|
// Decode implements the Decoder interface. |
|
func (_ LongAlgebraicNotation) Decode(pos *Position, s string) (*Move, error) { |
|
l := len(s) |
|
err := fmt.Errorf(`chess: failed to decode long algebraic notation text "%s" for position %s`, s, pos.String()) |
|
if l < 4 || l > 5 { |
|
return nil, err |
|
} |
|
s1, ok := strToSquareMap[s[0:2]] |
|
if !ok { |
|
return nil, err |
|
} |
|
s2, ok := strToSquareMap[s[2:4]] |
|
if !ok { |
|
return nil, err |
|
} |
|
promo := NoPieceType |
|
if l == 5 { |
|
promo = pieceTypeFromChar(s[4:5]) |
|
if promo == NoPieceType { |
|
return nil, err |
|
} |
|
} |
|
m := &Move{s1: s1, s2: s2, promo: promo} |
|
p := pos.Board().Piece(s1) |
|
if p.Type() == King { |
|
if (s1 == E1 && s2 == G1) || (s1 == E8 && s2 == G8) { |
|
m.addTag(KingSideCastle) |
|
} else if (s1 == E1 && s2 == C1) || (s1 == E8 && s2 == C8) { |
|
m.addTag(QueenSideCastle) |
|
} |
|
} else if p.Type() == Pawn && s2 == pos.enPassantSquare { |
|
m.addTag(EnPassant) |
|
m.addTag(Capture) |
|
} |
|
c1 := p.Color() |
|
c2 := pos.Board().Piece(s2).Color() |
|
if c2 != NoColor && c1 != c2 { |
|
m.addTag(Capture) |
|
} |
|
return m, nil |
|
} |
|
|
|
// AlgebraicNotation (or Standard Algebraic Notation) is the |
|
// official chess notation used by FIDE. Examples: e2, e5, |
|
// O-O (short castling), e8=Q (promotion) |
|
type AlgebraicNotation struct{} |
|
|
|
// String implements the fmt.Stringer interface and returns |
|
// the notation's name. |
|
func (_ AlgebraicNotation) String() string { |
|
return "Algebraic Notation" |
|
} |
|
|
|
// Encode implements the Encoder interface. |
|
func (_ AlgebraicNotation) Encode(pos *Position, m *Move) string { |
|
checkChar := getCheckChar(pos, m) |
|
if m.HasTag(KingSideCastle) { |
|
return "O-O" + checkChar |
|
} else if m.HasTag(QueenSideCastle) { |
|
return "O-O-O" + checkChar |
|
} |
|
p := pos.Board().Piece(m.S1()) |
|
pChar := charFromPieceType(p.Type()) |
|
s1Str := formS1(pos, m) |
|
capChar := "" |
|
if m.HasTag(Capture) || m.HasTag(EnPassant) { |
|
capChar = "x" |
|
if p.Type() == Pawn && s1Str == "" { |
|
capChar = m.s1.File().String() + "x" |
|
} |
|
} |
|
promoText := charForPromo(m.promo) |
|
return pChar + s1Str + capChar + m.s2.String() + promoText + checkChar |
|
} |
|
|
|
// Decode implements the Decoder interface. |
|
func (_ AlgebraicNotation) Decode(pos *Position, s string) (*Move, error) { |
|
s = removeSubstrings(s, "?", "!", "+", "#", "e.p.") |
|
for _, m := range pos.ValidMoves() { |
|
str := AlgebraicNotation{}.Encode(pos, m) |
|
str = removeSubstrings(str, "?", "!", "+", "#", "e.p.") |
|
if str == s { |
|
return m, nil |
|
} |
|
} |
|
return nil, fmt.Errorf("chess: could not decode algebraic notation %s for position %s", s, pos.String()) |
|
} |
|
|
|
func getCheckChar(pos *Position, move *Move) string { |
|
if !move.HasTag(Check) { |
|
return "" |
|
} |
|
nextPos := pos.Update(move) |
|
if nextPos.Status() == Checkmate { |
|
return "#" |
|
} |
|
return "+" |
|
} |
|
|
|
func formS1(pos *Position, m *Move) string { |
|
p := pos.board.Piece(m.s1) |
|
if p.Type() == Pawn { |
|
return "" |
|
} |
|
|
|
var req, fileReq, rankReq bool |
|
moves := pos.ValidMoves() |
|
|
|
for _, mv := range moves { |
|
if mv.s1 != m.s1 && mv.s2 == m.s2 && p == pos.board.Piece(mv.s1) { |
|
req = true |
|
|
|
if mv.s1.File() == m.s1.File() { |
|
rankReq = true; |
|
} |
|
|
|
if mv.s1.Rank() == m.s1.Rank() { |
|
fileReq = true |
|
} |
|
} |
|
} |
|
|
|
var s1 = "" |
|
|
|
if fileReq || !rankReq && req { |
|
s1 = m.s1.File().String() |
|
} |
|
|
|
if rankReq { |
|
s1 += m.s1.Rank().String() |
|
} |
|
|
|
return s1 |
|
} |
|
|
|
func charForPromo(p PieceType) string { |
|
c := charFromPieceType(p) |
|
if c != "" { |
|
c = "=" + c |
|
} |
|
return c |
|
} |
|
|
|
func charFromPieceType(p PieceType) string { |
|
switch p { |
|
case King: |
|
return "K" |
|
case Queen: |
|
return "Q" |
|
case Rook: |
|
return "R" |
|
case Bishop: |
|
return "B" |
|
case Knight: |
|
return "N" |
|
} |
|
return "" |
|
} |
|
|
|
func pieceTypeFromChar(c string) PieceType { |
|
switch c { |
|
case "q": |
|
return Queen |
|
case "r": |
|
return Rook |
|
case "b": |
|
return Bishop |
|
case "n": |
|
return Knight |
|
} |
|
return NoPieceType |
|
} |
|
|
|
func removeSubstrings(s string, subs ...string) string { |
|
for _, sub := range subs { |
|
s = strings.Replace(s, sub, "", -1) |
|
} |
|
return s |
|
}
|
|
|