summaryrefslogtreecommitdiff
path: root/src
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
parent10f615418ebc5cf4a12b82bbcb95a74cbde8c339 (diff)
Features complete for v 0.1.0
Diffstat (limited to 'src')
-rw-r--r--src/game.rs46
-rw-r--r--src/gui.rs313
-rw-r--r--src/main.rs263
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;
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;
+ }
+}
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;
}
}