From 84898338d0875c258a4325d517dd7b7f9d0f83e1 Mon Sep 17 00:00:00 2001 From: tslil clingman <> Date: Sun, 9 Feb 2020 13:42:15 -0800 Subject: Implemented variant start types, no check for bugfree yet --- src/game.rs | 172 +++++++++++++++++++++++++++++++++++++++++++--------------- src/main.rs | 27 ++++++++- src/parser.rs | 41 ++++++++++++++ 3 files changed, 192 insertions(+), 48 deletions(-) (limited to 'src') diff --git a/src/game.rs b/src/game.rs index 2745073..c851e3d 100644 --- a/src/game.rs +++ b/src/game.rs @@ -230,12 +230,49 @@ impl GameState { self.board.get(self.pos_to_idx(pos)) } - fn is_legal_place(&self, player: Player, pos: &Position, stone: Stone) -> Result<(), String> { + fn is_legal_place( + &self, + player: Player, + pos: &Position, + stone: Stone, + check_start: Option, + ) -> Result<(), String> { /* In order to legally place a piece: 1. The desired square must be empty 2. The player must have sufficient pieces + 3. We must account for F{C,E}S */ if self.within_bounds(pos) { + if let Some(start_type) = check_start { + match start_type { + StartType::FCS => { + if ((pos.x == 0) && (pos.y == 0)) + || ((pos.x + 1 == self.size) && (pos.y == 0)) + || ((pos.x == 0) && (pos.y + 1 == self.size)) + || ((pos.x + 1 == self.size) && (pos.y + 1 == self.size)) + { + return Err(format!( + "At the start of an FCS game, {} may not place in a corner (such as {}).", + player, pos + )); + } + } + StartType::FES => { + if (pos.x == 0) + || (pos.y == 0) + || (pos.x + 1 == self.size) + || (pos.y + 1 == self.size) + { + return Err(format!( + "At the start of an FES game, {} may not place along any edge (and {} is on an edge).", + player, pos + )); + } + } + _ => (), + } + } + if let Some(stack) = &self.board.get(self.pos_to_idx(pos)) { if stack.len() > 0 { return Err(format!("{} is already occupied.", pos)); @@ -263,8 +300,9 @@ impl GameState { player: Player, pos: &Position, stone: Stone, + check_start: Option, ) -> Result<(Vec, GameState), String> { - self.is_legal_place(player, pos, stone)?; + self.is_legal_place(player, pos, stone, check_start)?; // Place stone let mut copy = self.copy(); @@ -666,27 +704,78 @@ enum TurnOrder { Normal, } -/* +#[derive(Clone, Copy)] pub enum StartType { CPS(u8), FCS, + FES, TPS, CZS, } -impl TurnOrder { - pub fn next_turn(&self, turn_num: usize, st: StartType) { - match st { - StartType::CPS(c) => { +impl fmt::Display for StartType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + StartType::CPS(n) => write!(f, "CPS{}", n), + StartType::FCS => write!(f, "FCS"), + StartType::FES => write!(f, "FES"), + StartType::TPS => write!(f, "TPS"), + StartType::CZS => write!(f, "CZS"), + } + } +} +impl StartType { + fn player_turnorder_at(&self, turn_num: usize) -> (Player, TurnOrder) { + match self { + StartType::CPS(c) => { + if turn_num + 1 > (*c as usize) { + if turn_num % 2 == 0 { + (Player::White, TurnOrder::Normal) + } else { + (Player::Black, TurnOrder::Normal) + } + } else { + if turn_num % 2 == 0 { + (Player::White, TurnOrder::WhitePlacesBlack) + } else { + (Player::Black, TurnOrder::BlackPlacesWhite) + } + } } + StartType::FCS => StartType::CPS(2).player_turnorder_at(turn_num), + StartType::FES => StartType::CPS(2).player_turnorder_at(turn_num), + StartType::TPS => match turn_num { + 0 => (Player::White, TurnOrder::WhitePlacesBlack), + 1 => (Player::White, TurnOrder::WhitePlacesBlack), + 2 => (Player::Black, TurnOrder::BlackPlacesWhite), + n => ( + if n % 2 == 0 { + Player::Black + } else { + Player::White + }, + TurnOrder::Normal, + ), + }, + StartType::CZS => match turn_num { + 0 => (Player::White, TurnOrder::WhitePlacesBlack), + n => ( + if n % 2 == 0 { + Player::White + } else { + Player::Black + }, + TurnOrder::Normal, + ), + }, } } } -*/ pub struct Game { size: u8, + start_type: StartType, current_player: Player, turn_order: TurnOrder, actions: Vec, @@ -703,6 +792,7 @@ impl Game { white_player_name: &str, black_player_name: &str, date_str: &str, + start_type: StartType, ) -> (Game, String) { let (size, warning) = if (size <= 3) || (size >= 8) { (5, format!("Warning: the requested game size of {}x{} is not supported, defaulting to 5x5.", @@ -711,17 +801,19 @@ impl Game { (size, String::new()) }; + let (current_player, turn_order) = start_type.player_turnorder_at(0); ( Game { - size: size, + size, + start_type, states: vec![GameState::new(size)], actions: Vec::new(), - current_player: Player::White, - turn_order: TurnOrder::WhitePlacesBlack, + current_player, + turn_order, + win_type: None, white_player_name: white_player_name.to_string(), black_player_name: black_player_name.to_string(), date_string: date_str.to_string(), - win_type: None, }, warning, ) @@ -813,10 +905,11 @@ impl Game { && (*player == Player::Black) && (*stone == Stone::Flat) { - state.place_stone(*player, pos, *stone) + state.place_stone(*player, pos, *stone, Some(self.start_type)) } else { - Err(String::from( - "At the start of the game, W must place a B flat.", + Err(format!( + "At the start of the game in {}, W must place a B flat.", + self.start_type )) } } @@ -825,21 +918,23 @@ impl Game { && (*player == Player::White) && (*stone == Stone::Flat) { - state.place_stone(*player, pos, *stone) + state.place_stone(*player, pos, *stone, Some(self.start_type)) } else { - Err(String::from( - "At the start of the game, B must place a W flat.", + Err(format!( + "At the start of the game in {}, B must place a W flat.", + self.start_type )) } } - TurnOrder::Normal => state.place_stone(*player, pos, *stone), + TurnOrder::Normal => state.place_stone(*player, pos, *stone, None), }, Action::Move(pos, direction, picked_up, drops) => match self.turn_order { TurnOrder::Normal => { state.move_stack(self.current_player, pos, *direction, *picked_up, drops) } - _ => Err(String::from( - "At the start of the game only placing flats is allowed.", + _ => Err(format!( + "At the start of the game in {} only placing flats is allowed.", + self.start_type )), }, }; @@ -849,20 +944,11 @@ impl Game { Ok((pos_vec, new_state)) => { self.states.push(new_state); self.actions.push(act); - self.current_player = match self.turn_order { - TurnOrder::WhitePlacesBlack => Player::Black, - TurnOrder::BlackPlacesWhite => Player::White, - TurnOrder::Normal => match self.current_player { - Player::Black => Player::White, - Player::White => Player::Black, - }, - }; - - self.turn_order = match self.turn_order { - TurnOrder::WhitePlacesBlack => TurnOrder::BlackPlacesWhite, - TurnOrder::BlackPlacesWhite => TurnOrder::Normal, - TurnOrder::Normal => TurnOrder::Normal, - }; + let (current_player, turn_order) = + self.start_type.player_turnorder_at(self.states.len()); + self.current_player = current_player; + self.turn_order = turn_order; + // This is safe as we have just added to the states vector self.win_type = self.last_state().check_win(); return Ok((pos_vec, self.win_type)); } @@ -873,15 +959,10 @@ impl Game { if self.actions.len() > 0 { self.actions.pop(); self.states.pop(); - self.turn_order = match self.actions.len() { - 0 => TurnOrder::WhitePlacesBlack, - 1 => TurnOrder::BlackPlacesWhite, - _ => TurnOrder::Normal, - }; - self.current_player = match self.current_player { - Player::Black => Player::White, - Player::White => Player::Black, - }; + let (current_player, turn_order) = + self.start_type.player_turnorder_at(self.states.len()); + self.turn_order = turn_order; + self.current_player = current_player; // I'm too lazy to work out exactly which squares must be // redrawn when an action is undone let mut redraw_all: Vec = Vec::new(); @@ -901,11 +982,12 @@ impl fmt::Display for Game { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, - "[Date \"{}\"]\n[Player1 \"{}\"]\n[Player2 \"{}\"]\n[Size \"{}\"]\n{}", + "[Date \"{}\"]\n[Player1 \"{}\"]\n[Player2 \"{}\"]\n[Size \"{}\"][Start \"{}\"]\n{}", self.date_string, self.white_player_name, self.black_player_name, self.size, + self.start_type, self.query_action_lines().join("\n"), ) } diff --git a/src/main.rs b/src/main.rs index 3c29ea3..f88fee5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ mod parser; use crate::game::*; use crate::gui::*; +use crate::parser::parse_start_type; use argparse::*; use chrono::Local; @@ -23,6 +24,7 @@ fn main() { let mut engine = String::new(); let mut name = String::new(); let mut size: u8 = 5; + let mut start_type_string = String::new(); { let mut ap = ArgumentParser::new(); @@ -41,6 +43,15 @@ fn main() { overridden by the arument ENGINE of --engine when applicable.", ); + ap.refer(&mut start_type_string) + .add_option( + &["-v", "--variant-start"], + Store, + "Select the rule variant for the start of play. Valid choices are CPSn, FCS, FES, TPS, and CZS. + The default starting type, which matches the original rules, is CPS2.", + ) + .metavar("VRNT"); + ap.refer(&mut engine).add_option( &["-e", "--engine"], Store, @@ -71,6 +82,16 @@ fn main() { ap.parse_args_or_exit(); } + let start_type; + if !start_type_string.is_empty() { + match parse_start_type(&start_type_string) { + Err(e) => panic!("Invalid start rule variant provided: {}", e), + Ok(st) => start_type = st, + } + } else { + start_type = StartType::CPS(2); + } + let call_proc; if !engine.is_empty() { p2_name = engine.clone(); @@ -87,7 +108,7 @@ fn main() { p1_white = false; } else { panic!( - "Player named as white ({}) is not a player in this game ({} and {}).", + "Player named as white ({}) is not a player in this game (only ``{}'' and ``{}'' are).", name, p1_name, p2_name ); } @@ -101,9 +122,9 @@ fn main() { let date_string = Local::now().to_string(); let (mut game, warning) = if p1_white { - Game::new(size, &p1_name, &p2_name, &date_string) + Game::new(size, &p1_name, &p2_name, &date_string, start_type) } else { - Game::new(size, &p2_name, &p1_name, &date_string) + Game::new(size, &p2_name, &p1_name, &date_string, start_type) }; let mut gui = GUI::new(&game, warning); diff --git a/src/parser.rs b/src/parser.rs index 68e49a8..c88d751 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -168,3 +168,44 @@ pub fn parse_action(stone_owner: Player, act: &str) -> Result { 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 followed by a positive integer < 256, e.g., CPS2.", + e.to_string() + )) + } + Ok(n) => return Ok(StartType::CPS(n)), + } + } + } + + return Err(format!("Unknown start type ``{}''", st)); +} -- cgit v1.3.1