diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/game.rs | 207 | ||||
| -rw-r--r-- | src/main.rs | 94 |
2 files changed, 221 insertions, 80 deletions
diff --git a/src/game.rs b/src/game.rs index 6307843..e05a807 100644 --- a/src/game.rs +++ b/src/game.rs @@ -101,7 +101,7 @@ pub struct Piece { } pub type Stack = Vec<Piece>; -pub struct GameState { +struct GameState { size: u8, black_flats: u8, white_flats: u8, @@ -113,11 +113,7 @@ pub struct GameState { // TODO: Generate all legal actions for a given player impl GameState { - pub fn get_size(&self) -> u8 { - self.size - } - - pub fn copy(&self) -> GameState { + fn copy(&self) -> GameState { let mut copy = GameState { size: self.size, black_flats: self.black_flats, @@ -127,6 +123,7 @@ impl GameState { board: Vec::new(), }; for i in 0..self.board.len() { + copy.board.push(Vec::new()); for j in 0..self.board[i].len() { copy.board[i].push(self.board[i][j]); } @@ -135,7 +132,7 @@ impl GameState { } // Defaults to 5x5 if requested things are out of range - pub fn new(size: u8) -> GameState { + fn new(size: u8) -> GameState { let (flats, caps) = match size { 3 => (10, 0), 4 => (15, 0), @@ -160,17 +157,17 @@ impl GameState { } } - fn remaining_pieces(&self, player: &Player, stone: &Stone) -> u8 { + fn remaining_pieces(&self, player: Player, stone: Stone) -> u8 { match player { Player::Black => { - if *stone == Stone::Capstone { + if stone == Stone::Capstone { self.black_capstones } else { self.black_flats } } Player::White => { - if *stone == Stone::Capstone { + if stone == Stone::Capstone { self.white_capstones } else { self.white_flats @@ -179,28 +176,16 @@ impl GameState { } } - fn within_bounds(&self, pos: &Position) -> bool { - if (pos.x < self.size) && (pos.y < self.size) { - true - } else { - false - } - } - - pub fn query_square(&self, pos: &Position) -> Option<&Stack> { - if self.within_bounds(pos) { - self.board.get(self.pos_to_idx(pos)) - } else { - None - } - } - fn pos_to_idx(&self, pos: &Position) -> usize { let y: usize = pos.y as usize; let x: usize = pos.x as usize; x + y * (self.size as usize) } + fn query_pos(&self, pos: &Position) -> Option<&Stack> { + self.board.get(self.pos_to_idx(pos)) + } + // Unconditional and so could lead to invalid state, but private fn place_stone(&self, player: Player, pos: &Position, stone: Stone) -> GameState { let mut copy = self.copy(); @@ -225,21 +210,29 @@ impl GameState { } k += d; match direction { - Direction::Up => pos_new.y + 1 >= self.size, - Direction::Down => pos_new.y == 0, - Direction::Left => pos_new.x + 1 >= self.size, - Direction::Right => pos_new.x == 0, + Direction::Up => pos_new.y += 1, + Direction::Down => pos_new.y -= 1, + Direction::Left => pos_new.x -= 1, + Direction::Right => pos_new.x += 1, }; } copy } } +enum TurnOrder { + BlackPlacesWhite, + WhitePlacesBlack, + Normal, +} + pub struct Game<L> where L: Fn(String), { size: u8, + current_player: Player, + turn_order: TurnOrder, white_player_name: String, black_player_name: String, actions: Vec<Action>, @@ -248,7 +241,7 @@ where } impl<L: Fn(String)> Game<L> { - fn new(size: u8, white_player_name: &str, black_player_name: &str, log: L) -> Game<L> { + pub fn new(size: u8, white_player_name: &str, black_player_name: &str, log: L) -> Game<L> { let size = if (size <= 3) || (size >= 8) { (log)(format!( "Warning: the requested game size of {}x{} is not supported, defaulting to 5x5.", @@ -262,29 +255,82 @@ impl<L: Fn(String)> Game<L> { size: size, white_player_name: white_player_name.to_string(), black_player_name: black_player_name.to_string(), - turn: 0, states: vec![GameState::new(size)], actions: Vec::new(), + current_player: Player::Black, + turn_order: TurnOrder::BlackPlacesWhite, log: log, } } + fn within_bounds(&self, pos: &Position) -> bool { + if (pos.x < self.size) && (pos.y < self.size) { + true + } else { + false + } + } + + // Relying on only ::new(...) being used to make instances + + fn last_state(&self) -> &GameState { + &self.states[self.states.len() - 1] + } + + pub fn query_square(&self, pos: &Position) -> Option<&Stack> { + if self.within_bounds(pos) { + self.last_state().query_pos(pos) + } else { + None + } + } + + pub fn query_pieces(&self, player: Player, stone: Stone) -> u8 { + self.last_state().remaining_pieces(player, stone) + } + + pub fn query_current_player(&self) -> Player { + self.current_player + } + + pub fn query_action(&self, turn: u16) -> Option<&Action> { + self.actions.get(turn as usize) + } + pub fn get_size(&self) -> u8 { self.size } + pub fn query_action_lines(&self) -> String { + let mut result = String::new(); + let mut newline = false; + result += "0. "; + for i in 0..self.actions.len() { + if newline { + // TODO: Is placing the opponent's first stone the zeroeth action? + result += &format!("{}. ", i); + } + result += &format!("{} ", self.actions[i]); + if !newline { + result.push('\n'); + } + newline = !newline; + } + result + } + fn is_legal_place(&self, player: &Player, pos: &Position, stone: &Stone) -> bool { if let Some(state) = self.states.last() { /* In order to legally place a piece: 1. The desired square must be empty 2. The player must have sufficient pieces */ - if state.within_bounds(pos) { + if self.within_bounds(pos) { if let Some(stack) = &state.board.get(state.pos_to_idx(pos)) { if stack.len() > 0 { (self.log)(format!("{} is already occupied.", pos)); false - } else if state.remaining_pieces(&player, &stone) == 0 { + } else if state.remaining_pieces(*player, *stone) == 0 { (self.log)(format!( "{} has no more remaining {} pieces.", player, stone @@ -320,7 +366,7 @@ impl<L: Fn(String)> Game<L> { drops: &Vec<u8>, ) -> bool { if let Some(state) = self.states.last() { - if state.within_bounds(pos) { + if self.within_bounds(pos) { /* Rules for moving a stack: 0. There are stones 1. Top stone belongs to player @@ -335,7 +381,9 @@ impl<L: Fn(String)> Game<L> { (self.log)(format!("{} has no stones to move.", pos)); return false; } else if drops.len() == 0 { - (self.log)(format!("A drop sequence for must be specified for a move.")); + (self.log)(String::from( + "A drop sequence for must be specified for a move.", + )); return false; } else if stack[0].player != *player { (self.log)(format!( @@ -370,7 +418,7 @@ impl<L: Fn(String)> Game<L> { Direction::Right => pos_new.x == 0, } } { - (self.log)(format!("A move may not extend past the board.")); + (self.log)(String::from("A move may not extend past the board.")); return false; } else { match direction { @@ -383,13 +431,15 @@ impl<L: Fn(String)> Game<L> { if stack.len() > 0 { match stack[0].stone { Stone::Capstone => { - (self.log)(format!("A move may not cover a capstone.")); + (self.log)(String::from( + "A move may not cover a capstone.", + )); return false; } Stone::Standing => { if (steps > 1) || (!cap) { - (self.log)(format!( - "A move may not cover a standing stone." + (self.log)(String::from( + "A move may not cover a standing stone.", )); return false; } @@ -419,8 +469,8 @@ impl<L: Fn(String)> Game<L> { false } } else { - (self.log)(format!( - "Internal error: cannot check action with no game state." + (self.log)(String::from( + "Internal error: cannot check action with no game state.", )); false } @@ -428,21 +478,75 @@ impl<L: Fn(String)> Game<L> { pub fn is_legal_action(&self, act: &Action) -> bool { match act { - Action::Place(player, pos, stone) => self.is_legal_place(player, pos, stone), - Action::Move(player, pos, direction, drops) => { - self.is_legal_move(player, pos, direction, drops) - } + Action::Place(player, pos, stone) => match self.turn_order { + TurnOrder::BlackPlacesWhite => { + if (self.current_player == Player::Black) + && (*player == Player::White) + && (*stone == Stone::Flat) + { + self.is_legal_place(player, pos, stone) + } else { + (self.log)(String::from( + "At the start of the game, B must place a W flat.", + )); + false + } + } + TurnOrder::WhitePlacesBlack => { + if (self.current_player == Player::White) + && (*player == Player::Black) + && (*stone == Stone::Flat) + { + self.is_legal_place(player, pos, stone) + } else { + (self.log)(String::from( + "At the start of the game, W must place a B flat.", + )); + false + } + } + TurnOrder::Normal => self.is_legal_place(player, pos, stone), + }, + Action::Move(player, pos, direction, drops) => match self.turn_order { + TurnOrder::Normal => { + if *player == self.current_player { + self.is_legal_move(player, pos, direction, drops) + } else { + (self.log)(format!( + "{} may not take actions on {}'s turn.", + player, self.current_player + )); + false + } + } + _ => { + (self.log)(String::from( + "At the start of the game only placing flats is allowed.", + )); + false + } + }, } } - pub fn perform_action(&mut self, act: &Action) -> bool { - if self.is_legal_action(act) { + pub fn perform_action(&mut self, act: Action) -> bool { + if self.is_legal_action(&act) { let state = &self.states[self.states.len() - 1]; - let new_state = match act { + let new_state = match &act { Action::Place(player, pos, stone) => state.place_stone(*player, pos, *stone), Action::Move(_, pos, direction, drops) => state.move_stack(pos, *direction, drops), }; self.states.push(new_state); + self.actions.push(act); + self.current_player = match self.current_player { + Player::Black => Player::White, + Player::White => Player::Black, + }; + self.turn_order = match self.turn_order { + TurnOrder::BlackPlacesWhite => TurnOrder::WhitePlacesBlack, + TurnOrder::WhitePlacesBlack => TurnOrder::Normal, + TurnOrder::Normal => TurnOrder::Normal, + }; true } else { false @@ -455,7 +559,10 @@ impl<L: Fn(String)> fmt::Display for Game<L> { write!( f, "[Date \"\"]\n[Player1 \"{}\"]\n[Player2 \"{}\"]\n[Size \"{}\"]\n{}", - self.white_player_name, self.black_player_name, self.size, ptn + self.white_player_name, + self.black_player_name, + self.size, + self.query_action_lines() ) } } diff --git a/src/main.rs b/src/main.rs index d664025..9ccefde 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,11 +4,40 @@ use pancurses::Input::*; use pancurses::*; mod game; - use crate::game::*; -struct BoardGui { - win: Window, +pub struct GameStateGUI { + action_window: Window, + pieces_window: Window, +} + +impl GameStateGUI { + pub fn new(ypos: i32, xpos: i32, height: i32, width: i32) -> GameStateGUI { + GameStateGUI { + pieces_window: newwin(2, width, ypos, xpos), + action_window: newwin(height, width, ypos + 3, xpos), + } + } + + pub fn update<L: Fn(String)>(&self, game: &Game<L>) { + self.pieces_window.clear(); + self.pieces_window.addstr(format!( + "W: {:2} B: {:2}\nWC: {} BC: {}", + game.query_pieces(Player::White, Stone::Flat), + game.query_pieces(Player::Black, Stone::Flat), + game.query_pieces(Player::White, Stone::Capstone), + game.query_pieces(Player::Black, Stone::Capstone), + )); + self.pieces_window.refresh(); + + self.action_window.clear(); + self.action_window.addstr(game.query_action_lines()); + self.action_window.refresh(); + } +} + +struct BoardGUI { + board_window: Window, cell_windows: Vec<Window>, size: u8, cell_height: u8, @@ -26,7 +55,7 @@ enum BGAction { None, } -impl BoardGui { +impl BoardGUI { fn draw_grid(&self) { let s: i32 = self.size as i32; let h: i32 = self.cell_height as i32; @@ -35,26 +64,27 @@ impl BoardGui { for x in 0..w * s + 1 { if y % h == 0 { if x % w == 0 { - self.win.mvaddch(y, x + self.hoff, '+'); + self.board_window.mvaddch(y, x + self.hoff, '+'); } else { - self.win.mvaddch(y, x + self.hoff, '-'); + self.board_window.mvaddch(y, x + self.hoff, '-'); } } else if x % w == 0 { - self.win.mvaddch(y, x + self.hoff, '|'); + self.board_window.mvaddch(y, x + self.hoff, '|'); } } } for y in 0..s { - self.win.mvaddstr(y * h + h / 2, 0, format!("{}.", s - y)); + self.board_window + .mvaddstr(y * h + h / 2, 0, format!("{}.", s - y)); } for x in 0..s { - self.win.mvaddstr( + self.board_window.mvaddstr( s * h + 1, w * x + w / 2 + self.hoff, format!("{:x}.", x + 10), ); } - self.win.refresh(); + self.board_window.refresh(); } fn pos_to_idx(&self, pos: &Position) -> usize { @@ -107,16 +137,15 @@ impl BoardGui { } } - // Is this really the best way to do this? - fn draw_game_state(&self, game_state: &GameState) { - // ???? - assert!(game_state.get_size() == self.size); + fn update<L: Fn(String)>(&self, game: &Game<L>) { + // Is this really the `best' way? + assert!(game.get_size() == self.size); let mut pos = Position { x: 0, y: 0 }; for y in 0..self.size { pos.y = y; for x in 0..self.size { pos.x = x; - if let Some(stack) = game_state.query_square(&pos) { + if let Some(stack) = game.query_square(&pos) { self.draw_stack(&pos, stack); } } @@ -155,8 +184,8 @@ impl BoardGui { } Character('q') => BGAction::Quit, x => { - self.win.addstr(format!("{:?}", x)); - self.win.refresh(); + self.board_window.addstr(format!("{:?}", x)); + self.board_window.refresh(); BGAction::None } } @@ -172,7 +201,7 @@ impl BoardGui { has_colours: bool, white_colour: u8, black_colour: u8, - ) -> BoardGui { + ) -> BoardGUI { let mut cell_windows: Vec<Window> = Vec::new(); let hoff = hoff as i32; @@ -185,8 +214,8 @@ impl BoardGui { cell_windows.push(newwin(h - 1, w - 1, (s - y - 1) * h + 1, hoff + x * w + 1)); } - let bg = BoardGui { - win: newwin(h * s + 3, w * s + 3 + hoff, ypos, xpos), + let bg = BoardGUI { + board_window: newwin(h * s + 3, w * s + 3 + hoff, ypos, xpos), cell_windows: cell_windows, size: size, cell_height: cell_height, @@ -210,7 +239,7 @@ impl BoardGui { // } bg.mv_to_cell(&bg.cur_cell, 0); - bg.win.refresh(); + bg.board_window.refresh(); bg } } @@ -226,9 +255,9 @@ impl MoveEntry { self.win.refresh(); } - fn read_action(&self) -> &str { - "a1" - } + // fn read_action(&self) -> &str { + // "a1" + // } } struct MessageLog { @@ -272,26 +301,31 @@ fn main() { screen.refresh(); + let mut bg = BoardGUI::new(0, 0, 5, 4, 8, 3, has_colours, white_colour, black_colour); + let gs = GameStateGUI::new(0, 5 * 8 + 3 + 3, 100, 20); + let mew = newwin(1, 30, 25, 0); let mlw = newwin(3, 70, 26, 0); - - let mut bg = BoardGui::new(0, 0, 5, 4, 8, 3, has_colours, white_colour, black_colour); - let me = MoveEntry { win: mew }; - let ml = MessageLog { win: mlw, has_colours: has_colours, red_colour: red_colour, }; - me.init(); ml.init(); let log = |s| MessageLog::log_error(&ml, s); - let game_state = GameState::new(5); + let mut game = Game::new(5, "tslil", "taktician", log); + let pos = Position { x: 0, y: 0 }; let action = Action::Place(Player::White, pos, Stone::Flat); + + game.perform_action(action); + + bg.update(&game); + gs.update(&game); + // game_state.perform_action(&action); // game_state.is_legal_action(&Action::Move(Player::White, pos, Direction::Up, vec![0, 1])); // bg.draw_game_state(&game_state); |
