diff options
| author | tslil clingman <> | 2020-02-10 17:36:50 -0800 |
|---|---|---|
| committer | tslil clingman <> | 2020-02-10 17:36:50 -0800 |
| commit | 15f64980573085e9ab6a9d47fc2a6e8092d6d109 (patch) | |
| tree | e0b6e1119d642592326deaad2f2634d027a98c2d /src | |
| parent | 942c13ee26b180316f46921531c737fa8eb4197e (diff) | |
Feature completion for 0.2.0
Diffstat (limited to 'src')
| -rw-r--r-- | src/consulter.rs | 8 | ||||
| -rw-r--r-- | src/game.rs | 62 | ||||
| -rw-r--r-- | src/gui.rs | 92 | ||||
| -rw-r--r-- | src/main.rs | 53 |
4 files changed, 129 insertions, 86 deletions
diff --git a/src/consulter.rs b/src/consulter.rs index 091eec0..250fd1c 100644 --- a/src/consulter.rs +++ b/src/consulter.rs @@ -1,11 +1,11 @@ -use std::fs::File; +use std::fs::{remove_file, File}; use std::io::{Error, ErrorKind, Result, Write}; use std::process::Command; use crate::game::*; use crate::gui::*; -const FILE_NAME: &str = "/tmp/takwrap_ptn_consult.ptn"; +pub const FILE_NAME: &str = "takwrap_ptn_consult.ptn"; pub fn consult_next_action(game: &Game, proc: &str) -> Result<GUIResult> { let file = File::create(FILE_NAME)?; @@ -19,3 +19,7 @@ pub fn consult_next_action(game: &Game, proc: &str) -> Result<GUIResult> { Ok(raw) => Ok(GUIResult::Raw(raw.trim().to_string())), } } + +pub fn consulter_clean_up() -> Result<()> { + remove_file(FILE_NAME) +} diff --git a/src/game.rs b/src/game.rs index b49f626..18e6fe5 100644 --- a/src/game.rs +++ b/src/game.rs @@ -252,7 +252,7 @@ impl GameState { || ((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 {}).", + "At the start of game in FCS, {} may not be placed in a corner (such as {}).", player, pos )); } @@ -264,7 +264,7 @@ impl GameState { || (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).", + "At the start of a game in FES, {} may not be placed along any edge (and {} is on an edge).", player, pos )); } @@ -726,26 +726,26 @@ impl fmt::Display for StartType { } impl StartType { - fn player_turnorder_at(&self, turn_num: usize) -> (Player, TurnOrder) { + fn player_turnorder_at(&self, ply: usize) -> (Player, TurnOrder) { match self { StartType::CPS(c) => { - if turn_num * 2 > (*c as usize) { - if turn_num % 2 == 0 { + if ply / 2 >= (*c as usize) { + if ply % 2 == 0 { (Player::White, TurnOrder::Normal) } else { (Player::Black, TurnOrder::Normal) } } else { - if turn_num % 2 == 0 { + if ply % 2 == 0 { (Player::White, TurnOrder::WhitePlacesBlack) } else { (Player::Black, TurnOrder::BlackPlacesWhite) } } } - StartType::FCS => StartType::CPS(1).player_turnorder_at(turn_num), - StartType::FES => StartType::CPS(1).player_turnorder_at(turn_num), - StartType::TPS => match turn_num { + StartType::FCS => StartType::CPS(1).player_turnorder_at(ply), + StartType::FES => StartType::CPS(1).player_turnorder_at(ply), + StartType::TPS => match ply { 0 => (Player::White, TurnOrder::WhitePlacesBlack), 1 => (Player::White, TurnOrder::WhitePlacesBlack), 2 => (Player::Black, TurnOrder::BlackPlacesWhite), @@ -758,10 +758,10 @@ impl StartType { TurnOrder::Normal, ), }, - StartType::CZS => match turn_num { + StartType::CZS => match ply { 0 => (Player::White, TurnOrder::WhitePlacesBlack), n => ( - if n % 2 == 0 { + if n % 2 == 1 { Player::White } else { Player::Black @@ -819,32 +819,26 @@ impl Game { ) } - // Relying on only ::new(...) being used to make instances - - fn last_state(&self) -> &GameState { - &self.states[self.states.len() - 1] - } - pub fn query_square(&self, pos: &Position) -> Option<&Stack> { self.last_state().query_pos(pos) } - pub fn query_current_player_name(&self) -> &str { + pub fn get_current_player_name(&self) -> &str { match self.current_player { Player::Black => &self.black_player_name, Player::White => &self.white_player_name, } } - pub fn query_pieces(&self, player: Player, stone: Stone) -> u8 { + pub fn get_pieces(&self, player: Player, stone: Stone) -> u8 { self.last_state().remaining_pieces(player, stone) } - pub fn query_current_player(&self) -> Player { + pub fn get_current_player(&self) -> Player { self.current_player } - pub fn query_stone_owner(&self) -> Player { + pub fn get_stone_owner(&self) -> Player { match self.turn_order { TurnOrder::WhitePlacesBlack => Player::Black, TurnOrder::BlackPlacesWhite => Player::White, @@ -852,15 +846,28 @@ impl Game { } } + pub fn get_start_type(&self) -> StartType { + self.start_type + } + pub fn get_size(&self) -> u8 { self.size } + pub fn get_ply(&self) -> usize { + self.states.len() - 1 + } + + // Relying on only ::new(...) being used to make instances + fn last_state(&self) -> &GameState { + &self.states[self.states.len() - 1] + } + pub fn get_date_string(&self) -> &str { &self.date_string } - pub fn query_action_lines(&self) -> Vec<String> { + pub fn get_action_lines(&self) -> Vec<String> { let mut result = Vec::new(); let mut newline = true; let num_actions = self.actions.len(); @@ -918,7 +925,7 @@ impl Game { && (*player == Player::White) && (*stone == Stone::Flat) { - state.place_stone(*player, pos, *stone, Some(self.start_type)) + state.place_stone(*player, pos, *stone, None) } else { Err(format!( "At the start of the game in {}, B must place a W flat.", @@ -945,7 +952,7 @@ impl Game { self.states.push(new_state); self.actions.push(act); let (current_player, turn_order) = - self.start_type.player_turnorder_at(self.states.len()); + self.start_type.player_turnorder_at(self.get_ply()); self.current_player = current_player; self.turn_order = turn_order; // This is safe as we have just added to the states vector @@ -959,8 +966,7 @@ impl Game { if self.actions.len() > 0 { self.actions.pop(); self.states.pop(); - let (current_player, turn_order) = - self.start_type.player_turnorder_at(self.states.len()); + let (current_player, turn_order) = self.start_type.player_turnorder_at(self.get_ply()); self.turn_order = turn_order; self.current_player = current_player; // I'm too lazy to work out exactly which squares must be @@ -982,13 +988,13 @@ impl fmt::Display for Game { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, - "[Date \"{}\"]\n[Player1 \"{}\"]\n[Player2 \"{}\"]\n[Size \"{}\"][Start \"{}\"]\n{}", + "[Date \"{}\"]\n[Player1 \"{}\"]\n[Player2 \"{}\"]\n[Size \"{}\"]\n[Variant \"{}\"]\n{}", self.date_string, self.white_player_name, self.black_player_name, self.size, self.start_type, - self.query_action_lines().join("\n"), + self.get_action_lines().join("\n"), ) } } @@ -14,6 +14,7 @@ use crate::parser::*; pub enum GUIResult { Undo, Quit, + Save, SwitchElement, Raw(String), } @@ -63,7 +64,7 @@ impl GameLog { } self.pieces_window.addstr(format!( "W: {:2}", - game.query_pieces(Player::White, Stone::Flat) + game.get_pieces(Player::White, Stone::Flat) )); self.pieces_window.attrset(Attribute::Normal); @@ -74,10 +75,14 @@ impl GameLog { } self.pieces_window.addstr(format!( "B: {:2}", - game.query_pieces(Player::Black, Stone::Flat), + game.get_pieces(Player::Black, Stone::Flat), )); self.pieces_window.attrset(Attribute::Normal); + + self.pieces_window + .addstr(format!(" Variant: {}", game.get_start_type().to_string())); + self.pieces_window.mv(1, 0); if self.has_colours { @@ -85,7 +90,7 @@ impl GameLog { } self.pieces_window.addstr(format!( "WC: {}", - game.query_pieces(Player::White, Stone::Capstone) + game.get_pieces(Player::White, Stone::Capstone) )); self.pieces_window.attrset(Attribute::Normal); @@ -96,13 +101,16 @@ impl GameLog { } self.pieces_window.addstr(format!( "BC: {}", - game.query_pieces(Player::Black, Stone::Capstone), + game.get_pieces(Player::Black, Stone::Capstone), )); self.pieces_window.attrset(Attribute::Normal); + self.pieces_window + .addstr(format!(" Ply: {}", game.get_ply() + 1)); + self.pieces_window.mv(3, 0); - self.pieces_window.addstr("Turn: "); - match game.query_current_player() { + self.pieces_window.addstr("Active player: "); + match game.get_current_player() { Player::Black => { if self.has_colours { self.pieces_window.attrset(ColorPair(self.black_colour)); @@ -114,9 +122,9 @@ impl GameLog { } } } - self.pieces_window.addstr(game.query_current_player_name()); + self.pieces_window.addstr(game.get_current_player_name()); - let new_lines = game.query_action_lines(); + let new_lines = game.get_action_lines(); if new_lines.len() > self.num_lines { self.offset = new_lines.len() - self.num_lines; } else { @@ -161,6 +169,7 @@ impl GameLog { Character('\t') => return GUIResult::SwitchElement, Character('U') => return GUIResult::Undo, Character('Q') => return GUIResult::Quit, + Character('S') => return GUIResult::Save, _ => (), } } @@ -285,6 +294,7 @@ impl BoardGUI { self.board_window.untouch(); } + /* pub fn read_action(&mut self) -> GUIResult { loop { if let Some(key) = self.board_window.getch() { @@ -322,7 +332,7 @@ impl BoardGUI { } } } - } + }*/ pub fn new( ypos: i32, @@ -509,12 +519,12 @@ enum InputSource { } const ACTION_HEADER: &str = "Enter action in PTN"; -const LOG_HEADER: &str = "U to undo a turn | <UP>/<DOWN> to scroll | Q to quit"; +const LOG_HEADER: &str = "U to undo a turn | <UP>/<DOWN> to scroll | S to save PTN | Q to quit"; pub struct GUI { screen: Window, game_log: GameLog, - message_log: MessageLog, + pub message_log: MessageLog, action_entry: ActionEntry, board_gui: BoardGUI, input_source: InputSource, @@ -533,6 +543,25 @@ impl GUI { self.screen.refresh(); } + fn save(&self, game: &Game) { + let file = File::create(format!("Game_{}.ptn", game.get_date_string())); + + match file { + Err(e) => { + self.message_log + .log_error(format!("Unable to open PTN file for writing: {}", e)); + } + Ok(file) => { + if let Err(e) = write!(&file, "{}", game.to_string()) { + self.message_log + .log_error(format!("Unable to write PTN to file: {}", e)); + } else { + self.message_log.log_message(String::from("PTN saved.")); + } + } + } + } + pub fn new(game: &Game, warning: String) -> GUI { let screen = initscr(); cbreak(); @@ -623,7 +652,7 @@ impl GUI { ) -> Result<GUIResult, String> { match self.input_source { InputSource::Player => { - let player = game.query_current_player(); + let player = game.get_current_player(); let inp = match player { Player::Black => { @@ -662,7 +691,7 @@ impl GUI { p1_white: bool, input: Result<GUIResult, String>, ) -> bool { - let player = game.query_current_player(); + let player = game.get_current_player(); let engine_turn = call_proc && ((p1_white && player == Player::Black) || ((!p1_white) && player == Player::White)); @@ -681,6 +710,11 @@ impl GUI { self.screen.getch(); return false; } + GUIResult::Save => { + self.save(game); + self.screen.getch(); + return false; + } GUIResult::SwitchElement => match self.input_source { InputSource::Player => { self.input_source = InputSource::Log; @@ -703,7 +737,7 @@ impl GUI { } }, GUIResult::Raw(raw) => { - let stone_owner = game.query_stone_owner(); + let stone_owner = game.get_stone_owner(); let act = parse_action(stone_owner, &raw); match act { Err(err) => { @@ -714,7 +748,7 @@ impl GUI { } } Ok(act) => { - let pn = game.query_current_player_name(); + let pn = game.get_current_player_name(); self.message_log.log_message(format!( "{} performs {}{}", pn, @@ -736,7 +770,7 @@ impl GUI { return false; } else { self.message_log.log_error(format!( - "Cannot perform \"{}\": {}", + "Cannot perform \"{}\": p{}", act_string, e )); } @@ -754,31 +788,7 @@ impl GUI { if let Some(inp) = self.screen.getch() { match inp { Character('S') => { - let file = File::create(format!( - "Game_{}.ptn", - game.get_date_string() - )); - - match file { - Err(e) => { - self.message_log.log_error(format!( - "Unable to open PTN file for writing: {}", - e - )); - } - Ok(file) => { - if let Err(e) = write!( - &file, - "{}", - game.to_string() - ) { - self.message_log.log_error(format!( - "Unable to write PTN to file: {}", - e - )); - } - } - } + self.save(game); return false; } Character('Q') => { diff --git a/src/main.rs b/src/main.rs index 50bfcf4..31414b8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ mod game; mod gui; mod parser; +use crate::consulter::consulter_clean_up; use crate::game::*; use crate::gui::*; use crate::parser::parse_start_type; @@ -28,45 +29,59 @@ fn main() { { let mut ap = ArgumentParser::new(); - ap.set_description("Test description."); + ap.set_description( + "A TUI + implementation of and wrapper for engines for the game of + Tak.", + ); ap.refer(&mut p1_name).add_option( &["--player1"], Store, - "Name of first player, defaults to $USER. See --name-white below for Black/White assignment.", + "Name of first player, defaults to $USER. See --name-white + below for Black/White assignment.", ); ap.refer(&mut p2_name).add_option( &["--player2"], Store, "Name of second player, defaults to ``Player2'' and is - overridden by the arument ENGINE of --engine when applicable.", + 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 CPS1.", + "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 CPS1. See ENGINE below for a warning on + this.", ) .metavar("VRNT"); ap.refer(&mut engine).add_option( &["-e", "--engine"], Store, - "Name of process which will be used as a computer opponent. - The process must accept a single argument point to a PTN file - of the current game state, and must generate a single PTN action - on STDOUT. The engine will replace the second player, and the name - of player 2 is set to this argument.", + "Name of process which will be used as a computer + opponent. The process must accept a single argument point + to a PTN file of the current game state, and must generate + a single PTN action on STDOUT. The engine will replace the + second player, and the name of player 2 is set to this + argument. Note: ensure that your engine understands the + [Variant XXX] directive if you choose a variant (above).", ); - ap.refer(&mut name).add_option( - &["--name-white"], - Store, - "Force the player named NAME to play white. If not supplied, assignment is ``random''.", - ).metavar("NAME"); + ap.refer(&mut name) + .add_option( + &["--name-white"], + Store, + "Force the player named NAME to play white. If not + supplied, assignment is ``random''.", + ) + .metavar("NAME"); ap.refer(&mut size).add_option( &["-s", "--size"], @@ -136,6 +151,14 @@ fn main() { } } + // Clean up file + if call_proc { + if let Err(e) = consulter_clean_up() { + gui.message_log + .log_error(format!("Error in cleaning up temporary files: {}", e)); + } + } + // board_gui.read_action(); endwin(); } |
