Skip to content

Commit 41d8a00

Browse files
put move validation logic in separate file
1 parent 86d69ff commit 41d8a00

File tree

2 files changed

+176
-168
lines changed

2 files changed

+176
-168
lines changed

board.go

Lines changed: 0 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -61,174 +61,6 @@ func (brd *Board) KingSq(c uint8) int {
6161
return furthestForward(c, brd.pieces[c][KING])
6262
}
6363

64-
// Used to verify that a killer or hash move is legal.
65-
func (brd *Board) LegalMove(m Move, inCheck bool) bool {
66-
if inCheck {
67-
return brd.EvadesCheck(m)
68-
} else {
69-
return brd.PseudolegalAvoidsCheck(m)
70-
}
71-
}
72-
73-
// Moves generated while in check should already be legal, since we determine this
74-
// as a side-effect of generating evasions.
75-
func (brd *Board) AvoidsCheck(m Move, inCheck bool) bool {
76-
return inCheck || brd.PseudolegalAvoidsCheck(m)
77-
}
78-
79-
func (brd *Board) PseudolegalAvoidsCheck(m Move) bool {
80-
switch m.Piece() {
81-
case PAWN:
82-
if m.CapturedPiece() == PAWN && brd.TypeAt(m.To()) == EMPTY { // En-passant
83-
return pinnedCanMove(brd, m.From(), m.To(), brd.c, brd.Enemy()) &&
84-
isPinned(brd, int(brd.enpTarget), brd.c, brd.Enemy())&sqMaskOn[m.To()] > 0
85-
} else {
86-
return pinnedCanMove(brd, m.From(), m.To(), brd.c, brd.Enemy())
87-
}
88-
case KING:
89-
return !isAttackedBy(brd, brd.AllOccupied(), m.To(), brd.Enemy(), brd.c)
90-
default:
91-
return pinnedCanMove(brd, m.From(), m.To(), brd.c, brd.Enemy())
92-
}
93-
}
94-
95-
// Only called when in check
96-
func (brd *Board) EvadesCheck(m Move) bool {
97-
piece, from, to := m.Piece(), m.From(), m.To()
98-
c, e := brd.c, brd.Enemy()
99-
100-
if piece == KING {
101-
return !isAttackedBy(brd, occAfterMove(brd.AllOccupied(), from, to), to, e, c)
102-
} else {
103-
occ := brd.AllOccupied()
104-
kingSq := brd.KingSq(c)
105-
threats := colorAttackMap(brd, occ, kingSq, e, c)
106-
107-
// TODO: EvadesCheck() called from non-check position in rare cases. Examples:
108-
// 5r1k/1b3p1p/pp3p1q/3n4/1P2R3/P2B1PP1/7P/6K1 w - - 0 1
109-
// 8/PPKR4/1Bn4P/3P3R/8/2p4r/pp4p1/r6k w - - 5 2 (r h3h5 x r)...?
110-
111-
if threats == 0 {
112-
fmt.Println("info string EvadesCheck() called from non-check position!")
113-
// m.Print()
114-
// brd.PrintDetails()
115-
return true // no threats to evade.
116-
}
117-
118-
if popCount(threats) > 1 {
119-
return false // only king moves can escape from double check.
120-
}
121-
if (threats|intervening[furthestForward(e, threats)][kingSq])&sqMaskOn[to] == 0 {
122-
return false // the moving piece must kill or block the attacking piece.
123-
}
124-
if !pinnedCanMove(brd, from, to, c, e) {
125-
return false // the moving piece can't be pinned to the king.
126-
}
127-
if brd.enpTarget != SQ_INVALID && piece == PAWN && m.CapturedPiece() == PAWN && // En-passant
128-
brd.TypeAt(to) == EMPTY {
129-
occ = occAfterMove(occ, from, to) & sqMaskOff[brd.enpTarget]
130-
131-
return colorAttackMap(brd, occ, kingSq, e, c) == 0
132-
// return attacks_after_move(brd, occ, occ&brd.occupied[e], king_sq, e, c) == 0
133-
}
134-
}
135-
return true
136-
}
137-
138-
func (brd *Board) ValidMove(m Move, inCheck bool) bool {
139-
if !m.IsMove() {
140-
return false
141-
}
142-
c, e := brd.c, brd.Enemy()
143-
piece, from, to, capturedPiece := m.Piece(), m.From(), m.To(), m.CapturedPiece()
144-
145-
if brd.TypeAt(from) != piece || brd.pieces[c][piece]&sqMaskOn[from] == 0 {
146-
// fmt.Printf("No piece of this type available at from square!{%s}", m.ToString())
147-
return false
148-
}
149-
if sqMaskOn[to]&brd.occupied[c] > 0 {
150-
// fmt.Printf("To square occupied by own piece!{%s}", m.ToString())
151-
return false
152-
}
153-
if capturedPiece == KING || brd.pieces[c][KING] == 0 {
154-
// fmt.Printf("King capture detected!{%s}", m.ToString())
155-
return false
156-
}
157-
switch piece {
158-
case PAWN:
159-
var diff int
160-
if c == WHITE {
161-
diff = to - from
162-
} else {
163-
diff = from - to
164-
}
165-
if diff < 0 {
166-
// fmt.Printf("Invalid pawn movement direction!{%s}", m.ToString())
167-
return false
168-
} else if diff == 8 {
169-
return brd.TypeAt(to) == EMPTY
170-
} else if diff == 16 {
171-
return brd.TypeAt(to) == EMPTY && brd.TypeAt(pawnStopSq[c][from]) == EMPTY
172-
} else if capturedPiece == EMPTY {
173-
// fmt.Printf("Invalid pawn move!{%s}", m.ToString())
174-
return false
175-
} else {
176-
if capturedPiece == PAWN && brd.TypeAt(to) == EMPTY {
177-
if brd.enpTarget != SQ_INVALID && pawnStopSq[e][to] == int(brd.enpTarget) {
178-
return true
179-
} else {
180-
// fmt.Printf("Invalid En-passant move!{%s}", m.ToString())
181-
return false
182-
}
183-
} else {
184-
return brd.TypeAt(to) == capturedPiece
185-
}
186-
}
187-
case KING:
188-
if abs(to-from) == 2 { // validate castle moves
189-
occ := brd.AllOccupied()
190-
if c == WHITE && !inCheck && (brd.castle&12) > 0 {
191-
switch to {
192-
case C1:
193-
if (brd.castle&C_WQ > uint8(0)) && castleQueensideIntervening[WHITE]&occ == 0 &&
194-
!isAttackedBy(brd, occ, B1, e, c) && !isAttackedBy(brd, occ, C1, e, c) &&
195-
!isAttackedBy(brd, occ, D1, e, c) {
196-
return true
197-
}
198-
case G1:
199-
if (brd.castle&C_WK > uint8(0)) && castleKingsideIntervening[WHITE]&occ == 0 &&
200-
!isAttackedBy(brd, occ, F1, e, c) && !isAttackedBy(brd, occ, G1, e, c) {
201-
return true
202-
}
203-
}
204-
} else if c == BLACK && !inCheck && (brd.castle&3) > 0 {
205-
switch to {
206-
case C8:
207-
if (brd.castle&C_BQ > uint8(0)) && castleQueensideIntervening[BLACK]&occ == 0 &&
208-
!isAttackedBy(brd, occ, B8, e, c) && !isAttackedBy(brd, occ, C8, e, c) &&
209-
!isAttackedBy(brd, occ, D8, e, c) {
210-
return true
211-
}
212-
case G8:
213-
if (brd.castle&C_BK > uint8(0)) && castleKingsideIntervening[BLACK]&occ == 0 &&
214-
!isAttackedBy(brd, occ, F8, e, c) && !isAttackedBy(brd, occ, G8, e, c) {
215-
return true
216-
}
217-
}
218-
}
219-
return false
220-
}
221-
case KNIGHT:
222-
// no special treatment needed for knights.
223-
default:
224-
if slidingAttacks(piece, brd.AllOccupied(), from)&sqMaskOn[to] == 0 {
225-
return false
226-
}
227-
}
228-
229-
return brd.TypeAt(to) == capturedPiece
230-
}
231-
23264
func (brd *Board) MayPromote(m Move) bool {
23365
if m.Piece() != PAWN {
23466
return false

move_validation.go

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
//-----------------------------------------------------------------------------------
2+
// ♛ GopherCheck ♛
3+
// Copyright © 2014 Stephen J. Lovell
4+
//-----------------------------------------------------------------------------------
5+
6+
package main
7+
8+
import "fmt"
9+
10+
// Used to verify that a killer or hash move is legal.
11+
func (brd *Board) LegalMove(m Move, inCheck bool) bool {
12+
if inCheck {
13+
return brd.EvadesCheck(m)
14+
} else {
15+
return brd.PseudolegalAvoidsCheck(m)
16+
}
17+
}
18+
19+
// Moves generated while in check should already be legal, since we determine this
20+
// as a side-effect of generating evasions.
21+
func (brd *Board) AvoidsCheck(m Move, inCheck bool) bool {
22+
return inCheck || brd.PseudolegalAvoidsCheck(m)
23+
}
24+
25+
func (brd *Board) PseudolegalAvoidsCheck(m Move) bool {
26+
switch m.Piece() {
27+
case PAWN:
28+
if m.CapturedPiece() == PAWN && brd.TypeAt(m.To()) == EMPTY { // En-passant
29+
return pinnedCanMove(brd, m.From(), m.To(), brd.c, brd.Enemy()) &&
30+
isPinned(brd, int(brd.enpTarget), brd.c, brd.Enemy())&sqMaskOn[m.To()] > 0
31+
} else {
32+
return pinnedCanMove(brd, m.From(), m.To(), brd.c, brd.Enemy())
33+
}
34+
case KING:
35+
return !isAttackedBy(brd, brd.AllOccupied(), m.To(), brd.Enemy(), brd.c)
36+
default:
37+
return pinnedCanMove(brd, m.From(), m.To(), brd.c, brd.Enemy())
38+
}
39+
}
40+
41+
// Only called when in check
42+
func (brd *Board) EvadesCheck(m Move) bool {
43+
piece, from, to := m.Piece(), m.From(), m.To()
44+
c, e := brd.c, brd.Enemy()
45+
46+
if piece == KING {
47+
return !isAttackedBy(brd, occAfterMove(brd.AllOccupied(), from, to), to, e, c)
48+
} else {
49+
occ := brd.AllOccupied()
50+
kingSq := brd.KingSq(c)
51+
threats := colorAttackMap(brd, occ, kingSq, e, c)
52+
53+
// TODO: EvadesCheck() called from non-check position in rare cases. Examples:
54+
// 5r1k/1b3p1p/pp3p1q/3n4/1P2R3/P2B1PP1/7P/6K1 w - - 0 1
55+
// 8/PPKR4/1Bn4P/3P3R/8/2p4r/pp4p1/r6k w - - 5 2 (r h3h5 x r)...?
56+
57+
if threats == 0 {
58+
fmt.Println("info string EvadesCheck() called from non-check position!")
59+
// m.Print()
60+
// brd.PrintDetails()
61+
return true // no threats to evade.
62+
}
63+
64+
if popCount(threats) > 1 {
65+
return false // only king moves can escape from double check.
66+
}
67+
if (threats|intervening[furthestForward(e, threats)][kingSq])&sqMaskOn[to] == 0 {
68+
return false // the moving piece must kill or block the attacking piece.
69+
}
70+
if !pinnedCanMove(brd, from, to, c, e) {
71+
return false // the moving piece can't be pinned to the king.
72+
}
73+
if brd.enpTarget != SQ_INVALID && piece == PAWN && m.CapturedPiece() == PAWN && // En-passant
74+
brd.TypeAt(to) == EMPTY {
75+
occ = occAfterMove(occ, from, to) & sqMaskOff[brd.enpTarget]
76+
77+
return colorAttackMap(brd, occ, kingSq, e, c) == 0
78+
// return attacks_after_move(brd, occ, occ&brd.occupied[e], king_sq, e, c) == 0
79+
}
80+
}
81+
return true
82+
}
83+
84+
func (brd *Board) ValidMove(m Move, inCheck bool) bool {
85+
if !m.IsMove() {
86+
return false
87+
}
88+
c, e := brd.c, brd.Enemy()
89+
piece, from, to, capturedPiece := m.Piece(), m.From(), m.To(), m.CapturedPiece()
90+
91+
if brd.TypeAt(from) != piece || brd.pieces[c][piece]&sqMaskOn[from] == 0 {
92+
// fmt.Printf("No piece of this type available at from square!{%s}", m.ToString())
93+
return false
94+
}
95+
if sqMaskOn[to]&brd.occupied[c] > 0 {
96+
// fmt.Printf("To square occupied by own piece!{%s}", m.ToString())
97+
return false
98+
}
99+
if capturedPiece == KING || brd.pieces[c][KING] == 0 {
100+
// fmt.Printf("King capture detected!{%s}", m.ToString())
101+
return false
102+
}
103+
switch piece {
104+
case PAWN:
105+
var diff int
106+
if c == WHITE {
107+
diff = to - from
108+
} else {
109+
diff = from - to
110+
}
111+
if diff < 0 {
112+
// fmt.Printf("Invalid pawn movement direction!{%s}", m.ToString())
113+
return false
114+
} else if diff == 8 {
115+
return brd.TypeAt(to) == EMPTY
116+
} else if diff == 16 {
117+
return brd.TypeAt(to) == EMPTY && brd.TypeAt(pawnStopSq[c][from]) == EMPTY
118+
} else if capturedPiece == EMPTY {
119+
// fmt.Printf("Invalid pawn move!{%s}", m.ToString())
120+
return false
121+
} else {
122+
if capturedPiece == PAWN && brd.TypeAt(to) == EMPTY {
123+
if brd.enpTarget != SQ_INVALID && pawnStopSq[e][to] == int(brd.enpTarget) {
124+
return true
125+
} else {
126+
// fmt.Printf("Invalid En-passant move!{%s}", m.ToString())
127+
return false
128+
}
129+
} else {
130+
return brd.TypeAt(to) == capturedPiece
131+
}
132+
}
133+
case KING:
134+
if abs(to-from) == 2 { // validate castle moves
135+
occ := brd.AllOccupied()
136+
if c == WHITE && !inCheck && (brd.castle&12) > 0 {
137+
switch to {
138+
case C1:
139+
if (brd.castle&C_WQ > uint8(0)) && castleQueensideIntervening[WHITE]&occ == 0 &&
140+
!isAttackedBy(brd, occ, B1, e, c) && !isAttackedBy(brd, occ, C1, e, c) &&
141+
!isAttackedBy(brd, occ, D1, e, c) {
142+
return true
143+
}
144+
case G1:
145+
if (brd.castle&C_WK > uint8(0)) && castleKingsideIntervening[WHITE]&occ == 0 &&
146+
!isAttackedBy(brd, occ, F1, e, c) && !isAttackedBy(brd, occ, G1, e, c) {
147+
return true
148+
}
149+
}
150+
} else if c == BLACK && !inCheck && (brd.castle&3) > 0 {
151+
switch to {
152+
case C8:
153+
if (brd.castle&C_BQ > uint8(0)) && castleQueensideIntervening[BLACK]&occ == 0 &&
154+
!isAttackedBy(brd, occ, B8, e, c) && !isAttackedBy(brd, occ, C8, e, c) &&
155+
!isAttackedBy(brd, occ, D8, e, c) {
156+
return true
157+
}
158+
case G8:
159+
if (brd.castle&C_BK > uint8(0)) && castleKingsideIntervening[BLACK]&occ == 0 &&
160+
!isAttackedBy(brd, occ, F8, e, c) && !isAttackedBy(brd, occ, G8, e, c) {
161+
return true
162+
}
163+
}
164+
}
165+
return false
166+
}
167+
case KNIGHT:
168+
// no special treatment needed for knights.
169+
default:
170+
if slidingAttacks(piece, brd.AllOccupied(), from)&sqMaskOn[to] == 0 {
171+
return false
172+
}
173+
}
174+
175+
return brd.TypeAt(to) == capturedPiece
176+
}

0 commit comments

Comments
 (0)