pub fn perft(board: &mut Board, depth: u32) -> u64Expand 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
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}