extern crate argparse; extern crate chrono; mod consulter; 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; struct GUI { screen: Window, // width: i32, // height: i32, game_log: GameLog, message_log: MessageLog, action_entry: ActionEntry, board_gui: BoardGUI, } fn init_gui(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); } screen.addstr(format!( "takwrap {} | to switch GUI element", env!("CARGO_PKG_VERSION").to_string() )); screen.mvchgat(0, 0, -1, A_REVERSE, -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, width - board_right, 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, // width, // height, game_log, message_log, action_entry, board_gui, } } fn main() { let mut p1_name = match env::var("USER") { Err(_) => String::from("Player1"), Ok(user) => user, }; let mut p2_name = String::from("Player2"); let mut engine = String::new(); let mut name = String::new(); let mut size: u8 = 5; { let mut ap = ArgumentParser::new(); ap.set_description("Test description."); ap.refer(&mut p1_name).add_option( &["--player1"], Store, "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.", ); 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.", ); 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"], Store, "Size of board (one dimension), default is 5.", ); ap.add_option( &["-V", "--version"], Print(env!("CARGO_PKG_VERSION").to_string()), "Show version", ); ap.parse_args_or_exit(); } let call_proc; if !engine.is_empty() { p2_name = engine.clone(); call_proc = true; // Perhaps there's a better way. } else { call_proc = false; } let gui = init_gui(size); let p1_white; if !name.is_empty() { if name == p1_name { p1_white = true; } else if name == p2_name { p1_white = false; } else { panic!( "Player named as white ({}) is not a player in this game ({} and {}).", name, p1_name, p2_name ); } } else { p1_white = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { Ok(n) => n.as_millis() % 2 == 0, Err(_) => false, }; } let date_string = Local::now().to_string(); let (mut game, warning) = if p1_white { Game::new(size, &p1_name, &p2_name, &date_string) } else { Game::new(size, &p2_name, &p1_name, &date_string) }; gui.message_log.log_error(warning); gui.game_log.update(&game); loop { let player = game.query_current_player(); let engine_turn = call_proc && ((p1_white && player == Player::Black) || ((!p1_white) && player == Player::White)); let result = match player { Player::Black => { if call_proc && p1_white { gui.message_log .log_message(String::from("Opponent is thinking...")); consult_next_action(&game, &engine) } else { Ok(gui.action_entry.read_action()) } } Player::White => { if call_proc && (!p1_white) { gui.message_log .log_message(String::from("Opponent is thinking...")); consult_next_action(&game, &engine) } else { Ok(gui.action_entry.read_action()) } } }; match result { Err(e) => { gui.message_log.log_error(format!("Engine error: {}", e)); break; } Ok(gui_result) => match gui_result { GUIResult::SwitchElement => { if engine_turn { gui.message_log.log_error(String::from( "Internal error: engine passed switch-element message somehow.", )); break; } } GUIResult::Undo => { if engine_turn { gui.message_log.log_error(String::from( "Internal error: engine passed undo message somehow.", )); break; } } GUIResult::Raw(raw) => { let stone_owner = game.query_stone_owner(); let act = parse_action(stone_owner, &raw); match act { Err(err) => { gui.message_log .log_error(format!("Input \"{}\": {}", raw, err)); if engine_turn { break; } } Ok(act) => { let pn = game.query_current_player_name(); gui.message_log.log_message(format!( "{} performs {}{}", pn, act, if stone_owner != player { format!(" with a {} stone.", stone_owner) } else { String::from(".") } )); match game.perform_action(act) { Err(e) => { gui.message_log.log_error(e); if engine_turn { gui.message_log.log_error(String::from( "Error: external and internal game states disagree.", )); break; } } Ok((pos_vec, win)) => { gui.board_gui.update(&game, pos_vec); gui.game_log.update(&game); if let Some(w) = win { gui.message_log.log_message(format!("{}", w)); break; } } } } } } }, } } gui.screen.getch(); // board_gui.read_action(); endwin(); }