aboutsummaryrefslogtreecommitdiff
path: root/src/game.rs
diff options
context:
space:
mode:
authortslil clingman <>2020-01-18 18:40:32 -0800
committertslil clingman <>2020-01-18 18:40:32 -0800
commita134b701949a735c4f340c5e2dce189261830f6b (patch)
tree7359665c21efc3cfa19ad689ca8669b91b193ed8 /src/game.rs
parentcdbcc1d2faf198629f5d598bb9a7ba6ab15a922a (diff)
Working on first turn tracking and the interface
Diffstat (limited to 'src/game.rs')
-rw-r--r--src/game.rs207
1 files changed, 157 insertions, 50 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()
)
}
}