diff options
| author | tslil clingman <> | 2020-02-01 11:42:12 -0800 |
|---|---|---|
| committer | tslil clingman <> | 2020-02-01 11:42:12 -0800 |
| commit | 8413caf1d6429bade43a90e4b8d4a662f0fd7deb (patch) | |
| tree | a36e52e395c071c5865839d66e8377b6d6a22499 | |
| parent | 10f615418ebc5cf4a12b82bbcb95a74cbde8c339 (diff) | |
Features complete for v 0.1.0
| -rw-r--r-- | src/game.rs | 46 | ||||
| -rw-r--r-- | src/gui.rs | 313 | ||||
| -rw-r--r-- | src/main.rs | 263 |
3 files changed, 339 insertions, 283 deletions
diff --git a/src/game.rs b/src/game.rs index 8716111..2745073 100644 --- a/src/game.rs +++ b/src/game.rs @@ -112,6 +112,7 @@ pub struct Piece { #[derive(Clone, Copy)] pub enum WinType { + Dragon, RoadWin(Player), FlatWin(Player), Draw, @@ -127,6 +128,7 @@ impl fmt::Display for WinType { WinType::RoadWin(Player::White) => "R-0", WinType::FlatWin(Player::Black) => "0-F", WinType::FlatWin(Player::White) => "F-0", + WinType::Dragon => "R-R", WinType::Draw => "1/2-1/2", } ) @@ -606,19 +608,17 @@ impl GameState { } } - if self.dfs(Player::Black, true, todo_black_up) - || self.dfs(Player::Black, false, todo_black_right) - { - return Some(WinType::RoadWin(Player::Black)); - } + let black_road = self.dfs(Player::Black, true, todo_black_up) + || self.dfs(Player::Black, false, todo_black_right); + let white_road = self.dfs(Player::White, true, todo_white_up) + || self.dfs(Player::White, false, todo_white_right); - if self.dfs(Player::White, true, todo_white_up) - || self.dfs(Player::White, false, todo_white_right) - { - return Some(WinType::RoadWin(Player::White)); + match (black_road, white_road) { + (false, false) => return None, + (true, false) => return Some(WinType::RoadWin(Player::Black)), + (false, true) => return Some(WinType::RoadWin(Player::White)), + (true, true) => return Some(WinType::Dragon), } - - return None; } fn check_flat_win(&self) -> Option<WinType> { @@ -651,7 +651,6 @@ impl GameState { return None; } - // TODO: this does not handle the case dragon edge case fn check_win(&self) -> Option<WinType> { if let Some(w) = self.check_road_win() { return Some(w); @@ -667,6 +666,25 @@ enum TurnOrder { Normal, } +/* +pub enum StartType { + CPS(u8), + FCS, + TPS, + CZS, +} + +impl TurnOrder { + pub fn next_turn(&self, turn_num: usize, st: StartType) { + match st { + StartType::CPS(c) => { + + } + } + } +} +*/ + pub struct Game { size: u8, current_player: Player, @@ -746,6 +764,10 @@ impl Game { self.size } + pub fn get_date_string(&self) -> &str { + &self.date_string + } + pub fn query_action_lines(&self) -> Vec<String> { let mut result = Vec::new(); let mut newline = true; @@ -3,7 +3,12 @@ extern crate pancurses; pub use pancurses::Input::*; pub use pancurses::*; +use std::fs::File; +use std::io::Write; + +use crate::consulter::*; use crate::game::*; +use crate::parser::*; #[derive(PartialEq)] pub enum GUIResult { @@ -367,15 +372,6 @@ impl BoardGUI { bg.draw_grid(); - // for i in 0..s * s { - // let y: i32 = i / s; - // let x: i32 = i % s; - // let y = (s - y - 1) * h + 1; - // let x = hoff + x * w + 1; - // bg.cell_windows[i as usize].addstr(format!("{}:({},{})", i, y, x)); - // bg.cell_windows[i as usize].refresh(); - // } - bg.mv_to_cell(&bg.cur_cell, 0); bg.board_window.refresh(); bg @@ -455,7 +451,7 @@ impl ActionEntry { } } - KeyEnter => break, // TODO: is this ever used? + KeyEnter => break, _ => (), }; } @@ -506,3 +502,300 @@ impl MessageLog { self.window.refresh(); } } + +enum InputSource { + Player, + Log, +} + +const ACTION_HEADER: &str = "Enter action in PTN"; +const LOG_HEADER: &str = "U to undo a turn | <UP>/<DOWN> to scroll | Q to quit"; + +pub struct GUI { + screen: Window, + game_log: GameLog, + message_log: MessageLog, + action_entry: ActionEntry, + board_gui: BoardGUI, + input_source: InputSource, +} + +impl GUI { + fn write_header(&self, header: &str) { + self.screen.mv(0, 0); + self.screen.clrtoeol(); + self.screen.addstr(format!( + "takwrap {} | <TAB> to switch GUI element | {}", + env!("CARGO_PKG_VERSION").to_string(), + header + )); + self.screen.mvchgat(0, 0, -1, A_REVERSE, -1); + self.screen.refresh(); + } + + pub fn new(game: &Game, warning: String) -> GUI { + let screen = initscr(); + cbreak(); + noecho(); + curs_set(2); + + let has_colours = has_colors(); + let (black_colour, white_colour, red_colour): (u8, u8, u8) = (1, 2, 3); + + if has_colours { + start_color(); + use_default_colors(); + init_pair(black_colour as i16, -1, COLOR_RED); + init_pair(white_colour as i16, -1, COLOR_BLUE); + init_pair(red_colour as i16, COLOR_RED, -1); + } + + let voff = 2; + + let height = screen.get_max_y() - voff; + + screen.refresh(); + + let cell_height = 4; + let cell_width = 8; + let hoff = 3; + let size = game.get_size(); + + let board_bottom = voff + (size * cell_height + 3) as i32; + let board_right = (hoff + size * cell_width + 3) as i32; + + let board_gui = BoardGUI::new( + voff, + 0, + size, + cell_height, + cell_width, + hoff, + has_colours, + white_colour, + black_colour, + ); + + let game_log = GameLog::new( + voff, + board_right, + height - 2, + 30, + has_colours, + white_colour, + black_colour, + ); + + // Need room for '8___011111111', for example + let action_entry = ActionEntry::new(board_bottom, 0, 13); + + let message_log = MessageLog::new( + board_bottom + 2, + 0, + height - board_bottom - 1, + board_right, + has_colours, + red_colour, + ); + + let mut gui = GUI { + screen, + game_log, + message_log, + action_entry, + board_gui, + input_source: InputSource::Player, + }; + + gui.write_header(ACTION_HEADER); + gui.message_log.log_error(warning); + gui.game_log.update(game); + + gui + } + + pub fn query_input( + &mut self, + game: &Game, + call_proc: bool, + p1_white: bool, + engine: &str, + ) -> Result<GUIResult, String> { + match self.input_source { + InputSource::Player => { + let player = game.query_current_player(); + + let inp = match player { + Player::Black => { + if call_proc && p1_white { + self.message_log + .log_message(String::from("Opponent is thinking...")); + consult_next_action(&game, &engine) + } else { + Ok(self.action_entry.read_action()) + } + } + Player::White => { + if call_proc && (!p1_white) { + self.message_log + .log_message(String::from("Opponent is thinking...")); + consult_next_action(&game, &engine) + } else { + Ok(self.action_entry.read_action()) + } + } + }; + + match inp { + Err(e) => Err(format!("Error: {}", e)), + Ok(ok) => Ok(ok), + } + } + InputSource::Log => Ok(self.game_log.read_action()), + } + } + + pub fn handle_input( + &mut self, + game: &mut Game, + call_proc: bool, + p1_white: bool, + input: Result<GUIResult, String>, + ) -> bool { + let player = game.query_current_player(); + let engine_turn = call_proc + && ((p1_white && player == Player::Black) || ((!p1_white) && player == Player::White)); + + match input { + Err(e) => { + if engine_turn { + self.message_log + .log_error(format!("Unrecoverable error: {}", e)); + return false; + } + } + Ok(gui_result) => match gui_result { + GUIResult::Quit => { + self.message_log + .log_message(String::from("Press any key to quit.")); + self.screen.getch(); + return false; + } + GUIResult::SwitchElement => match self.input_source { + InputSource::Player => { + self.input_source = InputSource::Log; + self.write_header(LOG_HEADER); + } + InputSource::Log => { + self.input_source = InputSource::Player; + self.write_header(ACTION_HEADER); + } + }, + GUIResult::Undo => match game.undo() { + Ok(pos_vec) => { + self.message_log + .log_message(String::from("Game undone by one turn.")); + self.board_gui.update(&game, pos_vec); + self.game_log.update(&game); + } + Err(err) => { + self.message_log.log_error(err); + } + }, + GUIResult::Raw(raw) => { + let stone_owner = game.query_stone_owner(); + let act = parse_action(stone_owner, &raw); + match act { + Err(err) => { + self.message_log + .log_error(format!("Input \"{}\": {}", raw, err)); + if engine_turn { + return false; + } + } + Ok(act) => { + let pn = game.query_current_player_name(); + self.message_log.log_message(format!( + "{} performs {}{}", + pn, + act, + if stone_owner != player { + format!(" with a {} stone.", stone_owner) + } else { + String::from(".") + } + )); + let act_string = act.to_string(); + match game.perform_action(act) { + Err(e) => { + if engine_turn { + self.message_log.log_error(String::from( + "Error: external and internal game states disagree. Press any key to quit.", + )); + self.screen.getch(); + return false; + } else { + self.message_log.log_error(format!( + "Cannot perform \"{}\": {}", + act_string, e + )); + } + } + + Ok((pos_vec, win)) => { + self.board_gui.update(&game, pos_vec); + self.game_log.update(&game); + if let Some(w) = win { + self.message_log.log_message(format!( + "Game over: {}\nPress S to save PTN and quit, or Q to quit.", + w + )); + loop { + 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 + )); + } + } + } + return false; + } + Character('Q') => { + return false; + } + _ => (), + } + } + } + } + } + } + } + } + } + }, + } + return true; + } +} diff --git a/src/main.rs b/src/main.rs index ec8bc7a..3c29ea3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,268 +6,14 @@ mod game; mod gui; mod parser; -use crate::consulter::*; use crate::game::*; use crate::gui::*; -use crate::parser::*; use argparse::*; use chrono::Local; use std::env; use std::time::SystemTime; -enum InputSource { - Player, - Log, -} - -struct GUI { - screen: Window, - game_log: GameLog, - message_log: MessageLog, - action_entry: ActionEntry, - board_gui: BoardGUI, - input_source: InputSource, -} - -impl GUI { - fn write_header(&self, header: &str) { - self.screen.mv(0, 0); - self.screen.clrtoeol(); - self.screen.addstr(format!( - "takwrap {} | <TAB> to switch GUI element | {}", - env!("CARGO_PKG_VERSION").to_string(), - header - )); - self.screen.mvchgat(0, 0, -1, A_REVERSE, -1); - self.screen.refresh(); - } - - fn new(size: u8) -> GUI { - let screen = initscr(); - cbreak(); - noecho(); - curs_set(2); - - let has_colours = has_colors(); - let (black_colour, white_colour, red_colour): (u8, u8, u8) = (1, 2, 3); - - if has_colours { - start_color(); - use_default_colors(); - init_pair(black_colour as i16, -1, COLOR_RED); - init_pair(white_colour as i16, -1, COLOR_BLUE); - init_pair(red_colour as i16, COLOR_RED, -1); - } - - let voff = 2; - - // let width = screen.get_max_x(); - let height = screen.get_max_y() - voff; - - screen.refresh(); - - let cell_height = 4; - let cell_width = 8; - let hoff = 3; - - let board_bottom = voff + (size * cell_height + 3) as i32; - let board_right = (hoff + size * cell_width + 3) as i32; - - let board_gui = BoardGUI::new( - voff, - 0, - size, - cell_height, - cell_width, - hoff, - has_colours, - white_colour, - black_colour, - ); - - let game_log = GameLog::new( - voff, - board_right, - height - 2, - 30, - has_colours, - white_colour, - black_colour, - ); - - // Need room for '8___011111111', for example - let action_entry = ActionEntry::new(board_bottom, 0, 13); - - let message_log = MessageLog::new( - board_bottom + 2, - 0, - height - board_bottom - 1, - board_right, - has_colours, - red_colour, - ); - - GUI { - screen, - game_log, - message_log, - action_entry, - board_gui, - input_source: InputSource::Player, - } - } - - fn query_input( - &mut self, - game: &Game, - call_proc: bool, - p1_white: bool, - engine: &str, - ) -> Result<GUIResult, String> { - match self.input_source { - InputSource::Player => { - let player = game.query_current_player(); - - let inp = match player { - Player::Black => { - if call_proc && p1_white { - self.message_log - .log_message(String::from("Opponent is thinking...")); - consult_next_action(&game, &engine) - } else { - Ok(self.action_entry.read_action()) - } - } - Player::White => { - if call_proc && (!p1_white) { - self.message_log - .log_message(String::from("Opponent is thinking...")); - consult_next_action(&game, &engine) - } else { - Ok(self.action_entry.read_action()) - } - } - }; - - match inp { - Err(e) => Err(format!("Error: {}", e)), - Ok(ok) => Ok(ok), - } - } - InputSource::Log => Ok(self.game_log.read_action()), - } - } - - fn handle_input( - &mut self, - game: &mut Game, - call_proc: bool, - p1_white: bool, - input: Result<GUIResult, String>, - ) -> bool { - let player = game.query_current_player(); - let engine_turn = call_proc - && ((p1_white && player == Player::Black) || ((!p1_white) && player == Player::White)); - - match input { - Err(e) => { - if engine_turn { - self.message_log - .log_error(format!("Unrecoverable error: {}", e)); - return false; - } - } - Ok(gui_result) => match gui_result { - GUIResult::Quit => { - self.message_log - .log_message(String::from("Press any key to quit.")); - return false; - } - GUIResult::SwitchElement => match self.input_source { - InputSource::Player => { - self.input_source = InputSource::Log; - self.write_header(LOG_HEADER); - } - InputSource::Log => { - self.input_source = InputSource::Player; - self.write_header(ACTION_HEADER); - } - }, - GUIResult::Undo => match game.undo() { - Ok(pos_vec) => { - self.message_log - .log_message(String::from("Game undone by one turn.")); - self.board_gui.update(&game, pos_vec); - self.game_log.update(&game); - } - Err(err) => { - self.message_log.log_error(err); - } - }, - GUIResult::Raw(raw) => { - let stone_owner = game.query_stone_owner(); - let act = parse_action(stone_owner, &raw); - match act { - Err(err) => { - self.message_log - .log_error(format!("Input \"{}\": {}", raw, err)); - if engine_turn { - return false; - } - } - Ok(act) => { - let pn = game.query_current_player_name(); - self.message_log.log_message(format!( - "{} performs {}{}", - pn, - act, - if stone_owner != player { - format!(" with a {} stone.", stone_owner) - } else { - String::from(".") - } - )); - let act_string = act.to_string(); - match game.perform_action(act) { - Err(e) => { - if engine_turn { - self.message_log.log_error(String::from( - "Error: external and internal game states disagree. Press any key to quit.", - )); - return false; - } else { - self.message_log.log_error(format!( - "Cannot perform \"{}\": {}", - act_string, e - )); - } - } - - Ok((pos_vec, win)) => { - self.board_gui.update(&game, pos_vec); - self.game_log.update(&game); - if let Some(w) = win { - self.message_log.log_message(format!( - "Game over: {}\nPress any key to quit.", - w - )); - return false; - } - } - } - } - } - } - }, - } - return true; - } -} - -const ACTION_HEADER: &str = "Enter action in PTN"; -const LOG_HEADER: &str = "U to undo a turn | <UP>/<DOWN> to scroll | Q to quit"; - fn main() { let mut p1_name = match env::var("USER") { Err(_) => String::from("Player1"), @@ -329,13 +75,10 @@ fn main() { if !engine.is_empty() { p2_name = engine.clone(); call_proc = true; - // Perhaps there's a better way. } else { call_proc = false; } - let mut gui = GUI::new(size); - let p1_white; if !name.is_empty() { if name == p1_name { @@ -349,6 +92,7 @@ fn main() { ); } } else { + // Very random choice p1_white = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { Ok(n) => n.as_millis() % 2 == 0, Err(_) => false, @@ -361,15 +105,12 @@ fn main() { } else { Game::new(size, &p2_name, &p1_name, &date_string) }; - gui.message_log.log_error(warning); - gui.game_log.update(&game); - gui.write_header(ACTION_HEADER); + let mut gui = GUI::new(&game, warning); loop { let inp = gui.query_input(&game, call_proc, p1_white, &engine); if !gui.handle_input(&mut game, call_proc, p1_white, inp) { - gui.screen.getch(); break; } } |
