diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/game.rs | 165 | ||||
| -rw-r--r-- | src/gui.rs | 116 | ||||
| -rw-r--r-- | src/main.rs | 49 | ||||
| -rw-r--r-- | src/parser.rs | 8 |
4 files changed, 250 insertions, 88 deletions
diff --git a/src/game.rs b/src/game.rs index 1d61732..2922121 100644 --- a/src/game.rs +++ b/src/game.rs @@ -228,7 +228,7 @@ impl GameState { player: Player, pos: &Position, stone: Stone, - ) -> Result<GameState, String> { + ) -> Result<(Vec<Position>, GameState), String> { if let Err(e) = self.is_legal_place(player, pos, stone) { return Err(e); } @@ -256,7 +256,7 @@ impl GameState { } } } - Ok(copy) + Ok((vec![pos.clone()], copy)) } fn is_legal_move( @@ -268,37 +268,70 @@ impl GameState { ) -> 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 + - There are stones + - Drops have been specified + - Must actually move at least one stone + - Top stone belongs to player + - Zero or more dropped on starting square + - One or more on each subsequent square + - Total number of stones moved does not exceed the carry capacity + - Moved the entire stack (up to the carry capacity) + - Direction does not contain a capstone + - Wall may only appear on last spot if it's capstone alone that covers + - 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 { + let stack_len = stack.len(); + let drops_len = drops.len(); + + if stack_len == 0 { return Err(format!("{} has no stones to move.", pos)); - } else if drops.len() == 0 { + }; + + 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 + }; + + if drops_len == 1 && drops[0] == 1 { + return Err(String::from( + "A valid move must change the position of at least a single stone.", )); - } else if drops[0] > 1 { + } + + if stack[stack_len - 1].player != player { return Err(format!( - "A move may only drop 0 or 1 stones at it's origin square." + "{} may not move the stack at {} as it belongs to {}.", + player, + pos, + stack[stack_len - 1].player )); - // Do the sum in u32 just in case ? - } else if drops.iter().map(|&d| d as u32).sum::<u32>() > self.size as u32 { + }; + + for i in 0..drops_len { + if i > 0 && drops[i] == 0 { + return Err(String::from( + "A move may not drop 0 stones past the first square.", + )); + } + } + + let num_dropped = drops.iter().map(|&d| d as usize).sum::<usize>(); + let carry_capacity = self.size as usize; + if num_dropped > carry_capacity { return Err(format!( - "A move may not exceed the carry limit of {} stones.", + "A move may not exceed the carry capacity of {} stones.", self.size )); } + + if num_dropped != std::cmp::max(carry_capacity, stack_len) { + return Err(String::from( + "A move must effect the whole stack, up to the carry limit.", + )); + } + 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 }; @@ -309,8 +342,8 @@ impl GameState { 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::Left => pos_new.x == 0, + Direction::Right => pos_new.x + 1 >= self.size, } } { return Err(String::from("A move may not extend past the board.")); @@ -318,8 +351,8 @@ impl GameState { 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, + 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 { @@ -365,30 +398,53 @@ impl GameState { pos: &Position, direction: Direction, drops: &Vec<u8>, - ) -> Result<GameState, String> { + ) -> Result<(Vec<Position>, 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 mut pos_vec: Vec<Position> = vec![pos.clone()]; + + let idx = copy.pos_to_idx(&pos); let stack: &Stack = &self.board[self.pos_to_idx(pos)]; - let mut k = 0; - for &d in drops { - let idx = copy.pos_to_idx(&pos_new); - for i in 0..d { - copy.board[idx].push(stack[(k + i) as usize]); + copy.board[idx].clear(); + + let num_drops = drops.len(); + let mut offset = 0; + for drop_idx in 0..num_drops { + let pos = pos_vec[pos_vec.len() - 1]; + let idx = copy.pos_to_idx(&pos); + let num = drops[drop_idx]; + + for i in 0..num { + copy.board[idx].push(stack[(offset + i) as usize]); + } + offset += num; + + if drop_idx + 1 < num_drops { + pos_vec.push(match direction { + Direction::Up => Position { + x: pos.x, + y: pos.y + 1, + }, + Direction::Down => Position { + x: pos.x, + y: pos.y - 1, + }, + Direction::Left => Position { + x: pos.x - 1, + y: pos.y, + }, + Direction::Right => Position { + x: pos.x + 1, + y: pos.y, + }, + }); } - k += d; - 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, - }; } - Ok(copy) + Ok((pos_vec, copy)) } } @@ -449,6 +505,14 @@ impl Game { self.current_player } + pub fn query_stone_owner(&self) -> Player { + match self.turn_order { + TurnOrder::BlackPlacesWhite => Player::White, + TurnOrder::WhitePlacesBlack => Player::Black, + TurnOrder::Normal => self.current_player, + } + } + // pub fn query_action(&self, turn: u16) -> Option<&Action> { // self.actions.get(turn as usize) // } @@ -459,15 +523,15 @@ impl Game { pub fn query_action_lines(&self) -> String { let mut result = String::new(); - let mut newline = false; + let mut newline = true; result += "0. "; for i in 0..self.actions.len() { - if newline { + if i > 1 && newline { // TODO: Is placing the opponent's first stone the zeroeth action? result += &format!("{}. ", i); } result += &format!("{} ", self.actions[i]); - if !newline { + if i > 0 && !newline { result.push('\n'); } newline = !newline; @@ -475,7 +539,7 @@ impl Game { result } - pub fn perform_action(&mut self, act: Action) -> Result<(), String> { + pub fn perform_action(&mut self, act: Action) -> Result<Vec<Position>, String> { let maybe_state = self.states.last(); if maybe_state.is_none() { return Err(String::from("Internal error: cannot find last game state")); @@ -528,19 +592,24 @@ impl Game { match new_state_either { Err(e) => return Err(e), - Ok(new_state) => { + Ok((pos_vec, 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.current_player = match self.turn_order { + TurnOrder::BlackPlacesWhite => Player::White, + TurnOrder::WhitePlacesBlack => Player::White, + TurnOrder::Normal => 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(()); + return Ok(pos_vec); } } } @@ -8,27 +8,96 @@ use crate::game::*; pub struct GameLog { action_window: Window, pieces_window: Window, + has_colours: bool, + white_colour: u8, + black_colour: u8, } impl GameLog { - pub fn new(ypos: i32, xpos: i32, height: i32, width: i32) -> GameLog { + pub fn new( + ypos: i32, + xpos: i32, + height: i32, + width: i32, + has_colours: bool, + white_colour: u8, + black_colour: u8, + ) -> GameLog { GameLog { - pieces_window: newwin(2, width, ypos, xpos), - action_window: newwin(height, width, ypos + 3, xpos), + pieces_window: newwin(4, width, ypos, xpos), + action_window: newwin(height, width, ypos + 5, xpos), + has_colours: has_colours, + white_colour: white_colour, + black_colour: black_colour, } } pub fn update(&self, game: &Game) { - self.pieces_window.clear(); + // No need to clear, always stays the same size + self.pieces_window.mv(0, 0); + + if self.has_colours { + self.pieces_window.attrset(ColorPair(self.white_colour)); + } + self.pieces_window.addstr(format!( + "W: {:2}", + game.query_pieces(Player::White, Stone::Flat) + )); + + self.pieces_window.attrset(Attribute::Normal); + self.pieces_window.addstr(" "); + + if self.has_colours { + self.pieces_window.attrset(ColorPair(self.black_colour)); + } self.pieces_window.addstr(format!( - "W: {:2} B: {:2}\nWC: {} BC: {}", - game.query_pieces(Player::White, Stone::Flat), + "B: {:2}", game.query_pieces(Player::Black, Stone::Flat), - game.query_pieces(Player::White, Stone::Capstone), + )); + + self.pieces_window.attrset(Attribute::Normal); + self.pieces_window.mv(1, 0); + + if self.has_colours { + self.pieces_window.attrset(ColorPair(self.white_colour)); + } + self.pieces_window.addstr(format!( + "WC: {}", + game.query_pieces(Player::White, Stone::Capstone) + )); + + self.pieces_window.attrset(Attribute::Normal); + self.pieces_window.addstr(" "); + + if self.has_colours { + self.pieces_window.attrset(ColorPair(self.black_colour)); + } + self.pieces_window.addstr(format!( + "BC: {}", game.query_pieces(Player::Black, Stone::Capstone), )); + self.pieces_window.attrset(Attribute::Normal); + + self.pieces_window.mv(3, 0); + self.pieces_window.addstr("Player: "); + match game.query_current_player() { + Player::Black => { + if self.has_colours { + self.pieces_window.attrset(ColorPair(self.black_colour)); + } + self.pieces_window.addstr("Black"); + } + Player::White => { + if self.has_colours { + self.pieces_window.attrset(ColorPair(self.white_colour)); + } + self.pieces_window.addstr("White"); + } + } + self.pieces_window.refresh(); + // Perhaps it's best to avoid clearing things? Overwrite the specific characters instead? self.action_window.clear(); self.action_window.addstr(game.query_action_lines()); self.action_window.refresh(); @@ -124,7 +193,7 @@ impl BoardGUI { fn draw_stack(&self, pos: &Position, stack: &Stack) { if let Some(win) = self.cell_windows.get(self.pos_to_idx(pos)) { - win.mv(0, 0); + win.clear(); let l = stack.len() as i32; if l > 0 { for i in 0..l { @@ -136,19 +205,24 @@ impl BoardGUI { } } - pub fn update(&self, game: &Game) { + pub fn update(&self, game: &Game, squares: Vec<Position>) { // 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.query_square(&pos) { - self.draw_stack(&pos, stack); - } + // for x in 0..self.size { + // for y in 0..self.size { + // let pos = Position { x: x, y: y }; + // if let Some(stack) = game.query_square(&pos) { + // self.draw_stack(&pos, stack); + // } + // } + // } + for p in squares { + if let Some(stack) = game.query_square(&p) { + self.draw_stack(&p, stack); } } + // Prevent redraw of the grid itself? + self.board_window.touch(); } pub fn read_action(&mut self) -> BGAction { @@ -201,12 +275,14 @@ impl BoardGUI { white_colour: u8, black_colour: u8, ) -> BoardGUI { - let mut cell_windows: Vec<Window> = Vec::new(); - let hoff = hoff as i32; let s: i32 = size as i32; let h: i32 = cell_height as i32; let w: i32 = cell_width as i32; + + let board_window = newwin(h * s + 3, w * s + 3 + hoff, ypos, xpos); + + let mut cell_windows: Vec<Window> = Vec::new(); for i in 0..s * s { let y: i32 = i / s; let x: i32 = i % s; @@ -214,7 +290,7 @@ impl BoardGUI { } let bg = BoardGUI { - board_window: newwin(h * s + 3, w * s + 3 + hoff, ypos, xpos), + board_window: board_window, cell_windows: cell_windows, size: size, cell_height: cell_height, diff --git a/src/main.rs b/src/main.rs index 2100073..6bbe52d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,7 +36,7 @@ fn main() { 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( + let board_gui = BoardGUI::new( 0, 0, size, @@ -48,7 +48,15 @@ fn main() { black_colour, ); - let game_log = GameLog::new(0, board_right, height, 20); + 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); @@ -64,30 +72,37 @@ fn main() { 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); - - if let Err(e) = game.perform_action(action) { - message_log.log_error(e); - } - - board_gui.update(&game); game_log.update(&game); loop { let inp = action_entry.read_action(); - match parse_action(game.query_current_player(), &inp) { - Ok(act) => message_log.log_error(format!("{}", act)), + let stone_owner = game.query_stone_owner(); + let player = game.query_current_player(); + match parse_action(stone_owner, &inp) { Err(err) => message_log.log_error(format!("Input \"{}\": {}", inp, err)), + Ok(act) => { + 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) => message_log.log_error(e), + Ok(pos_vec) => { + board_gui.update(&game, pos_vec); + game_log.update(&game); + } + } + } } 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(); } diff --git a/src/parser.rs b/src/parser.rs index 346ecb2..a1d9a32 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -116,9 +116,11 @@ fn parse_place(player: Player, pl: &str) -> Result<Action, String> { } if (x < 'a') || (x > 'h') { - return Err(String::from( - "Placements must specify a valid position on the game board.", - )); + return Err(if (x >= 'A') && (x <= 'Z') { + format!("Unrecognised stone type '{}' in placement.", x) + } else { + String::from("Placements must specify a valid position on the game board.") + }); } let x: u8 = x as u8 - 'a' as u8; |
