aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/game.rs165
-rw-r--r--src/gui.rs116
-rw-r--r--src/main.rs49
-rw-r--r--src/parser.rs8
4 files changed, 250 insertions, 88 deletions
diff --git a/src/game.rs b/src/game.rs
index 1d61732..2922121 100644
--- a/src/game.rs
+++ b/src/game.rs
@@ -228,7 +228,7 @@ impl GameState {
player: Player,
pos: &Position,
stone: Stone,
- ) -> Result<GameState, String> {
+ ) -> Result<(Vec<Position>, GameState), String> {
if let Err(e) = self.is_legal_place(player, pos, stone) {
return Err(e);
}
@@ -256,7 +256,7 @@ impl GameState {
}
}
}
- Ok(copy)
+ Ok((vec![pos.clone()], copy))
}
fn is_legal_move(
@@ -268,37 +268,70 @@ impl GameState {
) -> Result<(), String> {
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
+ - There are stones
+ - Drops have been specified
+ - Must actually move at least one stone
+ - Top stone belongs to player
+ - Zero or more dropped on starting square
+ - One or more on each subsequent square
+ - Total number of stones moved does not exceed the carry capacity
+ - Moved the entire stack (up to 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)) {
- if stack.len() == 0 {
+ let stack_len = stack.len();
+ let drops_len = drops.len();
+
+ if stack_len == 0 {
return Err(format!("{} has no stones to move.", pos));
- } else if drops.len() == 0 {
+ };
+
+ if drops_len == 0 {
return Err(String::from(
"A drop sequence for must be specified for a move.",
));
- } else if stack[0].player != player {
- return Err(format!(
- "{} may not move the stack at {} as it belongs to {}.",
- player, pos, stack[0].player
+ };
+
+ if drops_len == 1 && drops[0] == 1 {
+ return Err(String::from(
+ "A valid move must change the position of at least a single stone.",
));
- } else if drops[0] > 1 {
+ }
+
+ if stack[stack_len - 1].player != player {
return Err(format!(
- "A move may only drop 0 or 1 stones at it's origin square."
+ "{} may not move the stack at {} as it belongs to {}.",
+ player,
+ pos,
+ stack[stack_len - 1].player
));
- // Do the sum in u32 just in case ?
- } else if drops.iter().map(|&d| d as u32).sum::<u32>() > self.size as u32 {
+ };
+
+ for i in 0..drops_len {
+ if i > 0 && drops[i] == 0 {
+ return Err(String::from(
+ "A move may not drop 0 stones past the first square.",
+ ));
+ }
+ }
+
+ let num_dropped = drops.iter().map(|&d| d as usize).sum::<usize>();
+ let carry_capacity = self.size as usize;
+ if num_dropped > carry_capacity {
return Err(format!(
- "A move may not exceed the carry limit of {} stones.",
+ "A move may not exceed the carry capacity of {} stones.",
self.size
));
}
+
+ if num_dropped != std::cmp::max(carry_capacity, stack_len) {
+ return Err(String::from(
+ "A move must effect the whole stack, up to the carry limit.",
+ ));
+ }
+
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 };
@@ -309,8 +342,8 @@ impl GameState {
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::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."));
@@ -318,8 +351,8 @@ impl GameState {
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,
+ 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 {
@@ -365,30 +398,53 @@ impl GameState {
pos: &Position,
direction: Direction,
drops: &Vec<u8>,
- ) -> Result<GameState, String> {
+ ) -> Result<(Vec<Position>, GameState), String> {
if let Err(e) = self.is_legal_move(player, pos, direction, drops) {
return Err(e);
}
let mut copy = self.copy();
- let mut pos_new = Position { x: pos.x, y: pos.y };
+ 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 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]);
+ copy.board[idx].clear();
+
+ let num_drops = drops.len();
+ let mut offset = 0;
+ for drop_idx in 0..num_drops {
+ let pos = pos_vec[pos_vec.len() - 1];
+ let idx = copy.pos_to_idx(&pos);
+ let num = drops[drop_idx];
+
+ for i in 0..num {
+ copy.board[idx].push(stack[(offset + i) as usize]);
+ }
+ offset += num;
+
+ if drop_idx + 1 < num_drops {
+ pos_vec.push(match direction {
+ Direction::Up => Position {
+ x: pos.x,
+ y: pos.y + 1,
+ },
+ Direction::Down => Position {
+ x: pos.x,
+ y: pos.y - 1,
+ },
+ Direction::Left => Position {
+ x: pos.x - 1,
+ y: pos.y,
+ },
+ Direction::Right => Position {
+ x: pos.x + 1,
+ y: pos.y,
+ },
+ });
}
- k += d;
- 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,
- };
}
- Ok(copy)
+ Ok((pos_vec, copy))
}
}
@@ -449,6 +505,14 @@ impl Game {
self.current_player
}
+ pub fn query_stone_owner(&self) -> Player {
+ match self.turn_order {
+ TurnOrder::BlackPlacesWhite => Player::White,
+ TurnOrder::WhitePlacesBlack => Player::Black,
+ TurnOrder::Normal => self.current_player,
+ }
+ }
+
// pub fn query_action(&self, turn: u16) -> Option<&Action> {
// self.actions.get(turn as usize)
// }
@@ -459,15 +523,15 @@ impl Game {
pub fn query_action_lines(&self) -> String {
let mut result = String::new();
- let mut newline = false;
+ let mut newline = true;
result += "0. ";
for i in 0..self.actions.len() {
- if newline {
+ if i > 1 && newline {
// TODO: Is placing the opponent's first stone the zeroeth action?
result += &format!("{}. ", i);
}
result += &format!("{} ", self.actions[i]);
- if !newline {
+ if i > 0 && !newline {
result.push('\n');
}
newline = !newline;
@@ -475,7 +539,7 @@ impl Game {
result
}
- pub fn perform_action(&mut self, act: Action) -> Result<(), String> {
+ pub fn perform_action(&mut self, act: Action) -> Result<Vec<Position>, String> {
let maybe_state = self.states.last();
if maybe_state.is_none() {
return Err(String::from("Internal error: cannot find last game state"));
@@ -528,19 +592,24 @@ impl Game {
match new_state_either {
Err(e) => return Err(e),
- Ok(new_state) => {
+ Ok((pos_vec, new_state)) => {
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.current_player = match self.turn_order {
+ TurnOrder::BlackPlacesWhite => Player::White,
+ TurnOrder::WhitePlacesBlack => Player::White,
+ TurnOrder::Normal => 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,
};
- return Ok(());
+ return Ok(pos_vec);
}
}
}
diff --git a/src/gui.rs b/src/gui.rs
index 8875c9d..12bc31f 100644
--- a/src/gui.rs
+++ b/src/gui.rs
@@ -8,27 +8,96 @@ use crate::game::*;
pub struct GameLog {
action_window: Window,
pieces_window: Window,
+ has_colours: bool,
+ white_colour: u8,
+ black_colour: u8,
}
impl GameLog {
- pub fn new(ypos: i32, xpos: i32, height: i32, width: i32) -> GameLog {
+ pub fn new(
+ ypos: i32,
+ xpos: i32,
+ height: i32,
+ width: i32,
+ has_colours: bool,
+ white_colour: u8,
+ black_colour: u8,
+ ) -> GameLog {
GameLog {
- pieces_window: newwin(2, width, ypos, xpos),
- action_window: newwin(height, width, ypos + 3, xpos),
+ pieces_window: newwin(4, width, ypos, xpos),
+ action_window: newwin(height, width, ypos + 5, xpos),
+ has_colours: has_colours,
+ white_colour: white_colour,
+ black_colour: black_colour,
}
}
pub fn update(&self, game: &Game) {
- self.pieces_window.clear();
+ // No need to clear, always stays the same size
+ self.pieces_window.mv(0, 0);
+
+ if self.has_colours {
+ self.pieces_window.attrset(ColorPair(self.white_colour));
+ }
+ self.pieces_window.addstr(format!(
+ "W: {:2}",
+ game.query_pieces(Player::White, Stone::Flat)
+ ));
+
+ self.pieces_window.attrset(Attribute::Normal);
+ self.pieces_window.addstr(" ");
+
+ if self.has_colours {
+ self.pieces_window.attrset(ColorPair(self.black_colour));
+ }
self.pieces_window.addstr(format!(
- "W: {:2} B: {:2}\nWC: {} BC: {}",
- game.query_pieces(Player::White, Stone::Flat),
+ "B: {:2}",
game.query_pieces(Player::Black, Stone::Flat),
- game.query_pieces(Player::White, Stone::Capstone),
+ ));
+
+ self.pieces_window.attrset(Attribute::Normal);
+ self.pieces_window.mv(1, 0);
+
+ if self.has_colours {
+ self.pieces_window.attrset(ColorPair(self.white_colour));
+ }
+ self.pieces_window.addstr(format!(
+ "WC: {}",
+ game.query_pieces(Player::White, Stone::Capstone)
+ ));
+
+ self.pieces_window.attrset(Attribute::Normal);
+ self.pieces_window.addstr(" ");
+
+ if self.has_colours {
+ self.pieces_window.attrset(ColorPair(self.black_colour));
+ }
+ self.pieces_window.addstr(format!(
+ "BC: {}",
game.query_pieces(Player::Black, Stone::Capstone),
));
+ self.pieces_window.attrset(Attribute::Normal);
+
+ self.pieces_window.mv(3, 0);
+ self.pieces_window.addstr("Player: ");
+ match game.query_current_player() {
+ Player::Black => {
+ if self.has_colours {
+ self.pieces_window.attrset(ColorPair(self.black_colour));
+ }
+ self.pieces_window.addstr("Black");
+ }
+ Player::White => {
+ if self.has_colours {
+ self.pieces_window.attrset(ColorPair(self.white_colour));
+ }
+ self.pieces_window.addstr("White");
+ }
+ }
+
self.pieces_window.refresh();
+ // Perhaps it's best to avoid clearing things? Overwrite the specific characters instead?
self.action_window.clear();
self.action_window.addstr(game.query_action_lines());
self.action_window.refresh();
@@ -124,7 +193,7 @@ impl BoardGUI {
fn draw_stack(&self, pos: &Position, stack: &Stack) {
if let Some(win) = self.cell_windows.get(self.pos_to_idx(pos)) {
- win.mv(0, 0);
+ win.clear();
let l = stack.len() as i32;
if l > 0 {
for i in 0..l {
@@ -136,19 +205,24 @@ impl BoardGUI {
}
}
- pub fn update(&self, game: &Game) {
+ pub fn update(&self, game: &Game, squares: Vec<Position>) {
// 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.query_square(&pos) {
- self.draw_stack(&pos, stack);
- }
+ // for x in 0..self.size {
+ // for y in 0..self.size {
+ // let pos = Position { x: x, y: y };
+ // if let Some(stack) = game.query_square(&pos) {
+ // self.draw_stack(&pos, stack);
+ // }
+ // }
+ // }
+ for p in squares {
+ if let Some(stack) = game.query_square(&p) {
+ self.draw_stack(&p, stack);
}
}
+ // Prevent redraw of the grid itself?
+ self.board_window.touch();
}
pub fn read_action(&mut self) -> BGAction {
@@ -201,12 +275,14 @@ impl BoardGUI {
white_colour: u8,
black_colour: u8,
) -> BoardGUI {
- let mut cell_windows: Vec<Window> = Vec::new();
-
let hoff = hoff as i32;
let s: i32 = size as i32;
let h: i32 = cell_height as i32;
let w: i32 = cell_width as i32;
+
+ let board_window = newwin(h * s + 3, w * s + 3 + hoff, ypos, xpos);
+
+ let mut cell_windows: Vec<Window> = Vec::new();
for i in 0..s * s {
let y: i32 = i / s;
let x: i32 = i % s;
@@ -214,7 +290,7 @@ impl BoardGUI {
}
let bg = BoardGUI {
- board_window: newwin(h * s + 3, w * s + 3 + hoff, ypos, xpos),
+ board_window: board_window,
cell_windows: cell_windows,
size: size,
cell_height: cell_height,
diff --git a/src/main.rs b/src/main.rs
index 2100073..6bbe52d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -36,7 +36,7 @@ fn main() {
let board_bottom = (size * cell_height + 3) as i32;
let board_right = (size * cell_width + hoff + 3) as i32;
- let mut board_gui = BoardGUI::new(
+ let board_gui = BoardGUI::new(
0,
0,
size,
@@ -48,7 +48,15 @@ fn main() {
black_colour,
);
- let game_log = GameLog::new(0, board_right, height, 20);
+ let game_log = GameLog::new(
+ 0,
+ board_right,
+ height,
+ 20,
+ has_colours,
+ white_colour,
+ black_colour,
+ );
// Need room for '8___011111111', for example
let action_entry = ActionEntry::new(board_bottom, 0, 13);
@@ -64,30 +72,37 @@ fn main() {
let (mut game, warning) = Game::new(5, "tslil", "taktician");
message_log.log_error(warning);
-
- let pos = Position { x: 0, y: 0 };
- let action = Action::Place(Player::White, pos, Stone::Flat);
-
- if let Err(e) = game.perform_action(action) {
- message_log.log_error(e);
- }
-
- board_gui.update(&game);
game_log.update(&game);
loop {
let inp = action_entry.read_action();
- match parse_action(game.query_current_player(), &inp) {
- Ok(act) => message_log.log_error(format!("{}", act)),
+ let stone_owner = game.query_stone_owner();
+ let player = game.query_current_player();
+ match parse_action(stone_owner, &inp) {
Err(err) => message_log.log_error(format!("Input \"{}\": {}", inp, err)),
+ Ok(act) => {
+ message_log.log_message(format!(
+ "{} performs {}{}",
+ player,
+ act,
+ if stone_owner != player {
+ format!(" with a {} stone.", stone_owner)
+ } else {
+ String::from(".")
+ }
+ ));
+ match game.perform_action(act) {
+ Err(e) => message_log.log_error(e),
+ Ok(pos_vec) => {
+ board_gui.update(&game, pos_vec);
+ game_log.update(&game);
+ }
+ }
+ }
}
action_entry.clear_entry_area();
}
- // 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);
-
// board_gui.read_action();
// endwin();
}
diff --git a/src/parser.rs b/src/parser.rs
index 346ecb2..a1d9a32 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -116,9 +116,11 @@ fn parse_place(player: Player, pl: &str) -> Result<Action, String> {
}
if (x < 'a') || (x > 'h') {
- return Err(String::from(
- "Placements must specify a valid position on the game board.",
- ));
+ return Err(if (x >= 'A') && (x <= 'Z') {
+ format!("Unrecognised stone type '{}' in placement.", x)
+ } else {
+ String::from("Placements must specify a valid position on the game board.")
+ });
}
let x: u8 = x as u8 - 'a' as u8;