beginning computer
This commit is contained in:
parent
8dbc2eb586
commit
74a2757df9
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
|||||||
run: wasm-pack test --firefox --chrome --headless
|
run: wasm-pack test --firefox --chrome --headless
|
||||||
|
|
||||||
- name: Build Rust for WASM
|
- name: Build Rust for WASM
|
||||||
run: wasm-pack build
|
run: wasm-pack build --release
|
||||||
|
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
|
@ -3,6 +3,14 @@ use wasm_bindgen::prelude::*;
|
|||||||
|
|
||||||
use std::fmt::{Display, Write};
|
use std::fmt::{Display, Write};
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum MoveType {
|
||||||
|
Move = 0,
|
||||||
|
Jump = 1,
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
263
src/board/mod.rs
263
src/board/mod.rs
@ -26,8 +26,8 @@ pub const STD_HEIGHT: usize = 8;
|
|||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub struct Piece {
|
pub struct Piece {
|
||||||
team: Team,
|
pub team: Team,
|
||||||
strength: Strength
|
pub strength: Strength
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
@ -74,13 +74,14 @@ impl<T: Clone + Copy> Direction<T> {
|
|||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub struct Square {
|
pub struct Square {
|
||||||
/// Game piece if square is occupied
|
/// Game piece if square is occupied
|
||||||
occupant: Option<Piece>,
|
pub occupant: Option<Piece>,
|
||||||
/// Description of whether the square is occupied or an unplayable, i.e. off-lattice square
|
/// Description of whether the square is occupied or an unplayable, i.e. off-lattice square
|
||||||
state: SquareState
|
pub state: SquareState
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl Square {
|
impl Square {
|
||||||
|
/// Standard constructor function to form square from state and occupant
|
||||||
#[wasm_bindgen(constructor)]
|
#[wasm_bindgen(constructor)]
|
||||||
pub fn new(state: SquareState, occupant: Option<Piece>) -> Square{
|
pub fn new(state: SquareState, occupant: Option<Piece>) -> Square{
|
||||||
Square {
|
Square {
|
||||||
@ -89,6 +90,7 @@ impl Square {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function for a well-formed piece square by team and strength
|
||||||
pub fn pc(team: Team, strength: Strength) -> Square {
|
pub fn pc(team: Team, strength: Strength) -> Square {
|
||||||
Square {
|
Square {
|
||||||
occupant: Some(Piece::new(team, strength)),
|
occupant: Some(Piece::new(team, strength)),
|
||||||
@ -96,12 +98,21 @@ impl Square {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function for a well-formed empty square
|
||||||
pub fn empty() -> Square {
|
pub fn empty() -> Square {
|
||||||
Square {
|
Square {
|
||||||
occupant: None,
|
occupant: None,
|
||||||
state: Empty,
|
state: Empty,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function for a well-formed unplayable square
|
||||||
|
pub fn unplay() -> Square {
|
||||||
|
Square {
|
||||||
|
occupant: None,
|
||||||
|
state: Unplayable,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rank 2 tensor index to identify a board square by row and column
|
/// Rank 2 tensor index to identify a board square by row and column
|
||||||
@ -133,19 +144,19 @@ impl Display for BrdIdx {
|
|||||||
|
|
||||||
/// Single state of a checkers board
|
/// Single state of a checkers board
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct Board {
|
pub struct Board {
|
||||||
/// 1D backing array of board squares for the 2D game board
|
/// 1D backing array of board squares for the 2D game board
|
||||||
cells: Vec<Square>,
|
cells: Vec<Square>,
|
||||||
width: usize,
|
pub width: usize,
|
||||||
height: usize,
|
pub height: usize,
|
||||||
|
|
||||||
current_turn: Team
|
pub current_turn: Team
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////
|
////////////////////
|
||||||
// PRIV FUNCS
|
// UNBOUND FUNCS
|
||||||
///////////////////
|
////////////////////
|
||||||
|
|
||||||
impl Board {
|
impl Board {
|
||||||
/// Get a mutable reference to a board square by 1D array index
|
/// Get a mutable reference to a board square by 1D array index
|
||||||
@ -160,7 +171,7 @@ impl Board {
|
|||||||
///
|
///
|
||||||
/// Some(Vec<usize>): A variable length vector of 1D indices for diagonally adjacent squares.
|
/// 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
|
/// 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>> {
|
pub fn adjacent_indices(&self, idx: BrdIdx) -> Option<Vec<usize>> {
|
||||||
if self.cell_state(self.cell_idx(idx)) == Unplayable {
|
if self.cell_state(self.cell_idx(idx)) == Unplayable {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@ -204,7 +215,7 @@ impl Board {
|
|||||||
|
|
||||||
/// Get a [`Direction`] structure for the diagonally adjacent squares of a given board square
|
/// 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
|
/// Similar to the [`Board::adjacent_indices`] function with differently formatted results
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// [`None`]: If the given square is unplayable
|
/// [`None`]: If the given square is unplayable
|
||||||
@ -243,7 +254,7 @@ impl Board {
|
|||||||
Some(dir)
|
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
|
/// Filter an array of diagonal indices (Like those from [`Board::adjacent_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> {
|
pub fn filter_indices(&self, idx: BrdIdx, player: Team, indices: Vec<usize>) -> Vec<usize> {
|
||||||
indices.into_iter().filter(|i| {
|
indices.into_iter().filter(|i| {
|
||||||
match player {
|
match player {
|
||||||
@ -253,8 +264,8 @@ impl Board {
|
|||||||
}).collect()
|
}).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn player_diagonal_indices(&self, idx: BrdIdx, player: Team) -> Option<Vec<usize>> {
|
pub fn player_adjacent_indices(&self, idx: BrdIdx, player: Team) -> Option<Vec<usize>> {
|
||||||
match self.diagonal_indices(idx) {
|
match self.adjacent_indices(idx) {
|
||||||
Some(x) => Some(self.filter_indices(idx, player, x)),
|
Some(x) => Some(self.filter_indices(idx, player, x)),
|
||||||
None => None
|
None => None
|
||||||
}
|
}
|
||||||
@ -357,7 +368,7 @@ impl Board {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// cast the signed
|
/// Get the difference between two [`BrdIdx`] objects
|
||||||
pub fn idx_diffs(from: BrdIdx, to: BrdIdx) -> (isize, isize) {
|
pub fn idx_diffs(from: BrdIdx, to: BrdIdx) -> (isize, isize) {
|
||||||
// cast to signed ints so that -1 will work for black moves
|
// cast to signed ints so that -1 will work for black moves
|
||||||
(to.row as isize - from.row as isize, to.col as isize - from.col as isize)
|
(to.row as isize - from.row as isize, to.col as isize - from.col as isize)
|
||||||
@ -572,93 +583,6 @@ impl Board {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unwrap the jumpee piece from the square and [`Board::check_jumpee_team`] with [`Moveable`] response
|
|
||||||
pub fn validate_jumpee(jumpee: Square, from_occ: Piece) -> Moveable {
|
|
||||||
// check whether jumpee is an opponent's piece
|
|
||||||
match jumpee.occupant {
|
|
||||||
None => panic!("No occupant found when checking the from: {:?} , jumpee: {:?}", from_occ, jumpee),
|
|
||||||
Some(jumpee_occupant_uw) => {
|
|
||||||
if Board::check_jumpee_team(from_occ, jumpee_occupant_uw) {
|
|
||||||
return Moveable::Allowed;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return Moveable::JumpingSameTeam;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check that the source piece and the jumpee are of opposing teams
|
|
||||||
pub fn check_jumpee_team(from: Piece, jumpee: Piece) -> bool {
|
|
||||||
return from.team.opponent() == jumpee.team
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Initialise a game board without game pieces
|
|
||||||
#[wasm_bindgen(constructor)]
|
|
||||||
pub fn new(width: usize, height: usize, current_turn: Team) -> Board {
|
|
||||||
let total_cells = width * height;
|
|
||||||
|
|
||||||
let mut cells: Vec<Square> = Vec::with_capacity(total_cells);
|
|
||||||
let mut playable = false;
|
|
||||||
|
|
||||||
for i in 0..height {
|
|
||||||
for _ in 0..width {
|
|
||||||
if playable {
|
|
||||||
cells.push(Square::new(Empty, None));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
cells.push(Square::new(Unplayable, None));
|
|
||||||
}
|
|
||||||
playable = !playable;
|
|
||||||
}
|
|
||||||
playable = i % 2 == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Board {
|
|
||||||
cells,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
|
|
||||||
current_turn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reset the given board to a starting layout with 3 rows of opposing pieces
|
|
||||||
pub fn init_game(board: Board, piece_rows: usize) -> Board {
|
|
||||||
let mut new_board = board.clone();
|
|
||||||
for (idx, row) in RowSquareIterator::new(&board).enumerate() {
|
|
||||||
for (jdx, square) in row.iter().enumerate() {
|
|
||||||
|
|
||||||
if square.state == Empty || square.state == Occupied {
|
|
||||||
if idx < piece_rows {
|
|
||||||
let cell_idx = new_board.cell_index(idx, jdx);
|
|
||||||
new_board.cells[cell_idx] = Square::pc(White, Man);
|
|
||||||
} else if idx >= board.height - piece_rows {
|
|
||||||
let cell_idx = new_board.cell_index(idx, jdx);
|
|
||||||
new_board.cells[cell_idx] = Square::pc(Black, Man);
|
|
||||||
} else {
|
|
||||||
let cell_idx = new_board.cell_index(idx, jdx);
|
|
||||||
new_board.cells[cell_idx] = Square::new(
|
|
||||||
Empty,
|
|
||||||
None
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
new_board
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the [`Board::current_turn`] parameter
|
|
||||||
pub fn current_turn(&self) -> Team {
|
|
||||||
self.current_turn
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_turn(&mut self, new_team: Team) {
|
|
||||||
self.current_turn = new_team;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a pointer to the backing array of board squares, [`Board::cells`]
|
/// Get a pointer to the backing array of board squares, [`Board::cells`]
|
||||||
pub fn cells(&self) -> *const Square {
|
pub fn cells(&self) -> *const Square {
|
||||||
self.cells.as_ptr()
|
self.cells.as_ptr()
|
||||||
@ -716,6 +640,137 @@ impl Board {
|
|||||||
pub fn cell_state(&self, idx: usize) -> SquareState {
|
pub fn cell_state(&self, idx: usize) -> SquareState {
|
||||||
self.cell(idx).state
|
self.cell(idx).state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn apply_move(&self, from: BrdIdx, to: BrdIdx) -> Board {
|
||||||
|
let mut new = self.clone();
|
||||||
|
|
||||||
|
let from_idx = self.cell_idx(from);
|
||||||
|
let to_idx = self.cell_idx(to);
|
||||||
|
|
||||||
|
// make move update
|
||||||
|
new.set_cell(
|
||||||
|
to_idx, // destination square
|
||||||
|
self.cell(from_idx) // source piece
|
||||||
|
);
|
||||||
|
|
||||||
|
// remove old piece
|
||||||
|
new.set_cell(
|
||||||
|
from_idx, // destination square
|
||||||
|
Square::empty() // empty piece
|
||||||
|
);
|
||||||
|
|
||||||
|
new
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_jump(&self, from: BrdIdx, to: BrdIdx) -> Board {
|
||||||
|
let mut new = self.clone();
|
||||||
|
|
||||||
|
let from_idx = self.cell_idx(from);
|
||||||
|
let to_idx = self.cell_idx(to);
|
||||||
|
|
||||||
|
// make move update
|
||||||
|
new.set_cell(
|
||||||
|
to_idx, // destination square
|
||||||
|
self.cell(from_idx) // source piece
|
||||||
|
);
|
||||||
|
|
||||||
|
// remove old piece
|
||||||
|
new.set_cell(
|
||||||
|
from_idx, // destination square
|
||||||
|
Square::empty() // empty piece
|
||||||
|
);
|
||||||
|
|
||||||
|
// remove jumpee
|
||||||
|
new.set_cell(
|
||||||
|
self.jumpee_idx(from, to), // destination square
|
||||||
|
Square::empty() // empty piece
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
new
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////
|
||||||
|
// BOUND TYPE FUNCS
|
||||||
|
/////////////////////////
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl Board {
|
||||||
|
/// Unwrap the jumpee piece from the square and [`Board::check_jumpee_team`] with [`Moveable`] response
|
||||||
|
pub fn validate_jumpee(jumpee: Square, from_occ: Piece) -> Moveable {
|
||||||
|
// check whether jumpee is an opponent's piece
|
||||||
|
match jumpee.occupant {
|
||||||
|
None => panic!("No occupant found when checking the from: {:?} , jumpee: {:?}", from_occ, jumpee),
|
||||||
|
Some(jumpee_occupant_uw) => {
|
||||||
|
if Board::check_jumpee_team(from_occ, jumpee_occupant_uw) {
|
||||||
|
return Moveable::Allowed;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Moveable::JumpingSameTeam;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check that the source piece and the jumpee are of opposing teams
|
||||||
|
pub fn check_jumpee_team(from: Piece, jumpee: Piece) -> bool {
|
||||||
|
return from.team.opponent() == jumpee.team
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialise a game board without game pieces
|
||||||
|
#[wasm_bindgen(constructor)]
|
||||||
|
pub fn new(width: usize, height: usize, current_turn: Team) -> Board {
|
||||||
|
let total_cells = width * height;
|
||||||
|
|
||||||
|
let mut cells: Vec<Square> = Vec::with_capacity(total_cells);
|
||||||
|
let mut playable = false;
|
||||||
|
|
||||||
|
for i in 0..height {
|
||||||
|
for _ in 0..width {
|
||||||
|
if playable {
|
||||||
|
cells.push(Square::empty());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cells.push(Square::unplay());
|
||||||
|
}
|
||||||
|
playable = !playable;
|
||||||
|
}
|
||||||
|
playable = i % 2 == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Board {
|
||||||
|
cells,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
|
||||||
|
current_turn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset the given board to a starting layout with 3 rows of opposing pieces
|
||||||
|
pub fn init_game(board: Board, piece_rows: usize) -> Board {
|
||||||
|
let mut new_board = board.clone();
|
||||||
|
for (idx, row) in RowSquareIterator::new(&board).enumerate() {
|
||||||
|
for (jdx, square) in row.iter().enumerate() {
|
||||||
|
|
||||||
|
if square.state == Empty || square.state == Occupied {
|
||||||
|
if idx < piece_rows {
|
||||||
|
let cell_idx = new_board.cell_index(idx, jdx);
|
||||||
|
new_board.cells[cell_idx] = Square::pc(White, Man);
|
||||||
|
} else if idx >= board.height - piece_rows {
|
||||||
|
let cell_idx = new_board.cell_index(idx, jdx);
|
||||||
|
new_board.cells[cell_idx] = Square::pc(Black, Man);
|
||||||
|
} else {
|
||||||
|
let cell_idx = new_board.cell_index(idx, jdx);
|
||||||
|
new_board.cells[cell_idx] = Square::empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new_board
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Board {
|
impl Display for Board {
|
||||||
|
@ -43,7 +43,7 @@ fn set_cell() {
|
|||||||
let idx = 1;
|
let idx = 1;
|
||||||
|
|
||||||
let mut board = Board::new(8, 8, Black);
|
let mut board = Board::new(8, 8, Black);
|
||||||
let square = Square::new(Occupied, Some(Piece::new(White, Man)));
|
let square = Square::pc(White, Man);
|
||||||
|
|
||||||
board.set_cell(idx, square);
|
board.set_cell(idx, square);
|
||||||
assert_eq!(square, board.cell(idx));
|
assert_eq!(square, board.cell(idx));
|
||||||
@ -106,51 +106,51 @@ fn first_square_row_5_unplayable() {
|
|||||||
#[wasm_bindgen_test]
|
#[wasm_bindgen_test]
|
||||||
fn moveable_indices_unplayable() {
|
fn moveable_indices_unplayable() {
|
||||||
let board = Board::new(8, 8, Black);
|
let board = Board::new(8, 8, Black);
|
||||||
assert_eq!(None, board.diagonal_indices(BrdIdx::from(7, 7)));
|
assert_eq!(None, board.adjacent_indices(BrdIdx::from(7, 7)));
|
||||||
assert_eq!(None, board.diagonal_indices(BrdIdx::from(0, 0)));
|
assert_eq!(None, board.adjacent_indices(BrdIdx::from(0, 0)));
|
||||||
assert_eq!(None, board.diagonal_indices(BrdIdx::from(1, 1)));
|
assert_eq!(None, board.adjacent_indices(BrdIdx::from(1, 1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
#[wasm_bindgen_test]
|
||||||
fn moveable_indices_central() {
|
fn moveable_indices_central() {
|
||||||
let board = Board::new(8, 8, Black);
|
let board = Board::new(8, 8, Black);
|
||||||
assert_eq!(Some(vec![1, 3, 17, 19]), board.diagonal_indices(BrdIdx::from(1, 2)));
|
assert_eq!(Some(vec![1, 3, 17, 19]), board.adjacent_indices(BrdIdx::from(1, 2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
#[wasm_bindgen_test]
|
||||||
fn moveable_indices_top_row() {
|
fn moveable_indices_top_row() {
|
||||||
let board = Board::new(8, 8, Black);
|
let board = Board::new(8, 8, Black);
|
||||||
assert_eq!(Some(vec![8, 10]), board.diagonal_indices(BrdIdx::from(0, 1)));
|
assert_eq!(Some(vec![8, 10]), board.adjacent_indices(BrdIdx::from(0, 1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
#[wasm_bindgen_test]
|
||||||
fn moveable_indices_left_column() {
|
fn moveable_indices_left_column() {
|
||||||
let board = Board::new(8, 8, Black);
|
let board = Board::new(8, 8, Black);
|
||||||
assert_eq!(Some(vec![1, 17]), board.diagonal_indices(BrdIdx::from(1, 0)));
|
assert_eq!(Some(vec![1, 17]), board.adjacent_indices(BrdIdx::from(1, 0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
#[wasm_bindgen_test]
|
||||||
fn moveable_indices_bottom_row() {
|
fn moveable_indices_bottom_row() {
|
||||||
let board = Board::new(8, 8, Black);
|
let board = Board::new(8, 8, Black);
|
||||||
assert_eq!(Some(vec![49, 51]), board.diagonal_indices(BrdIdx::from(7, 2)));
|
assert_eq!(Some(vec![49, 51]), board.adjacent_indices(BrdIdx::from(7, 2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
#[wasm_bindgen_test]
|
||||||
fn moveable_indices_right_column() {
|
fn moveable_indices_right_column() {
|
||||||
let board = Board::new(8, 8, Black);
|
let board = Board::new(8, 8, Black);
|
||||||
assert_eq!(Some(vec![14, 30]), board.diagonal_indices(BrdIdx::from(2, 7)));
|
assert_eq!(Some(vec![14, 30]), board.adjacent_indices(BrdIdx::from(2, 7)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
#[wasm_bindgen_test]
|
||||||
fn moveable_indices_top_right() {
|
fn moveable_indices_top_right() {
|
||||||
let board = Board::new(8, 8, Black);
|
let board = Board::new(8, 8, Black);
|
||||||
assert_eq!(Some(vec![14]), board.diagonal_indices(BrdIdx::from(0, 7)));
|
assert_eq!(Some(vec![14]), board.adjacent_indices(BrdIdx::from(0, 7)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
#[wasm_bindgen_test]
|
||||||
fn moveable_indices_bottom_left() {
|
fn moveable_indices_bottom_left() {
|
||||||
let board = Board::new(8, 8, Black);
|
let board = Board::new(8, 8, Black);
|
||||||
assert_eq!(Some(vec![49]), board.diagonal_indices(BrdIdx::from(7, 0)));
|
assert_eq!(Some(vec![49]), board.adjacent_indices(BrdIdx::from(7, 0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////
|
//////////////////////
|
||||||
@ -214,15 +214,15 @@ fn jumpable_indices_bottom_left() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
#[wasm_bindgen_test]
|
||||||
fn black_diagonal_indices() {
|
fn black_adjacent_indices() {
|
||||||
let board = Board::new(8, 8, Black);
|
let board = Board::new(8, 8, Black);
|
||||||
assert_eq!(Some(vec![1, 3]), board.player_diagonal_indices(BrdIdx::from(1, 2), Black));
|
assert_eq!(Some(vec![1, 3]), board.player_adjacent_indices(BrdIdx::from(1, 2), Black));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
#[wasm_bindgen_test]
|
||||||
fn white_diagonal_indices() {
|
fn white_adjacent_indices() {
|
||||||
let board = Board::new(8, 8, Black);
|
let board = Board::new(8, 8, Black);
|
||||||
assert_eq!(Some(vec![17, 19]), board.player_diagonal_indices(BrdIdx::from(1, 2), White));
|
assert_eq!(Some(vec![17, 19]), board.player_adjacent_indices(BrdIdx::from(1, 2), White));
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////
|
////////////////
|
||||||
@ -253,22 +253,12 @@ fn check_jumpee_same_teams() {
|
|||||||
|
|
||||||
#[wasm_bindgen_test]
|
#[wasm_bindgen_test]
|
||||||
fn check_validate_jumpee_opposing_teams() {
|
fn check_validate_jumpee_opposing_teams() {
|
||||||
let jumpee_square = Square::new(
|
let jumpee_square = Square::pc(White, Man);
|
||||||
Occupied,
|
|
||||||
Some(
|
|
||||||
Piece::new(White, Man)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
let from_piece = Piece::new(Black, Man);
|
let from_piece = Piece::new(Black, Man);
|
||||||
|
|
||||||
assert_eq!(Board::validate_jumpee(jumpee_square, from_piece), Moveable::Allowed);
|
assert_eq!(Board::validate_jumpee(jumpee_square, from_piece), Moveable::Allowed);
|
||||||
|
|
||||||
let jumpee_square = Square::new(
|
let jumpee_square = Square::pc(Black, Man);
|
||||||
Occupied,
|
|
||||||
Some(
|
|
||||||
Piece::new(Black, Man)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
let from_piece = Piece::new(White, Man);
|
let from_piece = Piece::new(White, Man);
|
||||||
|
|
||||||
assert_eq!(Board::validate_jumpee(jumpee_square, from_piece), Moveable::Allowed);
|
assert_eq!(Board::validate_jumpee(jumpee_square, from_piece), Moveable::Allowed);
|
||||||
@ -445,7 +435,7 @@ fn can_move() {
|
|||||||
let to = BrdIdx::from(0, 0);
|
let to = BrdIdx::from(0, 0);
|
||||||
assert_eq!(board.can_move(from, to), Moveable::Unplayable);
|
assert_eq!(board.can_move(from, to), Moveable::Unplayable);
|
||||||
|
|
||||||
board.set_turn(Black);
|
board.current_turn = Black;
|
||||||
|
|
||||||
// wrong teams piece
|
// wrong teams piece
|
||||||
let from = BrdIdx::from(2, 1);
|
let from = BrdIdx::from(2, 1);
|
||||||
|
147
src/comp/mod.rs
Normal file
147
src/comp/mod.rs
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
//! AI player logic
|
||||||
|
|
||||||
|
use indextree::{Arena, NodeId, Node};
|
||||||
|
|
||||||
|
extern crate wasm_bindgen;
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
use crate::log;
|
||||||
|
|
||||||
|
use crate::board::{Square, Board, BrdIdx};
|
||||||
|
use crate::board::enums::{SquareState, MoveType, Moveable, Team, Strength};
|
||||||
|
use crate::board::iter::{PieceIterator};
|
||||||
|
|
||||||
|
use Team::*;
|
||||||
|
use Strength::*;
|
||||||
|
use SquareState::*;
|
||||||
|
|
||||||
|
use std::fmt::{Display, Write};
|
||||||
|
|
||||||
|
#[cfg(test)] pub mod tests;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub struct Move {
|
||||||
|
from: BrdIdx,
|
||||||
|
to: BrdIdx,
|
||||||
|
mv_type: MoveType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Move {
|
||||||
|
pub fn new(from: BrdIdx, to: BrdIdx, mv_type: MoveType) -> Move {
|
||||||
|
Move {
|
||||||
|
from, to, mv_type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Root-level structure for managing the game as a collection of board states
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Computer {
|
||||||
|
pub tree: Arena<Board>,
|
||||||
|
pub root_node_id: NodeId,
|
||||||
|
pub search_depth: usize,
|
||||||
|
pub team: Team,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Computer {
|
||||||
|
pub fn new(initial_board: Board, search_depth: usize, team: Team) -> Computer {
|
||||||
|
let mut tree = Arena::new();
|
||||||
|
let root_node_id = tree.new_node(initial_board);
|
||||||
|
Computer {
|
||||||
|
tree,
|
||||||
|
root_node_id,
|
||||||
|
search_depth,
|
||||||
|
team
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_board(&mut self, new_board: Board) {
|
||||||
|
let mut tree = Arena::new();
|
||||||
|
tree.new_node(new_board);
|
||||||
|
self.tree = tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn available_turns(&self, board: &Board) -> Vec<Move> {
|
||||||
|
|
||||||
|
// allocate capacity for 2 moves per piece, likely too much but will be shrunk
|
||||||
|
// to reduce memory re-allocations
|
||||||
|
let mut moves = Vec::with_capacity(board.num_player(board.current_turn) * 2);
|
||||||
|
|
||||||
|
// get all pieces in the board
|
||||||
|
for (idx, square) in PieceIterator::new(board) {
|
||||||
|
|
||||||
|
match square.occupant {
|
||||||
|
None => continue,
|
||||||
|
Some(piece) => {
|
||||||
|
|
||||||
|
// filter for current team's pieces
|
||||||
|
if piece.team == board.current_turn {
|
||||||
|
let from_brd_idx = board.board_index(idx);
|
||||||
|
let adj_op = board.adjacent_indices(from_brd_idx);
|
||||||
|
let jump_op = board.jumpable_indices(from_brd_idx);
|
||||||
|
|
||||||
|
// iterate over adjacent indices
|
||||||
|
if let Some(adj) = adj_op {
|
||||||
|
for i in adj {
|
||||||
|
let to_brd_idx = board.board_index(i);
|
||||||
|
|
||||||
|
// check if can move
|
||||||
|
if board.can_move(from_brd_idx, to_brd_idx) == Moveable::Allowed {
|
||||||
|
moves.push(Move::new(from_brd_idx, to_brd_idx, MoveType::Move));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Unable to unwrap adjacent indices, from: {}, brd: {}", from_brd_idx, board);
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterate over adjacent indices
|
||||||
|
if let Some(jump) = jump_op {
|
||||||
|
for i in jump {
|
||||||
|
let to_brd_idx = board.board_index(i);
|
||||||
|
|
||||||
|
// check if can move
|
||||||
|
if board.can_move(from_brd_idx, to_brd_idx) == Moveable::Allowed {
|
||||||
|
moves.push(Move::new(from_brd_idx, to_brd_idx, MoveType::Jump));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Unable to unwrap adjacent indices, from: {}, brd: {}", from_brd_idx, board);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
moves.shrink_to_fit();
|
||||||
|
moves
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gen_tree(&mut self, board: Board) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_boards(&mut self, boards: Vec<Board>) -> Vec<NodeId> {
|
||||||
|
|
||||||
|
boards
|
||||||
|
.into_iter().map(
|
||||||
|
|b| self.tree.new_node(b)
|
||||||
|
).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_move_boards(&self, board: &Board) -> Vec<Board> {
|
||||||
|
|
||||||
|
self.available_turns(board)
|
||||||
|
.into_iter().map(
|
||||||
|
|m| {
|
||||||
|
match m.mv_type {
|
||||||
|
MoveType::Move => board.apply_move(m.from, m.to),
|
||||||
|
MoveType::Jump => board.apply_jump(m.from, m.to),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_move(&self) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
81
src/comp/tests.rs
Normal file
81
src/comp/tests.rs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
use super::*;
|
||||||
|
use wasm_bindgen_test::*;
|
||||||
|
|
||||||
|
wasm_bindgen_test_configure!(run_in_browser);
|
||||||
|
|
||||||
|
#[wasm_bindgen_test]
|
||||||
|
fn initial_tree_size() {
|
||||||
|
let brd = Board::new(3, 2, White);
|
||||||
|
let comp = Computer::new(brd, 3, White);
|
||||||
|
|
||||||
|
assert!(!comp.tree.is_empty());
|
||||||
|
assert_eq!(comp.tree.count(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen_test]
|
||||||
|
fn available_moves() {
|
||||||
|
// . W .
|
||||||
|
// _ . _
|
||||||
|
|
||||||
|
let brd = Board::new(3, 2, White);
|
||||||
|
let mut brd2 = brd.clone();
|
||||||
|
let comp = Computer::new(brd, 3, White);
|
||||||
|
|
||||||
|
// log!("{}", brd2);
|
||||||
|
|
||||||
|
// can move left and right from central square
|
||||||
|
brd2.set_cell(brd2.cell_index(0, 1), Square::pc(White, Man));
|
||||||
|
|
||||||
|
// log!("{}", brd2);
|
||||||
|
|
||||||
|
let moves = comp.available_turns(&brd2);
|
||||||
|
|
||||||
|
// log!("{:?}", moves);
|
||||||
|
|
||||||
|
assert_eq!(moves.len(), 2);
|
||||||
|
assert!(moves.into_iter().all(|m| m.mv_type == MoveType::Move));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen_test]
|
||||||
|
fn available_moves_jumps() {
|
||||||
|
// . W . _
|
||||||
|
// _ . B .
|
||||||
|
// . _ . _
|
||||||
|
// _ . _ .
|
||||||
|
|
||||||
|
let brd = Board::new(4, 4, White);
|
||||||
|
let mut brd2 = brd.clone();
|
||||||
|
let comp = Computer::new(brd, 3, White);
|
||||||
|
|
||||||
|
// log!("{}", brd2);
|
||||||
|
|
||||||
|
brd2.set_cell(brd2.cell_index(0, 1), Square::pc(White, Man));
|
||||||
|
brd2.set_cell(brd2.cell_index(1, 2), Square::pc(Black, Man));
|
||||||
|
|
||||||
|
// log!("{}", brd2);
|
||||||
|
|
||||||
|
let moves = comp.available_turns(&brd2);
|
||||||
|
|
||||||
|
// log!("{:?}", moves);
|
||||||
|
|
||||||
|
assert_eq!(moves.len(), 2);
|
||||||
|
assert!(moves[0].mv_type == MoveType::Move);
|
||||||
|
assert!(moves[1].mv_type == MoveType::Jump);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen_test]
|
||||||
|
fn available_moves_std_brd() {
|
||||||
|
let brd = Board::init_game(Board::new(8, 8, White), 3);
|
||||||
|
let mut brd2 = brd.clone();
|
||||||
|
let comp = Computer::new(brd, 3, White);
|
||||||
|
|
||||||
|
// log!("{}", brd2);
|
||||||
|
|
||||||
|
let moves = comp.available_turns(&brd2);
|
||||||
|
|
||||||
|
// log!("{:?}", moves);
|
||||||
|
|
||||||
|
// 2 diag moves per piece except the last
|
||||||
|
assert_eq!(moves.len(), 7);
|
||||||
|
assert!(moves.into_iter().all(|m| m.mv_type == MoveType::Move));
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
use crate::board::Board;
|
use crate::board::Board;
|
||||||
use indextree::Arena;
|
|
||||||
|
|
||||||
extern crate wasm_bindgen;
|
extern crate wasm_bindgen;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
@ -22,7 +21,6 @@ use std::fmt::{Display, Write};
|
|||||||
pub struct Game {
|
pub struct Game {
|
||||||
current: Board,
|
current: Board,
|
||||||
previous_boards: Vec<Board>,
|
previous_boards: Vec<Board>,
|
||||||
// tree: Arena<Board>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Game {
|
impl Game {
|
||||||
@ -39,6 +37,11 @@ impl Game {
|
|||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl Game {
|
impl Game {
|
||||||
|
/// Current turn's team
|
||||||
|
pub fn current_turn(&self) -> Team {
|
||||||
|
self.current.current_turn
|
||||||
|
}
|
||||||
|
|
||||||
/// Attempt to make a move given a source and destination index
|
/// Attempt to make a move given a source and destination index
|
||||||
pub fn make_move(&mut self, from: BrdIdx, to: BrdIdx) {
|
pub fn make_move(&mut self, from: BrdIdx, to: BrdIdx) {
|
||||||
let able = self.current.can_move(from, to);
|
let able = self.current.can_move(from, to);
|
||||||
@ -58,59 +61,19 @@ impl Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// board has been changed, update player turn
|
// board has been changed, update player turn
|
||||||
self.current.set_turn(self.current.current_turn().opponent());
|
self.current.current_turn = self.current.current_turn.opponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update board state with given move and push new board into current state
|
/// Update board state with given move and push new board into current state
|
||||||
pub fn execute_move(&mut self, from: BrdIdx, to: BrdIdx) {
|
pub fn execute_move(&mut self, from: BrdIdx, to: BrdIdx) {
|
||||||
let mut new_board = self.current.clone();
|
|
||||||
|
|
||||||
let from_idx = self.current.cell_idx(from);
|
|
||||||
let to_idx = self.current.cell_idx(to);
|
|
||||||
|
|
||||||
// make move update
|
|
||||||
new_board.set_cell(
|
|
||||||
to_idx, // destination square
|
|
||||||
self.current.cell(from_idx) // source piece
|
|
||||||
);
|
|
||||||
|
|
||||||
// remove old piece
|
|
||||||
new_board.set_cell(
|
|
||||||
from_idx, // destination square
|
|
||||||
Square::empty() // empty piece
|
|
||||||
);
|
|
||||||
|
|
||||||
// set new board to current and push current to stack
|
// set new board to current and push current to stack
|
||||||
self.push_new_board(new_board);
|
self.push_new_board(self.current.apply_move(from, to));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update board state with given jump move and push new board into current state
|
/// Update board state with given jump move and push new board into current state
|
||||||
pub fn execute_jump(&mut self, from: BrdIdx, to: BrdIdx) {
|
pub fn execute_jump(&mut self, from: BrdIdx, to: BrdIdx) {
|
||||||
let mut new_board = self.current.clone();
|
|
||||||
|
|
||||||
let from_idx = self.current.cell_idx(from);
|
|
||||||
let to_idx = self.current.cell_idx(from);
|
|
||||||
|
|
||||||
// make move update
|
|
||||||
new_board.set_cell(
|
|
||||||
to_idx, // destination square
|
|
||||||
self.current.cell(from_idx) // source piece
|
|
||||||
);
|
|
||||||
|
|
||||||
// remove old piece
|
|
||||||
new_board.set_cell(
|
|
||||||
from_idx, // destination square
|
|
||||||
Square::empty() // empty piece
|
|
||||||
);
|
|
||||||
|
|
||||||
// remove jumpee
|
|
||||||
new_board.set_cell(
|
|
||||||
self.current.jumpee_idx(from, to), // destination square
|
|
||||||
Square::empty() // empty piece
|
|
||||||
);
|
|
||||||
|
|
||||||
// set new board to current and push current to stack
|
// set new board to current and push current to stack
|
||||||
self.push_new_board(new_board);
|
self.push_new_board(self.current.apply_jump(from, to));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push current board into the previous turns and set given board to current
|
/// Push current board into the previous turns and set given board to current
|
||||||
|
@ -11,7 +11,7 @@ use crate::board::enums::Strength::*;
|
|||||||
#[wasm_bindgen_test]
|
#[wasm_bindgen_test]
|
||||||
fn make_move() {
|
fn make_move() {
|
||||||
let mut game = Game::new(8, 8, 3, Black);
|
let mut game = Game::new(8, 8, 3, Black);
|
||||||
log!("{}", game);
|
// log!("{}", game);
|
||||||
// log!("{:?}", game);
|
// log!("{:?}", game);
|
||||||
|
|
||||||
let from = BrdIdx::from(5, 2);
|
let from = BrdIdx::from(5, 2);
|
||||||
@ -22,7 +22,7 @@ fn make_move() {
|
|||||||
|
|
||||||
assert_eq!(board.cell(board.cell_index(4, 1)), Square::pc(Black, Man));
|
assert_eq!(board.cell(board.cell_index(4, 1)), Square::pc(Black, Man));
|
||||||
|
|
||||||
log!("{}", game);
|
// log!("{}", game);
|
||||||
|
|
||||||
let from = BrdIdx::from(2, 1);
|
let from = BrdIdx::from(2, 1);
|
||||||
let to = BrdIdx::from(3, 2);
|
let to = BrdIdx::from(3, 2);
|
||||||
@ -32,7 +32,7 @@ fn make_move() {
|
|||||||
|
|
||||||
assert_eq!(board.cell(board.cell_index(3, 2)), Square::pc(White, Man));
|
assert_eq!(board.cell(board.cell_index(3, 2)), Square::pc(White, Man));
|
||||||
|
|
||||||
log!("{}", game);
|
// log!("{}", game);
|
||||||
// log!("{}", game.previous_board(0));
|
// log!("{}", game.previous_board(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ pub mod board;
|
|||||||
pub mod utils;
|
pub mod utils;
|
||||||
pub mod game;
|
pub mod game;
|
||||||
pub mod player;
|
pub mod player;
|
||||||
|
pub mod comp;
|
||||||
|
|
||||||
extern crate wasm_bindgen;
|
extern crate wasm_bindgen;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
Loading…
Reference in New Issue
Block a user