aboutsummaryrefslogtreecommitdiff
path: root/src/game.rs
diff options
context:
space:
mode:
authortslil clingman <>2021-01-09 22:42:12 -0500
committertslil clingman <>2021-01-09 22:44:29 -0500
commita0f3a7f8ba9036d1fcb18941a54077082f15cf3c (patch)
treebb3943b7bc3cee704fbc7ad7c4e4c1ca0204d5f7 /src/game.rs
parent1cb3c0ea50ce55278413dfd368285cb622130a30 (diff)
Added .rustfmt.toml, tabs for indentation, spaces for alignment
Diffstat (limited to 'src/game.rs')
-rw-r--r--src/game.rs1747
1 files changed, 872 insertions, 875 deletions
diff --git a/src/game.rs b/src/game.rs
index d8fd268..7e96a2e 100644
--- a/src/game.rs
+++ b/src/game.rs
@@ -1,18 +1,18 @@
/*
- This file is part of Takwrap.
+ This file is part of Takwrap.
- Takwrap is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
+ Takwrap is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
- Takwrap is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
+ Takwrap is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with Takwrap. If not, see <https://www.gnu.org/licenses/>.
+ You should have received a copy of the GNU General Public License
+ along with Takwrap. If not, see <https://www.gnu.org/licenses/>.
*/
use std::collections::HashSet;
@@ -20,136 +20,136 @@ use std::fmt;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct Position {
- pub x: u8,
- pub y: u8,
+ 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)
- }
+ 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,
+ 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",
- }
- )
- }
+ 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,
+ 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",
- }
- )
- }
+ 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,
+ 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 => ">",
- }
- )
- }
+ 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(Position, Direction, u8, Vec<u8>),
+ Place(Player, Position, Stone),
+ Move(Position, Direction, u8, Vec<u8>),
}
impl fmt::Display for Action {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- Action::Place(_, pos, stone) => write!(f, "{}{}", stone, pos),
- Action::Move(pos, direction, picked_up, drops) => {
- match (*picked_up > 1, drops.len() > 1) {
- (true, true) => write!(
- f,
- "{}{}{}{}",
- picked_up,
- pos,
- direction,
- drops.into_iter().map(|q| q.to_string()).collect::<String>(),
- ),
- (true, false) => write!(f, "{}{}{}", picked_up, pos, direction),
- // (false, true) should be impossible
- _ => write!(f, "{}{}", pos, direction),
- }
- }
- }
- }
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Action::Place(_, pos, stone) => write!(f, "{}{}", stone, pos),
+ Action::Move(pos, direction, picked_up, drops) => {
+ match (*picked_up > 1, drops.len() > 1) {
+ (true, true) => write!(
+ f,
+ "{}{}{}{}",
+ picked_up,
+ pos,
+ direction,
+ drops.into_iter().map(|q| q.to_string()).collect::<String>(),
+ ),
+ (true, false) => write!(f, "{}{}{}", picked_up, pos, direction),
+ // (false, true) should be impossible
+ _ => write!(f, "{}{}", pos, direction),
+ }
+ }
+ }
+ }
}
#[derive(Clone, Copy)]
pub struct Piece {
- pub player: Player,
- pub stone: Stone,
+ pub player: Player,
+ pub stone: Stone,
}
#[derive(Clone, Copy)]
pub enum WinType {
- Dragon,
- RoadWin(Player),
- FlatWin(Player),
- Draw,
+ Dragon,
+ RoadWin(Player),
+ FlatWin(Player),
+ Draw,
}
impl fmt::Display for WinType {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(
- f,
- "{}",
- match self {
- WinType::RoadWin(Player::Black) => "0-R",
- WinType::RoadWin(Player::White) => "R-0",
- WinType::FlatWin(Player::Black) => "0-F",
- WinType::FlatWin(Player::White) => "F-0",
- WinType::Dragon => "R-R",
- WinType::Draw => "1/2-1/2",
- }
- )
- }
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "{}",
+ match self {
+ WinType::RoadWin(Player::Black) => "0-R",
+ WinType::RoadWin(Player::White) => "R-0",
+ WinType::FlatWin(Player::Black) => "0-F",
+ WinType::FlatWin(Player::White) => "F-0",
+ WinType::Dragon => "R-R",
+ WinType::Draw => "1/2-1/2",
+ }
+ )
+ }
}
pub type Stack = Vec<Piece>;
@@ -158,8 +158,8 @@ pub type Stack = Vec<Piece>;
const-generics would solve a lot of my consistency problems here,
GameState<const size : u8> {
- ...
- board: [Stack; size],
+ ...
+ board: [Stack; size],
}
but at the time of writing this is not yet part of the language.
@@ -168,863 +168,860 @@ dependent type system ;)
*/
struct GameState {
- size: u8,
- black_flats: u8,
- white_flats: u8,
- black_capstones: u8,
- white_capstones: u8,
- board: Vec<Stack>,
+ 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 {
- 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() {
- copy.board.push(Vec::new());
- for j in 0..self.board[i].len() {
- copy.board[i].push(self.board[i][j]);
- }
- }
- copy
- }
+ 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() {
+ copy.board.push(Vec::new());
+ 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
- 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
- },
- }
- }
+ // Defaults to 5x5 if requested things are out of range
+ 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 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
- }
- }
+ 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;
- x + y * (self.size as usize)
- }
+ 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))
- }
+ fn query_pos(&self, pos: &Position) -> Option<&Stack> {
+ self.board.get(self.pos_to_idx(pos))
+ }
- fn is_legal_place(
- &self,
- player: Player,
- pos: &Position,
- stone: Stone,
- check_start: Option<StartType>,
- ) -> Result<(), String> {
- /* In order to legally place a piece:
- 1. The desired square must be empty
- 2. The player must have sufficient pieces
- 3. We must account for F{C,E}S
- */
- if self.within_bounds(pos) {
- if let Some(start_type) = check_start {
- match start_type {
- StartType::FCS => {
- if ((pos.x == 0) && (pos.y == 0))
- || ((pos.x + 1 == self.size) && (pos.y == 0))
- || ((pos.x == 0) && (pos.y + 1 == self.size))
- || ((pos.x + 1 == self.size) && (pos.y + 1 == self.size))
- {
- return Err(format!(
- "At the start of game in FCS, {} may not be placed in a corner (such as {}).",
- player, pos
- ));
- }
- }
- StartType::FES => {
- if (pos.x == 0)
- || (pos.y == 0)
- || (pos.x + 1 == self.size)
- || (pos.y + 1 == self.size)
- {
- return Err(format!(
- "At the start of a game in FES, {} may not be placed along any edge (and {} is on an edge).",
- player, pos
- ));
- }
- }
- _ => (),
- }
- }
+ fn is_legal_place(
+ &self,
+ player: Player,
+ pos: &Position,
+ stone: Stone,
+ check_start: Option<StartType>,
+ ) -> Result<(), String> {
+ /* In order to legally place a piece:
+ 1. The desired square must be empty
+ 2. The player must have sufficient pieces
+ 3. We must account for F{C,E}S
+ */
+ if self.within_bounds(pos) {
+ if let Some(start_type) = check_start {
+ match start_type {
+ StartType::FCS => {
+ if ((pos.x == 0) && (pos.y == 0))
+ || ((pos.x + 1 == self.size) && (pos.y == 0))
+ || ((pos.x == 0) && (pos.y + 1 == self.size))
+ || ((pos.x + 1 == self.size) && (pos.y + 1 == self.size))
+ {
+ return Err(format!(
+ "At the start of game in FCS, {} may not be placed in a corner (such as {}).",
+ player, pos
+ ));
+ }
+ }
+ StartType::FES => {
+ if (pos.x == 0) || (pos.y == 0) || (pos.x + 1 == self.size) || (pos.y + 1 == self.size)
+ {
+ return Err(format!(
+ "At the start of a game in FES, {} may not be placed along any edge (and {} is on an edge).",
+ player, 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));
- }
- }
+ 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,
- check_start: Option<StartType>,
- ) -> Result<(Vec<Position>, GameState), String> {
- self.is_legal_place(player, pos, stone, check_start)?;
+ fn place_stone(
+ &self,
+ player: Player,
+ pos: &Position,
+ stone: Stone,
+ check_start: Option<StartType>,
+ ) -> Result<(Vec<Position>, GameState), String> {
+ self.is_legal_place(player, pos, stone, check_start)?;
- // Place stone
- let mut copy = self.copy();
- let idx = self.pos_to_idx(&pos);
- copy.board[idx].push(Piece {
- player: player,
- stone: stone,
- });
+ // Place stone
+ let mut copy = self.copy();
+ let idx = self.pos_to_idx(&pos);
+ copy.board[idx].push(Piece {
+ player: player,
+ stone: stone,
+ });
- // Decrease count
- match player {
- Player::Black => {
- if stone == Stone::Capstone {
- copy.black_capstones -= 1;
- } else {
- copy.black_flats -= 1;
- }
- }
- Player::White => {
- if stone == Stone::Capstone {
- copy.white_capstones -= 1;
- } else {
- copy.white_flats -= 1;
- }
- }
- }
- Ok((vec![pos.clone()], copy))
- }
+ // Decrease count
+ match player {
+ Player::Black => {
+ if stone == Stone::Capstone {
+ copy.black_capstones -= 1;
+ } else {
+ copy.black_flats -= 1;
+ }
+ }
+ Player::White => {
+ if stone == Stone::Capstone {
+ copy.white_capstones -= 1;
+ } else {
+ copy.white_flats -= 1;
+ }
+ }
+ }
+ Ok((vec![pos.clone()], copy))
+ }
- fn is_legal_move(
- &self,
- player: Player,
- pos: &Position,
- direction: Direction,
- picked_up: u8,
- drops: &Vec<u8>,
- ) -> Result<(), String> {
- if self.within_bounds(pos) {
- /* Rules for moving a stack:
- - There are stones
- - Drops have been specified
- - Must actually move at least one stone
- - Top stone belongs to player
- - One or more on each subsequent square
- - Total number of stones picked up does not exceed 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)) {
- let stack_len = stack.len();
- let drops_len = drops.len();
+ fn is_legal_move(
+ &self,
+ player: Player,
+ pos: &Position,
+ direction: Direction,
+ picked_up: u8,
+ drops: &Vec<u8>,
+ ) -> Result<(), String> {
+ if self.within_bounds(pos) {
+ /* Rules for moving a stack:
+ - There are stones
+ - Drops have been specified
+ - Must actually move at least one stone
+ - Top stone belongs to player
+ - One or more on each subsequent square
+ - Total number of stones picked up does not exceed 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)) {
+ let stack_len = stack.len();
+ let drops_len = drops.len();
- if stack_len == 0 {
- return Err(format!("{} has no stones to move.", pos));
- };
+ if drops_len == 0 {
+ return Err(String::from(
+ "A drop sequence for must be specified for a move.",
+ ));
+ };
- if drops_len == 0 {
- return Err(String::from(
- "A drop sequence for must be specified for a move.",
- ));
- };
+ if picked_up == 0 {
+ return Err(String::from(
+ "A valid move must change the position of at least a single stone.",
+ ));
+ }
- if picked_up == 0 {
- return Err(String::from(
- "A valid move must change the position of at least a single stone.",
- ));
- }
+ if stack[stack_len - 1].player != player {
+ return Err(format!(
+ "{} may not move the stack at {} as it belongs to {}.",
+ player,
+ pos,
+ stack[stack_len - 1].player
+ ));
+ };
- if stack[stack_len - 1].player != player {
- return Err(format!(
- "{} may not move the stack at {} as it belongs to {}.",
- player,
- pos,
- stack[stack_len - 1].player
- ));
- };
+ for i in 0..drops_len {
+ if drops[i] == 0 {
+ return Err(String::from(
+ "A move may not drop 0 stones on subsequent squares.",
+ ));
+ }
+ }
- for i in 0..drops_len {
- if drops[i] == 0 {
- return Err(String::from(
- "A move may not drop 0 stones on subsequent squares.",
- ));
- }
- }
+ let picked_up = picked_up as usize;
+ let sum_dropped = drops.iter().map(|&d| d as usize).sum::<usize>();
+ if sum_dropped != picked_up {
+ return Err(format!(
+ "Move intended to pick up {} stones but drop {}.",
+ picked_up, sum_dropped
+ ));
+ }
- let picked_up = picked_up as usize;
- let sum_dropped = drops.iter().map(|&d| d as usize).sum::<usize>();
- if sum_dropped != picked_up {
- return Err(format!(
- "Move intended to pick up {} stones but drop {}.",
- picked_up, sum_dropped
- ));
- }
+ let carry_capacity = self.size as usize;
+ if picked_up > carry_capacity {
+ return Err(format!(
+ "A move may not pick up more than the carry capacity of {} stones.",
+ self.size
+ ));
+ }
- let carry_capacity = self.size as usize;
- if picked_up > carry_capacity {
- return Err(format!(
- "A move may not pick up more than the carry capacity of {} stones.",
- self.size
- ));
- }
+ if stack_len < picked_up {
+ return Err(format!("{} has insuficiently many stones.", pos));
+ };
- let mut steps: usize = drops.len();
- let cap: bool = stack[0].stone == Stone::Capstone;
- let mut pos_new = Position { x: pos.x, y: pos.y };
+ let mut steps: usize = drops.len();
+ let cap: bool = stack[0].stone == Stone::Capstone;
+ let mut pos_new = Position { x: pos.x, y: pos.y };
- while steps > 0 {
- if {
- match direction {
- Direction::Up => pos_new.y + 1 >= self.size,
- Direction::Down => pos_new.y == 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."));
- } 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[stack.len() - 1].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
- ));
- }
- }
- steps -= 1;
- }
- 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));
- }
- }
+ while steps > 0 {
+ if {
+ match direction {
+ Direction::Up => pos_new.y + 1 >= self.size,
+ Direction::Down => pos_new.y == 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."));
+ } 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[stack.len() - 1].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
+ ));
+ }
+ }
+ steps -= 1;
+ }
+ 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,
- picked_up: u8,
- drops: &Vec<u8>,
- ) -> Result<(Vec<Position>, GameState), String> {
- self.is_legal_move(player, pos, direction, picked_up, drops)?;
+ fn move_stack(
+ &self,
+ player: Player,
+ pos: &Position,
+ direction: Direction,
+ picked_up: u8,
+ drops: &Vec<u8>,
+ ) -> Result<(Vec<Position>, GameState), String> {
+ self.is_legal_move(player, pos, direction, picked_up, drops)?;
- let mut copy = self.copy();
- let mut pos_vec: Vec<Position> = vec![pos.clone()];
+ let mut copy = self.copy();
+ 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 idx = copy.pos_to_idx(&pos);
+ let stack: &Stack = &self.board[self.pos_to_idx(pos)];
- let stack_height = copy.board[idx].len();
+ let stack_height = copy.board[idx].len();
- let mut offset = stack_height - (picked_up as usize);
+ let mut offset = stack_height - (picked_up as usize);
- for _i in offset..stack_height {
- copy.board[idx].pop();
- }
+ for _i in offset..stack_height {
+ copy.board[idx].pop();
+ }
- for drop_idx in 0..drops.len() {
- let new_pos = pos_vec[pos_vec.len() - 1];
- pos_vec.push(match direction {
- Direction::Up => Position {
- x: new_pos.x,
- y: new_pos.y + 1,
- },
- Direction::Down => Position {
- x: new_pos.x,
- y: new_pos.y - 1,
- },
- Direction::Left => Position {
- x: new_pos.x - 1,
- y: new_pos.y,
- },
- Direction::Right => Position {
- x: new_pos.x + 1,
- y: new_pos.y,
- },
- });
+ for drop_idx in 0..drops.len() {
+ let new_pos = pos_vec[pos_vec.len() - 1];
+ pos_vec.push(match direction {
+ Direction::Up => Position {
+ x: new_pos.x,
+ y: new_pos.y + 1,
+ },
+ Direction::Down => Position {
+ x: new_pos.x,
+ y: new_pos.y - 1,
+ },
+ Direction::Left => Position {
+ x: new_pos.x - 1,
+ y: new_pos.y,
+ },
+ Direction::Right => Position {
+ x: new_pos.x + 1,
+ y: new_pos.y,
+ },
+ });
- let new_pos = pos_vec[pos_vec.len() - 1];
- let idx = copy.pos_to_idx(&new_pos);
- let num = drops[drop_idx] as usize;
+ let new_pos = pos_vec[pos_vec.len() - 1];
+ let idx = copy.pos_to_idx(&new_pos);
+ let num = drops[drop_idx] as usize;
- for i in 0..num {
- // Are we flattening?
- if stack[offset + i].stone == Stone::Capstone {
- if let Some(top_piece) = copy.board[idx].last() {
- if top_piece.stone == Stone::Standing {
- let top_stone_idx = copy.board[idx].len() - 1;
- copy.board[idx][top_stone_idx].stone = Stone::Flat;
- }
- }
- }
- copy.board[idx].push(stack[offset + i]);
- }
- offset += num;
- }
- Ok((pos_vec, copy))
- }
+ for i in 0..num {
+ // Are we flattening?
+ if stack[offset + i].stone == Stone::Capstone {
+ if let Some(top_piece) = copy.board[idx].last() {
+ if top_piece.stone == Stone::Standing {
+ let top_stone_idx = copy.board[idx].len() - 1;
+ copy.board[idx][top_stone_idx].stone = Stone::Flat;
+ }
+ }
+ }
+ copy.board[idx].push(stack[offset + i]);
+ }
+ offset += num;
+ }
+ Ok((pos_vec, copy))
+ }
- // Either up or right
- fn dfs(&self, player: Player, up: bool, mut todo: Vec<Position>) -> bool {
- let mut seen: HashSet<Position> = HashSet::new();
- for i in 0..todo.len() {
- seen.insert(todo[i].clone());
- }
- loop {
- let more = todo.pop();
- if let Some(p) = more {
- if p.y + 1 < self.size {
- let p_up = Position { x: p.x, y: p.y + 1 };
- let i_up = self.pos_to_idx(&p_up);
- if !seen.contains(&p_up) && self.within_bounds(&p_up) {
- let l = self.board[i_up].len();
- if l > 0 {
- let piece = self.board[i_up][l - 1];
- if (piece.player == player) && (piece.stone != Stone::Standing) {
- if up && (p_up.y + 1 == self.size) {
- return true;
- } else {
- seen.insert(p_up);
- todo.push(p_up);
- }
- }
- }
- }
- }
+ // Either up or right
+ fn dfs(&self, player: Player, up: bool, mut todo: Vec<Position>) -> bool {
+ let mut seen: HashSet<Position> = HashSet::new();
+ for i in 0..todo.len() {
+ seen.insert(todo[i].clone());
+ }
+ loop {
+ let more = todo.pop();
+ if let Some(p) = more {
+ if p.y + 1 < self.size {
+ let p_up = Position { x: p.x, y: p.y + 1 };
+ let i_up = self.pos_to_idx(&p_up);
+ if !seen.contains(&p_up) && self.within_bounds(&p_up) {
+ let l = self.board[i_up].len();
+ if l > 0 {
+ let piece = self.board[i_up][l - 1];
+ if (piece.player == player) && (piece.stone != Stone::Standing) {
+ if up && (p_up.y + 1 == self.size) {
+ return true;
+ } else {
+ seen.insert(p_up);
+ todo.push(p_up);
+ }
+ }
+ }
+ }
+ }
- if p.x + 1 < self.size {
- let p_right = Position { x: p.x + 1, y: p.y };
- let i_right = self.pos_to_idx(&p_right);
- if !seen.contains(&p_right) && self.within_bounds(&p_right) {
- let l = self.board[i_right].len();
- if l > 0 {
- let piece = self.board[i_right][l - 1];
- if (piece.player == player) && (piece.stone != Stone::Standing) {
- if !up && (p_right.x + 1 == self.size) {
- return true;
- } else {
- seen.insert(p_right);
- todo.push(p_right);
- }
- }
- }
- }
- }
+ if p.x + 1 < self.size {
+ let p_right = Position { x: p.x + 1, y: p.y };
+ let i_right = self.pos_to_idx(&p_right);
+ if !seen.contains(&p_right) && self.within_bounds(&p_right) {
+ let l = self.board[i_right].len();
+ if l > 0 {
+ let piece = self.board[i_right][l - 1];
+ if (piece.player == player) && (piece.stone != Stone::Standing) {
+ if !up && (p_right.x + 1 == self.size) {
+ return true;
+ } else {
+ seen.insert(p_right);
+ todo.push(p_right);
+ }
+ }
+ }
+ }
+ }
- if p.y > 0 {
- let p_down = Position { x: p.x, y: p.y - 1 };
- let i_down = self.pos_to_idx(&p_down);
- if !seen.contains(&p_down) && self.within_bounds(&p_down) {
- let l = self.board[i_down].len();
- if l > 0 {
- let piece = self.board[i_down][l - 1];
- if (piece.player == player) && (piece.stone != Stone::Standing) {
- seen.insert(p_down);
- todo.push(p_down);
- }
- }
- }
- }
+ if p.y > 0 {
+ let p_down = Position { x: p.x, y: p.y - 1 };
+ let i_down = self.pos_to_idx(&p_down);
+ if !seen.contains(&p_down) && self.within_bounds(&p_down) {
+ let l = self.board[i_down].len();
+ if l > 0 {
+ let piece = self.board[i_down][l - 1];
+ if (piece.player == player) && (piece.stone != Stone::Standing) {
+ seen.insert(p_down);
+ todo.push(p_down);
+ }
+ }
+ }
+ }
- if p.x > 0 {
- let p_left = Position { x: p.x - 1, y: p.y };
- let i_left = self.pos_to_idx(&p_left);
- if !seen.contains(&p_left) && self.within_bounds(&p_left) {
- let l = self.board[i_left].len();
- if l > 0 {
- let piece = self.board[i_left][l - 1];
- if (piece.player == player) && (piece.stone != Stone::Standing) {
- seen.insert(p_left);
- todo.push(p_left);
- }
- }
- }
- }
+ if p.x > 0 {
+ let p_left = Position { x: p.x - 1, y: p.y };
+ let i_left = self.pos_to_idx(&p_left);
+ if !seen.contains(&p_left) && self.within_bounds(&p_left) {
+ let l = self.board[i_left].len();
+ if l > 0 {
+ let piece = self.board[i_left][l - 1];
+ if (piece.player == player) && (piece.stone != Stone::Standing) {
+ seen.insert(p_left);
+ todo.push(p_left);
+ }
+ }
+ }
+ }
- seen.insert(p);
- } else {
- break;
- }
- }
- return false;
- }
+ seen.insert(p);
+ } else {
+ break;
+ }
+ }
+ return false;
+ }
- fn check_road_win(&self) -> Option<WinType> {
- // Depth first search from bottom edge and left edge
+ fn check_road_win(&self) -> Option<WinType> {
+ // Depth first search from bottom edge and left edge
- let mut todo_black_up: Vec<Position> = Vec::new();
- let mut todo_white_up: Vec<Position> = Vec::new();
- let mut todo_black_right: Vec<Position> = Vec::new();
- let mut todo_white_right: Vec<Position> = Vec::new();
+ let mut todo_black_up: Vec<Position> = Vec::new();
+ let mut todo_white_up: Vec<Position> = Vec::new();
+ let mut todo_black_right: Vec<Position> = Vec::new();
+ let mut todo_white_right: Vec<Position> = Vec::new();
- for i in 0..self.size {
- let pos_up = Position { x: i, y: 0 };
- let pos_right = Position { x: 0, y: i };
- let stack_up = &self.board[self.pos_to_idx(&pos_up)];
- let stack_right = &self.board[self.pos_to_idx(&pos_right)];
+ for i in 0..self.size {
+ let pos_up = Position { x: i, y: 0 };
+ let pos_right = Position { x: 0, y: i };
+ let stack_up = &self.board[self.pos_to_idx(&pos_up)];
+ let stack_right = &self.board[self.pos_to_idx(&pos_right)];
- if !stack_up.is_empty() {
- match stack_up[stack_up.len() - 1].player {
- Player::Black => todo_black_up.push(pos_up),
- Player::White => todo_white_up.push(pos_up),
- }
- }
+ if !stack_up.is_empty() {
+ match stack_up[stack_up.len() - 1].player {
+ Player::Black => todo_black_up.push(pos_up),
+ Player::White => todo_white_up.push(pos_up),
+ }
+ }
- if !stack_right.is_empty() {
- match stack_right[stack_right.len() - 1].player {
- Player::Black => todo_black_right.push(pos_right),
- Player::White => todo_white_right.push(pos_right),
- }
- }
- }
+ if !stack_right.is_empty() {
+ match stack_right[stack_right.len() - 1].player {
+ Player::Black => todo_black_right.push(pos_right),
+ Player::White => todo_white_right.push(pos_right),
+ }
+ }
+ }
- let black_road = self.dfs(Player::Black, true, todo_black_up)
- || self.dfs(Player::Black, false, todo_black_right);
- let white_road = self.dfs(Player::White, true, todo_white_up)
- || self.dfs(Player::White, false, todo_white_right);
+ let black_road = self.dfs(Player::Black, true, todo_black_up)
+ || self.dfs(Player::Black, false, todo_black_right);
+ let white_road = self.dfs(Player::White, true, todo_white_up)
+ || self.dfs(Player::White, false, todo_white_right);
- match (black_road, white_road) {
- (false, false) => return None,
- (true, false) => return Some(WinType::RoadWin(Player::Black)),
- (false, true) => return Some(WinType::RoadWin(Player::White)),
- (true, true) => return Some(WinType::Dragon),
- }
- }
+ match (black_road, white_road) {
+ (false, false) => return None,
+ (true, false) => return Some(WinType::RoadWin(Player::Black)),
+ (false, true) => return Some(WinType::RoadWin(Player::White)),
+ (true, true) => return Some(WinType::Dragon),
+ }
+ }
- fn check_flat_win(&self) -> Option<WinType> {
- let full_board = self.board.iter().all(|s| s.len() > 0);
- let finished_flats = (self.black_flats == 0) || (self.white_flats == 0);
+ fn check_flat_win(&self) -> Option<WinType> {
+ let full_board = self.board.iter().all(|s| s.len() > 0);
+ let finished_flats = (self.black_flats == 0) || (self.white_flats == 0);
- if full_board || finished_flats {
- let count = self.board.iter().fold(0, |c, s| match s.last() {
- None => c,
- Some(piece) => {
- if piece.stone == Stone::Flat {
- match piece.player {
- Player::Black => c + 1,
- Player::White => c - 1,
- }
- } else {
- c
- }
- }
- });
- // Trichotomy of reals ...
- if count > 0 {
- return Some(WinType::FlatWin(Player::Black));
- } else if count == 0 {
- return Some(WinType::Draw);
- } else {
- return Some(WinType::FlatWin(Player::White));
- }
- }
- return None;
- }
+ if full_board || finished_flats {
+ let count = self.board.iter().fold(0, |c, s| match s.last() {
+ None => c,
+ Some(piece) => {
+ if piece.stone == Stone::Flat {
+ match piece.player {
+ Player::Black => c + 1,
+ Player::White => c - 1,
+ }
+ } else {
+ c
+ }
+ }
+ });
+ // Trichotomy of reals ...
+ if count > 0 {
+ return Some(WinType::FlatWin(Player::Black));
+ } else if count == 0 {
+ return Some(WinType::Draw);
+ } else {
+ return Some(WinType::FlatWin(Player::White));
+ }
+ }
+ return None;
+ }
- fn check_win(&self) -> Option<WinType> {
- if let Some(w) = self.check_road_win() {
- return Some(w);
- } else {
- return self.check_flat_win();
- }
- }
+ fn check_win(&self) -> Option<WinType> {
+ if let Some(w) = self.check_road_win() {
+ return Some(w);
+ } else {
+ return self.check_flat_win();
+ }
+ }
}
enum TurnOrder {
- WhitePlacesBlack,
- BlackPlacesWhite,
- Normal,
+ WhitePlacesBlack,
+ BlackPlacesWhite,
+ Normal,
}
#[derive(Clone, Copy)]
pub enum StartType {
- CPS(u8),
- FCS,
- FES,
- TPS,
- CZS,
+ CPS(u8),
+ FCS,
+ FES,
+ TPS,
+ CZS,
}
impl fmt::Display for StartType {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- StartType::CPS(n) => write!(f, "CPS{}", n),
- StartType::FCS => write!(f, "FCS"),
- StartType::FES => write!(f, "FES"),
- StartType::TPS => write!(f, "TPS"),
- StartType::CZS => write!(f, "CZS"),
- }
- }
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ StartType::CPS(n) => write!(f, "CPS{}", n),
+ StartType::FCS => write!(f, "FCS"),
+ StartType::FES => write!(f, "FES"),
+ StartType::TPS => write!(f, "TPS"),
+ StartType::CZS => write!(f, "CZS"),
+ }
+ }
}
impl StartType {
- fn player_turnorder_at(&self, ply: usize) -> (Player, TurnOrder) {
- match self {
- StartType::CPS(c) => {
- if ply / 2 >= (*c as usize) {
- if ply % 2 == 0 {
- (Player::White, TurnOrder::Normal)
- } else {
- (Player::Black, TurnOrder::Normal)
- }
- } else {
- if ply % 2 == 0 {
- (Player::White, TurnOrder::WhitePlacesBlack)
- } else {
- (Player::Black, TurnOrder::BlackPlacesWhite)
- }
- }
- }
- StartType::FCS => StartType::CPS(1).player_turnorder_at(ply),
- StartType::FES => StartType::CPS(1).player_turnorder_at(ply),
- StartType::TPS => match ply {
- 0 => (Player::White, TurnOrder::WhitePlacesBlack),
- 1 => (Player::White, TurnOrder::WhitePlacesBlack),
- 2 => (Player::Black, TurnOrder::BlackPlacesWhite),
- n => (
- if n % 2 == 0 {
- Player::Black
- } else {
- Player::White
- },
- TurnOrder::Normal,
- ),
- },
- StartType::CZS => match ply {
- 0 => (Player::White, TurnOrder::WhitePlacesBlack),
- n => (
- if n % 2 == 1 {
- Player::White
- } else {
- Player::Black
- },
- TurnOrder::Normal,
- ),
- },
- }
- }
+ fn player_turnorder_at(&self, ply: usize) -> (Player, TurnOrder) {
+ match self {
+ StartType::CPS(c) => {
+ if ply / 2 >= (*c as usize) {
+ if ply % 2 == 0 {
+ (Player::White, TurnOrder::Normal)
+ } else {
+ (Player::Black, TurnOrder::Normal)
+ }
+ } else {
+ if ply % 2 == 0 {
+ (Player::White, TurnOrder::WhitePlacesBlack)
+ } else {
+ (Player::Black, TurnOrder::BlackPlacesWhite)
+ }
+ }
+ }
+ StartType::FCS => StartType::CPS(1).player_turnorder_at(ply),
+ StartType::FES => StartType::CPS(1).player_turnorder_at(ply),
+ StartType::TPS => match ply {
+ 0 => (Player::White, TurnOrder::WhitePlacesBlack),
+ 1 => (Player::White, TurnOrder::WhitePlacesBlack),
+ 2 => (Player::Black, TurnOrder::BlackPlacesWhite),
+ n => (
+ if n % 2 == 0 {
+ Player::Black
+ } else {
+ Player::White
+ },
+ TurnOrder::Normal,
+ ),
+ },
+ StartType::CZS => match ply {
+ 0 => (Player::White, TurnOrder::WhitePlacesBlack),
+ n => (
+ if n % 2 == 1 {
+ Player::White
+ } else {
+ Player::Black
+ },
+ TurnOrder::Normal,
+ ),
+ },
+ }
+ }
}
pub struct Game {
- size: u8,
- start_type: StartType,
- current_player: Player,
- turn_order: TurnOrder,
- actions: Vec<Action>,
- states: Vec<GameState>,
- white_player_name: String,
- black_player_name: String,
- date_string: String,
- win_type: Option<WinType>,
+ size: u8,
+ start_type: StartType,
+ current_player: Player,
+ turn_order: TurnOrder,
+ actions: Vec<Action>,
+ states: Vec<GameState>,
+ white_player_name: String,
+ black_player_name: String,
+ date_string: String,
+ win_type: Option<WinType>,
}
impl Game {
- pub fn new(
- size: u8,
- white_player_name: &str,
- black_player_name: &str,
- date_str: &str,
- start_type: StartType,
- ) -> (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, String::new())
- };
+ pub fn new(
+ size: u8,
+ white_player_name: &str,
+ black_player_name: &str,
+ date_str: &str,
+ start_type: StartType,
+ ) -> (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, String::new())
+ };
- let (current_player, turn_order) = start_type.player_turnorder_at(0);
- (
- Game {
- size,
- start_type,
- states: vec![GameState::new(size)],
- actions: Vec::new(),
- current_player,
- turn_order,
- win_type: None,
- white_player_name: white_player_name.to_string(),
- black_player_name: black_player_name.to_string(),
- date_string: date_str.to_string(),
- },
- warning,
- )
- }
+ let (current_player, turn_order) = start_type.player_turnorder_at(0);
+ (
+ Game {
+ size,
+ start_type,
+ states: vec![GameState::new(size)],
+ actions: Vec::new(),
+ current_player,
+ turn_order,
+ win_type: None,
+ white_player_name: white_player_name.to_string(),
+ black_player_name: black_player_name.to_string(),
+ date_string: date_str.to_string(),
+ },
+ warning,
+ )
+ }
- pub fn query_square(&self, pos: &Position) -> Option<&Stack> {
- self.last_state().query_pos(pos)
- }
+ pub fn query_square(&self, pos: &Position) -> Option<&Stack> {
+ self.last_state().query_pos(pos)
+ }
- pub fn get_current_player_name(&self) -> &str {
- match self.current_player {
- Player::Black => &self.black_player_name,
- Player::White => &self.white_player_name,
- }
- }
+ pub fn get_current_player_name(&self) -> &str {
+ match self.current_player {
+ Player::Black => &self.black_player_name,
+ Player::White => &self.white_player_name,
+ }
+ }
- pub fn get_pieces(&self, player: Player, stone: Stone) -> u8 {
- self.last_state().remaining_pieces(player, stone)
- }
+ pub fn get_pieces(&self, player: Player, stone: Stone) -> u8 {
+ self.last_state().remaining_pieces(player, stone)
+ }
- pub fn get_current_player(&self) -> Player {
- self.current_player
- }
+ pub fn get_current_player(&self) -> Player {
+ self.current_player
+ }
- pub fn get_stone_owner(&self) -> Player {
- match self.turn_order {
- TurnOrder::WhitePlacesBlack => Player::Black,
- TurnOrder::BlackPlacesWhite => Player::White,
- TurnOrder::Normal => self.current_player,
- }
- }
+ pub fn get_stone_owner(&self) -> Player {
+ match self.turn_order {
+ TurnOrder::WhitePlacesBlack => Player::Black,
+ TurnOrder::BlackPlacesWhite => Player::White,
+ TurnOrder::Normal => self.current_player,
+ }
+ }
- pub fn get_start_type(&self) -> StartType {
- self.start_type
- }
+ pub fn get_start_type(&self) -> StartType {
+ self.start_type
+ }
- pub fn get_size(&self) -> u8 {
- self.size
- }
+ pub fn get_size(&self) -> u8 {
+ self.size
+ }
- pub fn get_ply(&self) -> usize {
- self.states.len() - 1
- }
+ pub fn get_ply(&self) -> usize {
+ self.states.len() - 1
+ }
- // Relying on only ::new(...) being used to make instances
- fn last_state(&self) -> &GameState {
- &self.states[self.states.len() - 1]
- }
+ // Relying on only ::new(...) being used to make instances
+ fn last_state(&self) -> &GameState {
+ &self.states[self.states.len() - 1]
+ }
- pub fn get_date_string(&self) -> &str {
- &self.date_string
- }
+ pub fn get_date_string(&self) -> &str {
+ &self.date_string
+ }
- pub fn get_action_lines(&self) -> Vec<String> {
- let mut result = Vec::new();
- let mut newline = true;
- let num_actions = self.actions.len();
- if num_actions > 0 {
- let mut line = String::new();
- line += "1. ";
- for i in 0..num_actions {
- if i > 1 && newline {
- // TODO: Is placing the opponent's first stone the zeroeth action?
- line += &format!("{}. ", i / 2 + 1);
- }
- line += &format!("{} ", self.actions[i].to_string());
- if i > 0 && !newline {
- result.push(line);
- line = String::new();
- }
- newline = !newline;
- }
- if !line.is_empty() {
- result.push(line);
- }
- }
- if self.win_type.is_some() {
- result.push(self.win_type.unwrap().to_string());
- }
- result
- }
+ pub fn get_action_lines(&self) -> Vec<String> {
+ let mut result = Vec::new();
+ let mut newline = true;
+ let num_actions = self.actions.len();
+ if num_actions > 0 {
+ let mut line = String::new();
+ line += "1. ";
+ for i in 0..num_actions {
+ if i > 1 && newline {
+ // TODO: Is placing the opponent's first stone the zeroeth action?
+ line += &format!("{}. ", i / 2 + 1);
+ }
+ line += &format!("{} ", self.actions[i].to_string());
+ if i > 0 && !newline {
+ result.push(line);
+ line = String::new();
+ }
+ newline = !newline;
+ }
+ if !line.is_empty() {
+ result.push(line);
+ }
+ }
+ if self.win_type.is_some() {
+ result.push(self.win_type.unwrap().to_string());
+ }
+ result
+ }
- pub fn perform_action(
- &mut self,
- act: Action,
- ) -> Result<(Vec<Position>, Option<WinType>), String> {
- let maybe_state = self.states.last();
- if maybe_state.is_none() {
- return Err(String::from("Internal error: cannot find last game state"));
- }
- let state = maybe_state.unwrap();
- let new_state_either = match &act {
- Action::Place(player, pos, stone) => match self.turn_order {
- TurnOrder::WhitePlacesBlack => {
- if (self.current_player == Player::White)
- && (*player == Player::Black)
- && (*stone == Stone::Flat)
- {
- state.place_stone(*player, pos, *stone, Some(self.start_type))
- } else {
- Err(format!(
- "At the start of the game in {}, W must place a B flat.",
- self.start_type
- ))
- }
- }
- TurnOrder::BlackPlacesWhite => {
- if (self.current_player == Player::Black)
- && (*player == Player::White)
- && (*stone == Stone::Flat)
- {
- state.place_stone(*player, pos, *stone, None)
- } else {
- Err(format!(
- "At the start of the game in {}, B must place a W flat.",
- self.start_type
- ))
- }
- }
- TurnOrder::Normal => state.place_stone(*player, pos, *stone, None),
- },
- Action::Move(pos, direction, picked_up, drops) => match self.turn_order {
- TurnOrder::Normal => {
- state.move_stack(self.current_player, pos, *direction, *picked_up, drops)
- }
- _ => Err(format!(
- "At the start of the game in {} only placing flats is allowed.",
- self.start_type
- )),
- },
- };
+ pub fn perform_action(
+ &mut self,
+ act: Action,
+ ) -> Result<(Vec<Position>, Option<WinType>), String> {
+ let maybe_state = self.states.last();
+ if maybe_state.is_none() {
+ return Err(String::from("Internal error: cannot find last game state"));
+ }
+ let state = maybe_state.unwrap();
+ let new_state_either = match &act {
+ Action::Place(player, pos, stone) => match self.turn_order {
+ TurnOrder::WhitePlacesBlack => {
+ if (self.current_player == Player::White)
+ && (*player == Player::Black)
+ && (*stone == Stone::Flat)
+ {
+ state.place_stone(*player, pos, *stone, Some(self.start_type))
+ } else {
+ Err(format!(
+ "At the start of the game in {}, W must place a B flat.",
+ self.start_type
+ ))
+ }
+ }
+ TurnOrder::BlackPlacesWhite => {
+ if (self.current_player == Player::Black)
+ && (*player == Player::White)
+ && (*stone == Stone::Flat)
+ {
+ state.place_stone(*player, pos, *stone, None)
+ } else {
+ Err(format!(
+ "At the start of the game in {}, B must place a W flat.",
+ self.start_type
+ ))
+ }
+ }
+ TurnOrder::Normal => state.place_stone(*player, pos, *stone, None),
+ },
+ Action::Move(pos, direction, picked_up, drops) => match self.turn_order {
+ TurnOrder::Normal => {
+ state.move_stack(self.current_player, pos, *direction, *picked_up, drops)
+ }
+ _ => Err(format!(
+ "At the start of the game in {} only placing flats is allowed.",
+ self.start_type
+ )),
+ },
+ };
- match new_state_either {
- Err(e) => return Err(e),
- Ok((pos_vec, new_state)) => {
- self.states.push(new_state);
- self.actions.push(act);
- let (current_player, turn_order) =
- self.start_type.player_turnorder_at(self.get_ply());
- self.current_player = current_player;
- self.turn_order = turn_order;
- // This is safe as we have just added to the states vector
- self.win_type = self.last_state().check_win();
- return Ok((pos_vec, self.win_type));
- }
- }
- }
+ match new_state_either {
+ Err(e) => return Err(e),
+ Ok((pos_vec, new_state)) => {
+ self.states.push(new_state);
+ self.actions.push(act);
+ let (current_player, turn_order) = self.start_type.player_turnorder_at(self.get_ply());
+ self.current_player = current_player;
+ self.turn_order = turn_order;
+ // This is safe as we have just added to the states vector
+ self.win_type = self.last_state().check_win();
+ return Ok((pos_vec, self.win_type));
+ }
+ }
+ }
- pub fn undo(&mut self) -> Result<Vec<Position>, String> {
- if self.actions.len() > 0 {
- self.actions.pop();
- self.states.pop();
- let (current_player, turn_order) = self.start_type.player_turnorder_at(self.get_ply());
- self.turn_order = turn_order;
- self.current_player = current_player;
- // I'm too lazy to work out exactly which squares must be
- // redrawn when an action is undone
- let mut redraw_all: Vec<Position> = Vec::new();
- for x in 0..self.size {
- for y in 0..self.size {
- redraw_all.push(Position { x, y });
- }
- }
- Ok(redraw_all)
- } else {
- Err(String::from("Cannot undo in an empty game!"))
- }
- }
+ pub fn undo(&mut self) -> Result<Vec<Position>, String> {
+ if self.actions.len() > 0 {
+ self.actions.pop();
+ self.states.pop();
+ let (current_player, turn_order) = self.start_type.player_turnorder_at(self.get_ply());
+ self.turn_order = turn_order;
+ self.current_player = current_player;
+ // I'm too lazy to work out exactly which squares must be
+ // redrawn when an action is undone
+ let mut redraw_all: Vec<Position> = Vec::new();
+ for x in 0..self.size {
+ for y in 0..self.size {
+ redraw_all.push(Position { x, y });
+ }
+ }
+ Ok(redraw_all)
+ } else {
+ Err(String::from("Cannot undo in an empty game!"))
+ }
+ }
}
impl fmt::Display for Game {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(
- f,
- "[Date \"{}\"]\n[Player1 \"{}\"]\n[Player2 \"{}\"]\n[Size \"{}\"]\n[Variant \"{}\"]\n{}",
- self.date_string,
- self.white_player_name,
- self.black_player_name,
- self.size,
- self.start_type,
- self.get_action_lines().join("\n"),
- )
- }
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "[Date \"{}\"]\n[Player1 \"{}\"]\n[Player2 \"{}\"]\n[Size \"{}\"]\n[Variant \"{}\"]\n{}",
+ self.date_string,
+ self.white_player_name,
+ self.black_player_name,
+ self.size,
+ self.start_type,
+ self.get_action_lines().join("\n"),
+ )
+ }
}