use std::prelude::v1::*;
use std::collections::BTreeMap;
use std::fmt;
#[cfg(feature = "std")]
use regex::Regex;
use crate::attack::*;
use crate::color::*;
use crate::common::*;
use crate::fen::FEN;
use crate::game::Game;
use crate::piece_move_notation::PieceMoveNotation;
use crate::piece_move_generator::PieceMoveGenerator;
use crate::search::*;
#[derive(Debug)]
pub struct PGN {
headers: BTreeMap<String, String>,
body: String,
}
lazy_static! {
static ref DEFAULT_HEADERS: Vec<(String, String)> = vec![
("1Event".to_string(), "?".to_string()),
("2Site".to_string(), "?".to_string()),
("3White".to_string(), "?".to_string()),
("4Black".to_string(), "?".to_string()),
("5Result".to_string(), "*".to_string()),
];
}
impl PGN {
fn new() -> PGN {
PGN {
headers: DEFAULT_HEADERS.clone().into_iter().collect(),
body: "".to_string(),
}
}
pub fn white(&self) -> String {
self.headers["3White"].clone()
}
pub fn black(&self) -> String {
self.headers["4Black"].clone()
}
pub fn result(&self) -> String {
self.headers["5Result"].clone()
}
pub fn set_white(&mut self, white: &str) {
self.headers.insert("3White".to_string(), white.to_string());
}
pub fn set_black(&mut self, black: &str) {
self.headers.insert("4Black".to_string(), black.to_string());
}
pub fn set_result(&mut self, result: &str) {
self.headers.insert("5Result".to_string(), result.to_string());
}
pub fn set_fen(&mut self, fen: &str) {
self.headers.insert("FEN".to_string(), fen.to_string());
self.headers.insert("SetUp".to_string(), "1".to_string());
}
pub fn set_header(&mut self, key: &str, val: &str) {
self.headers.insert(key.to_string(), val.to_string());
}
}
impl fmt::Display for PGN {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for (key, val) in self.headers.iter() {
writeln!(f, "[{} \"{}\"]", key.trim_start_matches(char::is_numeric), val)?;
}
writeln!(f)?;
write!(f, "{}", self.body)?;
Ok(())
}
}
#[cfg(feature = "std")]
impl From<&str> for PGN {
fn from(s: &str) -> PGN {
lazy_static! {
static ref RE: Regex = Regex::new("\\[(?P<key>\\w+) \"(?P<val>.*)\"\\]").unwrap();
}
let mut pgn = PGN::new();
for line in s.lines() {
match RE.captures(line) {
Some(header) => {
if !pgn.body.is_empty() {
pgn = PGN::new();
}
let key = header["key"].to_string();
let val = header["val"].to_string();
let mut is_default_header = false;
for (k, _) in DEFAULT_HEADERS.iter() {
if key == k.trim_start_matches(char::is_numeric) {
pgn.set_header(k, &val);
is_default_header = true;
break;
}
}
if !is_default_header {
pgn.set_header(&key, &val);
}
}
None => {
if !line.is_empty() {
pgn.body.push_str(line);
pgn.body.push('\n');
}
}
}
}
pgn
}
}
pub trait ToPGN {
fn to_pgn(&mut self) -> PGN;
}
impl ToPGN for Game {
fn to_pgn(&mut self) -> PGN {
let mut pgn = PGN::new();
let starting_fen = self.starting_fen.clone();
if starting_fen != DEFAULT_FEN {
pgn.set_fen(&starting_fen);
}
let result = if self.is_mate() {
if self.is_check(WHITE) {
"0-1"
} else if self.is_check(BLACK) {
"1-0"
} else {
"1/2-1/2"
}
} else {
"*"
};
pgn.set_result(result);
let moves = self.plies.clone();
self.load_fen(&starting_fen).unwrap();
let mut first_move = true;
let mut line = String::new();
for m in moves {
let fm = self.positions.fullmoves();
if self.side() == WHITE {
line.push_str(&format!("{}. ", fm));
} else if first_move {
line.push_str(&format!("{}. ... ", fm));
}
first_move = false;
line.push_str(&self.move_to_san(m));
self.make_move(m);
self.plies.push(m);
if self.is_mate() {
line.push('#');
} else if self.is_check(self.side()) {
line.push('+');
}
if line.len() > 70 {
pgn.body.push_str(&format!("{}\n", line));
line = String::new();
} else {
line.push(' ');
}
}
pgn.body.push_str(&format!("{}{}\n", line, result));
pgn
}
}
#[cfg(feature = "std")]
pub trait LoadPGN {
fn walk_pgn<F>(&mut self, pgn: &PGN, callback: F) where F: FnMut(&mut Game);
fn load_pgn(&mut self, pgn: PGN);
}
#[cfg(feature = "std")]
impl LoadPGN for Game {
fn walk_pgn<F>(&mut self, pgn: &PGN, mut callback: F) where F: FnMut(&mut Game) {
self.clear();
let starting_fen = pgn.headers.get("FEN").map_or(DEFAULT_FEN, String::as_str);
self.load_fen(starting_fen).unwrap();
let mut comment_level = 0;
let mut variation_level = 0;
for line in pgn.body.lines() {
for word in line.split(' ') {
if word.starts_with(';') {
break;
}
comment_level += word.matches('{').count();
comment_level -= word.matches('}').count();
variation_level += word.matches('(').count();
variation_level -= word.matches(')').count();
if comment_level > 0 || variation_level > 0 {
continue;
}
self.tt.clear();
self.moves.clear_all();
if let Some(m) = self.parse_move(word) {
self.make_move(m);
self.plies.push(m);
callback(self);
}
}
}
}
fn load_pgn(&mut self, pgn: PGN) {
self.walk_pgn(&pgn, |_| {});
}
}
#[cfg(feature = "std")]
#[cfg(test)]
mod tests {
use std::fs;
use crate::square::*;
use crate::common::*;
use crate::game::Game;
use crate::piece_move::PieceMove;
use crate::piece_move_generator::PieceMoveGenerator;
use super::*;
#[test]
fn test_game_to_pgn() {
let mut game = Game::from_fen(DEFAULT_FEN).unwrap();
let moves = vec![
PieceMove::new(F2, F3, QUIET_MOVE),
PieceMove::new(E7, E5, DOUBLE_PAWN_PUSH),
PieceMove::new(G2, G4, DOUBLE_PAWN_PUSH),
PieceMove::new(D8, H4, QUIET_MOVE)
];
for m in moves {
game.make_move(m);
game.plies.push(m);
}
let pgn = game.to_pgn();
let content = fs::read_to_string("tests/fool.pgn").unwrap();
assert_eq!(pgn.to_string(), content);
}
#[test]
fn test_string_to_pgn() {
let content = fs::read_to_string("tests/fool.pgn").unwrap();
let pgn = PGN::from(content.as_str());
assert_eq!(pgn.to_string(), content);
assert_eq!(pgn.result(), "0-1".to_string());
}
#[test]
fn test_game_load_pgn() {
let mut game = Game::new();
let s1 = fs::read_to_string("tests/fool.pgn").unwrap();
let pgn = PGN::from(s1.as_str());
game.load_pgn(pgn);
assert_eq!(game.plies.len(), 4);
let s2 = fs::read_to_string("tests/zukertort_vs_steinitz_1886.pgn").unwrap();
let pgn = PGN::from(s2.as_str());
game.load_pgn(pgn);
assert_eq!(game.plies.len(), 58);
let pgn = PGN::from(format!("{}\n{}", s1, s2).as_str());
game.load_pgn(pgn);
assert_eq!(game.plies.len(), 58);
}
}