/*
This file is part of Takwrap.
Takwrap is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Takwrap is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Takwrap. If not, see .
*/
use crate::game::*;
fn parse_move(mv: &str) -> Result {
let mut chars = mv.chars();
let c = chars.next();
if c.is_none() {
return Err(String::from("Empty string cannot be parsed."));
}
let x;
let picked_up;
let dc = c.unwrap();
if (dc >= 'a') && (dc <= 'h') {
// Optional drop number omitted, assume everything
x = dc;
picked_up = 1;
} else {
if (dc < '1') || ('9' < dc) {
return Err(String::from(
"Moves must begin with a digit in 1-8 indicating the stones picked up.",
));
}
picked_up = dc.to_digit(10).unwrap() as u8;
let c = chars.next();
if c.is_none() {
return Err(String::from("Moves must specify a stack position."));
}
x = c.unwrap();
}
if (x < 'a') || (x > 'h') {
return Err(String::from(
"Moves must specify a valid position on the game board.",
));
}
let x: u8 = x as u8 - 'a' as u8;
let c = chars.next();
if c.is_none() {
return Err(String::from("Moves must specify a stack position."));
}
let y = c.unwrap();
if (y < '1') || (y > '8') {
return Err(String::from(
"Moves must specify a position on the game board.",
));
}
let y: u8 = (y.to_digit(10).unwrap() - 1) as u8;
let c = chars.next();
if c.is_none() {
return Err(String::from("Moves must specify a move direction."));
}
let c = c.unwrap();
// This is so cool! Rust supports some very nice patterns, and
// checks for uninitialised variables too!
let dir;
match c {
'+' => dir = Direction::Up,
'-' => dir = Direction::Down,
'<' => dir = Direction::Left,
'>' => dir = Direction::Right,
_ => return Err(String::from("The valid directions are +,-,<, and >.")),
}
let mut drops: Vec = Vec::new();
for d in chars {
if let Some(u) = d.to_digit(10) {
if u > 9 {
return Err(String::from(
"No more than 8 pieces may be dropped on a given square.",
));
} else {
drops.push(u as u8)
}
} else {
return Err(String::from(
"A move must specify a number of pieces dropped for each square.",
));
}
}
let pos = Position { x: x, y: y };
let sum = drops.iter().map(|&d| d as u32).sum::();
if sum == 0 {
// Omitted all the drops, it's a total stack move
drops.push(picked_up);
} else if sum != picked_up as u32 {
return Err(format!(
"The move called for {} stones, but {} were dropped.",
picked_up, sum
));
}
Ok(Action::Move(pos, dir, picked_up, drops))
}
fn parse_place(pl: &str, stone_owner: Player) -> Result {
let mut chars = pl.chars();
let c = chars.next();
if c.is_none() {
return Err(String::from("An action cannot be blank."));
}
let stone_c = c.unwrap();
let stone;
let x;
if (stone_c == 'F') || (stone_c == 'C') || (stone_c == 'S') {
match stone_c {
'C' => stone = Stone::Capstone,
'S' => stone = Stone::Standing,
_ => stone = Stone::Flat,
}
let c = chars.next();
if c.is_none() {
return Err(String::from(
"Placements must specify a position on the game board.",
));
}
x = c.unwrap();
} else {
stone = Stone::Flat;
x = stone_c;
}
if (x < 'a') || (x > 'h') {
return Err(if (x >= 'A') && (x <= 'Z') {
format!("Unrecognised stone type '{}' in placement.", x)
} else {
String::from("Placements must specify a valid position on the game board.")
});
}
let x: u8 = x as u8 - 'a' as u8;
let c = chars.next();
if c.is_none() {
return Err(String::from(
"Placements must specify a position on the game board.",
));
}
let y = c.unwrap();
if (y < '1') || (y > '8') {
return Err(String::from(
"Placements must specify a valid position on the game board.",
));
}
let y: u8 = (y.to_digit(10).unwrap() - 1) as u8;
Ok(Action::Place(stone_owner, Position { x: x, y: y }, stone))
}
pub fn parse_action(stone_owner: Player, act: &str) -> Result {
if act
.chars()
.any(|c| c == '+' || c == '-' || c == '>' || c == '<')
{
parse_move(act)
} else {
parse_place(act, stone_owner)
}
}
pub fn parse_start_type(st: &str) -> Result {
let st = st.trim().to_uppercase();
if st == StartType::FCS.to_string() {
return Ok(StartType::FCS);
};
if st == StartType::FES.to_string() {
return Ok(StartType::FES);
};
if st == StartType::TPS.to_string() {
return Ok(StartType::TPS);
};
if st == StartType::CZS.to_string() {
return Ok(StartType::CZS);
};
if st.len() < 4 {
return Err(String::from(
"Expecting CPSn as
alternatives failed to match, but variant string was too
short.",
));
} else {
if st[..3] == String::from("CPS") {
let num = st[3..].parse::();
match num {
Err(e) => {
return Err(format!(
"Failed to parse number, {}. CPS must be
immediately followed by a positive integer < 256, e.g., CPS1.",
e.to_string()
))
}
Ok(n) => return Ok(StartType::CPS(n)),
}
}
}
return Err(format!("Unknown start type ``{}''", st));
}