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.
225 lines
5.1 KiB
225 lines
5.1 KiB
4 years ago
|
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
|
||
|
}
|