aboutsummaryrefslogtreecommitdiff
path: root/src/game.rs
diff options
context:
space:
mode:
authortslil clingman <>2020-01-18 17:03:09 -0500
committertslil clingman <>2020-01-18 17:03:09 -0500
commitcdbcc1d2faf198629f5d598bb9a7ba6ab15a922a (patch)
tree9ba2960ba2444d879df42d574ba4b0390c0e8116 /src/game.rs
parentf6a94720061877903487ed98bfee9f19df264a9d (diff)
working on game tracking
Diffstat (limited to 'src/game.rs')
-rw-r--r--src/game.rs461
1 files changed, 461 insertions, 0 deletions
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<u8>),
+}
+
+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::<String>()
+ ),
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+pub struct Piece {
+ pub player: Player,
+ pub stone: Stone,
+}
+pub type Stack = Vec<Piece>;
+
+pub struct GameState {
+ size: u8,
+ black_flats: u8,
+ white_flats: u8,
+ black_capstones: u8,
+ white_capstones: u8,
+ board: Vec<Stack>,
+}
+
+// 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<Stack> = 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<u8>) -> 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<L>
+where
+ L: Fn(String),
+{
+ size: u8,
+ white_player_name: String,
+ black_player_name: String,
+ actions: Vec<Action>,
+ states: Vec<GameState>,
+ log: L,
+}
+
+impl<L: Fn(String)> Game<L> {
+ 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
+ } 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<u8>,
+ ) -> 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::<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)(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<L: Fn(String)> fmt::Display for Game<L> {
+ 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
+ )
+ }
+}