summaryrefslogtreecommitdiff
path: root/src/gui.rs
diff options
context:
space:
mode:
authortslil clingman <>2020-02-01 11:42:12 -0800
committertslil clingman <>2020-02-01 11:42:12 -0800
commit8413caf1d6429bade43a90e4b8d4a662f0fd7deb (patch)
treea36e52e395c071c5865839d66e8377b6d6a22499 /src/gui.rs
parent10f615418ebc5cf4a12b82bbcb95a74cbde8c339 (diff)
Features complete for v 0.1.0
Diffstat (limited to 'src/gui.rs')
-rw-r--r--src/gui.rs313
1 files changed, 303 insertions, 10 deletions
diff --git a/src/gui.rs b/src/gui.rs
index fb32508..74f55eb 100644
--- a/src/gui.rs
+++ b/src/gui.rs
@@ -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;
+ }
+}