adding docs, adj/jumpable direction funcs, starting can move
This commit is contained in:
parent
3734d68a64
commit
74708f147f
@ -52,6 +52,10 @@ impl Display for SquareState {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Moveable {
|
||||
Allowed = 0,
|
||||
Occupied = 1,
|
||||
OutOfBounds = 2,
|
||||
UnoccupiedSrc = 1,
|
||||
OccupiedDest = 2,
|
||||
OutOfBounds = 3,
|
||||
Unplayable = 4,
|
||||
WrongTeamSrc = 5,
|
||||
IllegalTrajectory = 6,
|
||||
}
|
214
src/board/mod.rs
214
src/board/mod.rs
@ -1,3 +1,4 @@
|
||||
//! Board module for components related to the checkers board and game structure
|
||||
|
||||
pub mod enums;
|
||||
use enums::*;
|
||||
@ -11,9 +12,12 @@ use std::option::Option;
|
||||
extern crate wasm_bindgen;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// Standard width of a checkers board is 8 squares
|
||||
pub const STD_WIDTH: usize = 8;
|
||||
/// Standard height of a checkers board is 8 squares
|
||||
pub const STD_HEIGHT: usize = 8;
|
||||
|
||||
/// Model a game piece by its team and strength (normal or kinged)
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct Piece {
|
||||
@ -29,15 +33,25 @@ impl Piece {
|
||||
}
|
||||
}
|
||||
|
||||
/// Model the standard diagonal movements by north west/east etc
|
||||
///
|
||||
/// Used as an absolute measure, i.e. not relative to the team making a move
|
||||
///
|
||||
/// Use options for when movements are blocked/unallowed contextually
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct Direction<T: Clone + Copy> {
|
||||
/// North West
|
||||
nw: Option<T>,
|
||||
/// North East
|
||||
ne: Option<T>,
|
||||
/// South East
|
||||
se: Option<T>,
|
||||
/// South West
|
||||
sw: Option<T>,
|
||||
}
|
||||
|
||||
impl<T: Clone + Copy> Direction<T> {
|
||||
/// Create an empty direction full of [`Option::None`]
|
||||
pub fn empty() -> Direction<T> {
|
||||
Direction {
|
||||
nw: Option::None,
|
||||
@ -48,10 +62,13 @@ impl<T: Clone + Copy> Direction<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Model board squares by a state and a possible occupying game piece
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct Square {
|
||||
/// Game piece if square is occupied
|
||||
occupant: Option<Piece>,
|
||||
/// Description of whether the square is occupied or an unplayable, i.e. off-lattice square
|
||||
state: SquareState
|
||||
}
|
||||
|
||||
@ -64,6 +81,7 @@ impl Square {
|
||||
}
|
||||
}
|
||||
|
||||
/// Model a rank 2 tensor index to identify a board square by row and column
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct BrdIdx {
|
||||
@ -80,6 +98,12 @@ impl BrdIdx {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for BrdIdx {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "({}, {})", self.row, self.col)
|
||||
}
|
||||
}
|
||||
|
||||
///////////////
|
||||
// BOARD
|
||||
///////////////
|
||||
@ -88,6 +112,7 @@ impl BrdIdx {
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone)]
|
||||
pub struct Board {
|
||||
/// 1D backing array of board squares for the 2D game board
|
||||
cells: Vec<Square>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
@ -96,10 +121,18 @@ pub struct Board {
|
||||
}
|
||||
|
||||
impl Board {
|
||||
/// Get a mutable reference to a board square by 1D array index
|
||||
pub fn cell_mut(&mut self, idx: usize) -> &mut Square {
|
||||
&mut self.cells[idx]
|
||||
}
|
||||
|
||||
/// Get the 1D array indices for the diagonally adjacent squares of a given board square
|
||||
///
|
||||
/// # Returns
|
||||
/// [`None`]: If the given square is unplayable
|
||||
///
|
||||
/// Some(Vec<usize>): A variable length vector of 1D indices for diagonally adjacent squares.
|
||||
/// Vector may be between 1 and 4 items long depending on the location of the given square
|
||||
pub fn diagonal_indices(&self, idx: BrdIdx) -> Option<Vec<usize>> {
|
||||
if self.cell_state(self.cell_idx(idx)) == SquareState::Unplayable {
|
||||
return None;
|
||||
@ -109,21 +142,18 @@ impl Board {
|
||||
let width_idx = self.width - 1;
|
||||
|
||||
let mut cells = Vec::with_capacity(4);
|
||||
let mut dir = Direction::empty();
|
||||
|
||||
if idx.row > 0 {
|
||||
if idx.col > 0 {
|
||||
cells.push(
|
||||
self.cell_index(idx.row - 1, idx.col - 1)
|
||||
);
|
||||
dir.nw = Option::Some(self.cell(self.cell_index(idx.row - 1, idx.col - 1)));
|
||||
}
|
||||
|
||||
if idx.col < width_idx {
|
||||
cells.push(
|
||||
self.cell_index(idx.row - 1, idx.col + 1)
|
||||
);
|
||||
dir.ne = Option::Some(self.cell(self.cell_index(idx.row - 1, idx.col + 1)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,22 +162,61 @@ impl Board {
|
||||
cells.push(
|
||||
self.cell_index(idx.row + 1, idx.col - 1)
|
||||
);
|
||||
dir.sw = Option::Some(self.cell(self.cell_index(idx.row + 1, idx.col - 1)));
|
||||
}
|
||||
|
||||
if idx.col < width_idx {
|
||||
cells.push(
|
||||
self.cell_index(idx.row + 1, idx.col + 1)
|
||||
);
|
||||
dir.se = Option::Some(self.cell(self.cell_index(idx.row + 1, idx.col + 1)));
|
||||
}
|
||||
}
|
||||
|
||||
cells.shrink_to_fit();
|
||||
Some(cells)
|
||||
// Some(dir)
|
||||
}
|
||||
|
||||
/// Get a [`Direction`] structure for the diagonally adjacent squares of a given board square
|
||||
///
|
||||
/// Similar to the [`Board::diagonal_indices`] function with differently formatted results
|
||||
///
|
||||
/// # Returns
|
||||
/// [`None`]: If the given square is unplayable
|
||||
///
|
||||
/// Some(Direction<Square>): A [`Direction`] structure for the diagonally adjacent squares.
|
||||
pub fn adjacent_dir(&self, idx: BrdIdx) -> Option<Direction<Square>> {
|
||||
if self.cell_state(self.cell_idx(idx)) == SquareState::Unplayable {
|
||||
return None;
|
||||
}
|
||||
|
||||
let height_idx = self.height - 1;
|
||||
let width_idx = self.width - 1;
|
||||
|
||||
let mut dir = Direction::empty();
|
||||
|
||||
if idx.row > 0 {
|
||||
if idx.col > 0 {
|
||||
dir.nw = Option::Some(self.cell(self.cell_index(idx.row - 1, idx.col - 1)));
|
||||
}
|
||||
|
||||
if idx.col < width_idx {
|
||||
dir.ne = Option::Some(self.cell(self.cell_index(idx.row - 1, idx.col + 1)));
|
||||
}
|
||||
}
|
||||
|
||||
if idx.row < height_idx {
|
||||
if idx.col > 0 {
|
||||
dir.sw = Option::Some(self.cell(self.cell_index(idx.row + 1, idx.col - 1)));
|
||||
}
|
||||
|
||||
if idx.col < width_idx {
|
||||
dir.se = Option::Some(self.cell(self.cell_index(idx.row + 1, idx.col + 1)));
|
||||
}
|
||||
}
|
||||
|
||||
Some(dir)
|
||||
}
|
||||
|
||||
/// Filter an array of diagonal indices (Like those from [`Board::diagonal_indices`], [`Board::jumpable_indices`]) for those that are legal for a team's man, i.e. solely up or down the board
|
||||
pub fn filter_indices(&self, idx: BrdIdx, player: Team, indices: Vec<usize>) -> Vec<usize> {
|
||||
indices.into_iter().filter(|i| {
|
||||
match player {
|
||||
@ -164,6 +233,13 @@ impl Board {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the 1D array indices for the diagonally jumpable squares of a given board square
|
||||
///
|
||||
/// # Returns
|
||||
/// [`None`]: If the given square is unplayable
|
||||
///
|
||||
/// Some(Vec<usize>): A variable length vector of 1D indices for diagonally jumpable squares.
|
||||
/// Vector may be between 1 and 4 items long depending on the location of the given square
|
||||
pub fn jumpable_indices(&self, idx: BrdIdx) -> Option<Vec<usize>> {
|
||||
if self.cell_state(self.cell_idx(idx)) == SquareState::Unplayable {
|
||||
return None;
|
||||
@ -206,6 +282,47 @@ impl Board {
|
||||
Some(cells)
|
||||
}
|
||||
|
||||
/// Get a [`Direction`] structure for the diagonally jumpable squares of a given board square
|
||||
///
|
||||
/// Similar to the [`Board::jumpable_indices`] function with differently formatted results
|
||||
///
|
||||
/// # Returns
|
||||
/// [`None`]: If the given square is unplayable
|
||||
///
|
||||
/// Some(Direction<Square>): A [`Direction`] structure for the diagonally jumpable squares.
|
||||
pub fn jumpable_dir(&self, idx: BrdIdx) -> Option<Direction<Square>> {
|
||||
if self.cell_state(self.cell_idx(idx)) == SquareState::Unplayable {
|
||||
return None;
|
||||
}
|
||||
|
||||
let height_idx = self.height - 1;
|
||||
let width_idx = self.width - 1;
|
||||
|
||||
let mut dir = Direction::empty();
|
||||
|
||||
if idx.row > 1 {
|
||||
if idx.col > 1 {
|
||||
dir.nw = Option::Some(self.cell(self.cell_index(idx.row - 2, idx.col - 2)));
|
||||
}
|
||||
|
||||
if idx.col < width_idx - 1 {
|
||||
dir.ne = Option::Some(self.cell(self.cell_index(idx.row - 2, idx.col + 2)));
|
||||
}
|
||||
}
|
||||
|
||||
if idx.row < height_idx - 1 {
|
||||
if idx.col > 1 {
|
||||
dir.sw = Option::Some(self.cell(self.cell_index(idx.row + 2, idx.col - 2)));
|
||||
}
|
||||
|
||||
if idx.col < width_idx - 1 {
|
||||
dir.se = Option::Some(self.cell(self.cell_index(idx.row + 2, idx.col + 2)));
|
||||
}
|
||||
}
|
||||
|
||||
Some(dir)
|
||||
}
|
||||
|
||||
pub fn player_jumpable_indices(&self, idx: BrdIdx, player: Team) -> Option<Vec<usize>> {
|
||||
match self.jumpable_indices(idx) {
|
||||
Some(x) => Some(self.filter_indices(idx, player, x)),
|
||||
@ -216,32 +333,98 @@ impl Board {
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Board {
|
||||
/// Get a copy of a board square by 1D array index
|
||||
pub fn cell(&self, idx: usize) -> Square {
|
||||
self.cells[idx]
|
||||
}
|
||||
|
||||
/// Get a copy of a board square by 2D [`BrdIdx`] index
|
||||
pub fn grid_cell(&self, idx: BrdIdx) -> Square {
|
||||
self.cell(self.cell_idx(idx))
|
||||
}
|
||||
|
||||
/// Transform a 2D row/column board index into a single 1D index for use with [`Board::cells`]
|
||||
pub fn cell_index(&self, row: usize, col: usize) -> usize {
|
||||
(row * self.width) + col
|
||||
}
|
||||
|
||||
/// Similar to [`Board::cell_index`] but with a [`BrdIdx`] instead of separate indices. Transform a 2D row/column board index into a single 1D index for use with [`Board::cells`]
|
||||
pub fn cell_idx(&self, idx: BrdIdx) -> usize {
|
||||
self.cell_index(idx.row, idx.col)
|
||||
}
|
||||
|
||||
/// Transform a 1D array index (for [`Board::cells`]) into a 2D game board index (by row/col)
|
||||
pub fn board_index(&self, idx: usize) -> BrdIdx {
|
||||
let row = idx / self.width;
|
||||
let col = idx - (row * self.width);
|
||||
BrdIdx::from(row, col)
|
||||
}
|
||||
|
||||
// pub fn can_move(&self, from: BrdIdx, to: BrdIdx) -> bool {
|
||||
// let diagonals = self.diagonal_indices(from);
|
||||
pub fn can_move(&self, from: BrdIdx, to: BrdIdx) -> Moveable {
|
||||
|
||||
if from.row > self.height - 1 || from.col > self.width - 1 {
|
||||
return Moveable::OutOfBounds;
|
||||
}
|
||||
|
||||
let from_square = self.cell(self.cell_idx(from));
|
||||
|
||||
match from_square.state {
|
||||
Empty => return Moveable::UnoccupiedSrc,
|
||||
Unplayable => return Moveable::Unplayable,
|
||||
Occupied => {
|
||||
|
||||
// if its not the current teams piece then error
|
||||
match from_square.occupant {
|
||||
None => panic!("Square is apparently occupied, but no occupant was found from: {}, to: {}, square: {:?}", from, to, from_square),
|
||||
Some(x) => {
|
||||
|
||||
// piece in the source square is not for the current turn's player
|
||||
if x.team != self.current_turn {
|
||||
return Moveable::WrongTeamSrc;
|
||||
}
|
||||
|
||||
// TODO: refactor to a IsMove()/IsJump() to check whether the move has a legal trajectory
|
||||
match x.strength {
|
||||
Man => {
|
||||
match self.current_turn {
|
||||
Black => {
|
||||
|
||||
},
|
||||
White => {
|
||||
|
||||
},
|
||||
};
|
||||
},
|
||||
King => {
|
||||
match self.current_turn {
|
||||
Black => {
|
||||
|
||||
},
|
||||
White => {
|
||||
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
// let diagonal = self.adjacent_dir(from);
|
||||
// let allowable_squares = Vec::with_capacity(4);
|
||||
|
||||
let jumpable = self.jumpable_dir(from);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// let is_adjacent = match self.current_turn {
|
||||
// Team::Black => diagonal.nw,
|
||||
// Team::White => {},
|
||||
// }
|
||||
|
||||
Moveable::Allowed
|
||||
}
|
||||
|
||||
/// Iniitalise a game board without game pieces
|
||||
pub fn new(width: usize, height: usize) -> Board {
|
||||
let total_cells = width * height;
|
||||
|
||||
@ -270,6 +453,7 @@ impl Board {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset the given board to a starting layout with 3 rows of opposing pieces
|
||||
pub fn init_game(board: Board) -> Board {
|
||||
let mut new_board = board.clone();
|
||||
for (idx, row) in RowSquareIterator::new(&board).enumerate() {
|
||||
@ -302,18 +486,22 @@ impl Board {
|
||||
new_board
|
||||
}
|
||||
|
||||
/// Get the [`Board::current_turn`] parameter
|
||||
pub fn current_turn(&self) -> Team {
|
||||
self.current_turn
|
||||
}
|
||||
|
||||
/// Get a pointer to the backing array of board squares, [`Board::cells`]
|
||||
pub fn cells(&self) -> *const Square {
|
||||
self.cells.as_ptr()
|
||||
}
|
||||
|
||||
/// Get the number of board squares
|
||||
pub fn num_cells(&self) -> usize {
|
||||
self.cells.len()
|
||||
}
|
||||
|
||||
/// Get the state of a board square by 1D array index
|
||||
pub fn cell_state(&self, idx: usize) -> SquareState {
|
||||
self.cell(idx).state
|
||||
}
|
||||
@ -524,11 +712,11 @@ mod tests {
|
||||
assert_eq!(Some(vec![42]), board.jumpable_indices(BrdIdx::from(7, 0)));
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn init_game() {
|
||||
let board = Board::init_game(Board::new(8, 8));
|
||||
log!("{}", board);
|
||||
}
|
||||
// #[wasm_bindgen_test]
|
||||
// fn init_game() {
|
||||
// let board = Board::init_game(Board::new(8, 8));
|
||||
// log!("{}", board);
|
||||
// }
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn black_diagonal_indices() {
|
||||
|
@ -4,6 +4,7 @@ use indextree::Arena;
|
||||
extern crate wasm_bindgen;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// Root-level structure for managing the game as a collection of board states
|
||||
#[wasm_bindgen]
|
||||
pub struct Game {
|
||||
current: Board,
|
||||
|
@ -1,3 +1,7 @@
|
||||
//! Draught
|
||||
//!
|
||||
//! An implementation of checkers/draughts in Rust WebAssembly with a minimax AI player
|
||||
|
||||
pub mod board;
|
||||
pub mod utils;
|
||||
pub mod game;
|
||||
@ -5,12 +9,16 @@ pub mod game;
|
||||
extern crate wasm_bindgen;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
pub use board::Board;
|
||||
pub use game::Game;
|
||||
|
||||
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
|
||||
// allocator.
|
||||
// #[cfg(feature = "wee_alloc")]
|
||||
// #[global_allocator]
|
||||
// static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||
|
||||
/// Wrap the [`web_sys`] access to the browser console in a macro for easy logging
|
||||
#[macro_export]
|
||||
macro_rules! log {
|
||||
( $( $t:tt )* ) => {
|
||||
|
Loading…
Reference in New Issue
Block a user