diff options
| author | tslil clingman <> | 2020-01-20 12:26:30 -0800 |
|---|---|---|
| committer | tslil clingman <> | 2020-01-20 12:26:30 -0800 |
| commit | 42e504281ba8c36c0bf5c679dd767f04310655f3 (patch) | |
| tree | 3433dc52915741f67ff46e687c96646bf5af2724 /src | |
| parent | 8110985e6636e511f0a2e85a6f30aa8ff109320d (diff) | |
bool + log ad hoc --> Result<_,_> & refactoring
Diffstat (limited to 'src')
| -rw-r--r-- | src/game.rs | 465 | ||||
| -rw-r--r-- | src/gui.rs | 43 | ||||
| -rw-r--r-- | src/main.rs | 62 | ||||
| -rw-r--r-- | src/parser.rs | 90 |
4 files changed, 352 insertions, 308 deletions
diff --git a/src/game.rs b/src/game.rs index 057a307..1d61732 100644 --- a/src/game.rs +++ b/src/game.rs @@ -177,6 +177,14 @@ impl GameState { } } + fn within_bounds(&self, pos: &Position) -> bool { + if (pos.x < self.size) && (pos.y < self.size) { + true + } else { + false + } + } + fn pos_to_idx(&self, pos: &Position) -> usize { let y: usize = pos.y as usize; let x: usize = pos.x as usize; @@ -187,8 +195,43 @@ impl GameState { 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 { + fn is_legal_place(&self, player: Player, pos: &Position, stone: Stone) -> Result<(), String> { + /* In order to legally place a piece: + 1. The desired square must be empty + 2. The player must have sufficient pieces + */ + if self.within_bounds(pos) { + if let Some(stack) = &self.board.get(self.pos_to_idx(pos)) { + if stack.len() > 0 { + return Err(format!("{} is already occupied.", pos)); + } else if self.remaining_pieces(player, stone) == 0 { + return Err(format!( + "{} has no more remaining {} pieces.", + player, stone + )); + } else { + return Ok(()); + } + } else { + return Err(format!( + "Internal error: lookup for {} failed in is_legal_place.", + pos + )); + } + } else { + return Err(format!("Position {} is not within bounds.", pos)); + } + } + + fn place_stone( + &self, + player: Player, + pos: &Position, + stone: Stone, + ) -> Result<GameState, String> { + if let Err(e) = self.is_legal_place(player, pos, stone) { + return Err(e); + } // Place stone let mut copy = self.copy(); let idx = self.pos_to_idx(&pos); @@ -213,10 +256,120 @@ impl GameState { } } } - copy + Ok(copy) } - fn move_stack(&self, pos: &Position, direction: Direction, drops: &Vec<u8>) -> GameState { + fn is_legal_move( + &self, + player: Player, + pos: &Position, + direction: Direction, + drops: &Vec<u8>, + ) -> Result<(), String> { + if self.within_bounds(pos) { + /* Rules for moving a stack: + 0. There are stones + 1. Top stone belongs to player + 2. Zero or One stones dropped on starting square + 3. Total number of stones moved does not exceed the carry capacity + 4. Direction does not contain a capstone + 5. Wall may only appear on last spot if it's capstone alone that covers + 6. All stones are used up before then end of the board is met + */ + if let Some(stack) = &self.board.get(self.pos_to_idx(pos)) { + if stack.len() == 0 { + return Err(format!("{} has no stones to move.", pos)); + } else if drops.len() == 0 { + return Err(String::from( + "A drop sequence for must be specified for a move.", + )); + } else if stack[0].player != player { + return Err(format!( + "{} may not move the stack at {} as it belongs to {}.", + player, pos, stack[0].player + )); + } else if drops[0] > 1 { + return Err(format!( + "A move may only drop 0 or 1 stones at it's origin square." + )); + // Do the sum in u32 just in case ? + } else if drops.iter().map(|&d| d as u32).sum::<u32>() > self.size as u32 { + return Err(format!( + "A move may not exceed the carry limit of {} stones.", + self.size + )); + } + let mut steps: usize = drops.len() - 1; + let cap: bool = stack[0].stone == Stone::Capstone; + let mut pos_new = Position { x: pos.x, y: pos.y }; + + while steps > 0 { + steps -= 1; + if { + 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, + } + } { + return Err(String::from("A move may not extend past the board.")); + } else { + match direction { + Direction::Up => pos_new.y += 1, + Direction::Down => pos_new.y -= 1, + Direction::Left => pos_new.x += 1, + Direction::Right => pos_new.x -= 1, + } + if let Some(stack) = &self.board.get(self.pos_to_idx(&pos_new)) { + if stack.len() > 0 { + match stack[0].stone { + Stone::Capstone => { + return Err(String::from( + "A move may not cover a capstone.", + )); + } + Stone::Standing => { + if (steps > 1) || (!cap) { + return Err(String::from( + "A move may not cover a standing stone.", + )); + } + } + Stone::Flat => (), + } + } + } else { + return Err(format!( + "Internal error: lookup for {} failed in is_legal_move (1).", + pos_new + )); + } + } + } + return Ok(()); + } else { + return Err(format!( + "Internal error: lookup for {} failed in is_legal_move (2).", + pos + )); + } + } else { + return Err(format!("Position {} is not within bounds.", pos)); + } + } + + fn move_stack( + &self, + player: Player, + pos: &Position, + direction: Direction, + drops: &Vec<u8>, + ) -> Result<GameState, String> { + if let Err(e) = self.is_legal_move(player, pos, direction, drops) { + return Err(e); + } + let mut copy = self.copy(); let mut pos_new = Position { x: pos.x, y: pos.y }; let stack: &Stack = &self.board[self.pos_to_idx(pos)]; @@ -235,7 +388,7 @@ impl GameState { Direction::Right => pos_new.x += 1, }; } - copy + Ok(copy) } } @@ -245,12 +398,7 @@ enum TurnOrder { Normal, } -// TODO: Redo this logging story with Result<...,String> instead - -pub struct Game<L> -where - L: Fn(String), -{ +pub struct Game { size: u8, current_player: Player, turn_order: TurnOrder, @@ -258,38 +406,29 @@ where black_player_name: String, actions: Vec<Action>, states: Vec<GameState>, - log: L, } -impl<L: Fn(String)> 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.", - size, size - )); - 5 +impl Game { + pub fn new(size: u8, white_player_name: &str, black_player_name: &str) -> (Game, String) { + let (size, warning) = if (size <= 3) || (size >= 8) { + (5, format!("Warning: the requested game size of {}x{} is not supported, defaulting to 5x5.", + size, size)) } else { - size + (size, String::new()) }; - Game { - size: size, - white_player_name: white_player_name.to_string(), - black_player_name: black_player_name.to_string(), - 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 - } + ( + Game { + size: size, + white_player_name: white_player_name.to_string(), + black_player_name: black_player_name.to_string(), + states: vec![GameState::new(size)], + actions: Vec::new(), + current_player: Player::Black, + turn_order: TurnOrder::BlackPlacesWhite, + }, + warning, + ) } // Relying on only ::new(...) being used to make instances @@ -299,11 +438,7 @@ impl<L: Fn(String)> Game<L> { } pub fn query_square(&self, pos: &Position) -> Option<&Stack> { - if self.within_bounds(pos) { - self.last_state().query_pos(pos) - } else { - None - } + self.last_state().query_pos(pos) } pub fn query_pieces(&self, player: Player, stone: Stone) -> u8 { @@ -314,9 +449,9 @@ impl<L: Fn(String)> Game<L> { self.current_player } - pub fn query_action(&self, turn: u16) -> Option<&Action> { - self.actions.get(turn as usize) - } + // pub fn query_action(&self, turn: u16) -> Option<&Action> { + // self.actions.get(turn as usize) + // } pub fn get_size(&self) -> u8 { self.size @@ -340,177 +475,24 @@ impl<L: Fn(String)> Game<L> { 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 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 { - (self.log)(format!( - "{} has no more remaining {} pieces.", - player, stone - )); - false - } else { - true - } - } else { - (self.log)(format!( - "Internal error: lookup for {} failed in is_legal_place.", - pos - )); - false - } - } else { - (self.log)(format!("Position {} is not within bounds.", pos)); - false - } - } else { - (self.log)(format!( - "Internal error: cannot check action with no game state." - )); - false + pub fn perform_action(&mut self, act: Action) -> Result<(), String> { + let maybe_state = self.states.last(); + if maybe_state.is_none() { + return Err(String::from("Internal error: cannot find last game state")); } - } - - fn is_legal_move( - &self, - player: &Player, - pos: &Position, - direction: &Direction, - drops: &Vec<u8>, - ) -> bool { - if let Some(state) = self.states.last() { - if self.within_bounds(pos) { - /* Rules for moving a stack: - 0. There are stones - 1. Top stone belongs to player - 2. Zero or One stones dropped on starting square - 3. Total number of stones moved does not exceed the carry capacity - 4. Direction does not contain a capstone - 5. Wall may only appear on last spot if it's capstone alone that covers - 6. All stones are used up before then end of the board is met - */ - if let Some(stack) = &state.board.get(state.pos_to_idx(pos)) { - if stack.len() == 0 { - (self.log)(format!("{} has no stones to move.", pos)); - return false; - } else if drops.len() == 0 { - (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!( - "{} may not move the stack at {} as it belongs to {}.", - player, pos, stack[0].player - )); - return false; - } else if drops[0] > 1 { - (self.log)(format!( - "A move may only drop 0 or 1 stones at it's origin square." - )); - return false; - // Do the sum in u32 just in case ? - } else if drops.iter().map(|&d| d as u32).sum::<u32>() > state.size as u32 { - (self.log)(format!( - "A move may not exceed the carry limit of {} stones.", - state.size - )); - return false; - } - let mut steps: usize = drops.len() - 1; - let cap: bool = stack[0].stone == Stone::Capstone; - let mut pos_new = Position { x: pos.x, y: pos.y }; - - while steps > 0 { - steps -= 1; - if { - match direction { - Direction::Up => pos_new.y + 1 >= state.size, - Direction::Down => pos_new.y == 0, - Direction::Left => pos_new.x + 1 >= state.size, - Direction::Right => pos_new.x == 0, - } - } { - (self.log)(String::from("A move may not extend past the board.")); - return false; - } else { - match direction { - Direction::Up => pos_new.y += 1, - Direction::Down => pos_new.y -= 1, - Direction::Left => pos_new.x += 1, - Direction::Right => pos_new.x -= 1, - } - if let Some(stack) = &state.board.get(state.pos_to_idx(&pos_new)) { - if stack.len() > 0 { - match stack[0].stone { - Stone::Capstone => { - (self.log)(String::from( - "A move may not cover a capstone.", - )); - return false; - } - Stone::Standing => { - if (steps > 1) || (!cap) { - (self.log)(String::from( - "A move may not cover a standing stone.", - )); - return false; - } - } - Stone::Flat => (), - } - } - } else { - (self.log)(format!( - "Internal error: lookup for {} failed in is_legal_move (1).", - pos_new - )); - return false; - } - } - } - return true; - } else { - (self.log)(format!( - "Internal error: lookup for {} failed in is_legal_move (2).", - pos - )); - return false; - } - } else { - (self.log)(format!("Position {} is not within bounds.", pos)); - false - } - } else { - (self.log)(String::from( - "Internal error: cannot check action with no game state.", - )); - false - } - } - - pub fn is_legal_action(&self, act: &Action) -> bool { - match act { + let state = maybe_state.unwrap(); + let new_state_either = match &act { 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) + state.place_stone(*player, pos, *stone) } else { - (self.log)(String::from( + Err(String::from( "At the start of the game, B must place a W flat.", - )); - false + )) } } TurnOrder::WhitePlacesBlack => { @@ -518,64 +500,53 @@ impl<L: Fn(String)> Game<L> { && (*player == Player::Black) && (*stone == Stone::Flat) { - self.is_legal_place(player, pos, stone) + state.place_stone(*player, pos, *stone) } else { - (self.log)(String::from( + Err(String::from( "At the start of the game, W must place a B flat.", - )); - false + )) } } - TurnOrder::Normal => self.is_legal_place(player, pos, stone), + TurnOrder::Normal => state.place_stone(*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) + state.move_stack(*player, pos, *direction, drops) } else { - (self.log)(format!( + Err(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 - } + _ => Err(String::from( + "At the start of the game only placing flats is allowed.", + )), }, - } - } + }; - 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 { - 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 + match new_state_either { + Err(e) => return Err(e), + Ok(new_state) => { + 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, + }; + return Ok(()); + } } } } -impl<L: Fn(String)> fmt::Display for Game<L> { +impl fmt::Display for Game { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, @@ -18,7 +18,7 @@ impl GameLog { } } - pub fn update<L: Fn(String)>(&self, game: &Game<L>) { + pub fn update(&self, game: &Game) { self.pieces_window.clear(); self.pieces_window.addstr(format!( "W: {:2} B: {:2}\nWC: {} BC: {}", @@ -136,7 +136,7 @@ impl BoardGUI { } } - pub fn update<L: Fn(String)>(&self, game: &Game<L>) { + pub fn update(&self, game: &Game) { // Is this really the `best' way? assert!(game.get_size() == self.size); let mut pos = Position { x: 0, y: 0 }; @@ -253,8 +253,18 @@ pub struct ActionEntry { } impl ActionEntry { + pub fn clear_entry_area(&self) { + self.window.mv(0, self.xstart); + for _i in 0..self.max_len { + self.window.addch('_'); + } + + self.window.mv(0, self.xstart); + self.window.refresh(); + } + pub fn new(ypos: i32, xpos: i32, max_len: usize) -> ActionEntry { - let txt = "Action entry: "; + let txt = "Enter action: "; let xstart = txt.len() as i32; let width = max_len as i32 + xstart + 1; @@ -268,13 +278,7 @@ impl ActionEntry { ae.window.keypad(true); ae.window.addstr(txt); - - for _i in 0..max_len { - ae.window.addch('_'); - } - - ae.window.mv(0, xstart); - ae.window.refresh(); + ae.clear_entry_area(); ae } @@ -313,6 +317,8 @@ impl ActionEntry { pub struct MessageLog { window: Window, + has_colours: bool, + red_colour: u8, } impl MessageLog { @@ -326,15 +332,26 @@ impl MessageLog { ) -> MessageLog { let ml = MessageLog { window: newwin(height, width, ypos, xpos), + has_colours: has_colours, + red_colour: red_colour, }; - if has_colours { - ml.window.attrset(ColorPair(red_colour)); - } ml } + pub fn log_message(&self, str: String) { + self.window.clear(); + if self.has_colours { + self.window.attrset(Attribute::Normal); + } + self.window.addstr(str); + self.window.refresh(); + } + pub fn log_error(&self, str: String) { self.window.clear(); + if self.has_colours { + self.window.attrset(ColorPair(self.red_colour)); + } self.window.addstr(str); self.window.refresh(); } diff --git a/src/main.rs b/src/main.rs index c7281cf..2100073 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,9 @@ fn main() { 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); @@ -25,31 +28,66 @@ fn main() { screen.refresh(); - let mut board_gui = BoardGUI::new(0, 0, 5, 4, 8, 3, has_colours, white_colour, black_colour); - let game_log = GameLog::new(0, 5 * 8 + 3 + 3, 100, 20); - let message_log = MessageLog::new(26, 0, 3, 70, has_colours, red_colour); - let action_entry = ActionEntry::new(25, 0, 10); + let size = 5; + 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 mut 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); - let log = |s| message_log.log_error(s); - let mut game = Game::new(5, "tslil", "taktician", log); + // 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, + ); + + let (mut game, warning) = Game::new(5, "tslil", "taktician"); + message_log.log_error(warning); let pos = Position { x: 0, y: 0 }; let action = Action::Place(Player::White, pos, Stone::Flat); - game.perform_action(action); + if let Err(e) = game.perform_action(action) { + message_log.log_error(e); + } board_gui.update(&game); game_log.update(&game); - match parse_action(game.query_current_player(), &action_entry.read_action()) { - Ok(act) => log(format!("{}", act)), - Err(err) => log(err), + loop { + let inp = action_entry.read_action(); + match parse_action(game.query_current_player(), &inp) { + Ok(act) => message_log.log_error(format!("{}", act)), + Err(err) => message_log.log_error(format!("Input \"{}\": {}", inp, err)), + } + action_entry.clear_entry_area(); } // 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); - board_gui.read_action(); - endwin(); + // board_gui.read_action(); + // endwin(); } diff --git a/src/parser.rs b/src/parser.rs index 49ded9b..346ecb2 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,8 +1,3 @@ -// #[macro_use] -// extern crate nom; - -// use nom::*; - use crate::game::*; fn parse_move(player: Player, mv: &str) -> Result<Action, String> { @@ -29,7 +24,7 @@ fn parse_move(player: Player, mv: &str) -> Result<Action, String> { let x = c.unwrap(); if (x < 'a') || (x > 'h') { return Err(String::from( - "Moves must specify a position on the game board.", + "Moves must specify a valid position on the game board.", )); } let x: u8 = x as u8 - 'a' as u8; @@ -53,7 +48,7 @@ fn parse_move(player: Player, mv: &str) -> Result<Action, String> { } let c = c.unwrap(); - let mut dir = Direction::Up; + let dir; // This is so cool! Rust supports some very nice patterns! match c { '+' => dir = Direction::Up, '-' => dir = Direction::Down, @@ -91,7 +86,58 @@ fn parse_move(player: Player, mv: &str) -> Result<Action, String> { } fn parse_place(player: Player, pl: &str) -> Result<Action, String> { - Ok(Action::Place(player, Position { x: 0, y: 0 }, Stone::Flat)) + let mut chars = pl.chars(); + + let c = chars.next(); + if c.is_none() { + return Err(String::from("An action cannot be blank.")); + } + + let stone_c = c.unwrap(); + + let stone; + let x; + if (stone_c == 'F') || (stone_c == 'C') || (stone_c == 'S') { + match stone_c { + 'C' => stone = Stone::Capstone, + 'S' => stone = Stone::Standing, + _ => stone = Stone::Flat, + } + let c = chars.next(); + if c.is_none() { + return Err(String::from( + "Placements must specify a position on the game board.", + )); + } + x = c.unwrap(); + } else { + stone = Stone::Flat; + x = stone_c; + } + + if (x < 'a') || (x > 'h') { + return Err(String::from( + "Placements must specify a valid position on the game board.", + )); + } + let x: u8 = x as u8 - 'a' as u8; + + let c = chars.next(); + if c.is_none() { + return Err(String::from( + "Placements must specify a position on the game board.", + )); + } + + let y = c.unwrap(); + if (y < '1') || (y > '8') { + return Err(String::from( + "Placements must specify a valid position on the game board.", + )); + } + let y: u8 = (y.to_digit(10).unwrap() - 1) as u8; + + Ok(Action::Place(player, Position { x: x, y: y }, stone)) } pub fn parse_action(player: Player, act: &str) -> Result<Action, String> { @@ -104,31 +150,3 @@ pub fn parse_action(player: Player, act: &str) -> Result<Action, String> { parse_place(player, act) } } - -// fn parse_move(mv: &str) -> Result<Action, Err<&str>> { -// one_of!("abcdefgh"); -// Ok(Action::Place( -// Player::Black, -// Position { x: 0, y: 0 }, -// Stone::Flat, -// )) -// } - -// fn parse_place(pl: &str) -> Result<Action, Err<&str>> { -// Ok(Action::Place( -// Player::Black, -// Position { x: 0, y: 0 }, -// Stone::Flat, -// )) -// } - -// pub fn parse_action(act: &str) -> Result<Action, Err<&str>> { -// if act -// .chars() -// .any(|c| c == '+' || c == '-' || c == '>' || c == '<') -// { -// parse_move(act) -// } else { -// parse_place(act) -// } -// } |
