-
Notifications
You must be signed in to change notification settings - Fork 2
/
square_control.go
292 lines (265 loc) · 8.29 KB
/
square_control.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
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
package chess_engine
type SquareControl []PositionBitmap
func NewSquareControl() SquareControl {
attacks := make([]PositionBitmap, 128)
return attacks
}
func NewSquareControlFromBoard(board Board) SquareControl {
result := NewSquareControl()
for pos, piece := range board {
if piece != NoPiece {
result.addPiece(piece, Position(pos), board)
}
}
return result
}
func (s SquareControl) addPosition(color Color, pos, fromPos Position) {
ix := int(color)*64 + int(pos)
s[ix] = s[ix].Add(fromPos)
}
func (s SquareControl) removePosition(color Color, pos, fromPos Position) {
ix := int(color)*64 + int(pos)
s[ix] = s[ix].Remove(fromPos)
}
func (s SquareControl) addPiece(piece Piece, pos Position, board Board) {
if piece == NoPiece {
return
}
for _, line := range pos.GetAttackVectors(piece) {
for _, toPos := range line {
s.addPosition(piece.Color(), toPos, pos)
if !s.shouldContinue(board, toPos, piece.Color()) {
break
}
}
}
}
func (s SquareControl) removePiece(piece Piece, fromPos Position) {
if piece == NoPiece {
return
}
for _, line := range fromPos.GetAttackVectors(piece) {
for _, toPos := range line {
s.removePosition(piece.Color(), toPos, fromPos)
}
}
}
func (s SquareControl) HasPiecePosition(color Color, pos, fromPos Position) bool {
return s.Get(color, pos).IsSet(fromPos)
}
func (s SquareControl) shouldContinue(board Board, pos Position, color Color) bool {
if board.IsEmpty(pos) {
return true
} else if board.IsOpposingPiece(pos, color) {
if board[pos].ToNormalizedPiece() != King {
return false
}
return true
}
return false
}
func (s SquareControl) Get(color Color, pos Position) PositionBitmap {
return s[int(color)*64+int(pos)]
}
// Get all the attacks by @color. Ignores pins. Ignores promotions (TODO?)
func (s SquareControl) GetAttacksOnSquare(color Color, pos Position) []*Move {
attacks := s.Get(color, pos).ToPositions()
result := make([]*Move, len(attacks))
for i, a := range attacks {
move := NewMove(a, pos)
result[i] = move
}
return result
}
func (s SquareControl) getAttacksOnSquareForBothColours(pos Position) []Position {
result := []Position{}
for _, p := range s.Get(White, pos).ToPositions() {
result = append(result, p)
}
for _, p := range s.Get(Black, pos).ToPositions() {
result = append(result, p)
}
return result
}
// Whether or not @color attacks the @square
func (s SquareControl) AttacksSquare(color Color, square Position) bool {
return !s.Get(color, square).IsEmpty()
}
func (s SquareControl) Copy() SquareControl {
result := make([]PositionBitmap, 128)
copy(result, s)
return result
}
func (s SquareControl) GetPinnedPieces(board Board, color Color, kingPos Position) map[Position][]Position {
result := map[Position][]Position{}
// Look at all the diagonals and lines emanating from the king's position
for _, line := range kingPos.GetQueenMoves() {
for _, pos := range line {
if board.IsEmpty(pos) {
continue
} else if board.IsOpposingPiece(pos, color) {
break
} else {
// We have found one of our pieces within a clear line of the king.
pieceVector := NewMove(pos, kingPos).Vector().Normalize()
// Look at the pieces that are attacking the square
for _, attackerPos := range s.Get(color.Opposite(), pos).ToPositions() {
piece := board[attackerPos]
normPiece := piece.ToNormalizedPiece()
// Pawns, kings and knights can't pin other pieces
if !normPiece.IsRayPiece() {
continue
}
// Check if the attacker and the potentially pinned piece share the same
// vector (= are they on the same line?). If so, our piece is pinned.
attackVector := NewMove(attackerPos, pos).Vector().Normalize()
if attackVector.Eq(pieceVector) {
//fmt.Println("Piece", board[pos], pos, "is pinned by", piece, attackerPos, kingPos, pieceVector)
result[pos] = append(result[pos], attackerPos)
}
}
break
}
}
}
return result
}
func (s SquareControl) ApplyMove(move *Move, piece, capturedPiece Piece, board Board, enpassantSquare Position) SquareControl {
// 1. Remove all the old attacks by piece and capturedPiece
//
// Castling:
// We also remove the rook.
//
// En passant:
// We also remove the captured pawn.
//
// Promotions:
// No special case.
attacks := s.Copy()
attacks.removePiece(piece, move.From)
if capturedPiece != NoPiece {
attacks.removePiece(capturedPiece, move.To)
}
castles := move.GetRookCastlesMove(piece)
if castles != nil {
attacks.removePiece(Rook.ToPiece(piece.Color()), castles.From)
}
enpassant := move.GetEnPassantCapture(piece, enpassantSquare)
if enpassant != nil {
attacks.removePiece(Pawn.ToPiece(piece.OppositeColor()), *enpassant)
}
// 2. Now that the piece has moved, the pieces that were previously blocked
// by it potentially get some additional attack vectors so we should update
// our copy. The code below gets all the pieces looking at move.From and
// continues their path, marking positions on the way.
//
// Castling:
// We don't have to do anything extra for castling, because the corners of
// the board are a special case from which you can never block another
// piece.
//
// En passant:
// We need to do the same for the captured pawn, because it leaves behind a hole.
//
// Promotions:
// Promotions are also not affected.
//
for _, fromPos := range attacks.getAttacksOnSquareForBothColours(move.From) {
extendPiece := board[fromPos]
color := extendPiece.Color()
// Special case for the king, because in our implementation the opponent's
// pieces are already looking through the king to make sure the king can't
// escape into a square that would be under check.
if piece == WhiteKing && Color(color) == Black {
continue
} else if piece == BlackKing && Color(color) == White {
continue
}
// Not relevant for Pawns and Knights and King
if !extendPiece.IsRayPiece() {
continue
}
vector := NewMove(move.From, fromPos).Vector().Normalize()
for _, pos := range vector.FollowVectorUntilEdgeOfBoard(move.From) {
attacks.addPosition(color, pos, fromPos)
if !s.shouldContinue(board, pos, Color(color)) {
break
}
}
}
if enpassant != nil {
for _, fromPos := range attacks.getAttacksOnSquareForBothColours(*enpassant) {
extendPiece := board[fromPos]
color := extendPiece.Color()
// Not relevant for Pawns and Knights and King
if !extendPiece.IsRayPiece() {
continue
}
vector := NewMove(*enpassant, fromPos).Vector().Normalize()
for _, pos := range vector.FollowVectorUntilEdgeOfBoard(*enpassant) {
attacks.addPosition(color, pos, fromPos)
if !s.shouldContinue(board, pos, Color(color)) {
break
}
}
}
}
// 3. The piece has moved, which might block some other pieces.
// The code below follows the paths from the square and removes pieces that
// are now blocked from reaching it. This only applies if this move wasn't a
// capture.
//
// Castling:
// Normal case applies.
// The only moves you can block on the back rank are from pieces that are
// also on the back rank, since there can't be anything between the king
// and the rook, that only leaves positions between the king and the other
// edge of the board, but the king move is already covered in the normal
// case so we don't have to do anything.
//
// En passant:
// Normal case applies.
//
// Promotions:
// Normal case applies.
if capturedPiece == NoPiece {
for _, fromPos := range attacks.getAttacksOnSquareForBothColours(move.To) {
blockPiece := board[fromPos]
color := blockPiece.Color()
// Not relevant for Pawns and Knights and King
if !blockPiece.IsRayPiece() {
continue
}
vector := NewMove(move.To, fromPos).Vector().Normalize()
for _, pos := range vector.FollowVectorUntilEdgeOfBoard(move.To) {
if attacks.HasPiecePosition(color, pos, fromPos) {
attacks.removePosition(color, pos, fromPos)
continue
}
break
}
}
}
// 4. Add all the attacks for the new piece
//
// Castling:
// Also add the rook
//
// En passant:
// Normal case applies.
//
// Promotions:
// Add the new piece instead of the pawn
if move.Promote == NoPiece {
attacks.addPiece(piece, move.To, board)
} else {
attacks.addPiece(move.Promote, move.To, board)
}
if castles != nil {
attacks.addPiece(Rook.ToPiece(piece.Color()), castles.To, board)
}
return attacks
}
func (s SquareControl) String() string {
return ""
}