1use chess::{Board, ChessMove};
2use std::collections::HashMap;
3use std::str::FromStr;
4
5#[derive(Debug, Clone)]
7pub struct OpeningEntry {
8 pub evaluation: f32,
9 pub best_moves: Vec<(ChessMove, f32)>, pub name: String,
11 pub eco_code: Option<String>, }
13
14#[derive(Clone)]
16pub struct OpeningBook {
17 entries: HashMap<String, OpeningEntry>,
19}
20
21impl Default for OpeningBook {
22 fn default() -> Self {
23 Self::new()
24 }
25}
26
27impl OpeningBook {
28 pub fn new() -> Self {
30 Self {
31 entries: HashMap::new(),
32 }
33 }
34
35 pub fn with_standard_openings() -> Self {
37 let mut book = Self::new();
38 book.add_standard_openings();
39 book
40 }
41
42 pub fn add_opening(
44 &mut self,
45 fen: &str,
46 evaluation: f32,
47 best_moves: Vec<(ChessMove, f32)>,
48 name: String,
49 eco_code: Option<String>,
50 ) -> Result<(), String> {
51 Board::from_str(fen).map_err(|_e| "Processing...".to_string())?;
53
54 let entry = OpeningEntry {
55 evaluation,
56 best_moves,
57 name,
58 eco_code,
59 };
60
61 self.entries.insert(fen.to_string(), entry);
62 Ok(())
63 }
64
65 pub fn lookup(&self, board: &Board) -> Option<&OpeningEntry> {
67 let fen = board.to_string();
68 self.entries.get(&fen)
69 }
70
71 pub fn contains(&self, board: &Board) -> bool {
73 let fen = board.to_string();
74 self.entries.contains_key(&fen)
75 }
76
77 pub fn get_all_openings(&self) -> &HashMap<String, OpeningEntry> {
79 &self.entries
80 }
81
82 pub fn get_random_opening(&self) -> Option<ChessMove> {
84 use rand::seq::SliceRandom;
85 let board = Board::default();
86 if let Some(entry) = self.lookup(&board) {
87 let moves: Vec<ChessMove> = entry.best_moves.iter().map(|(mv, _)| *mv).collect();
88 moves.choose(&mut rand::thread_rng()).copied()
89 } else {
90 None
91 }
92 }
93
94 fn add_standard_openings(&mut self) {
96 if let Ok(board) =
98 Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
99 {
100 let moves = vec![
101 (ChessMove::from_str("e2e4").unwrap(), 1.0), (ChessMove::from_str("d2d4").unwrap(), 0.9), (ChessMove::from_str("g1f3").unwrap(), 0.8), (ChessMove::from_str("c2c4").unwrap(), 0.7), ];
106 let _ = self.add_opening(
107 &board.to_string(),
108 0.0,
109 moves,
110 "Starting Position".to_string(),
111 None,
112 );
113 }
114
115 if let Ok(board) =
117 Board::from_str("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1")
118 {
119 let moves = vec![
120 (ChessMove::from_str("e7e5").unwrap(), 1.0), (ChessMove::from_str("c7c5").unwrap(), 0.9), (ChessMove::from_str("e7e6").unwrap(), 0.7), (ChessMove::from_str("c7c6").unwrap(), 0.6), ];
125 let _ = self.add_opening(
126 &board.to_string(),
127 0.25,
128 moves,
129 "King's Pawn Game".to_string(),
130 Some("B00".to_string()),
131 );
132 }
133
134 if let Ok(board) =
136 Board::from_str("rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2")
137 {
138 let moves = vec![
139 (ChessMove::from_str("g1f3").unwrap(), 1.0), (ChessMove::from_str("f2f4").unwrap(), 0.6), (ChessMove::from_str("b1c3").unwrap(), 0.5), ];
143 let _ = self.add_opening(
144 &board.to_string(),
145 0.15,
146 moves,
147 "Open Game".to_string(),
148 Some("C20".to_string()),
149 );
150 }
151
152 if let Ok(board) =
154 Board::from_str("rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2")
155 {
156 let moves = vec![
157 (ChessMove::from_str("g1f3").unwrap(), 1.0), (ChessMove::from_str("b1c3").unwrap(), 0.7), (ChessMove::from_str("f2f4").unwrap(), 0.5), ];
161 let _ = self.add_opening(
162 &board.to_string(),
163 0.3,
164 moves,
165 "Sicilian Defense".to_string(),
166 Some("B20".to_string()),
167 );
168 }
169
170 if let Ok(board) =
172 Board::from_str("rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq - 0 1")
173 {
174 let moves = vec![
175 (ChessMove::from_str("d7d5").unwrap(), 1.0), (ChessMove::from_str("g8f6").unwrap(), 0.9), (ChessMove::from_str("f7f5").unwrap(), 0.4), ];
179 let _ = self.add_opening(
180 &board.to_string(),
181 0.2,
182 moves,
183 "Queen's Pawn Game".to_string(),
184 Some("D00".to_string()),
185 );
186 }
187
188 if let Ok(board) =
190 Board::from_str("rnbqkbnr/ppp1pppp/8/3p4/2PP4/8/PP2PPPP/RNBQKBNR b KQkq - 0 2")
191 {
192 let moves = vec![
193 (ChessMove::from_str("d5c4").unwrap(), 0.7), (ChessMove::from_str("e7e6").unwrap(), 1.0), (ChessMove::from_str("c7c6").unwrap(), 0.8), (ChessMove::from_str("g8f6").unwrap(), 0.8), ];
198 let _ = self.add_opening(
199 &board.to_string(),
200 0.3,
201 moves,
202 "Queen's Gambit".to_string(),
203 Some("D06".to_string()),
204 );
205 }
206
207 if let Ok(board) =
209 Board::from_str("r1bqkbnr/pppp1ppp/2n5/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R b KQkq - 3 3")
210 {
211 let moves = vec![
212 (ChessMove::from_str("g8f6").unwrap(), 1.0), (ChessMove::from_str("f7f5").unwrap(), 0.6), (ChessMove::from_str("f8e7").unwrap(), 0.7), ];
216 let _ = self.add_opening(
217 &board.to_string(),
218 0.4,
219 moves,
220 "Italian Game".to_string(),
221 Some("C50".to_string()),
222 );
223 }
224
225 if let Ok(board) =
227 Board::from_str("r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R b KQkq - 3 3")
228 {
229 let moves = vec![
230 (ChessMove::from_str("a7a6").unwrap(), 1.0), (ChessMove::from_str("g8f6").unwrap(), 0.8), (ChessMove::from_str("f7f5").unwrap(), 0.4), ];
234 let _ = self.add_opening(
235 &board.to_string(),
236 0.5,
237 moves,
238 "Ruy Lopez".to_string(),
239 Some("C60".to_string()),
240 );
241 }
242
243 self.add_deeper_openings();
245 self.add_modern_openings();
246 self.add_indian_defenses();
247 self.add_sicilian_variations();
248 self.add_french_caro_kann();
249 self.add_english_reti_systems();
250 self.add_gambit_systems();
251 }
252
253 fn add_deeper_openings(&mut self) {
255 if let Ok(board) =
257 Board::from_str("r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 4 4")
258 {
259 let moves = vec![
260 (ChessMove::from_str("d2d3").unwrap(), 0.9), (ChessMove::from_str("b1c3").unwrap(), 0.8), (ChessMove::from_str("e1g1").unwrap(), 0.7), ];
264 let _ = self.add_opening(
265 &board.to_string(),
266 0.3,
267 moves,
268 "Italian Game - Main Line".to_string(),
269 Some("C53".to_string()),
270 );
271 }
272
273 if let Ok(board) =
275 Board::from_str("rnbqkbnr/pppp1ppp/4p3/8/3PP3/8/PPP2PPP/RNBQKBNR b KQkq - 0 2")
276 {
277 let moves = vec![
278 (ChessMove::from_str("d7d5").unwrap(), 1.0), (ChessMove::from_str("c7c5").unwrap(), 0.6), ];
281 let _ = self.add_opening(
282 &board.to_string(),
283 -0.1,
284 moves,
285 "French Defense".to_string(),
286 Some("C00".to_string()),
287 );
288 }
289
290 if let Ok(board) =
292 Board::from_str("rnbqkbnr/pp1ppppp/2p5/8/3PP3/8/PPP2PPP/RNBQKBNR b KQkq - 0 2")
293 {
294 let moves = vec![
295 (ChessMove::from_str("d7d5").unwrap(), 1.0), ];
297 let _ = self.add_opening(
298 &board.to_string(),
299 0.0,
300 moves,
301 "Caro-Kann Defense".to_string(),
302 Some("B10".to_string()),
303 );
304 }
305
306 if let Ok(board) =
308 Board::from_str("rnbqkbnr/pppp1ppp/8/4p3/2P5/8/PP1PPPPP/RNBQKBNR w KQkq - 0 2")
309 {
310 let moves = vec![
311 (ChessMove::from_str("b1c3").unwrap(), 1.0), (ChessMove::from_str("g1f3").unwrap(), 0.8), (ChessMove::from_str("g2g3").unwrap(), 0.7), ];
315 let _ = self.add_opening(
316 &board.to_string(),
317 0.1,
318 moves,
319 "English Opening - King's English".to_string(),
320 Some("A20".to_string()),
321 );
322 }
323
324 if let Ok(board) =
326 Board::from_str("rnbqkb1r/pppppp1p/5np1/8/2PP4/8/PP2PPPP/RNBQKBNR w KQkq - 0 3")
327 {
328 let moves = vec![
329 (ChessMove::from_str("b1c3").unwrap(), 1.0), (ChessMove::from_str("g1f3").unwrap(), 0.9), (ChessMove::from_str("f2f3").unwrap(), 0.6), ];
333 let _ = self.add_opening(
334 &board.to_string(),
335 0.2,
336 moves,
337 "King's Indian Defense Setup".to_string(),
338 Some("E60".to_string()),
339 );
340 }
341
342 if let Ok(board) =
344 Board::from_str("rnbqk2r/pppp1ppp/4pn2/8/1bPP4/2N5/PP2PPPP/R1BQKBNR w KQkq - 2 4")
345 {
346 let moves = vec![
347 (ChessMove::from_str("d1c2").unwrap(), 0.9), (ChessMove::from_str("e2e3").unwrap(), 1.0), (ChessMove::from_str("a2a3").unwrap(), 0.7), ];
351 let _ = self.add_opening(
352 &board.to_string(),
353 0.1,
354 moves,
355 "Nimzo-Indian Defense".to_string(),
356 Some("E20".to_string()),
357 );
358 }
359
360 self.add_comprehensive_openings();
362 }
363
364 fn add_modern_openings(&mut self) {
366 if let Ok(board) =
368 Board::from_str("rnbqkb1r/ppp1pppp/5n2/3p4/3P1B2/5N2/PPP1PPPP/RN1QKB1R b KQkq - 3 3")
369 {
370 let moves = vec![
371 (ChessMove::from_str("c7c5").unwrap(), 1.0), (ChessMove::from_str("e7e6").unwrap(), 0.8), (ChessMove::from_str("c8f5").unwrap(), 0.9), ];
375 let _ = self.add_opening(
376 &board.to_string(),
377 0.1,
378 moves,
379 "London System vs ...d5".to_string(),
380 Some("D02".to_string()),
381 );
382 }
383
384 if let Ok(board) =
386 Board::from_str("rnbqkb1r/pppp1ppp/4pn2/8/2PP4/6P1/PP2PP1P/RNBQKBNR b KQkq - 0 3")
387 {
388 let moves = vec![
389 (ChessMove::from_str("d7d5").unwrap(), 1.0), (ChessMove::from_str("f8e7").unwrap(), 0.8), (ChessMove::from_str("c7c6").unwrap(), 0.7), ];
393 let _ = self.add_opening(
394 &board.to_string(),
395 0.2,
396 moves,
397 "Catalan Opening".to_string(),
398 Some("E00".to_string()),
399 );
400 }
401
402 if let Ok(board) =
404 Board::from_str("rnbqkb1r/pppppppp/5n2/8/3P4/8/PPP1PPPP/RNBQKBNR w KQkq - 1 2")
405 {
406 let moves = vec![
407 (ChessMove::from_str("c1g5").unwrap(), 1.0), (ChessMove::from_str("c2c4").unwrap(), 0.9), (ChessMove::from_str("g1f3").unwrap(), 0.8), ];
411 let _ = self.add_opening(
412 &board.to_string(),
413 0.2,
414 moves,
415 "Trompowsky Attack".to_string(),
416 Some("A45".to_string()),
417 );
418 }
419
420 if let Ok(board) =
422 Board::from_str("rnbqkb1r/ppp1pppp/5n2/3p4/8/5NP1/PPPPPP1P/RNBQKB1R b KQkq - 0 3")
423 {
424 let moves = vec![
425 (ChessMove::from_str("c8g4").unwrap(), 0.8), (ChessMove::from_str("e7e6").unwrap(), 1.0), (ChessMove::from_str("c7c6").unwrap(), 0.7), ];
429 let _ = self.add_opening(
430 &board.to_string(),
431 0.0,
432 moves,
433 "King's Indian Attack".to_string(),
434 Some("A07".to_string()),
435 );
436 }
437 }
438
439 fn add_indian_defenses(&mut self) {
441 if let Ok(board) =
443 Board::from_str("rnbqkb1r/p1pppppp/1p3n2/8/2PP4/5N2/PP2PPPP/RNBQKB1R w KQkq - 0 3")
444 {
445 let moves = vec![
446 (ChessMove::from_str("g2g3").unwrap(), 1.0), (ChessMove::from_str("a2a3").unwrap(), 0.8), (ChessMove::from_str("b1c3").unwrap(), 0.9), ];
450 let _ = self.add_opening(
451 &board.to_string(),
452 0.1,
453 moves,
454 "Queen's Indian Defense".to_string(),
455 Some("E12".to_string()),
456 );
457 }
458
459 if let Ok(board) =
461 Board::from_str("rnbqk2r/ppppppbp/5n2/8/2PP4/5N2/PP2PPPP/RNBQKB1R w KQkq - 2 4")
462 {
463 let moves = vec![
464 (ChessMove::from_str("c1d2").unwrap(), 1.0), (ChessMove::from_str("b1d2").unwrap(), 0.8), (ChessMove::from_str("d1c2").unwrap(), 0.7), ];
468 let _ = self.add_opening(
469 &board.to_string(),
470 0.0,
471 moves,
472 "Bogo-Indian Defense".to_string(),
473 Some("E11".to_string()),
474 );
475 }
476
477 if let Ok(board) =
479 Board::from_str("rnbqkb1r/ppp1pp1p/5np1/3p4/2PP4/2N5/PP2PPPP/R1BQKBNR w KQkq - 0 4")
480 {
481 let moves = vec![
482 (ChessMove::from_str("c4d5").unwrap(), 0.7), (ChessMove::from_str("g1f3").unwrap(), 1.0), (ChessMove::from_str("c1f4").unwrap(), 0.8), ];
486 let _ = self.add_opening(
487 &board.to_string(),
488 0.1,
489 moves,
490 "Grunfeld Defense".to_string(),
491 Some("D80".to_string()),
492 );
493 }
494
495 if let Ok(board) =
497 Board::from_str("rnbqkb1r/pp2pppp/3p1n2/2pP4/2P5/8/PP2PPPP/RNBQKBNR w KQkq - 0 4")
498 {
499 let moves = vec![
500 (ChessMove::from_str("b1c3").unwrap(), 1.0), (ChessMove::from_str("g1f3").unwrap(), 0.9), (ChessMove::from_str("f2f4").unwrap(), 0.7), ];
504 let _ = self.add_opening(
505 &board.to_string(),
506 0.3,
507 moves,
508 "Modern Benoni".to_string(),
509 Some("A60".to_string()),
510 );
511 }
512 }
513
514 fn add_sicilian_variations(&mut self) {
516 if let Ok(board) =
518 Board::from_str("rnbqkbnr/pp1ppp1p/6p1/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 3")
519 {
520 let moves = vec![
521 (ChessMove::from_str("g1f3").unwrap(), 1.0), (ChessMove::from_str("d2d4").unwrap(), 0.9), (ChessMove::from_str("f2f4").unwrap(), 0.6), ];
525 let _ = self.add_opening(
526 &board.to_string(),
527 0.2,
528 moves,
529 "Sicilian Accelerated Dragon".to_string(),
530 Some("B35".to_string()),
531 );
532 }
533
534 if let Ok(board) = Board::from_str(
536 "r1bqkb1r/1pp1pppp/p1np1n2/4p3/3P4/2N2N2/PPP1PPPP/R1BQKB1R w KQkq - 0 6",
537 ) {
538 let moves = vec![
539 (ChessMove::from_str("f3d5").unwrap(), 0.8), (ChessMove::from_str("c1g5").unwrap(), 1.0), (ChessMove::from_str("f1e2").unwrap(), 0.9), ];
543 let _ = self.add_opening(
544 &board.to_string(),
545 0.2,
546 moves,
547 "Sicilian Sveshnikov".to_string(),
548 Some("B33".to_string()),
549 );
550 }
551
552 if let Ok(board) =
554 Board::from_str("rnbqkb1r/pp3ppp/3ppn2/8/3P4/2N2N2/PPP1PPPP/R1BQKB1R w KQkq - 0 6")
555 {
556 let moves = vec![
557 (ChessMove::from_str("f2f3").unwrap(), 1.0), (ChessMove::from_str("f1e2").unwrap(), 0.9), (ChessMove::from_str("c1e3").unwrap(), 0.8), ];
561 let _ = self.add_opening(
562 &board.to_string(),
563 0.1,
564 moves,
565 "Sicilian Scheveningen".to_string(),
566 Some("B80".to_string()),
567 );
568 }
569
570 if let Ok(board) =
572 Board::from_str("rnbqkb1r/1pp2ppp/p3pn2/8/3P4/2N2N2/PPP1PPPP/R1BQKB1R w KQkq - 0 6")
573 {
574 let moves = vec![
575 (ChessMove::from_str("f1e2").unwrap(), 1.0), (ChessMove::from_str("g2g3").unwrap(), 0.8), (ChessMove::from_str("f2f4").unwrap(), 0.7), ];
579 let _ = self.add_opening(
580 &board.to_string(),
581 0.1,
582 moves,
583 "Sicilian Paulsen".to_string(),
584 Some("B40".to_string()),
585 );
586 }
587 }
588
589 fn add_french_caro_kann(&mut self) {
591 if let Ok(board) =
593 Board::from_str("rnbqk1nr/ppp2ppp/4p3/3p4/1b1PP3/2N5/PPP2PPP/R1BQKBNR w KQkq - 2 4")
594 {
595 let moves = vec![
596 (ChessMove::from_str("e4e5").unwrap(), 1.0), (ChessMove::from_str("f1d3").unwrap(), 0.8), (ChessMove::from_str("a2a3").unwrap(), 0.7), ];
600 let _ = self.add_opening(
601 &board.to_string(),
602 0.2,
603 moves,
604 "French Winawer".to_string(),
605 Some("C15".to_string()),
606 );
607 }
608
609 if let Ok(board) =
611 Board::from_str("rnbqkbnr/ppp2ppp/4p3/3p4/3PP3/8/PPPR1PPP/RNBQKBNR b KQkq - 1 3")
612 {
613 let moves = vec![
614 (ChessMove::from_str("c7c5").unwrap(), 1.0), (ChessMove::from_str("g8f6").unwrap(), 0.9), (ChessMove::from_str("b8c6").unwrap(), 0.8), ];
618 let _ = self.add_opening(
619 &board.to_string(),
620 -0.1,
621 moves,
622 "French Tarrasch".to_string(),
623 Some("C03".to_string()),
624 );
625 }
626
627 if let Ok(board) =
629 Board::from_str("rnbqkbnr/pp2pppp/2p5/3pP3/3P4/8/PPP2PPP/RNBQKBNR b KQkq - 0 3")
630 {
631 let moves = vec![
632 (ChessMove::from_str("c8f5").unwrap(), 1.0), (ChessMove::from_str("e7e6").unwrap(), 0.8), (ChessMove::from_str("h7h6").unwrap(), 0.6), ];
636 let _ = self.add_opening(
637 &board.to_string(),
638 0.0,
639 moves,
640 "Caro-Kann Advance".to_string(),
641 Some("B12".to_string()),
642 );
643 }
644
645 if let Ok(board) =
647 Board::from_str("rnbqkbnr/pp2pppp/2p5/3P4/8/8/PPP1PPPP/RNBQKBNR b KQkq - 0 3")
648 {
649 let moves = vec![
650 (ChessMove::from_str("d5c4").unwrap(), 1.0), ];
652 let _ = self.add_opening(
653 &board.to_string(),
654 0.0,
655 moves,
656 "Caro-Kann Exchange".to_string(),
657 Some("B13".to_string()),
658 );
659 }
660 }
661
662 fn add_english_reti_systems(&mut self) {
664 if let Ok(board) =
666 Board::from_str("rnbqkbnr/pp1ppppp/8/2p5/2P5/8/PP1PPPPP/RNBQKBNR w KQkq - 0 2")
667 {
668 let moves = vec![
669 (ChessMove::from_str("b1c3").unwrap(), 1.0), (ChessMove::from_str("g1f3").unwrap(), 0.9), (ChessMove::from_str("g2g3").unwrap(), 0.8), ];
673 let _ = self.add_opening(
674 &board.to_string(),
675 0.0,
676 moves,
677 "English Symmetrical".to_string(),
678 Some("A30".to_string()),
679 );
680 }
681
682 if let Ok(board) =
684 Board::from_str("rnbqkb1r/pppppppp/5n2/8/8/5N2/PPPPPPPP/RNBQKB1R w KQkq - 1 2")
685 {
686 let moves = vec![
687 (ChessMove::from_str("c2c4").unwrap(), 1.0), (ChessMove::from_str("g2g3").unwrap(), 0.9), (ChessMove::from_str("d2d4").unwrap(), 0.8), ];
691 let _ = self.add_opening(
692 &board.to_string(),
693 0.1,
694 moves,
695 "Reti Opening".to_string(),
696 Some("A04".to_string()),
697 );
698 }
699
700 if let Ok(board) =
702 Board::from_str("rnbqkbnr/pppppppp/8/8/5P2/8/PPPPP1PP/RNBQKBNR b KQkq - 0 1")
703 {
704 let moves = vec![
705 (ChessMove::from_str("d7d5").unwrap(), 1.0), (ChessMove::from_str("g8f6").unwrap(), 0.8), (ChessMove::from_str("e7e5").unwrap(), 0.9), ];
709 let _ = self.add_opening(
710 &board.to_string(),
711 -0.1,
712 moves,
713 "Bird's Opening".to_string(),
714 Some("A02".to_string()),
715 );
716 }
717 }
718
719 fn add_gambit_systems(&mut self) {
721 if let Ok(board) =
723 Board::from_str("rnbqkbnr/pppp1ppp/8/4p3/4PP2/8/PPPP2PP/RNBQKBNR b KQkq - 0 2")
724 {
725 let moves = vec![
726 (ChessMove::from_str("e5f4").unwrap(), 1.0), (ChessMove::from_str("d7d6").unwrap(), 0.7), (ChessMove::from_str("f8c5").unwrap(), 0.8), ];
730 let _ = self.add_opening(
731 &board.to_string(),
732 -0.2,
733 moves,
734 "King's Gambit".to_string(),
735 Some("C30".to_string()),
736 );
737 }
738
739 if let Ok(board) =
741 Board::from_str("rnbqkbnr/ppp1pppp/8/8/2pP4/8/PP2PPPP/RNBQKBNR w KQkq - 0 3")
742 {
743 let moves = vec![
744 (ChessMove::from_str("g1f3").unwrap(), 1.0), (ChessMove::from_str("e2e3").unwrap(), 0.9), (ChessMove::from_str("a2a4").unwrap(), 0.7), ];
748 let _ = self.add_opening(
749 &board.to_string(),
750 0.3,
751 moves,
752 "Queen's Gambit Accepted".to_string(),
753 Some("D20".to_string()),
754 );
755 }
756
757 if let Ok(board) =
759 Board::from_str("r1bqk1nr/pppp1ppp/2n5/2b1p3/1PB1P3/5N2/P1PP1PPP/RNBQK2R b KQkq - 2 4")
760 {
761 let moves = vec![
762 (ChessMove::from_str("c5b4").unwrap(), 1.0), (ChessMove::from_str("c5a5").unwrap(), 0.8), (ChessMove::from_str("c5e7").unwrap(), 0.7), ];
766 let _ = self.add_opening(
767 &board.to_string(),
768 0.4,
769 moves,
770 "Evans Gambit".to_string(),
771 Some("C51".to_string()),
772 );
773 }
774 }
775
776 fn add_comprehensive_openings(&mut self) {
778 if let Ok(board) =
780 Board::from_str("rnbqkbnr/ppp1pppp/8/3p4/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2")
781 {
782 let moves = vec![
783 (ChessMove::from_str("e4d5").unwrap(), 1.0), ];
785 let _ = self.add_opening(
786 &board.to_string(),
787 0.3,
788 moves,
789 "Scandinavian Defense".to_string(),
790 Some("B01".to_string()),
791 );
792 }
793
794 if let Ok(board) =
796 Board::from_str("rnbqkb1r/pppppppp/5n2/8/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 1 2")
797 {
798 let moves = vec![
799 (ChessMove::from_str("e4e5").unwrap(), 1.0), (ChessMove::from_str("b1c3").unwrap(), 0.8), ];
802 let _ = self.add_opening(
803 &board.to_string(),
804 0.2,
805 moves,
806 "Alekhine's Defense".to_string(),
807 Some("B02".to_string()),
808 );
809 }
810
811 if let Ok(board) =
813 Board::from_str("rnbqkb1r/ppp1pppp/3p1n2/8/3PP3/8/PPP2PPP/RNBQKBNR w KQkq - 0 3")
814 {
815 let moves = vec![
816 (ChessMove::from_str("b1c3").unwrap(), 1.0), (ChessMove::from_str("f2f4").unwrap(), 0.8), (ChessMove::from_str("g1f3").unwrap(), 0.9), ];
820 let _ = self.add_opening(
821 &board.to_string(),
822 0.1,
823 moves,
824 "Pirc Defense".to_string(),
825 Some("B07".to_string()),
826 );
827 }
828
829 if let Ok(board) =
831 Board::from_str("rnbqkb1r/1pp1pppp/p4n2/3p4/3P4/5N2/PPP1PPPP/RNBQKB1R w KQkq - 0 4")
832 {
833 let moves = vec![
834 (ChessMove::from_str("f1e2").unwrap(), 0.9), (ChessMove::from_str("f2f3").unwrap(), 0.8), (ChessMove::from_str("b1c3").unwrap(), 1.0), ];
838 let _ = self.add_opening(
839 &board.to_string(),
840 0.2,
841 moves,
842 "Sicilian Najdorf".to_string(),
843 Some("B90".to_string()),
844 );
845 }
846
847 if let Ok(board) =
849 Board::from_str("rnbqk2r/pp2ppbp/3p1np1/8/3P4/2N2N2/PPP1PPPP/R1BQKB1R w KQkq - 0 6")
850 {
851 let moves = vec![
852 (ChessMove::from_str("f2f3").unwrap(), 1.0), (ChessMove::from_str("f1e2").unwrap(), 0.8), (ChessMove::from_str("c1e3").unwrap(), 0.9), ];
856 let _ = self.add_opening(
857 &board.to_string(),
858 0.2,
859 moves,
860 "Sicilian Dragon".to_string(),
861 Some("B70".to_string()),
862 );
863 }
864
865 if let Ok(board) =
867 Board::from_str("r1bqkbnr/1ppp1ppp/p1n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 0 4")
868 {
869 let moves = vec![
870 (ChessMove::from_str("b5a4").unwrap(), 1.0), (ChessMove::from_str("b5c6").unwrap(), 0.6), ];
873 let _ = self.add_opening(
874 &board.to_string(),
875 0.4,
876 moves,
877 "Ruy Lopez - Morphy Defense".to_string(),
878 Some("C78".to_string()),
879 );
880 }
881
882 if let Ok(board) =
884 Board::from_str("r1bqk1nr/pppp1ppp/2n5/1Bb1p3/4P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 4 4")
885 {
886 let moves = vec![
887 (ChessMove::from_str("e1g1").unwrap(), 1.0), (ChessMove::from_str("d2d3").unwrap(), 0.9), (ChessMove::from_str("c2c3").unwrap(), 0.8), ];
891 let _ = self.add_opening(
892 &board.to_string(),
893 0.3,
894 moves,
895 "Ruy Lopez - Closed System".to_string(),
896 Some("C84".to_string()),
897 );
898 }
899
900 if let Ok(board) =
902 Board::from_str("r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 4 4")
903 {
904 let moves = vec![
905 (ChessMove::from_str("f3g5").unwrap(), 0.8), (ChessMove::from_str("d2d3").unwrap(), 1.0), (ChessMove::from_str("b1c3").unwrap(), 0.9), ];
909 let _ = self.add_opening(
910 &board.to_string(),
911 0.2,
912 moves,
913 "Two Knights Defense".to_string(),
914 Some("C55".to_string()),
915 );
916 }
917
918 if let Ok(board) =
920 Board::from_str("rnbqkbnr/ppppp1pp/8/5p2/3P4/8/PPP1PPPP/RNBQKBNR w KQkq - 0 2")
921 {
922 let moves = vec![
923 (ChessMove::from_str("g1f3").unwrap(), 1.0), (ChessMove::from_str("c2c4").unwrap(), 0.9), (ChessMove::from_str("g2g3").unwrap(), 0.8), ];
927 let _ = self.add_opening(
928 &board.to_string(),
929 0.3,
930 moves,
931 "Dutch Defense".to_string(),
932 Some("A80".to_string()),
933 );
934 }
935
936 if let Ok(board) =
938 Board::from_str("r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 4 4")
939 {
940 let moves = vec![
941 (ChessMove::from_str("f3g5").unwrap(), 0.8), (ChessMove::from_str("d2d3").unwrap(), 1.0), (ChessMove::from_str("b1c3").unwrap(), 0.9), ];
945 let _ = self.add_opening(
946 &board.to_string(),
947 0.3,
948 moves,
949 "Italian Game - Two Knights".to_string(),
950 Some("C55".to_string()),
951 );
952 }
953
954 if let Ok(board) =
956 Board::from_str("rnbqkbnr/pppp1ppp/8/4p3/4PP2/8/PPPP2PP/RNBQKBNR b KQkq - 0 2")
957 {
958 let moves = vec![
959 (ChessMove::from_str("e5f4").unwrap(), 1.0), (ChessMove::from_str("f8c5").unwrap(), 0.7), ];
962 let _ = self.add_opening(
963 &board.to_string(),
964 0.0,
965 moves,
966 "King's Gambit".to_string(),
967 Some("C30".to_string()),
968 );
969 }
970
971 if let Ok(board) =
973 Board::from_str("rnbqkbnr/ppp1pppp/8/8/2pP4/8/PP2PPPP/RNBQKBNR w KQkq - 0 3")
974 {
975 let moves = vec![
976 (ChessMove::from_str("g1f3").unwrap(), 1.0), (ChessMove::from_str("e2e3").unwrap(), 0.9), (ChessMove::from_str("e2e4").unwrap(), 0.6), ];
980 let _ = self.add_opening(
981 &board.to_string(),
982 0.2,
983 moves,
984 "Queen's Gambit Accepted".to_string(),
985 Some("D20".to_string()),
986 );
987 }
988
989 if let Ok(board) =
991 Board::from_str("rnbqkbnr/ppp2ppp/4p3/3p4/2PP4/8/PP2PPPP/RNBQKBNR w KQkq - 0 3")
992 {
993 let moves = vec![
994 (ChessMove::from_str("b1c3").unwrap(), 1.0), (ChessMove::from_str("g1f3").unwrap(), 0.9), (ChessMove::from_str("c4d5").unwrap(), 0.6), ];
998 let _ = self.add_opening(
999 &board.to_string(),
1000 0.2,
1001 moves,
1002 "Queen's Gambit Declined".to_string(),
1003 Some("D30".to_string()),
1004 );
1005 }
1006
1007 if let Ok(board) =
1009 Board::from_str("rnbqkbnr/ppp1pppp/8/3p4/3P4/5N2/PPP1PPPP/RNBQKB1R w KQkq - 0 2")
1010 {
1011 let moves = vec![
1012 (ChessMove::from_str("c1f4").unwrap(), 1.0), (ChessMove::from_str("c2c4").unwrap(), 0.8), ];
1015 let _ = self.add_opening(
1016 &board.to_string(),
1017 0.2,
1018 moves,
1019 "London System".to_string(),
1020 Some("D02".to_string()),
1021 );
1022 }
1023
1024 if let Ok(board) =
1026 Board::from_str("rnbqkbnr/ppp2ppp/4p3/3p4/2PP4/6P1/PP2PP1P/RNBQKBNR b KQkq - 0 3")
1027 {
1028 let moves = vec![
1029 (ChessMove::from_str("d5c4").unwrap(), 0.8), (ChessMove::from_str("g8f6").unwrap(), 1.0), (ChessMove::from_str("f8e7").unwrap(), 0.7), ];
1033 let _ = self.add_opening(
1034 &board.to_string(),
1035 0.3,
1036 moves,
1037 "Catalan Opening".to_string(),
1038 Some("E00".to_string()),
1039 );
1040 }
1041
1042 if let Ok(board) =
1044 Board::from_str("rnbqkb1r/ppp1pp1p/5np1/3p4/2PP4/2N5/PP2PPPP/R1BQKBNR w KQkq - 0 4")
1045 {
1046 let moves = vec![
1047 (ChessMove::from_str("c4d5").unwrap(), 0.8), (ChessMove::from_str("g1f3").unwrap(), 1.0), (ChessMove::from_str("f2f3").unwrap(), 0.7), ];
1051 let _ = self.add_opening(
1052 &board.to_string(),
1053 0.2,
1054 moves,
1055 "Grunfeld Defense".to_string(),
1056 Some("D80".to_string()),
1057 );
1058 }
1059
1060 if let Ok(board) =
1062 Board::from_str("rnbqkbnr/pp2pppp/8/2pp4/3P4/8/PPP1PPPP/RNBQKBNR w KQkq - 0 3")
1063 {
1064 let moves = vec![
1065 (ChessMove::from_str("d4d5").unwrap(), 1.0), (ChessMove::from_str("g1f3").unwrap(), 0.8), ];
1068 let _ = self.add_opening(
1069 &board.to_string(),
1070 0.3,
1071 moves,
1072 "Benoni Defense".to_string(),
1073 Some("A60".to_string()),
1074 );
1075 }
1076
1077 if let Ok(board) =
1079 Board::from_str("rnbqkbnr/ppppp1pp/8/5p2/3P4/8/PPP1PPPP/RNBQKBNR w KQkq - 0 2")
1080 {
1081 let moves = vec![
1082 (ChessMove::from_str("g1f3").unwrap(), 1.0), (ChessMove::from_str("g2g3").unwrap(), 0.8), (ChessMove::from_str("b1c3").unwrap(), 0.7), ];
1086 let _ = self.add_opening(
1087 &board.to_string(),
1088 0.1,
1089 moves,
1090 "Dutch Defense".to_string(),
1091 Some("A80".to_string()),
1092 );
1093 }
1094
1095 if let Ok(board) =
1097 Board::from_str("rnbqkbnr/pppp1ppp/8/4p3/4P3/2N5/PPPP1PPP/R1BQKBNR b KQkq - 1 2")
1098 {
1099 let moves = vec![
1100 (ChessMove::from_str("g8f6").unwrap(), 1.0), (ChessMove::from_str("b8c6").unwrap(), 0.8), (ChessMove::from_str("f7f5").unwrap(), 0.6), ];
1104 let _ = self.add_opening(
1105 &board.to_string(),
1106 0.2,
1107 moves,
1108 "Vienna Game".to_string(),
1109 Some("C25".to_string()),
1110 );
1111 }
1112
1113 if let Ok(board) =
1115 Board::from_str("rnbqkbnr/pppp1ppp/8/4p3/3PP3/8/PPP2PPP/RNBQKBNR b KQkq - 0 2")
1116 {
1117 let moves = vec![
1118 (ChessMove::from_str("e5d4").unwrap(), 1.0), (ChessMove::from_str("g8f6").unwrap(), 0.8), ];
1121 let _ = self.add_opening(
1122 &board.to_string(),
1123 0.3,
1124 moves,
1125 "Scotch Game".to_string(),
1126 Some("C45".to_string()),
1127 );
1128 }
1129
1130 if let Ok(board) =
1132 Board::from_str("rnbqkb1r/pppp1ppp/5n2/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3")
1133 {
1134 let moves = vec![
1135 (ChessMove::from_str("f3e5").unwrap(), 1.0), (ChessMove::from_str("d2d4").unwrap(), 0.8), ];
1138 let _ = self.add_opening(
1139 &board.to_string(),
1140 0.1,
1141 moves,
1142 "Petroff Defense".to_string(),
1143 Some("C42".to_string()),
1144 );
1145 }
1146
1147 if let Ok(board) =
1149 Board::from_str("rnbqkbnr/pppppppp/8/8/5P2/8/PPPPP1PP/RNBQKBNR b KQkq - 0 1")
1150 {
1151 let moves = vec![
1152 (ChessMove::from_str("d7d5").unwrap(), 1.0), (ChessMove::from_str("g8f6").unwrap(), 0.8), (ChessMove::from_str("e7e5").unwrap(), 0.7), ];
1156 let _ = self.add_opening(
1157 &board.to_string(),
1158 0.0,
1159 moves,
1160 "Bird's Opening".to_string(),
1161 Some("A02".to_string()),
1162 );
1163 }
1164
1165 self.add_extra_modern_openings();
1167 }
1168
1169 fn add_extra_modern_openings(&mut self) {
1171 if let Ok(board) =
1173 Board::from_str("rnbqkb1r/pp1ppp1p/5np1/2p5/3P4/5N2/PPP1PPPP/RNBQKB1R w KQkq - 0 4")
1174 {
1175 let moves = vec![
1176 (ChessMove::from_str("b1c3").unwrap(), 1.0), (ChessMove::from_str("c2c4").unwrap(), 0.8), (ChessMove::from_str("f1e2").unwrap(), 0.7), ];
1180 let _ = self.add_opening(
1181 &board.to_string(),
1182 0.2,
1183 moves,
1184 "Sicilian - Accelerated Dragon".to_string(),
1185 Some("B35".to_string()),
1186 );
1187 }
1188
1189 if let Ok(board) =
1191 Board::from_str("r1bqkb1r/1p2pppp/p1np1n2/4p3/3PP3/2N2N2/PPP2PPP/R1BQKB1R w KQkq - 0 6")
1192 {
1193 let moves = vec![
1194 (ChessMove::from_str("f1e2").unwrap(), 1.0), (ChessMove::from_str("f1c4").unwrap(), 0.8), (ChessMove::from_str("a2a4").unwrap(), 0.7), ];
1198 let _ = self.add_opening(
1199 &board.to_string(),
1200 0.1,
1201 moves,
1202 "Sicilian - Sveshnikov Variation".to_string(),
1203 Some("B33".to_string()),
1204 );
1205 }
1206
1207 if let Ok(board) =
1209 Board::from_str("rnbqkb1r/pppppppp/5n2/8/3P4/8/PPP1PPPP/RNBQKBNR w KQkq - 1 2")
1210 {
1211 let moves = vec![
1212 (ChessMove::from_str("c1g5").unwrap(), 1.0), (ChessMove::from_str("g1f3").unwrap(), 0.8), (ChessMove::from_str("b1c3").unwrap(), 0.7), ];
1216 let _ = self.add_opening(
1217 &board.to_string(),
1218 0.2,
1219 moves,
1220 "Trompowsky Attack".to_string(),
1221 Some("A45".to_string()),
1222 );
1223 }
1224
1225 if let Ok(board) =
1227 Board::from_str("rnbqkb1r/ppp1pppp/5n2/3p4/3P4/5N2/PPP1PPPP/RNBQKB1R w KQkq - 0 3")
1228 {
1229 let moves = vec![
1230 (ChessMove::from_str("c1g5").unwrap(), 1.0), (ChessMove::from_str("c2c4").unwrap(), 0.8), (ChessMove::from_str("e2e3").unwrap(), 0.7), ];
1234 let _ = self.add_opening(
1235 &board.to_string(),
1236 0.2,
1237 moves,
1238 "Torre Attack".to_string(),
1239 Some("D03".to_string()),
1240 );
1241 }
1242
1243 if let Ok(board) =
1245 Board::from_str("rnbqkbnr/ppp1pppp/8/3p4/3PP3/8/PPP2PPP/RNBQKBNR b KQkq - 0 2")
1246 {
1247 let moves = vec![
1248 (ChessMove::from_str("d5e4").unwrap(), 0.8), (ChessMove::from_str("g8f6").unwrap(), 1.0), (ChessMove::from_str("c7c6").unwrap(), 0.7), ];
1252 let _ = self.add_opening(
1253 &board.to_string(),
1254 0.0,
1255 moves,
1256 "Blackmar-Diemer Gambit".to_string(),
1257 Some("D00".to_string()),
1258 );
1259 }
1260
1261 if let Ok(board) =
1263 Board::from_str("rnbqkbnr/ppp1pppp/8/8/3p4/8/PPP1PPPP/RNBQKBNR w KQkq - 0 2")
1264 {
1265 let moves = vec![
1266 (ChessMove::from_str("d1d4").unwrap(), 1.0), (ChessMove::from_str("g1f3").unwrap(), 0.8), ];
1269 let _ = self.add_opening(
1270 &board.to_string(),
1271 0.3,
1272 moves,
1273 "Scandinavian - Main Line".to_string(),
1274 Some("B01".to_string()),
1275 );
1276 }
1277
1278 if let Ok(board) =
1280 Board::from_str("rnbqkbnr/pppppppp/8/8/1P6/8/P1PPPPPP/RNBQKBNR b KQkq - 0 1")
1281 {
1282 let moves = vec![
1283 (ChessMove::from_str("d7d5").unwrap(), 1.0), (ChessMove::from_str("g8f6").unwrap(), 0.8), (ChessMove::from_str("e7e5").unwrap(), 0.7), ];
1287 let _ = self.add_opening(
1288 &board.to_string(),
1289 0.0,
1290 moves,
1291 "Polish Opening".to_string(),
1292 Some("A00".to_string()),
1293 );
1294 }
1295
1296 if let Ok(board) =
1298 Board::from_str("rnbqkbnr/pppppppp/8/8/8/1P6/P1PPPPPP/RNBQKBNR b KQkq - 0 1")
1299 {
1300 let moves = vec![
1301 (ChessMove::from_str("d7d5").unwrap(), 1.0), (ChessMove::from_str("g8f6").unwrap(), 0.8), (ChessMove::from_str("e7e5").unwrap(), 0.7), ];
1305 let _ = self.add_opening(
1306 &board.to_string(),
1307 0.1,
1308 moves,
1309 "Nimzowitsch-Larsen Attack".to_string(),
1310 Some("A01".to_string()),
1311 );
1312 }
1313
1314 if let Ok(board) =
1316 Board::from_str("rnbqkbnr/pppppppp/8/8/8/5N2/PPPPPPPP/RNBQKB1R b KQkq - 1 1")
1317 {
1318 let moves = vec![
1319 (ChessMove::from_str("d7d5").unwrap(), 1.0), (ChessMove::from_str("g8f6").unwrap(), 0.9), (ChessMove::from_str("c7c5").unwrap(), 0.6), ];
1323 let _ = self.add_opening(
1324 &board.to_string(),
1325 0.1,
1326 moves,
1327 "Reti Opening".to_string(),
1328 Some("A04".to_string()),
1329 );
1330 }
1331
1332 if let Ok(board) =
1334 Board::from_str("rnbqkbnr/pppppppp/8/8/8/5NP1/PPPPPP1P/RNBQKB1R b KQkq - 0 2")
1335 {
1336 let moves = vec![
1337 (ChessMove::from_str("d7d5").unwrap(), 1.0), (ChessMove::from_str("g8f6").unwrap(), 0.8), (ChessMove::from_str("e7e6").unwrap(), 0.7), ];
1341 let _ = self.add_opening(
1342 &board.to_string(),
1343 0.1,
1344 moves,
1345 "King's Indian Attack".to_string(),
1346 Some("A07".to_string()),
1347 );
1348 }
1349
1350 if let Ok(board) =
1352 Board::from_str("rnbqkb1r/pppppppp/5n2/4p3/3P4/8/PPP1PPPP/RNBQKBNR w KQkq - 1 2")
1353 {
1354 let moves = vec![
1355 (ChessMove::from_str("d4e5").unwrap(), 1.0), (ChessMove::from_str("g1f3").unwrap(), 0.7), ];
1358 let _ = self.add_opening(
1359 &board.to_string(),
1360 0.2,
1361 moves,
1362 "Budapest Gambit".to_string(),
1363 Some("A51".to_string()),
1364 );
1365 }
1366
1367 if let Ok(board) =
1369 Board::from_str("rnbqkb1r/p1pppppp/5n2/1p6/2PP4/8/PP2PPPP/RNBQKBNR w KQkq - 0 3")
1370 {
1371 let moves = vec![
1372 (ChessMove::from_str("c4b5").unwrap(), 1.0), (ChessMove::from_str("g1f3").unwrap(), 0.8), (ChessMove::from_str("a2a4").unwrap(), 0.6), ];
1376 let _ = self.add_opening(
1377 &board.to_string(),
1378 0.3,
1379 moves,
1380 "Benko Gambit".to_string(),
1381 Some("A57".to_string()),
1382 );
1383 }
1384
1385 if let Ok(board) =
1387 Board::from_str("rnbqkbnr/pp1ppppp/8/2p5/2P5/8/PP1PPPPP/RNBQKBNR w KQkq - 0 2")
1388 {
1389 let moves = vec![
1390 (ChessMove::from_str("b1c3").unwrap(), 1.0), (ChessMove::from_str("g1f3").unwrap(), 0.8), (ChessMove::from_str("g2g3").unwrap(), 0.7), ];
1394 let _ = self.add_opening(
1395 &board.to_string(),
1396 0.1,
1397 moves,
1398 "English Opening - Symmetrical".to_string(),
1399 Some("A30".to_string()),
1400 );
1401 }
1402
1403 if let Ok(board) =
1405 Board::from_str("rnbqkbnr/pppppp1p/6p1/8/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2")
1406 {
1407 let moves = vec![
1408 (ChessMove::from_str("d2d4").unwrap(), 1.0), (ChessMove::from_str("b1c3").unwrap(), 0.8), (ChessMove::from_str("f2f4").unwrap(), 0.7), ];
1412 let _ = self.add_opening(
1413 &board.to_string(),
1414 0.2,
1415 moves,
1416 "Modern Defense".to_string(),
1417 Some("B06".to_string()),
1418 );
1419 }
1420
1421 }
1424
1425 pub fn stats(&self) -> OpeningBookStats {
1427 let total_positions = self.entries.len();
1428 let eco_codes: std::collections::HashSet<_> = self
1429 .entries
1430 .values()
1431 .filter_map(|entry| entry.eco_code.as_ref())
1432 .collect();
1433 let eco_classifications = eco_codes.len();
1434
1435 let avg_moves_per_position = if total_positions > 0 {
1436 self.entries
1437 .values()
1438 .map(|entry| entry.best_moves.len())
1439 .sum::<usize>() as f32
1440 / total_positions as f32
1441 } else {
1442 0.0
1443 };
1444
1445 OpeningBookStats {
1446 total_positions,
1447 eco_classifications,
1448 avg_moves_per_position,
1449 }
1450 }
1451}
1452
1453#[derive(Debug)]
1455pub struct OpeningBookStats {
1456 pub total_positions: usize,
1457 pub eco_classifications: usize,
1458 pub avg_moves_per_position: f32,
1459}
1460
1461#[cfg(test)]
1462mod tests {
1463 use super::*;
1464
1465 #[test]
1466 fn test_opening_book_creation() {
1467 let book = OpeningBook::new();
1468 assert_eq!(book.entries.len(), 0);
1469 }
1470
1471 #[test]
1472 fn test_standard_openings() {
1473 let book = OpeningBook::with_standard_openings();
1474 assert!(!book.entries.is_empty());
1475
1476 let board = Board::default();
1478 let entry = book.lookup(&board);
1479 assert!(entry.is_some());
1480 assert_eq!(entry.unwrap().name, "Starting Position");
1481 }
1482
1483 #[test]
1484 fn test_opening_lookup() {
1485 let mut book = OpeningBook::new();
1486 let board = Board::default();
1487
1488 assert!(!book.contains(&board));
1490
1491 let moves = vec![(ChessMove::from_str("e2e4").unwrap(), 1.0)];
1493 book.add_opening(
1494 &board.to_string(),
1495 0.0,
1496 moves,
1497 "Test Opening".to_string(),
1498 None,
1499 )
1500 .unwrap();
1501
1502 assert!(book.contains(&board));
1504 let entry = book.lookup(&board).unwrap();
1505 assert_eq!(entry.name, "Test Opening");
1506 assert_eq!(entry.best_moves.len(), 1);
1507 }
1508
1509 #[test]
1510 fn test_stats() {
1511 let book = OpeningBook::with_standard_openings();
1512 let stats = book.stats();
1513
1514 assert!(stats.total_positions > 0);
1515 assert!(stats.avg_moves_per_position > 0.0);
1516 }
1517
1518 #[test]
1519 fn test_invalid_fen_handling() {
1520 let mut book = OpeningBook::new();
1521
1522 let moves = vec![(ChessMove::from_str("e2e4").unwrap(), 1.0)];
1524 let result = book.add_opening(
1525 "invalid_fen_string",
1526 0.0,
1527 moves,
1528 "Invalid Opening".to_string(),
1529 None,
1530 );
1531
1532 assert!(result.is_err());
1533 assert_eq!(book.entries.len(), 0);
1534 }
1535
1536 #[test]
1537 fn test_eco_code_filtering() {
1538 let book = OpeningBook::with_standard_openings();
1539 let stats = book.stats();
1540
1541 assert!(stats.eco_classifications > 0);
1543
1544 let eco_entries: Vec<_> = book
1546 .entries
1547 .values()
1548 .filter(|entry| entry.eco_code.is_some())
1549 .collect();
1550
1551 assert!(!eco_entries.is_empty());
1552
1553 let has_e4_openings = book.entries.values().any(|entry| {
1555 entry.eco_code.as_deref() == Some("C20") || entry.eco_code.as_deref() == Some("C44")
1556 });
1557 assert!(has_e4_openings);
1558 }
1559
1560 #[test]
1561 fn test_opening_move_strengths() {
1562 let book = OpeningBook::with_standard_openings();
1563 let board = Board::default();
1564
1565 if let Some(entry) = book.lookup(&board) {
1566 for (_, strength) in &entry.best_moves {
1568 assert!(*strength >= 0.0 && *strength <= 1.0);
1569 }
1570
1571 let max_strength = entry
1573 .best_moves
1574 .iter()
1575 .map(|(_, strength)| *strength)
1576 .fold(0.0, f32::max);
1577 assert!(max_strength > 0.5);
1578 }
1579 }
1580
1581 #[test]
1582 fn test_non_starting_position_openings() {
1583 let book = OpeningBook::with_standard_openings();
1584
1585 if let Ok(board) =
1587 Board::from_str("r1bqkbnr/pppp1ppp/2n5/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R b KQkq - 3 3")
1588 {
1589 let entry = book.lookup(&board);
1590 if entry.is_some() {
1591 let opening = entry.unwrap();
1592 assert!(!opening.best_moves.is_empty());
1593 assert!(
1594 opening.name.contains("Italian")
1595 || opening.name.contains("Game")
1596 || opening.evaluation.abs() <= 1.0
1597 );
1598 }
1599 }
1600 }
1601
1602 #[test]
1603 fn test_opening_book_consistency() {
1604 let book = OpeningBook::with_standard_openings();
1605
1606 for (fen, entry) in &book.entries {
1608 assert!(
1609 Board::from_str(fen).is_ok(),
1610 "Invalid FEN in opening book: {fen}",
1611 );
1612 assert!(!entry.name.is_empty(), "Empty opening name");
1613 assert!(
1614 !entry.best_moves.is_empty(),
1615 "No moves for opening: {}",
1616 entry.name
1617 );
1618
1619 assert!(
1621 entry.evaluation >= -10.0 && entry.evaluation <= 10.0,
1622 "Unreasonable evaluation: {}",
1623 entry.evaluation
1624 );
1625 }
1626 }
1627
1628 #[test]
1629 fn test_duplicate_position_handling() {
1630 let mut book = OpeningBook::new();
1631 let board = Board::default();
1632 let fen = board.to_string();
1633
1634 let moves1 = vec![(ChessMove::from_str("e2e4").unwrap(), 1.0)];
1635 let moves2 = vec![(ChessMove::from_str("d2d4").unwrap(), 0.9)];
1636
1637 let result1 = book.add_opening(
1639 &fen,
1640 0.0,
1641 moves1,
1642 "First Entry".to_string(),
1643 Some("E00".to_string()),
1644 );
1645 assert!(result1.is_ok());
1646 assert_eq!(book.entries.len(), 1);
1647
1648 let result2 = book.add_opening(
1650 &fen,
1651 0.1,
1652 moves2,
1653 "Second Entry".to_string(),
1654 Some("D00".to_string()),
1655 );
1656 assert!(result2.is_ok());
1657 assert_eq!(book.entries.len(), 1);
1658
1659 let entry = book.lookup(&board).unwrap();
1661 assert_eq!(entry.name, "Second Entry");
1662 assert_eq!(entry.evaluation, 0.1);
1663 assert_eq!(entry.eco_code, Some("D00".to_string()));
1664 }
1665
1666 #[test]
1667 fn test_advanced_opening_coverage() {
1668 let book = OpeningBook::with_standard_openings();
1669 let stats = book.stats();
1670
1671 assert!(
1673 stats.total_positions >= 40,
1674 "Opening book should have substantial coverage, got {}",
1675 stats.total_positions
1676 );
1677
1678 let opening_names: Vec<_> = book.entries.values().map(|entry| &entry.name).collect();
1680
1681 let has_sicilian = opening_names.iter().any(|name| name.contains("Sicilian"));
1682 let has_french = opening_names.iter().any(|name| name.contains("French"));
1683 let has_caro_kann = opening_names.iter().any(|name| name.contains("Caro"));
1684 let has_queens_gambit = opening_names.iter().any(|name| name.contains("Queen"));
1685
1686 assert!(
1687 has_sicilian || has_french || has_caro_kann || has_queens_gambit,
1688 "Opening book should cover major opening families"
1689 );
1690 }
1691}