extern crate argparse; mod consulter; mod game; mod gui; mod parser; use crate::consulter::*; use crate::game::*; use crate::gui::*; use crate::parser::*; use argparse::*; 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 + 1, 0, height - board_bottom - 1, width, 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 = String::from("Player1"); let mut p2_name = String::from("Player2"); let mut computer_opponent = 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, Black/White assignment is still random.", ); ap.refer(&mut p2_name).add_option( &["--player2"], Store, "Name of second player, ignored if playing against a computer opponent.", ); ap.refer(&mut computer_opponent).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.", ); 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 human_first; let call_proc; if !computer_opponent.is_empty() { p2_name = computer_opponent.clone(); call_proc = true; // Perhaps there's a better way. human_first = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { Ok(n) => n.as_secs() % 2 == 0, Err(_) => false, }; } else { human_first = false; call_proc = false; } let gui = init_gui(size); let (mut game, warning) = Game::new(size, &p1_name, &p2_name); gui.message_log.log_error(warning); gui.game_log.update(&game); loop { let inp = match game.query_current_player() { Player::Black => { if call_proc && human_first { consult_next_action(&game, &computer_opponent) } else { human_action(&gui, &game) } } Player::White => { if call_proc && (!human_first) { consult_next_action(&game, &computer_opponent) } 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(); gui.message_log.log_message(format!( "{} performs {}{}", player, 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 && ((human_first && current == Player::Black) || ((!human_first) && 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(); }