From cdbcc1d2faf198629f5d598bb9a7ba6ab15a922a Mon Sep 17 00:00:00 2001 From: tslil clingman <> Date: Sat, 18 Jan 2020 17:03:09 -0500 Subject: working on game tracking --- src/board.rs | 362 ---------------------------------------------- src/game.rs | 461 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 24 ++-- 3 files changed, 474 insertions(+), 373 deletions(-) delete mode 100644 src/board.rs create mode 100644 src/game.rs diff --git a/src/board.rs b/src/board.rs deleted file mode 100644 index 578842c..0000000 --- a/src/board.rs +++ /dev/null @@ -1,362 +0,0 @@ -use std::fmt; - -#[derive(Clone, Copy)] -pub struct Position { - pub x: u8, - pub y: u8, -} -impl fmt::Display for Position { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:x}{}", 10 + self.x, self.y + 1) - } -} - -#[derive(PartialEq)] -pub enum Player { - Black, - White, -} - -impl fmt::Display for Player { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "{}", - match self { - Player::Black => "B", - Player::White => "W", - } - ) - } -} - -#[derive(PartialEq)] -pub enum Stone { - Flat, - Standing, - Capstone, -} - -impl fmt::Display for Stone { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "{}", - match self { - Stone::Flat => "", - Stone::Standing => "S", - Stone::Capstone => "C", - } - ) - } -} - -// #[derive(Clone, Copy)] -pub enum Direction { - Up, - Down, - Left, - Right, -} - -impl fmt::Display for Direction { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "{}", - match self { - Direction::Up => "+", - Direction::Down => "-", - Direction::Left => "<", - Direction::Right => ">", - } - ) - } -} - -pub enum Action { - Place(Player, Position, Stone), - Move(Player, Position, Direction, Vec), -} - -impl fmt::Display for Action { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Action::Place(player, pos, stone) => write!(f, "{}{}{}", pos, player, stone), - Action::Move(_, pos, direction, drops) => write!( - f, - "{}{}{}", - pos, - direction, - drops.into_iter().map(|q| q.to_string()).collect::() - ), - } - } -} - -pub type Stack = Vec<(Player, Stone)>; - -pub struct Board -where - L: Fn(String), -{ - size: u8, - black_flats: u8, - white_flats: u8, - black_capstones: u8, - white_capstones: u8, - board: Vec, - log: L, -} - -// How to solve code duplication between is_legal_action and -// perform_action? - -// TODO: Generate all legal actions for a given player - -impl Board { - pub fn get_size(&self) -> u8 { - self.size - } - - // Defaults to 5x5 if requested things are out of range - pub fn new(size: u8, log: L) -> (Board, bool) { - let (flats, caps, correct) = match size { - 3 => (10, 0, true), - 4 => (15, 0, true), - 6 => (30, 1, true), - 7 => (40, 2, true), - 8 => (50, 2, true), - _ => (21, 1, false), - }; - ( - Board { - size: size, - black_flats: flats, - white_flats: flats, - black_capstones: caps, - white_capstones: caps, - board: { - let mut v: Vec = Vec::new(); - for _i in 0..size * size { - v.push(Vec::new()); - } - v - }, - log: log, - }, - correct, - ) - } - - pub fn remaining_pieces(&self, player: &Player, stone: &Stone) -> u8 { - match player { - Player::Black => { - if *stone == Stone::Capstone { - self.black_capstones - } else { - self.black_flats - } - } - Player::White => { - if *stone == Stone::Capstone { - self.white_capstones - } else { - self.white_flats - } - } - } - } - - 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 is_legal_place(&self, player: &Player, pos: &Position, stone: &Stone) -> bool { - /* 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 { - (self.log)(format!("{} is already occupied.", pos)); - false - } else if self.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 - } - } - - fn is_legal_move( - &self, - player: &Player, - pos: &Position, - direction: &Direction, - drops: &Vec, - ) -> bool { - 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 { - (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.")); - return false; - } else if stack[0].0 != *player { - (self.log)(format!( - "{} may not move the stack at {} as it belongs to {}.", - player, pos, stack[0].0 - )); - 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::() > self.size as u32 { - (self.log)(format!( - "A move may not exceed the carry limit of {} stones.", - self.size - )); - return false; - } - let mut steps: usize = drops.len() - 1; - let cap: bool = stack[0].1 == 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, - } - } { - (self.log)(format!("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) = &self.board.get(self.pos_to_idx(&pos_new)) { - if stack.len() > 0 { - match stack[0].1 { - Stone::Capstone => { - (self.log)(format!("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." - )); - 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 - } - } - - 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) - } - } - } - - // Unconditional and so could lead to invalid state, but private - fn place_stone(&mut self, player: Player, pos: Position, stone: Stone) { - let idx = self.pos_to_idx(&pos); - self.board[idx].push((player, stone)); - } - - fn move_stack(&mut self, player: Player, pos: Position, direction: Direction, drops: Vec) {} - - pub fn perform_action(&mut self, act: Action) -> bool { - if self.is_legal_action(&act) { - match act { - Action::Place(player, pos, stone) => self.place_stone(player, pos, stone), - Action::Move(player, pos, direction, drops) => { - self.move_stack(player, pos, direction, drops) - } - } - true - } else { - false - } - } -} diff --git a/src/game.rs b/src/game.rs new file mode 100644 index 0000000..6307843 --- /dev/null +++ b/src/game.rs @@ -0,0 +1,461 @@ +use std::fmt; + +#[derive(Clone, Copy)] +pub struct Position { + pub x: u8, + pub y: u8, +} +impl fmt::Display for Position { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:x}{}", 10 + self.x, self.y + 1) + } +} + +#[derive(PartialEq, Clone, Copy)] +pub enum Player { + Black, + White, +} + +impl fmt::Display for Player { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{}", + match self { + Player::Black => "B", + Player::White => "W", + } + ) + } +} + +#[derive(PartialEq, Clone, Copy)] +pub enum Stone { + Flat, + Standing, + Capstone, +} + +impl fmt::Display for Stone { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{}", + match self { + Stone::Flat => "", + Stone::Standing => "S", + Stone::Capstone => "C", + } + ) + } +} + +#[derive(Clone, Copy)] +pub enum Direction { + Up, + Down, + Left, + Right, +} + +impl fmt::Display for Direction { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{}", + match self { + Direction::Up => "+", + Direction::Down => "-", + Direction::Left => "<", + Direction::Right => ">", + } + ) + } +} + +pub enum Action { + Place(Player, Position, Stone), + Move(Player, Position, Direction, Vec), +} + +impl fmt::Display for Action { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Action::Place(player, pos, stone) => write!(f, "{}{}{}", pos, player, stone), + Action::Move(_, pos, direction, drops) => write!( + f, + "{}{}{}", + pos, + direction, + drops.into_iter().map(|q| q.to_string()).collect::() + ), + } + } +} + +#[derive(Clone, Copy)] +pub struct Piece { + pub player: Player, + pub stone: Stone, +} +pub type Stack = Vec; + +pub struct GameState { + size: u8, + black_flats: u8, + white_flats: u8, + black_capstones: u8, + white_capstones: u8, + board: Vec, +} + +// TODO: Generate all legal actions for a given player + +impl GameState { + pub fn get_size(&self) -> u8 { + self.size + } + + pub fn copy(&self) -> GameState { + let mut copy = GameState { + size: self.size, + black_flats: self.black_flats, + white_flats: self.white_flats, + black_capstones: self.black_capstones, + white_capstones: self.white_capstones, + board: Vec::new(), + }; + for i in 0..self.board.len() { + for j in 0..self.board[i].len() { + copy.board[i].push(self.board[i][j]); + } + } + copy + } + + // Defaults to 5x5 if requested things are out of range + pub fn new(size: u8) -> GameState { + let (flats, caps) = match size { + 3 => (10, 0), + 4 => (15, 0), + 6 => (30, 1), + 7 => (40, 2), + 8 => (50, 2), + _ => (21, 1), + }; + GameState { + size: size, + black_flats: flats, + white_flats: flats, + black_capstones: caps, + white_capstones: caps, + board: { + let mut v: Vec = Vec::new(); + for _i in 0..size * size { + v.push(Vec::new()); + } + v + }, + } + } + + fn remaining_pieces(&self, player: &Player, stone: &Stone) -> u8 { + match player { + Player::Black => { + if *stone == Stone::Capstone { + self.black_capstones + } else { + self.black_flats + } + } + Player::White => { + if *stone == Stone::Capstone { + self.white_capstones + } else { + self.white_flats + } + } + } + } + + 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) + } + + // 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(); + let idx = self.pos_to_idx(&pos); + copy.board[idx].push(Piece { + player: player, + stone: stone, + }); + copy + } + + fn move_stack(&self, pos: &Position, direction: Direction, drops: &Vec) -> GameState { + 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)]; + + 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]); + } + 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, + }; + } + copy + } +} + +pub struct Game +where + L: Fn(String), +{ + size: u8, + white_player_name: String, + black_player_name: String, + actions: Vec, + states: Vec, + log: L, +} + +impl Game { + fn new(size: u8, white_player_name: &str, black_player_name: &str, log: L) -> Game { + 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 + } else { + size + }; + Game { + 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(), + log: log, + } + } + + pub fn get_size(&self) -> u8 { + self.size + } + + 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 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 + } + } + + fn is_legal_move( + &self, + player: &Player, + pos: &Position, + direction: &Direction, + drops: &Vec, + ) -> bool { + if let Some(state) = self.states.last() { + if state.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)(format!("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::() > 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)(format!("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)(format!("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." + )); + 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)(format!( + "Internal error: cannot check action with no game state." + )); + false + } + } + + 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) + } + } + } + + 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); + true + } else { + false + } + } +} + +impl fmt::Display for Game { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "[Date \"\"]\n[Player1 \"{}\"]\n[Player2 \"{}\"]\n[Size \"{}\"]\n{}", + self.white_player_name, self.black_player_name, self.size, ptn + ) + } +} diff --git a/src/main.rs b/src/main.rs index 6d110e1..d664025 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,9 +3,9 @@ extern crate pancurses; use pancurses::Input::*; use pancurses::*; -mod board; +mod game; -use crate::board::*; +use crate::game::*; struct BoardGui { win: Window, @@ -99,8 +99,8 @@ impl BoardGui { let l = stack.len() as i32; if l > 0 { for i in 0..l { - let (player, stone) = &stack[i as usize]; - self.draw_stone(win, i == l - 1, player, stone); + let piece = &stack[i as usize]; + self.draw_stone(win, i == l - 1, &piece.player, &piece.stone); } } win.refresh(); @@ -108,15 +108,15 @@ impl BoardGui { } // Is this really the best way to do this? - fn draw_board(&self, board: &Board) { + fn draw_game_state(&self, game_state: &GameState) { // ???? - assert!(board.get_size() == self.size); + assert!(game_state.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) = board.query_square(&pos) { + if let Some(stack) = game_state.query_square(&pos) { self.draw_stack(&pos, stack); } } @@ -288,11 +288,13 @@ fn main() { me.init(); ml.init(); - let (mut board, _) = Board::new(5, |s| MessageLog::log_error(&ml, s)); + let log = |s| MessageLog::log_error(&ml, s); + let game_state = GameState::new(5); let pos = Position { x: 0, y: 0 }; - board.perform_action(Action::Place(Player::White, pos, Stone::Flat)); - board.is_legal_action(&Action::Move(Player::White, pos, Direction::Up, vec![0, 1])); - bg.draw_board(&board); + let action = Action::Place(Player::White, pos, Stone::Flat); + // 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); loop { if let Some(inp) = screen.getch() { -- cgit v1.3.1