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::io::Error; 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 width = screen.get_max_x(); let height = screen.get_max_y(); 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.refresh(); let cell_height = 4; let cell_width = 8; let hoff = 3; let board_bottom = (size * cell_height + 3) as i32; let board_right = (size * cell_width + hoff + 3) as i32; let board_gui = BoardGUI::new( 0, 0, size, cell_height, cell_width, hoff, has_colours, white_colour, black_colour, ); let game_log = GameLog::new( 0, board_right, height, 20, 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 human_action(gui: &GUI, game: &Game) -> Result { loop { let inp = gui.action_entry.read_action(); match parse_action(game.query_stone_owner(), &inp) { Err(err) => { gui.message_log .log_error(format!("Input \"{}\": {}", inp, err)); gui.action_entry.clear_entry_area(); } Ok(action) => { gui.action_entry.clear_entry_area(); return Ok(action); } } } } 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 inp = match game.query_current_player() { Player::Black => { if call_proc && p1_white { consult_next_action(&game, &engine) } else { human_action(&gui, &game) } } Player::White => { if call_proc && (!p1_white) { consult_next_action(&game, &engine) } else { human_action(&gui, &game) } } }; match inp { Err(err) => { gui.message_log.log_error(format!("{}", err)); break; } Ok(act) => { let stone_owner = game.query_stone_owner(); let player = game.query_current_player(); 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); let current = game.query_current_player(); if call_proc && ((p1_white && current == Player::Black) || ((!p1_white) && current == Player::White)) { 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(); }