1use log::{log_enabled, info, Level};
2
3use shakmaty::variants::{Antichess, Atomic, Chess, Crazyhouse, Horde, KingOfTheHill, RacingKings, ThreeCheck};
4use shakmaty::san::{San};
5use shakmaty::uci::{Uci};
6use shakmaty::fen;
7use shakmaty::fen::Fen;
8use shakmaty::Position;
9use pgn_reader::{Visitor, Skip, RawHeader, SanPlus, BufferedReader};
10use serde::{Deserialize, Serialize};
11use std::fs::File;
12use std::io::{self, BufRead};
13use std::path::Path;
14use rand::Rng;
15
16#[derive(Debug)]
18pub enum Variant{
19 VariantAntichess,
20 VariantAtomic,
21 VariantChess960,
22 VariantCrazyhose,
23 VariantFromPosition,
24 VariantHorde,
25 VariantKingOfTheHill,
26 VariantRacingKings,
27 VariantStandard,
28 VariantThreeCheck,
29}
30
31use Variant::*;
32
33#[derive(Debug, Serialize, Deserialize)]
35pub struct SanUciFenEpd {
36 pub san: String,
37 pub uci: String,
38 pub fen_before: String,
39 pub epd_before: String,
40 pub fen_after: String,
41 pub epd_after: String,
42}
43
44#[derive(Debug, Serialize, Deserialize)]
46pub struct PgnInfo {
47 pub headers: std::collections::HashMap<String, String>,
48 pub moves: Vec<SanUciFenEpd>,
49}
50
51impl PgnInfo {
53 pub fn new() -> PgnInfo {
55 PgnInfo {
56 headers: std::collections::HashMap::new(),
57 moves: vec!(),
58 }
59 }
60
61 pub fn push(&mut self, san_uci_fen_epd: SanUciFenEpd) {
63 self.moves.push(san_uci_fen_epd);
64 }
65
66 pub fn insert_header<K, V>(&mut self, key: K, value: V)
68 where K: core::fmt::Display, V: core::fmt::Display {
69 let key = format!("{}", key);
70 let value = format!("{}", value);
71
72 self.headers.insert(key, value);
73 }
74
75 pub fn get_header<T>(&mut self, key:T) -> String
77 where T: core::fmt::Display {
78 let key = format!("{}", key);
79
80 self.headers.get(&key).unwrap_or(&"?".to_string()).to_string()
81 }
82}
83
84struct ParsingState{
86 antichess_pos: Antichess,
87 atomic_pos: Atomic,
88 chess_pos: Chess,
89 crazyhouse_pos: Crazyhouse,
90 horde_pos: Horde,
91 kingofthehill_pos: KingOfTheHill,
92 racingkings_pos: RacingKings,
93 three_check_pos: ThreeCheck,
94 variant: Variant,
95 check_custom_fen: bool,
96 pgn_info: PgnInfo,
97}
98
99impl ParsingState{
100 fn new() -> ParsingState{
101 ParsingState{
102 antichess_pos: Antichess::default(),
103 atomic_pos: Atomic::default(),
104 chess_pos: Chess::default(),
105 crazyhouse_pos: Crazyhouse::default(),
106 horde_pos: Horde::default(),
107 kingofthehill_pos: KingOfTheHill::default(),
108 racingkings_pos: RacingKings::default(),
109 three_check_pos: ThreeCheck::default(),
110 variant: VariantStandard,
111 check_custom_fen: true,
112 pgn_info: PgnInfo::new(),
113 }
114 }
115}
116
117macro_rules! gen_make_move {
118 ($($variant:tt,$pos:ident,)+) => (
119 fn make_move(parsing_state: &mut ParsingState, san_plus: SanPlus) {
120 match parsing_state.variant {
121 $(
122 $variant => {
123 let san_orig = san_plus.san;
124 let san_str = format!("{}", san_orig);
125 let san_result:std::result::Result<San, _> = san_str.parse();
126
127 match san_result {
128 Ok(san) => {
129 let move_result = san.to_move(&parsing_state.$pos);
130
131 match move_result {
132 Ok(m) => {
133 let uci_str = match parsing_state.variant {
134 VariantChess960 => Uci::from_chess960(&m).to_string(),
135 _ => Uci::from_standard(&m).to_string()
136 };
137 let fen_before_str = format!("{}", fen::fen(&parsing_state.$pos));
138 let epd_before_str = format!("{}", fen::epd(&parsing_state.$pos));
139
140 parsing_state.$pos.play_unchecked(&m);
141
142 let fen_after_str = format!("{}", fen::fen(&parsing_state.$pos));
143 let epd_after_str = format!("{}", fen::epd(&parsing_state.$pos));
144
145 let san_uci_fen_epd = SanUciFenEpd{
146 san: san_str,
147 uci: uci_str,
148 fen_before: fen_before_str,
149 epd_before: epd_before_str,
150 fen_after: fen_after_str,
151 epd_after: epd_after_str,
152 };
153
154 parsing_state.pgn_info.push(san_uci_fen_epd);
155 },
156 _ => println!("move error {:?}", move_result)
157 }
158 },
159 Err(err) => {
160 println!("san parsing error {:?}", err)
161 }
162 }
163 },
164 )+
165 };
166 }
167 )
168}
169
170gen_make_move!(
171 VariantStandard, chess_pos,
172 VariantChess960, chess_pos,
173 VariantFromPosition, chess_pos,
174 VariantAtomic, atomic_pos,
175 VariantAntichess, antichess_pos,
176 VariantCrazyhose, crazyhouse_pos,
177 VariantHorde, horde_pos,
178 VariantRacingKings, racingkings_pos,
179 VariantKingOfTheHill, kingofthehill_pos,
180 VariantThreeCheck, three_check_pos,
181);
182
183fn variant_name_to_variant(variant: &str) -> Variant {
185 match variant.to_lowercase().as_str() {
186 "antichess" | "anti chess" | "giveaway" | "give away" => VariantAntichess,
187 "atomic" => VariantAtomic,
188 "chess960" | "chess 960" => VariantChess960,
189 "crazyhouse" | "crazy house" => VariantCrazyhose,
190 "fromposition" | "from position" => VariantFromPosition,
191 "horde" => VariantHorde,
192 "kingofthehill" | "king of the hill" | "koth" => VariantKingOfTheHill,
193 "racingkings" | "racing kings" => VariantRacingKings,
194 "standard" => VariantStandard,
195 "threecheck" | "three check" | "3check" | "3 check" => VariantThreeCheck,
196 _ => VariantStandard,
197 }
198}
199
200impl Visitor for ParsingState {
202 type Result = String;
203
204 fn header(&mut self, key: &[u8], value: RawHeader<'_>) {
205 let key_str_result = std::str::from_utf8(key);
206 match key_str_result {
207 Ok(key_str) => {
208 let value_str_result = std::str::from_utf8(value.as_bytes());
209 match value_str_result {
210 Ok(value_str) => {
211 self.pgn_info.insert_header(key_str.to_string(), value_str.to_string());
212
213 if key_str == "Variant" {
214 self.variant = variant_name_to_variant(value_str);
215 }
216 },
217 Err(err) => println!("header value utf8 parse error {:?}", err)
218 }
219 }
220 Err(err) => println!("header key utf8 parse error {:?}", err)
221 }
222 }
223
224 fn begin_variation(&mut self) -> Skip {
225 Skip(true) }
227
228 fn san(&mut self, san_plus: SanPlus) {
229 if self.check_custom_fen {
230 self.check_custom_fen = false;
231
232 if let Some(fen) = self.pgn_info.headers.get("FEN") {
233 let castling_mode = match self.variant {
234 VariantChess960 => shakmaty::CastlingMode::Chess960,
235 _ => shakmaty::CastlingMode::Standard,
236 };
237
238 match self.variant {
239 VariantAntichess => {
240 let pos = Fen::from_ascii(fen.as_bytes()).ok()
241 .and_then(|f| f.position(castling_mode).ok());
242
243 if let Some(pos) = pos {
244 self.antichess_pos = pos;
245 }
246
247 },
248 VariantAtomic => {
249 let pos = Fen::from_ascii(fen.as_bytes()).ok()
250 .and_then(|f| f.position(castling_mode).ok());
251
252 if let Some(pos) = pos {
253 self.atomic_pos = pos;
254 }
255
256 },
257 VariantCrazyhose => {
258 let pos = Fen::from_ascii(fen.as_bytes()).ok()
259 .and_then(|f| f.position(castling_mode).ok());
260
261 if let Some(pos) = pos {
262 self.crazyhouse_pos = pos;
263 }
264
265 },
266 VariantHorde => {
267 let pos = Fen::from_ascii(fen.as_bytes()).ok()
268 .and_then(|f| f.position(castling_mode).ok());
269
270 if let Some(pos) = pos {
271 self.horde_pos = pos;
272 }
273
274 },
275 VariantKingOfTheHill => {
276 let pos = Fen::from_ascii(fen.as_bytes()).ok()
277 .and_then(|f| f.position(castling_mode).ok());
278
279 if let Some(pos) = pos {
280 self.kingofthehill_pos = pos;
281 }
282
283 },
284 VariantRacingKings => {
285 let pos = Fen::from_ascii(fen.as_bytes()).ok()
286 .and_then(|f| f.position(castling_mode).ok());
287
288 if let Some(pos) = pos {
289 self.racingkings_pos = pos;
290 }
291
292 },
293 VariantThreeCheck => {
294 let pos = Fen::from_ascii(fen.as_bytes()).ok()
295 .and_then(|f| f.position(castling_mode).ok());
296
297 if let Some(pos) = pos {
298 self.three_check_pos = pos;
299 }
300
301 },
302 _ => {
303 let pos = Fen::from_ascii(fen.as_bytes()).ok()
304 .and_then(|f| f.position(castling_mode).ok());
305
306 if let Some(pos) = pos {
307 self.chess_pos = pos;
308 }
309 }
310 }
311 }
312 }
313
314 make_move(self, san_plus);
315 }
316
317 fn end_game(&mut self) -> Self::Result {
318 let ser_result = serde_json::to_string(&self.pgn_info);
319
320 match ser_result {
321 Ok(ser_str) => {
322 ser_str
323 },
324 Err(err) => {
325 println!("{:?}", err);
326 "".to_string()
327 }
328 }
329 }
330}
331
332pub fn parse_pgn_to_json_string<T>(pgn_str: T) -> String
334where T: core::fmt::Display {
335 let pgn_str = pgn_str.to_string();
336 let pgn_bytes = pgn_str.as_bytes();
337
338 let mut reader = BufferedReader::new_cursor(&pgn_bytes);
339
340 let mut visitor = ParsingState::new();
341
342 match reader.read_game(&mut visitor) {
343 Ok(moves_opt) => moves_opt.unwrap_or("".to_string()),
344 Err(err) => {
345 println!("{:?}", err);
346 "".to_string()
347 }
348 }
349}
350
351pub fn parse_pgn_to_rust_struct<T>(pgn_str: T) -> PgnInfo
353where T: core::fmt::Display {
354 let parse_result = parse_pgn_to_json_string(pgn_str.to_string());
355
356 match serde_json::from_str::<PgnInfo>(&parse_result) {
357 Ok(pgn_info) => pgn_info,
358 _ => PgnInfo::new(),
359 }
360}
361
362pub fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
364where P: AsRef<Path>, {
365 let file = File::open(filename)?;
366 Ok(io::BufReader::new(file).lines())
367}
368
369pub struct PgnIterator {
371 pub lines: io::Lines<io::BufReader<File>>,
372}
373
374impl PgnIterator {
376 pub fn new<T>(filename: T) -> Option<PgnIterator>
377 where T: AsRef<Path> {
378 if let Ok(file) = File::open(filename) {
379 return Some(PgnIterator{
380 lines: io::BufReader::new(file).lines()
381 })
382 }
383
384 None
385 }
386}
387
388enum PgnReadingState {
390 WaitHead,
392 ReadHead,
394 WaitBody,
396 ReadBody,
398}
399
400impl std::iter::Iterator for PgnIterator {
402 type Item = String;
403
404 fn next(&mut self) -> Option<Self::Item> {
406 let mut state = PgnReadingState::WaitHead;
407
408 let mut accum = String::new();
409
410 loop{
411 if let Some(Ok(line)) = self.lines.next() {
412 if line.len() == 0 {
413 match state {
414 PgnReadingState::ReadBody => return Some(accum),
415 PgnReadingState::WaitHead => {},
416 PgnReadingState::ReadHead => {
417 accum = accum + &line + "\n";
418
419 state = PgnReadingState::WaitBody;
420 },
421 _ => accum = accum + &line + "\n"
422 }
423 } else {
424 match state {
425 PgnReadingState::WaitHead => {
426 if line.chars().next().unwrap() != '[' {
427 return None
429 }
430
431 accum = line;
432
433 state = PgnReadingState::ReadHead
434 },
435 PgnReadingState::WaitBody => {
436 accum = accum + &line + "\n";
437
438 state = PgnReadingState::ReadBody
439 },
440 _=> accum = accum + &line + "\n"
441 }
442 }
443 } else {
444 match state {
445 PgnReadingState::ReadBody => return Some(accum),
446 _ => return None
447 }
448 }
449 }
450 }
451}
452
453#[derive(Debug)]
455pub struct BookMove {
456 pub win: usize,
458 pub draw: usize,
460 pub loss: usize,
462 pub uci: String,
464 pub san: String,
466}
467
468impl BookMove {
470 pub fn new<U, S>(uci: U, san: S) -> BookMove
472 where U: core::fmt::Display, S: core::fmt::Display {
473 BookMove {
474 win: 0,
475 draw: 0,
476 loss: 0,
477 uci: uci.to_string(),
478 san: san.to_string(),
479 }
480 }
481
482 pub fn plays(&self) -> usize {
484 self.win + self.draw + self.loss
485 }
486
487 pub fn perf(&self) -> usize {
489 let plays = self.plays();
490
491 if plays == 0 {
492 return 0
493 }
494
495 ( ( ( 2 * self.win ) + self.draw ) * 50 ) / plays
496 }
497}
498
499#[derive(Debug)]
501pub struct BookPosition {
502 pub epd: String,
504 pub moves: std::collections::HashMap<String, BookMove>,
506}
507
508impl BookPosition {
510 pub fn new<T>(epd: T) -> BookPosition
512 where T: core::fmt::Display {
513 let epd = epd.to_string();
514
515 BookPosition {
516 epd: epd,
517 moves: std::collections::HashMap::new(),
518 }
519 }
520
521 pub fn total_plays(&self) -> usize {
523 let mut accum = 0;
524
525 for (_, m) in &self.moves {
526 accum += m.plays();
527 }
528
529 accum
530 }
531
532 pub fn total_perf(&self) -> usize {
534 let mut accum = 0;
535
536 for (_, m) in &self.moves {
537 accum += m.perf();
538 }
539
540 accum
541 }
542
543 pub fn get_random_weighted_by_plays(&self) -> Option<&BookMove> {
545 let mut rng = rand::thread_rng();
546
547 let t = self.total_plays();
548
549 if t == 0 {
550 return None;
551 }
552
553 let r = rng.gen_range(0..t);
554
555 let mut accum = 0;
556
557 for (_, m) in &self.moves {
558 accum += m.plays();
559
560 if accum >= r {
561 return Some(m);
562 }
563 }
564
565 return None
566 }
567
568 pub fn get_random_weighted_by_perf(&self) -> Option<&BookMove> {
570 let mut rng = rand::thread_rng();
571
572 let t = self.total_perf();
573
574 if t == 0 {
575 return None;
576 }
577
578 let r = rng.gen_range(0..t);
579
580 let mut accum = 0;
581
582 for (_, m) in &self.moves {
583 accum += m.perf();
584
585 if accum >= r {
586 return Some(m);
587 }
588 }
589
590 return None
591 }
592
593 pub fn get_random_mixed(&self, plays_weight: usize) -> Option<&BookMove> {
595 let mut rng = rand::thread_rng();
596
597 let r = rng.gen_range(0..100);
598
599 if r <= plays_weight {
600 return self.get_random_weighted_by_plays();
601 }
602
603 self.get_random_weighted_by_perf()
604 }
605}
606
607#[derive(Debug)]
609pub struct Book {
610 pub positions: std::collections::HashMap<String, BookPosition>,
612 pub max_depth: usize,
614 pub me: Option<String>,
616}
617
618pub fn turn_white<T>(epd: T) -> bool
620where T: core::fmt::Display {
621 let epd = epd.to_string();
622
623 let parts:Vec<&str> = epd.split(" ").collect();
624
625 parts[1] == "w"
626}
627
628impl Book {
630 pub fn new() -> Book {
632 Book {
633 positions: std::collections::HashMap::new(),
634 max_depth: 20,
635 me: None,
636 }
637 }
638
639 pub fn me<T>(mut self, me: T) -> Book
641 where T: core::fmt::Display {
642 self.me = Some(me.to_string());
643
644 self
645 }
646
647 pub fn max_depth<T>(mut self, max_depth: T) -> Book
649 where T: core::fmt::Display {
650 if let Ok(max_depth) = max_depth.to_string().parse() {
651 self.max_depth = max_depth;
652 }
653
654 self
655 }
656
657 pub fn parse<T>(&mut self, filename: T)
659 where T: AsRef<Path> + std::fmt::Display {
660 let show_filename = filename.to_string();
661
662 if log_enabled!(Level::Info) {
663 info!("parsing {}", show_filename);
664 }
665
666 let iter = PgnIterator::new(filename);
667
668 let mut games = 0;
669 let mut moves = 0;
670 let mut parsed_moves = 0;
671 let mut me_white = 0;
672 let mut me_black = 0;
673
674 if let Some(iter) = iter {
675 for pgn in iter {
676 let mut parsed = parse_pgn_to_rust_struct(pgn);
677
678 if let Some(me) = self.me.to_owned() {
679 let white = parsed.get_header("White");
680 let black = parsed.get_header("Black");
681
682 if me == white {
683 me_white += 1;
684 }
685
686 if me == black {
687 me_black += 1;
688 }
689 }
690
691 let result = match parsed.get_header("Result").as_str() {
692 "1-0" => 2,
693 "0-1" => 0,
694 _ => 1,
695 };
696
697 let mut max_move = self.max_depth;
698
699 let len = parsed.moves.len();
700
701 if len < max_move {
702 max_move = len;
703 }
704
705 games += 1;
706 moves += len;
707 parsed_moves += max_move;
708
709 for i in 0..max_move {
710 let m = &parsed.moves[i];
711
712 let pos = self.positions.entry(m.epd_before.to_owned()).or_insert(BookPosition::new(m.epd_before.to_owned()));
713
714 let pm = pos.moves.entry(m.uci.to_owned()).or_insert(BookMove::new(m.uci.to_owned(), m.san.to_owned()));
715
716 let result_wrt = match turn_white(m.epd_before.to_owned()) {
717 true => result,
718 _ => 2 - result,
719 };
720
721 match result_wrt {
722 2 => pm.win += 1,
723 1 => pm.draw += 1,
724 _ => pm.loss += 1,
725 }
726 }
727 }
728 }
729
730 if log_enabled!(Level::Info) {
731 info!("parsing {} done, total games {}, total moves {}, parsed moves {}, me white {}, me black {}", show_filename, games, moves, parsed_moves, me_white, me_black);
732 }
733 }
734}