aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/game.rs207
-rw-r--r--src/main.rs94
2 files changed, 221 insertions, 80 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()
)
}
}
diff --git a/src/main.rs b/src/main.rs
index d664025..9ccefde 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -4,11 +4,40 @@ use pancurses::Input::*;
use pancurses::*;
mod game;
-
use crate::game::*;
-struct BoardGui {
- win: Window,
+pub struct GameStateGUI {
+ action_window: Window,
+ pieces_window: Window,
+}
+
+impl GameStateGUI {
+ pub fn new(ypos: i32, xpos: i32, height: i32, width: i32) -> GameStateGUI {
+ GameStateGUI {
+ pieces_window: newwin(2, width, ypos, xpos),
+ action_window: newwin(height, width, ypos + 3, xpos),
+ }
+ }
+
+ pub fn update<L: Fn(String)>(&self, game: &Game<L>) {
+ self.pieces_window.clear();
+ self.pieces_window.addstr(format!(
+ "W: {:2} B: {:2}\nWC: {} BC: {}",
+ game.query_pieces(Player::White, Stone::Flat),
+ game.query_pieces(Player::Black, Stone::Flat),
+ game.query_pieces(Player::White, Stone::Capstone),
+ game.query_pieces(Player::Black, Stone::Capstone),
+ ));
+ self.pieces_window.refresh();
+
+ self.action_window.clear();
+ self.action_window.addstr(game.query_action_lines());
+ self.action_window.refresh();
+ }
+}
+
+struct BoardGUI {
+ board_window: Window,
cell_windows: Vec<Window>,
size: u8,
cell_height: u8,
@@ -26,7 +55,7 @@ enum BGAction {
None,
}
-impl BoardGui {
+impl BoardGUI {
fn draw_grid(&self) {
let s: i32 = self.size as i32;
let h: i32 = self.cell_height as i32;
@@ -35,26 +64,27 @@ impl BoardGui {
for x in 0..w * s + 1 {
if y % h == 0 {
if x % w == 0 {
- self.win.mvaddch(y, x + self.hoff, '+');
+ self.board_window.mvaddch(y, x + self.hoff, '+');
} else {
- self.win.mvaddch(y, x + self.hoff, '-');
+ self.board_window.mvaddch(y, x + self.hoff, '-');
}
} else if x % w == 0 {
- self.win.mvaddch(y, x + self.hoff, '|');
+ self.board_window.mvaddch(y, x + self.hoff, '|');
}
}
}
for y in 0..s {
- self.win.mvaddstr(y * h + h / 2, 0, format!("{}.", s - y));
+ self.board_window
+ .mvaddstr(y * h + h / 2, 0, format!("{}.", s - y));
}
for x in 0..s {
- self.win.mvaddstr(
+ self.board_window.mvaddstr(
s * h + 1,
w * x + w / 2 + self.hoff,
format!("{:x}.", x + 10),
);
}
- self.win.refresh();
+ self.board_window.refresh();
}
fn pos_to_idx(&self, pos: &Position) -> usize {
@@ -107,16 +137,15 @@ impl BoardGui {
}
}
- // Is this really the best way to do this?
- fn draw_game_state(&self, game_state: &GameState) {
- // ????
- assert!(game_state.get_size() == self.size);
+ fn update<L: Fn(String)>(&self, game: &Game<L>) {
+ // 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_state.query_square(&pos) {
+ if let Some(stack) = game.query_square(&pos) {
self.draw_stack(&pos, stack);
}
}
@@ -155,8 +184,8 @@ impl BoardGui {
}
Character('q') => BGAction::Quit,
x => {
- self.win.addstr(format!("{:?}", x));
- self.win.refresh();
+ self.board_window.addstr(format!("{:?}", x));
+ self.board_window.refresh();
BGAction::None
}
}
@@ -172,7 +201,7 @@ impl BoardGui {
has_colours: bool,
white_colour: u8,
black_colour: u8,
- ) -> BoardGui {
+ ) -> BoardGUI {
let mut cell_windows: Vec<Window> = Vec::new();
let hoff = hoff as i32;
@@ -185,8 +214,8 @@ impl BoardGui {
cell_windows.push(newwin(h - 1, w - 1, (s - y - 1) * h + 1, hoff + x * w + 1));
}
- let bg = BoardGui {
- win: newwin(h * s + 3, w * s + 3 + hoff, ypos, xpos),
+ let bg = BoardGUI {
+ board_window: newwin(h * s + 3, w * s + 3 + hoff, ypos, xpos),
cell_windows: cell_windows,
size: size,
cell_height: cell_height,
@@ -210,7 +239,7 @@ impl BoardGui {
// }
bg.mv_to_cell(&bg.cur_cell, 0);
- bg.win.refresh();
+ bg.board_window.refresh();
bg
}
}
@@ -226,9 +255,9 @@ impl MoveEntry {
self.win.refresh();
}
- fn read_action(&self) -> &str {
- "a1"
- }
+ // fn read_action(&self) -> &str {
+ // "a1"
+ // }
}
struct MessageLog {
@@ -272,26 +301,31 @@ fn main() {
screen.refresh();
+ let mut bg = BoardGUI::new(0, 0, 5, 4, 8, 3, has_colours, white_colour, black_colour);
+ let gs = GameStateGUI::new(0, 5 * 8 + 3 + 3, 100, 20);
+
let mew = newwin(1, 30, 25, 0);
let mlw = newwin(3, 70, 26, 0);
-
- let mut bg = BoardGui::new(0, 0, 5, 4, 8, 3, has_colours, white_colour, black_colour);
-
let me = MoveEntry { win: mew };
-
let ml = MessageLog {
win: mlw,
has_colours: has_colours,
red_colour: red_colour,
};
-
me.init();
ml.init();
let log = |s| MessageLog::log_error(&ml, s);
- let game_state = GameState::new(5);
+ let mut game = Game::new(5, "tslil", "taktician", log);
+
let pos = Position { x: 0, y: 0 };
let action = Action::Place(Player::White, pos, Stone::Flat);
+
+ game.perform_action(action);
+
+ bg.update(&game);
+ gs.update(&game);
+
// 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);