Skip to content

Commit bbfcbc2

Browse files
committed
Refactor SEE impl to be threshold-based
Small speedup since SEE can now early return if it is clear the move will not pass threshold.
1 parent b30c2ed commit bbfcbc2

7 files changed

Lines changed: 154 additions & 48 deletions

File tree

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
*.txt
21
*.prof
32
*.test
43
builds/

engine/moveorder.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ func (mp *MovePicker) NextMove() Move {
139139
case GOOD_CAPTURES:
140140
if mp.getNextAndSwap(mp.captures, mp.currIdx) {
141141
move := mp.captures[mp.currIdx].move
142-
if SEE(move, mp.board) < 0 {
142+
if !SEE(move, mp.board, 0) {
143143
mp.badCaptures = append(mp.badCaptures, mp.captures[mp.currIdx])
144144
mp.currIdx++
145145
continue

engine/search.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,11 @@ func (s *Searcher) Pvs(depth int, alpha int, beta int, doNull bool, prevMove Mov
140140
staticEval = EvaluateNNUE(s.Position)
141141
} else {
142142
staticEval = int(entry.staticEval)
143-
if int(entry.score) > staticEval && (entry.bd == EXACT || entry.bd == LOWER) {
144-
staticEval = int(entry.score)
143+
if ttScore > staticEval && (entry.bd == EXACT || entry.bd == LOWER) {
144+
staticEval = ttScore
145145
}
146-
if int(entry.score) < staticEval && (entry.bd == EXACT || entry.bd == UPPER) {
147-
staticEval = int(entry.score)
146+
if ttScore < staticEval && (entry.bd == EXACT || entry.bd == UPPER) {
147+
staticEval = ttScore
148148
}
149149
}
150150

@@ -282,13 +282,13 @@ func (s *Searcher) Pvs(depth int, alpha int, beta int, doNull bool, prevMove Mov
282282

283283
// SEE QUIET PRUNING
284284
seeMargin := -Params.SEE_QUIET_PRUNING_MULT * lmrDepth * lmrDepth
285-
if isQuiet && SEE(move, s.Position) < seeMargin {
285+
if isQuiet && !SEE(move, s.Position, seeMargin) {
286286
continue
287287
}
288288

289289
// SEE CAPTURE PRUNING
290290
seeMargin = -Params.SEE_CAPTURE_PRUNING_MULT * depth
291-
if (move.movetype == CAPTURE || move.movetype == EN_PASSANT) && SEE(move, s.Position) < seeMargin {
291+
if move.IsCapture() && !SEE(move, s.Position, seeMargin) {
292292
continue
293293
}
294294
}

engine/see.go

Lines changed: 64 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,49 +16,81 @@ var SEE_PIECE_VALUES = [6]int{
1616
// capture the square the move went to. Once we do this until there are no more captures left to that square,
1717
// we'll have a decent idea how much this capture gains/loses material.
1818
//
19-
// Pseudocode implementation from https://www.chessprogramming.org/SEE_-_The_Swap_Algorithm
20-
func SEE(move Move, b *Board) int {
21-
gain := [32]int{}
22-
d := 0
23-
mayXRay := b.occupied & ^(b.pieces[W_K] | b.pieces[W_N] | b.pieces[B_K] | b.pieces[B_N]) // pawns, bishops, rooks. queens
24-
fromSet := SQUARE_TO_BITBOARD[move.from]
25-
occ := b.occupied ^ SQUARE_TO_BITBOARD[move.from] ^ SQUARE_TO_BITBOARD[move.to]
26-
attadef := b.AllAttackersOf(move.to, occ)
19+
// Implementation inspired from Ethereal/Weiss/Stormphrax's implementation of SEE thresholding
20+
func SEE(move Move, b *Board, threshold int) bool {
21+
stm := b.turn
22+
gain := 0
23+
24+
// Determine gain of the first move
25+
if move.movetype == EN_PASSANT {
26+
gain += SEE_PIECE_VALUES[PAWN]
27+
} else if move.movetype == CAPTURE || move.movetype == CAPTURE_AND_PROMOTION {
28+
gain += SEE_PIECE_VALUES[PieceToPieceType(move.captured)]
29+
}
30+
if move.movetype == PROMOTION || move.movetype == CAPTURE_AND_PROMOTION {
31+
gain += SEE_PIECE_VALUES[PieceToPieceType(move.promote)] - SEE_PIECE_VALUES[PAWN]
32+
}
33+
34+
// Subtract threshold to keep our logic in terms of greater and less than 0
35+
gain -= threshold
2736

28-
stm := ReverseColor(b.turn)
29-
initial_gain := 0
30-
if move.captured != EMPTY {
31-
initial_gain += SEE_PIECE_VALUES[PieceToPieceType(move.captured)]
37+
// If our gain is already negative after making the move, it will not be able to increase
38+
if gain < 0 {
39+
return false
3240
}
3341

34-
attacker := PieceToPieceType(move.piece)
35-
seen := u64(0)
42+
// If we were to lose the moving piece (or promoted piece), are we still above threshold?
43+
// If so, no need to continue with SEE, just return true
44+
nextLoss := SEE_PIECE_VALUES[PieceToPieceType(move.piece)]
45+
if move.movetype == PROMOTION || move.movetype == CAPTURE_AND_PROMOTION {
46+
nextLoss = SEE_PIECE_VALUES[PieceToPieceType(move.promote)]
47+
}
48+
gain -= nextLoss
49+
if gain >= 0 {
50+
return true
51+
}
52+
53+
sq := move.to
54+
occ := b.occupied ^ SQUARE_TO_BITBOARD[move.from] ^ SQUARE_TO_BITBOARD[move.to]
55+
queens := b.pieces[B_Q] | b.pieces[W_Q]
56+
bishops := b.pieces[B_B] | b.pieces[W_B] | queens
57+
rooks := b.pieces[B_R] | b.pieces[W_R] | queens
3658

37-
gain[d] = initial_gain
59+
var attackerPiece PieceType
3860

39-
for fromSet != 0 {
40-
d++
41-
gain[d] = SEE_PIECE_VALUES[attacker] - gain[d-1]
42-
attadef &= ^fromSet
43-
occ &= ^fromSet
44-
seen |= fromSet
61+
attackers := b.AllAttackersOf(sq, occ)
62+
nextTm := ReverseColor(stm)
4563

46-
if fromSet&mayXRay != 0 {
47-
attadef |= b.XRayAttacks(move.to, occ) & ^seen
64+
for {
65+
ourAttackers := attackers & b.colors[nextTm]
66+
67+
if ourAttackers == 0 {
68+
break
4869
}
4970

50-
fromSet = getLeastValuablePiece(b, attadef, stm, &attacker)
71+
next := getLeastValuablePiece(b, ourAttackers, nextTm, &attackerPiece)
72+
occ ^= next
5173

52-
stm = ReverseColor(stm)
53-
}
74+
if attackerPiece == PAWN || attackerPiece == BISHOP || attackerPiece == QUEEN {
75+
attackers |= BishopAttacks(sq, occ) & bishops
76+
}
5477

55-
// Negamax-ing gains
56-
for d > 1 {
57-
d--
58-
gain[d-1] = -Max(-gain[d-1], gain[d])
59-
}
78+
if attackerPiece == ROOK || attackerPiece == QUEEN {
79+
attackers |= RookAttacks(sq, occ) & rooks
80+
}
81+
82+
attackers &= occ
83+
gain = -gain - 1 - SEE_PIECE_VALUES[attackerPiece]
84+
nextTm = ReverseColor(nextTm)
6085

61-
return gain[0]
86+
if gain >= 0 {
87+
if attackerPiece == KING && attackers&b.colors[nextTm] != 0 {
88+
nextTm = ReverseColor(nextTm)
89+
}
90+
break
91+
}
92+
}
93+
return stm != nextTm
6294
}
6395

6496
func getLeastValuablePiece(b *Board, attadef u64, stm Color, piece *PieceType) u64 {

engine/see_test.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,21 @@ func TestSee(t *testing.T) {
3737
s.Position.InitFEN(fen + " 0 1")
3838
move := FromUCI(uci, s.Position)
3939

40-
if move.promote != EMPTY {
41-
fmt.Println("skip promotion")
42-
continue
40+
res := SEE(move, s.Position, score)
41+
42+
fmt.Printf("\n\n")
43+
if !res {
44+
t.Fatalf("Expected %d, got %t", score, res)
4345
}
4446

45-
res := SEE(move, s.Position)
47+
res = SEE(move, s.Position, score+1)
48+
if res {
49+
t.Fatalf("Expected %d, got %t", score, res)
50+
}
4651

47-
fmt.Printf("\n\n")
48-
if res != score {
49-
t.Fatalf("Expected %d, got %d", score, res)
52+
res = SEE(move, s.Position, score-1)
53+
if !res {
54+
t.Fatalf("Expected %d, got %t", score, res)
5055
}
5156
}
5257
}

engine/test_data/see_test.txt

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
6k1/1pp4p/p1pb4/6q1/3P1pRr/2P4P/PP1Br1P1/5RKN w - - | f1f4 | -100 | P - R + B
2+
5rk1/1pp2q1p/p1pb4/8/3P1NP1/2P5/1P1BQ1P1/5RK1 b - - | d6f4 | 0 | -N + B
3+
4R3/2r3p1/5bk1/1p1r3p/p2PR1P1/P1BK1P2/1P6/8 b - - | h5g4 | 0
4+
4R3/2r3p1/5bk1/1p1r1p1p/p2PR1P1/P1BK1P2/1P6/8 b - - | h5g4 | 0
5+
4r1k1/5pp1/nbp4p/1p2p2q/1P2P1b1/1BP2N1P/1B2QPPK/3R4 b - - | g4f3 | 0
6+
2r1r1k1/pp1bppbp/3p1np1/q3P3/2P2P2/1P2B3/P1N1B1PP/2RQ1RK1 b - - | d6e5 | 100 | P
7+
7r/5qpk/p1Qp1b1p/3r3n/BB3p2/5p2/P1P2P2/4RK1R w - - | e1e8 | 0
8+
6rr/6pk/p1Qp1b1p/2n5/1B3p2/5p2/P1P2P2/4RK1R w - - | e1e8 | -500 | -R
9+
7r/5qpk/2Qp1b1p/1N1r3n/BB3p2/5p2/P1P2P2/4RK1R w - - | e1e8 | -500 | -R
10+
6RR/4bP2/8/8/5r2/3K4/5p2/4k3 w - - | f7f8q | 200 | B - P
11+
6RR/4bP2/8/8/5r2/3K4/5p2/4k3 w - - | f7f8n | 200 | N - P
12+
7R/5P2/8/8/6r1/3K4/5p2/4k3 w - - | f7f8q | 800 | Q - P
13+
7R/5P2/8/8/6r1/3K4/5p2/4k3 w - - | f7f8b | 200 | B - P
14+
7R/4bP2/8/8/1q6/3K4/5p2/4k3 w - - | f7f8r | -100 | -P
15+
8/4kp2/2npp3/1Nn5/1p2PQP1/7q/1PP1B3/4KR1r b - - | h1f1 | 0
16+
8/4kp2/2npp3/1Nn5/1p2P1P1/7q/1PP1B3/4KR1r b - - | h1f1 | 0
17+
2r2r1k/6bp/p7/2q2p1Q/3PpP2/1B6/P5PP/2RR3K b - - | c5c1 | 100 | R - Q + R
18+
r2qk1nr/pp2ppbp/2b3p1/2p1p3/8/2N2N2/PPPP1PPP/R1BQR1K1 w kq - | f3e5 | 100 | P
19+
6r1/4kq2/b2p1p2/p1pPb3/p1P2B1Q/2P4P/2B1R1P1/6K1 w - - | f4e5 | 0
20+
3q2nk/pb1r1p2/np6/3P2Pp/2p1P3/2R4B/PQ3P1P/3R2K1 w - h6 | g5h6 | 0
21+
3q2nk/pb1r1p2/np6/3P2Pp/2p1P3/2R1B2B/PQ3P1P/3R2K1 w - h6 | g5h6 | 100 | P
22+
2r4r/1P4pk/p2p1b1p/7n/BB3p2/2R2p2/P1P2P2/4RK2 w - - | c3c8 | 500 | R
23+
2r4k/2r4p/p7/2b2p1b/4pP2/1BR5/P1R3PP/2Q4K w - - | c3c5 | 300 | B
24+
8/pp6/2pkp3/4bp2/2R3b1/2P5/PP4B1/1K6 w - - | g2c6 | -200 | P - B
25+
4q3/1p1pr1k1/1B2rp2/6p1/p3PP2/P3R1P1/1P2R1K1/4Q3 b - - | e6e4 | -400 | P - R
26+
4q3/1p1pr1kb/1B2rp2/6p1/p3PP2/P3R1P1/1P2R1K1/4Q3 b - - | h7e4 | 100 | P
27+
3r3k/3r4/2n1n3/8/3p4/2PR4/1B1Q4/3R3K w - - | d3d4 | -100 | P - R + N - P + N - B + R - Q + R
28+
1k1r4/1ppn3p/p4b2/4n3/8/P2N2P1/1PP1R1BP/2K1Q3 w - - | d3e5 | 100 | N - N + B - R + N
29+
1k1r3q/1ppn3p/p4b2/4p3/8/P2N2P1/1PP1R1BP/2K1Q3 w - - | d3e5 | -200 | P - N
30+
rnb2b1r/ppp2kpp/5n2/4P3/q2P3B/5R2/PPP2PPP/RN1QKB2 w Q - | h4f6 | 100 | N - B + P
31+
r2q1rk1/2p1bppp/p2p1n2/1p2P3/4P1b1/1nP1BN2/PP3PPP/RN1QR1K1 b - - | g4f3 | 0 | N - B
32+
r1bqkb1r/2pp1ppp/p1n5/1p2p3/3Pn3/1B3N2/PPP2PPP/RNBQ1RK1 b kq - | c6d4 | 0 | P - N + N - P
33+
r1bq1r2/pp1ppkbp/4N1p1/n3P1B1/8/2N5/PPP2PPP/R2QK2R w KQ - | e6g7 | 0 | B - N
34+
r1bq1r2/pp1ppkbp/4N1pB/n3P3/8/2N5/PPP2PPP/R2QK2R w KQ - | e6g7 | 300 | B
35+
rnq1k2r/1b3ppp/p2bpn2/1p1p4/3N4/1BN1P3/PPP2PPP/R1BQR1K1 b kq - | d6h2 | -200 | P - B
36+
rn2k2r/1bq2ppp/p2bpn2/1p1p4/3N4/1BN1P3/PPP2PPP/R1BQR1K1 b kq - | d6h2 | 100 | P
37+
r2qkbn1/ppp1pp1p/3p1rp1/3Pn3/4P1b1/2N2N2/PPP2PPP/R1BQKB1R b KQq - | g4f3 | 100 | N - B + P
38+
rnbq1rk1/pppp1ppp/4pn2/8/1bPP4/P1N5/1PQ1PPPP/R1B1KBNR b KQ - | b4c3 | 0 | N - B
39+
r4rk1/3nppbp/bq1p1np1/2pP4/8/2N2NPP/PP2PPB1/R1BQR1K1 b - - | b6b2 | -800 | P - Q
40+
r4rk1/1q1nppbp/b2p1np1/2pP4/8/2N2NPP/PP2PPB1/R1BQR1K1 b - - | f6d5 | -200 | P - N
41+
1r3r2/5p2/4p2p/2k1n1P1/2PN1nP1/1P3P2/8/2KR1B1R b - - | b8b3 | -400 | P - R
42+
1r3r2/5p2/4p2p/4n1P1/kPPN1nP1/5P2/8/2KR1B1R b - - | b8b4 | 100 | P
43+
2r2rk1/5pp1/pp5p/q2p4/P3n3/1Q3NP1/1P2PP1P/2RR2K1 b - - | c8c1 | 0 | R - R
44+
5rk1/5pp1/2r4p/5b2/2R5/6Q1/R1P1qPP1/5NK1 b - - | f5c2 | -100 | P - B + R - Q + R
45+
1r3r1k/p4pp1/2p1p2p/qpQP3P/2P5/3R4/PP3PP1/1K1R4 b - - | a5a2 | -800 | P - Q
46+
1r5k/p4pp1/2p1p2p/qpQP3P/2P2P2/1P1R4/P4rP1/1K1R4 b - - | a5a2 | 100 | P
47+
r2q1rk1/1b2bppp/p2p1n2/1ppNp3/3nP3/P2P1N1P/BPP2PP1/R1BQR1K1 w - - | d5e7 | 0 | B - N
48+
rnbqrbn1/pp3ppp/3p4/2p2k2/4p3/3B1K2/PPP2PPP/RNB1Q1NR w - - | d3e4 | 100 | P
49+
rnb1k2r/p3p1pp/1p3p1b/7n/1N2N3/3P1PB1/PPP1P1PP/R2QKB1R w KQkq - | e4d6 | -200 | -N + P
50+
r1b1k2r/p4npp/1pp2p1b/7n/1N2N3/3P1PB1/PPP1P1PP/R2QKB1R w KQkq - | e4d6 | 0 | -N + N
51+
2r1k2r/pb4pp/5p1b/2KB3n/4N3/2NP1PB1/PPP1P1PP/R2Q3R w k - | d5c6 | -300 | -B
52+
2r1k2r/pb4pp/5p1b/2KB3n/1N2N3/3P1PB1/PPP1P1PP/R2Q3R w k - | d5c6 | 0 | -B + B
53+
2r1k3/pbr3pp/5p1b/2KB3n/1N2N3/3P1PB1/PPP1P1PP/R2Q3R w - - | d5c6 | -300 | -B + B - N
54+
5k2/p2P2pp/8/1pb5/1Nn1P1n1/6Q1/PPP4P/R3K1NR w KQ - | d7d8q | 800 | (Q - P)
55+
r4k2/p2P2pp/8/1pb5/1Nn1P1n1/6Q1/PPP4P/R3K1NR w KQ - | d7d8q | -100 | (Q - P) - Q
56+
5k2/p2P2pp/1b6/1p6/1Nn1P1n1/8/PPP4P/R2QK1NR w KQ - | d7d8q | 200 | (Q - P) - Q + B
57+
4kbnr/p1P1pppp/b7/4q3/7n/8/PP1PPPPP/RNBQKBNR w KQk - | c7c8q | -100 | (Q - P) - Q
58+
4kbnr/p1P1pppp/b7/4q3/7n/8/PPQPPPPP/RNB1KBNR w KQk - | c7c8q | 200 | (Q - P) - Q + B
59+
4kbnr/p1P1pppp/b7/4q3/7n/8/PPQPPPPP/RNB1KBNR w KQk - | c7c8q | 200 | (Q - P)
60+
4kbnr/p1P4p/b1q5/5pP1/4n3/5Q2/PP1PPP1P/RNB1KBNR w KQk f6 | g5f6 | 0 | P - P
61+
4kbnr/p1P4p/b1q5/5pP1/4n3/5Q2/PP1PPP1P/RNB1KBNR w KQk f6 | g5f6 | 0 | P - P
62+
4kbnr/p1P4p/b1q5/5pP1/4n2Q/8/PP1PPP1P/RNB1KBNR w KQk f6 | g5f6 | 0 | P - P
63+
1n2kb1r/p1P4p/2qb4/5pP1/4n2Q/8/PP1PPP1P/RNB1KBNR w KQk - | c7b8q | 200 | N + (Q - P) - Q
64+
rnbqk2r/pp3ppp/2p1pn2/3p4/3P4/N1P1BN2/PPB1PPPb/R2Q1RK1 w kq - | g1h2 | 300 | B
65+
3N4/2K5/2n5/1k6/8/8/8/8 b - - | c6d8 | 0 | N - N
66+
3n3r/2P5/8/1k6/8/8/3Q4/4K3 w - - | c7d8q | 700 | (N + Q - P) - Q + R
67+
r2n3r/2P1P3/4N3/1k6/8/8/8/4K3 w - - | e6d8 | 300 | N
68+
8/8/8/1k6/6b1/4N3/2p3K1/3n4 w - - | e3d1 | 0 | N - N
69+
8/8/1k6/8/8/2N1N3/4p1K1/3n4 w - - | c3d1 | 100 | N - (N + Q - P) + Q
70+
r1bqk1nr/pppp1ppp/2n5/1B2p3/1b2P3/5N2/PPPP1PPP/RNBQK2R w KQkq - | e1g1 | 0

engine/ttable.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ func ProbeTT(b *Board, alpha int, beta int, depth uint8, m *Move) (ProbeResult,
110110
}
111111
}
112112

113-
return FAIL, 0, entry
113+
return FAIL, int(entry.score), entry
114114
}
115115

116116
return NULL, 0, &TTEntry{}

0 commit comments

Comments
 (0)