Skip to main content

perft

Function perft 

Source
pub fn perft(board: &mut Board, depth: u32) -> u64
Expand description

Count the number of legal moves at each node to the given depth (perft).

This is the standard perft (performance test) function used to verify move-generator correctness against a reference engine.

Examples found in repository?
examples/perft.rs (line 18)
3fn main() {
4    atomic_movegen::attacks::init();
5    let args: Vec<String> = env::args().collect();
6    if args.len() < 3 {
7        eprintln!("Usage: perft <fen> <depth>");
8        std::process::exit(1);
9    }
10
11    let fen = &args[1];
12    let depth: u32 = args[2]
13        .parse()
14        .expect("Depth must be a non-negative integer");
15
16    match atomic_movegen::board::Board::from_fen(fen) {
17        Ok(mut board) => {
18            let nodes = atomic_movegen::perft(&mut board, depth);
19            println!("{}", nodes);
20        }
21        Err(e) => {
22            eprintln!("Error parsing FEN: {}", e);
23            std::process::exit(1);
24        }
25    }
26}
More examples
Hide additional examples
examples/perft_divide.rs (line 33)
8fn main() {
9    atomic_movegen::attacks::init();
10    let args: Vec<String> = env::args().collect();
11    if args.len() < 3 {
12        eprintln!("Usage: perft_divide <fen> <depth>");
13        return;
14    }
15    let fen = &args[1];
16    let depth: u32 = args[2].parse().unwrap_or(1);
17
18    let mut board = Board::from_fen(fen).expect("Invalid FEN");
19
20    let mut moves = MoveList::new();
21    movegen::generate_legal(&board, &mut moves);
22    moves
23        .as_mut_slice()
24        .sort_by_key(|m| (m.from_sq() as u16, m.to_sq() as u16));
25
26    let mut total = 0u64;
27    for &m in moves.as_slice() {
28        let mut state = atomic_movegen::board::StateInfo::new();
29        board.do_move(m, &mut state);
30        let cnt = if depth <= 1 {
31            1
32        } else {
33            perft(&mut board, depth - 1)
34        };
35        board.undo_move(m, &state);
36        total += cnt;
37        println!("{}{}: {}", sq_str(m.from_sq()), sq_str(m.to_sq()), cnt);
38    }
39    println!("\nNodes searched: {}", total);
40}
examples/verify_perft.rs (line 181)
106fn main() {
107    atomic_movegen::attacks::init();
108    let args: Vec<String> = env::args().collect();
109
110    // Parse optional max depth (first positional arg).
111    let max_depth: u32 = if args.len() > 1 {
112        args[1].parse().unwrap_or(6)
113    } else {
114        6
115    };
116
117    // Parse optional file path (second positional arg, or default).
118    let path = if args.len() > 2 {
119        &args[2]
120    } else {
121        "perft_values.md"
122    };
123
124    // Limit to 0 means "no limit".
125    let max_depth = if max_depth == 0 { u32::MAX } else { max_depth };
126
127    let cases = parse_perft_values(path);
128
129    eprintln!(
130        "=== Perft verification ===\n  Source:     {path}\n  Max depth:  {}\n  Test cases: {}\n",
131        if max_depth == u32::MAX {
132            "unlimited".to_string()
133        } else {
134            max_depth.to_string()
135        },
136        cases.len()
137    );
138
139    let mut passed = 0u64;
140    let mut failed = 0u64;
141    let mut detail_lines: Vec<String> = Vec::new();
142
143    // Track timing for each test case.
144    struct CaseTiming {
145        number: usize,
146        elapsed: std::time::Duration,
147    }
148    let mut timings: Vec<CaseTiming> = Vec::with_capacity(cases.len());
149    let total_timer = Instant::now();
150
151    for case in &cases {
152        let mut board = match Board::from_fen(&case.fen) {
153            Ok(b) => b,
154            Err(e) => {
155                detail_lines.push(format!(
156                    "  Test #{}: INVALID FEN — {} ({})",
157                    case.number, case.fen, e
158                ));
159                failed += 1;
160                continue;
161            }
162        };
163
164        // Determine which depths to actually run.
165        let depths_to_test: Vec<(u32, u64)> = case
166            .depths
167            .iter()
168            .copied()
169            .filter(|(d, _)| *d <= max_depth)
170            .collect();
171
172        if depths_to_test.is_empty() {
173            continue;
174        }
175
176        let mut ok = true;
177        let case_timer = Instant::now();
178
179        // Try each depth.
180        for &(depth, expected) in &depths_to_test {
181            let result = perft(&mut board, depth);
182            if result != expected {
183                detail_lines.push(format!(
184                    "  Test #{} FAIL depth={}: expected {}, got {}",
185                    case.number, depth, expected, result
186                ));
187                ok = false;
188            }
189        }
190
191        let elapsed = case_timer.elapsed();
192        timings.push(CaseTiming {
193            number: case.number,
194            elapsed,
195        });
196
197        if ok {
198            passed += 1;
199            // Print a compact one-liner with time.
200            println!(
201                "  Test #{:<4} PASS ({} depth{}) [{:.3} s]",
202                case.number,
203                depths_to_test.len(),
204                if depths_to_test.len() == 1 { "" } else { "s" },
205                elapsed.as_secs_f64(),
206            );
207        } else {
208            failed += 1;
209        }
210    }
211
212    let total_elapsed = total_timer.elapsed();
213
214    // Print any failure detail lines.
215    if !detail_lines.is_empty() {
216        eprintln!("\n--- Failures ---");
217        for line in &detail_lines {
218            eprintln!("{line}");
219        }
220    }
221
222    // Compute summary stats.
223    let total_tests = passed + failed;
224    let fast = timings.iter().min_by(|a, b| a.elapsed.cmp(&b.elapsed));
225    let slow = timings.iter().max_by(|a, b| a.elapsed.cmp(&b.elapsed));
226    let total_time: std::time::Duration = timings.iter().map(|t| t.elapsed).sum();
227    let avg_time = if !timings.is_empty() {
228        total_time / timings.len() as u32
229    } else {
230        std::time::Duration::ZERO
231    };
232
233    // Summary block.
234    eprintln!();
235    eprintln!("--- Summary ---");
236    if let Some(f) = fast {
237        eprintln!(
238            "  Fastest:     Test #{} ({:.3} s)",
239            f.number,
240            f.elapsed.as_secs_f64()
241        );
242    }
243    if let Some(s) = slow {
244        eprintln!(
245            "  Slowest:     Test #{} ({:.3} s)",
246            s.number,
247            s.elapsed.as_secs_f64()
248        );
249    }
250    eprintln!("  Average:     {:.3} s per test", avg_time.as_secs_f64());
251    eprintln!("  Total time:  {:.3} s", total_elapsed.as_secs_f64());
252    eprintln!("  Result:      {passed}/{total_tests} passed, {failed}/{total_tests} failed");
253
254    if failed > 0 {
255        process::exit(1);
256    }
257}