summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/game.rs172
-rw-r--r--src/main.rs27
-rw-r--r--src/parser.rs41
3 files changed, 192 insertions, 48 deletions
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<StartType>,
+ ) -> 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<StartType>,
) -> Result<(Vec<Position>, 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<Action>,
@@ -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<Position> = 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<Action, String> {
parse_place(act, stone_owner)
}
}
+
+pub fn parse_start_type(st: &str) -> Result<StartType, String> {
+ 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::<u8>();
+ 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));
+}