-
Notifications
You must be signed in to change notification settings - Fork 1
/
move.go
219 lines (192 loc) · 5.66 KB
/
move.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
package board
import (
"fmt"
"strings"
)
// MoveType indicates the type of move. The no-progress counter is reset with any non-Normal move.
type MoveType uint8
const (
Normal MoveType = 1 + iota
Push // Pawn move
Jump // Pawn 2-square move
EnPassant // Implicitly a pawn capture
QueenSideCastle
KingSideCastle
Capture
Promotion
CapturePromotion
)
func (m MoveType) String() string {
switch m {
case Normal:
return "Normal"
case Push:
return "Push"
case Jump:
return "Jump"
case EnPassant:
return "EnPassant"
case QueenSideCastle:
return "QueenSideCastle"
case KingSideCastle:
return "KingSideCastle"
case Capture:
return "Capture"
case Promotion:
return "Promotion"
case CapturePromotion:
return "CapturePromotion"
default:
return "?"
}
}
// TODO(herohde) 2/21/2021: add remarks, like "dubious", to represent standard notation?
// Move represents a not-necessarily legal move along with contextual metadata. 64bits.
type Move struct {
Type MoveType
From, To Square
Piece Piece // moved piece
Promotion Piece // desired piece for promotion, if any.
Capture Piece // captured piece, if any. Not set if EnPassant.
}
// ParseMove parses a move in pure algebraic coordinate notation, such as "a2a4" or "a7a8q".
// The parsed move does not contain contextual information like castling or en passant.
func ParseMove(str string) (Move, error) {
runes := []rune(str)
if len(runes) < 4 || len(runes) > 5 {
return Move{}, fmt.Errorf("invalid move: '%v'", str)
}
from, err := ParseSquare(runes[0], runes[1])
if err != nil {
return Move{}, fmt.Errorf("invalid from: '%v': %v", str, err)
}
to, err := ParseSquare(runes[2], runes[3])
if err != nil {
return Move{}, fmt.Errorf("invalid to: '%v': %v", str, err)
}
if len(runes) == 5 {
promo, ok := ParsePiece(runes[4])
if !ok || promo == Pawn || promo == King {
return Move{}, fmt.Errorf("invalid promotion: '%v'", str)
}
return Move{From: from, To: to, Promotion: promo}, nil
}
return Move{From: from, To: to}, nil
}
// IsInvalid true iff the move is of invalid type. Convenience function.
func (m Move) IsInvalid() bool {
return m.Type == 0
}
// IsCapture returns true iff the move is a Capture or CapturePromotion. Convenience function.
func (m Move) IsCapture() bool {
return m.Type == CapturePromotion || m.Type == Capture
}
// IsPromotion returns true iff the move is a Promotion or CapturePromotion. Convenience function.
func (m Move) IsPromotion() bool {
return m.Type == CapturePromotion || m.Type == Promotion
}
// IsCastle returns true iff the move is a KingSideCastle or QueenSideCastle. Convenience function.
func (m Move) IsCastle() bool {
return m.Type == KingSideCastle || m.Type == QueenSideCastle
}
// EnPassantTarget return the e.p target square, if a Jump move. For e2-e4, it turns e3.
func (m Move) EnPassantTarget() (Square, bool) {
if m.Type != Jump {
return 0, false
}
if m.To.Rank() == Rank4 { // White
return NewSquare(m.To.File(), Rank3), true
} else {
return NewSquare(m.To.File(), Rank6), true
}
}
// EnPassantCapture return the e.p capture square, if a EnPassant move. For d4*e3 e.p, it turns e4.
func (m Move) EnPassantCapture() (Square, bool) {
if m.Type != EnPassant {
return 0, false
}
if m.To.Rank() == Rank3 { // Black
return NewSquare(m.To.File(), Rank4), true
} else {
return NewSquare(m.To.File(), Rank5), true
}
}
// CastlingRookMove returns the implicit rook move (from, to), if a KingSideCastle or QueenSideCastle move.
func (m Move) CastlingRookMove() (Square, Square, bool) {
switch {
case m.Type == KingSideCastle && m.From == E1:
return H1, F1, true
case m.Type == QueenSideCastle && m.From == E1:
return A1, D1, true
case m.Type == KingSideCastle && m.From == E8:
return H8, F8, true
case m.Type == QueenSideCastle && m.From == E8:
return A8, D8, true
default:
return 0, 0, false
}
}
// CastlingRightsLost returns the castling rights that are definitely not present after this move.
// If king moves, rights are lost. Ditto if rook moves or is captured.
func (m Move) CastlingRightsLost() Castling {
switch {
case m.From == E1:
return WhiteKingSideCastle | WhiteQueenSideCastle
case m.From == A1 || m.To == A1:
return WhiteQueenSideCastle
case m.From == H1 || m.To == H1:
return WhiteKingSideCastle
case m.From == E8:
return BlackKingSideCastle | BlackQueenSideCastle
case m.From == A8 || m.To == A8:
return BlackQueenSideCastle
case m.From == H8 || m.To == H8:
return BlackKingSideCastle
default:
return NoCastlingRights
}
}
func (m Move) Equals(o Move) bool {
return m.From == o.From && m.To == o.To && m.Promotion == o.Promotion
}
func (m Move) String() string {
if m.IsInvalid() {
return "invalid"
}
switch m.Type {
case Promotion:
return fmt.Sprintf("%v-%v=%v", m.From, m.To, m.Promotion)
case CapturePromotion:
return fmt.Sprintf("%v*%v=%v", m.From, m.To, m.Promotion)
case EnPassant:
return fmt.Sprintf("%v*%v e.p.", m.From, m.To)
case KingSideCastle:
return fmt.Sprintf("0-0")
case QueenSideCastle:
return fmt.Sprintf("0-0-0")
case Capture:
return fmt.Sprintf("%v%v*%v", ignorePawn(m.Piece), m.From, m.To)
default:
return fmt.Sprintf("%v%v-%v", ignorePawn(m.Piece), m.From, m.To)
}
}
// PrintMoves prints a list of moves.
func PrintMoves(list []Move) string {
return FormatMoves(list, func(m Move) string {
return m.String()
})
}
// FormatMoves formats a list of moves.
func FormatMoves(list []Move, fn func(Move) string) string {
var ret []string
for _, m := range list {
ret = append(ret, fn(m))
}
return strings.Join(ret, " ")
}
func ignorePawn(piece Piece) string {
if piece == Pawn || piece == NoPiece {
return ""
}
return piece.String()
}