use textwrap;
use regex::Regex;
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::io::BufReader;
use super::board::filter::BoardFilter;
use super::board::Board;
use super::moves;
pub struct Game {
pub header: HashMap<String, String>,
moves: Vec<Box<dyn moves::Move>>,
result: String,
}
impl Clone for Game {
fn clone(&self) -> Self {
let mut clone = Game {
header: HashMap::new(),
moves: vec![],
result: String::from(""),
};
for (key, val) in self.header.iter() {
clone.header.insert(key.clone(), val.clone());
}
let matcher = moves::Matches::new(); for mv in &self.moves {
let cloned_move = matcher.new_move(&mv.to_string()).unwrap();
clone.moves.push(cloned_move);
}
clone.result = self.result.clone();
clone
}
}
impl Game {
pub(super) fn from_pgn(reader: &mut BufReader<File>, matcher: &moves::Matches) -> std::io::Result<Option<Game>> {
let header_line = Regex::new("\\[(\\w+) \"(.*)\"\\]").unwrap();
let mut game = Game {
header: HashMap::new(),
moves: vec![],
result: String::from(""),
};
let mut line = "".to_string();
loop {
let len = &reader.read_line(&mut line)?;
if *len == 0 {
return Ok(None); }
line = line.trim().to_string();
if !line.is_empty() {
break;
}
}
while let Some(captures) = header_line.captures(&line) {
game.header
.insert(captures[1].to_string(), captures[2].to_string());
line = "".to_string();
let len = &reader.read_line(&mut line)?;
if *len == 0 {
return Ok(Some(game)); }
line = line.trim().to_string();
if line.is_empty() {
break;
}
}
loop {
line = line.trim().to_string();
if !line.is_empty() {
break;
}
line = "".to_string();
let len = &reader.read_line(&mut line)?;
if *len == 0 {
return Ok(Some(game)); }
}
let mut move_str = "".to_string();
loop {
line = line.trim().to_string();
if let Some(index) = line.find(';') {
line.truncate(index);
}
if line.is_empty() || line.starts_with("%") {
break;
}
move_str.push_str(&line);
move_str.push(' ');
line = "".to_string();
let len = &reader.read_line(&mut line)?;
if *len == 0 {
break;
}
}
matcher.comment.replace_all(&move_str, "");
matcher.nag.replace_all(&move_str, "");
let move_number = Regex::new("\\^\\d+").unwrap(); for token in move_str.split(' ') {
let token = token.trim();
if !token.is_empty() {
if token == "1-0" || token == "0-1" || token == "1/2-1/2" || token == "*" {
game.result = token.to_string().clone();
} else if !move_number.is_match(&token) {
if matcher.is_valid(&token) {
game.moves.push(matcher.new_move(&token).unwrap());
}
}
}
}
if game.header.is_empty() && game.moves.is_empty() {
Ok(None)
} else {
Ok(Some(game))
}
}
pub(super) fn meets_filter(&self, filter: &BoardFilter) -> bool {
let mut board = self.start_position();
for mv in self.moves.iter() {
if filter.is_match(&board) {
return true;
}
if let Ok(update) = mv.make_move(&board) {
board = update;
} else {
panic!("Illegal move found in game: {}", mv);
}
}
filter.is_match(&board)
}
pub(super) fn to_file(&self, out_file: &mut dyn std::io::Write) -> std::io::Result<()> {
let roster = ["Event", "Site", "Date", "Round", "White", "Black", "Result"];
for key in roster.iter() {
if self.header.contains_key(&key.to_string()) {
out_file.write_all(
format!(
"[{} \"{}\"]\n",
key,
self.header.get(&key.to_string()).unwrap()
)
.as_bytes(),
)?;
}
}
for (key, val) in self.header.iter() {
if !roster.contains(&key.as_str()) {
out_file.write_all(format!("[{} \"{}\"]\n", key, val).as_bytes())?;
}
}
out_file.write_all(b"\n")?;
let mut moves = String::from("");
for (n, mv) in self.moves.iter().enumerate() {
if n % 2 == 0 {
moves.push_str(&format!("{}. ", (n / 2) + 1));
}
moves.push_str(&mv.to_string());
moves.push(' ');
}
moves.push_str(&self.result);
moves.push_str("\n");
for line in textwrap::wrap(&moves, 80).iter() {
out_file.write_all(line.as_bytes())?;
out_file.write_all(b"\n")?;
}
Ok(())
}
pub fn total_half_moves(&self) -> usize {
self.moves.len()
}
pub fn iter(&self) -> GameIter {
GameIter {
game: &self,
board: self.start_position(),
posn: 0,
}
}
fn start_position(&self) -> Board {
if self.header.contains_key("fen") {
if let Ok(board) = Board::from_fen(&self.header.get("fen").unwrap()) {
board
} else {
panic!("Invalid start position given in FEN");
}
} else {
Board::start_position()
}
}
}
pub struct GameIter<'a> {
game: &'a Game,
board: Board,
posn: usize,
}
impl<'a> Iterator for GameIter<'a> {
type Item = (Board, String);
fn next(&mut self) -> Option<Self::Item> {
if self.game.moves.is_empty() {
None
} else if self.posn <= self.game.moves.len() {
if self.posn > 0 {
let curr_move = &self.game.moves[self.posn - 1];
if let Ok(new_board) = curr_move.make_move(&self.board) {
self.board = new_board;
} else {
panic!("Illegal move found in game: {}", curr_move);
}
}
self.posn += 1;
if self.posn - 1 < self.game.moves.len() {
Some((
self.board.clone(),
self.game.moves[self.posn - 1].to_string(),
))
} else {
Some((self.board.clone(), self.game.result.clone()))
}
} else {
None
}
}
}