fastpath_test.gno
1.62 Kb · 62 lines
1package minichess
2
3import "testing"
4
5// fastPerft walks the tree the way the search does (in-place make/unmake
6// with the cheap legality and check filters) and cross-checks the filters
7// against full attack tests at every node.
8func fastPerft(t *testing.T, e *engine, depth, ply int, inCheck bool) int {
9 if depth == 0 {
10 return 1
11 }
12 pb := e.buf(ply)
13 e.gen(pb, false)
14 n := 0
15 for i := 0; i < pb.nc+pb.nq; i++ {
16 m := 0
17 if i < pb.nc {
18 m = pb.caps[i]
19 } else {
20 m = pb.quiets[i-pb.nc]
21 }
22 u := e.make(m)
23 fast := e.moveIsLegal(m, u, inCheck)
24 full := !e.attacked(e.kings[1-e.turn], e.turn)
25 if fast != full {
26 t.Fatalf("moveIsLegal mismatch for %s", decode(m))
27 }
28 if full {
29 gc := e.givesCheck(m, u)
30 if gc != e.inCheck() {
31 t.Fatalf("givesCheck mismatch for %s", decode(m))
32 }
33 n += fastPerft(t, e, depth-1, ply+1, gc)
34 }
35 e.unmake(m, u)
36 }
37 return n
38}
39
40func TestFastPathPerft(t *testing.T) {
41 cases := []struct {
42 fen string
43 depth int
44 want int
45 }{
46 {"r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1", 2, 2039},
47 {"8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 1", 3, 2812},
48 {"r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1", 3, 9467},
49 {"rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8", 2, 1486},
50 }
51 for _, c := range cases {
52 p := mustFEN(t, c.fen)
53 e := newEngine(&p)
54 before := p.FEN()
55 if got := fastPerft(t, e, c.depth, 0, e.inCheck()); got != c.want {
56 t.Fatalf("%s: got %d want %d", c.fen, got, c.want)
57 }
58 if after := e.position(p.Fullmove).FEN(); after != before {
59 t.Fatalf("make/unmake did not restore: %s vs %s", after, before)
60 }
61 }
62}