added selected highlighting, king checking
This commit is contained in:
parent
13a17d538b
commit
6d2eb8ab0a
@ -581,6 +581,7 @@ impl Board {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get cell index of jumpee square given from and to locations
|
||||
pub fn jumpee_idx(&self, from: BrdIdx, to: BrdIdx) -> usize {
|
||||
let (row_diff, col_diff) = Board::idx_diffs(from, to);
|
||||
self.cell_idx(
|
||||
@ -628,13 +629,20 @@ impl Board {
|
||||
let mut white: isize = 0;
|
||||
|
||||
for (_, square) in PieceIterator::new(self) {
|
||||
if let Some(x) = square.occupant {
|
||||
match x.team {
|
||||
if let Some(piece) = square.occupant {
|
||||
|
||||
// kings are move valuable than men
|
||||
let increment = match piece.strength {
|
||||
Man => 1,
|
||||
King => 2,
|
||||
};
|
||||
|
||||
match piece.team {
|
||||
Black => {
|
||||
black += 1;
|
||||
black += increment;
|
||||
},
|
||||
White => {
|
||||
white += 1;
|
||||
white += increment;
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -648,6 +656,7 @@ impl Board {
|
||||
self.cell(idx).state
|
||||
}
|
||||
|
||||
/// Get new board derived from current with given move applied
|
||||
pub fn apply_move(&self, from: BrdIdx, to: BrdIdx) -> Board {
|
||||
let mut new = self.clone();
|
||||
|
||||
@ -666,9 +675,12 @@ impl Board {
|
||||
Square::empty() // empty piece
|
||||
);
|
||||
|
||||
Board::check_kinged(&mut new, to);
|
||||
|
||||
new
|
||||
}
|
||||
|
||||
/// Get new board derived from current with given jump applied
|
||||
pub fn apply_jump(&self, from: BrdIdx, to: BrdIdx) -> Board {
|
||||
let mut new = self.clone();
|
||||
|
||||
@ -693,9 +705,18 @@ impl Board {
|
||||
Square::empty() // empty piece
|
||||
);
|
||||
|
||||
Board::check_kinged(&mut new, to);
|
||||
|
||||
new
|
||||
}
|
||||
|
||||
/// Get row index for current team, top row for black, bottom row for white
|
||||
pub fn king_row_idx(&self) -> usize {
|
||||
match self.current_turn {
|
||||
White => self.height - 1,
|
||||
Black => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////
|
||||
@ -725,6 +746,22 @@ impl Board {
|
||||
return from.team.opponent() == jumpee.team
|
||||
}
|
||||
|
||||
/// Check and apply king strength
|
||||
fn check_kinged(new_board: &mut Board, idx: BrdIdx) {
|
||||
if new_board.king_row_idx() == idx.row {
|
||||
let cell_idx = new_board.cell_idx(idx);
|
||||
let cell = new_board.cell(cell_idx);
|
||||
match cell.occupant {
|
||||
Some(piece) => {
|
||||
new_board.set_cell(cell_idx, Square::pc(piece.team, King));
|
||||
},
|
||||
None => {
|
||||
panic!("No piece found when checking king, idx: {}", idx);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialise a game board without game pieces
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(width: usize, height: usize, current_turn: Team) -> Board {
|
||||
|
@ -37,7 +37,6 @@ impl Move {
|
||||
/// 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,
|
||||
@ -48,19 +47,13 @@ impl 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;
|
||||
}
|
||||
|
||||
/// Get vector of available moves for a given board
|
||||
pub fn available_turns(&self, board: &Board) -> Vec<Move> {
|
||||
|
||||
// allocate capacity for 2 moves per piece, likely too much but will be shrunk
|
||||
@ -94,7 +87,7 @@ impl Computer {
|
||||
panic!("Unable to unwrap adjacent indices, from: {}, brd: {}", from_brd_idx, board);
|
||||
}
|
||||
|
||||
// iterate over adjacent indices
|
||||
// iterate over jumpable indices
|
||||
if let Some(jump) = jump_op {
|
||||
for i in jump {
|
||||
let to_brd_idx = board.board_index(i);
|
||||
@ -116,29 +109,26 @@ impl Computer {
|
||||
moves
|
||||
}
|
||||
|
||||
// pub fn gen_tree(&mut self, tree: &mut Arena<Board>, board: Board) {
|
||||
pub fn gen_tree(&mut self, tree: &mut Arena<Board>, board: Board) {
|
||||
|
||||
// let boards = self.get_move_boards(&board);
|
||||
// possible boards from given
|
||||
let boards = self.get_move_boards(&board);
|
||||
|
||||
// let root_id = vec!(tree.new_node(board));
|
||||
// let ids = self.insert_boards(boards);
|
||||
// root node of tree
|
||||
let root = tree.new_node(board);
|
||||
|
||||
// for d in 0..self.search_depth {
|
||||
// insert possible boards
|
||||
let ids = self.insert_boards(tree, boards);
|
||||
// append ids to root node
|
||||
ids.iter().for_each( |id| root.append(*id, tree) );
|
||||
|
||||
// for root in root_id.iter(){
|
||||
// for id in ids.into_iter() {
|
||||
// root.append(id, tree);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
// }
|
||||
|
||||
pub fn insert_boards(&mut self, boards: Vec<Board>) -> Vec<NodeId> {
|
||||
pub fn insert_boards(&mut self, tree: &mut Arena<Board>, boards: Vec<Board>) -> Vec<NodeId> {
|
||||
|
||||
boards
|
||||
.into_iter().map(
|
||||
|b| self.tree.new_node(b)
|
||||
|b| tree.new_node(b)
|
||||
).collect()
|
||||
}
|
||||
|
||||
|
@ -3,19 +3,20 @@ use wasm_bindgen_test::*;
|
||||
|
||||
use crate::board::Square;
|
||||
use crate::board::enums::Strength::*;
|
||||
use crate::log;
|
||||
|
||||
use Team::*;
|
||||
|
||||
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);
|
||||
// #[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);
|
||||
}
|
||||
// assert!(!comp.tree.is_empty());
|
||||
// assert_eq!(comp.tree.count(), 1);
|
||||
// }
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn available_moves() {
|
||||
@ -84,3 +85,21 @@ fn available_moves_std_brd() {
|
||||
assert_eq!(moves.len(), 7);
|
||||
assert!(moves.into_iter().all(|m| m.mv_type == MoveType::Move));
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn tree_insert() {
|
||||
let brd = Board::init_game(Board::new(8, 8, White), 3);
|
||||
let brd2 = brd.clone();
|
||||
let mut comp = Computer::new(brd, 3, White);
|
||||
|
||||
// log!("{}", brd2);
|
||||
|
||||
let moves = comp.available_turns(&brd2);
|
||||
log!("{}", moves.len());
|
||||
|
||||
let mut tree = Arena::new();
|
||||
comp.gen_tree(&mut tree, brd2);
|
||||
|
||||
log!("{}", tree.count());
|
||||
}
|
||||
|
||||
|
@ -8,11 +8,11 @@ use wasm_bindgen::prelude::*;
|
||||
use crate::log;
|
||||
|
||||
use crate::board::{Square, BrdIdx};
|
||||
use crate::board::enums::{Moveable, Team};
|
||||
use crate::board::enums::{SquareState, Moveable, Team};
|
||||
use crate::paint::Painter;
|
||||
|
||||
// use Team::*;
|
||||
// use SquareState::*;
|
||||
use SquareState::*;
|
||||
|
||||
use std::fmt::{Display};
|
||||
|
||||
@ -23,6 +23,7 @@ use std::fmt::{Display};
|
||||
#[derive(Debug)]
|
||||
pub struct Game {
|
||||
current: Board,
|
||||
selected_piece: Option<BrdIdx>,
|
||||
previous_boards: Vec<Board>,
|
||||
painter: Option<Painter>
|
||||
}
|
||||
@ -56,10 +57,35 @@ impl Game {
|
||||
self.current.current_turn
|
||||
}
|
||||
|
||||
/// Get square on current board for given index
|
||||
pub fn current_cell_state(&self, idx: &BrdIdx) -> Square {
|
||||
self.current.cell(self.current.cell_idx(*idx))
|
||||
}
|
||||
|
||||
/// Set given index as selected piece
|
||||
/// TODO: Check whether valid square?
|
||||
pub fn set_selected(&mut self, idx: &BrdIdx) {
|
||||
|
||||
if self.current.cell(self.current.cell_idx(*idx)).state != Occupied {
|
||||
panic!("Tried to select an unoccupied or empty square");
|
||||
}
|
||||
|
||||
self.selected_piece = Some(*idx);
|
||||
match &mut self.painter {
|
||||
Some(p) => p.set_selected(&Some(*idx)),
|
||||
None => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear currently selected piece
|
||||
pub fn clear_selected(&mut self) {
|
||||
self.selected_piece = None;
|
||||
match &mut self.painter {
|
||||
Some(p) => p.set_selected(&None),
|
||||
None => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to make a move given a source and destination index
|
||||
pub fn make_move(&mut self, from: BrdIdx, to: BrdIdx) -> Moveable {
|
||||
let able = self.current.can_move(from, to);
|
||||
@ -114,6 +140,7 @@ impl Game {
|
||||
current: Board::init_game(
|
||||
Board::new(width, height, first_turn), piece_rows,
|
||||
),
|
||||
selected_piece: None,
|
||||
previous_boards: Vec::with_capacity(10),
|
||||
painter: None,
|
||||
}
|
||||
@ -124,6 +151,7 @@ impl Game {
|
||||
current: Board::init_game(
|
||||
Board::new(width, height, first_turn), piece_rows,
|
||||
),
|
||||
selected_piece: None,
|
||||
previous_boards: Vec::with_capacity(10),
|
||||
painter: Some(
|
||||
Painter::new(canvas_width, canvas_height, canvas_id)
|
||||
|
47
src/paint.rs
47
src/paint.rs
@ -10,7 +10,7 @@ use web_sys::CanvasRenderingContext2d;
|
||||
use std::f64;
|
||||
|
||||
use crate::log;
|
||||
use crate::board::{Board};
|
||||
use crate::board::{Board, BrdIdx};
|
||||
use crate::board::iter::PieceIterator;
|
||||
|
||||
use crate::board::enums::Team::*;
|
||||
@ -32,11 +32,16 @@ const DRAW_OUTLINE: bool = true;
|
||||
const WHITE_PIECE: &str = "#dbdbdb";
|
||||
/// Default hex colour value for black pieces
|
||||
const BLACK_PIECE: &str = "#ed0000";
|
||||
/// Default hex colour value for selected piece
|
||||
const SELECTED_PIECE: &str = "#fffd78";
|
||||
|
||||
/// Default hex colour value for white piece outline
|
||||
const WHITE_PIECE_OUTLINE: &str = "#9c9c9c";
|
||||
/// Default hex colour value for black piece outline
|
||||
const BLACK_PIECE_OUTLINE: &str = "#a60000";
|
||||
/// Default hex colour value for selected piece outline
|
||||
// const SELECTED_PIECE_OUTLINE: &str = "#dedc73";
|
||||
const SELECTED_PIECE_OUTLINE: &str = "#d1cf45";
|
||||
/// Default hex colour value for black piece outline
|
||||
const KING_OUTLINE: &str = "#ffea00";
|
||||
/// Whether to outline pieces
|
||||
@ -53,15 +58,18 @@ const PIECE_MARGIN: f64 = 10.0;
|
||||
pub struct Painter {
|
||||
canvas: HtmlCanvasElement,
|
||||
context: CanvasRenderingContext2d,
|
||||
selected_idx: Option<BrdIdx>,
|
||||
|
||||
white_square: JsValue,
|
||||
black_square: JsValue,
|
||||
|
||||
white_piece: JsValue,
|
||||
black_piece: JsValue,
|
||||
selected_piece: JsValue,
|
||||
|
||||
white_piece_line: JsValue,
|
||||
black_piece_line: JsValue,
|
||||
selected_piece_line: JsValue,
|
||||
king_line: JsValue,
|
||||
|
||||
piece_lines: bool,
|
||||
@ -76,6 +84,11 @@ pub struct Painter {
|
||||
}
|
||||
|
||||
impl Painter {
|
||||
/// Set selected piece by board index
|
||||
pub fn set_selected(&mut self, idx: &Option<BrdIdx>) {
|
||||
self.selected_idx = *idx;
|
||||
}
|
||||
|
||||
/// Get a canvas by element ID
|
||||
fn get_canvas(canvas_id: &str) -> HtmlCanvasElement {
|
||||
// JS WINDOW
|
||||
@ -142,15 +155,18 @@ impl Painter {
|
||||
canvas,
|
||||
context,
|
||||
width, height,
|
||||
selected_idx: None,
|
||||
|
||||
white_square: JsValue::from_str(WHITE_SQUARE),
|
||||
black_square: JsValue::from_str(BLACK_SQUARE),
|
||||
|
||||
white_piece: JsValue::from_str(WHITE_PIECE),
|
||||
black_piece: JsValue::from_str(BLACK_PIECE),
|
||||
selected_piece: JsValue::from_str(SELECTED_PIECE),
|
||||
|
||||
white_piece_line: JsValue::from_str(WHITE_PIECE_OUTLINE),
|
||||
black_piece_line: JsValue::from_str(BLACK_PIECE_OUTLINE),
|
||||
selected_piece_line: JsValue::from_str(SELECTED_PIECE_OUTLINE),
|
||||
king_line: JsValue::from_str(KING_OUTLINE),
|
||||
piece_lines: DRAW_PIECE_OUTLINES,
|
||||
piece_line_width: PIECE_OUTLINE_WIDTH,
|
||||
@ -172,15 +188,18 @@ impl Painter {
|
||||
canvas,
|
||||
context,
|
||||
width, height,
|
||||
selected_idx: None,
|
||||
|
||||
white_square: JsValue::from_str(WHITE_SQUARE),
|
||||
black_square: JsValue::from_str(BLACK_SQUARE),
|
||||
|
||||
white_piece: JsValue::from_str(WHITE_PIECE),
|
||||
black_piece: JsValue::from_str(BLACK_PIECE),
|
||||
selected_piece: JsValue::from_str(SELECTED_PIECE),
|
||||
|
||||
white_piece_line: JsValue::from_str(WHITE_PIECE_OUTLINE),
|
||||
black_piece_line: JsValue::from_str(BLACK_PIECE_OUTLINE),
|
||||
selected_piece_line: JsValue::from_str(SELECTED_PIECE_OUTLINE),
|
||||
king_line: JsValue::from_str(KING_OUTLINE),
|
||||
piece_lines: DRAW_PIECE_OUTLINES,
|
||||
piece_line_width: PIECE_OUTLINE_WIDTH,
|
||||
@ -336,5 +355,31 @@ impl Painter {
|
||||
None => panic!("No piece found when attempting to draw, idx: {}, square: {:?}", idx, square),
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(selected_idx) = self.selected_idx {
|
||||
self.context.set_fill_style(&self.selected_piece);
|
||||
self.context.set_stroke_style(&self.selected_piece_line);
|
||||
|
||||
let center_x: f64 = (selected_idx.col as f64 * cell_width as f64) + (cell_width as f64) / 2.0;
|
||||
let center_y: f64 = (selected_idx.row as f64 * cell_height as f64) + (cell_height as f64) / 2.0;
|
||||
|
||||
self.context.begin_path();
|
||||
match self.context.arc(
|
||||
center_x,
|
||||
center_y,
|
||||
(cell_width as f64 / 2.0) - PIECE_MARGIN, // radius
|
||||
0.0, // start angle
|
||||
f64::consts::PI * 2.0) // end angle
|
||||
{
|
||||
Ok(res) => res,
|
||||
Err(err) => log!("Failed to paint selected piece, idx: {}, {:?}", selected_idx, err),
|
||||
};
|
||||
self.context.fill();
|
||||
|
||||
if self.piece_lines {
|
||||
self.context.set_line_width(self.piece_line_width);
|
||||
self.context.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -41,16 +41,18 @@
|
||||
</div>
|
||||
<div class="row p-3">
|
||||
<div class="col-sm-12">
|
||||
<a href="doc/draught" class="btn btn-secondary">Docs</a>
|
||||
<!-- <button id="startBtn" class="btn btn-primary">Start</button> -->
|
||||
<a href="doc/draught" class="btn btn-secondary" target="_blank">Docs</a>
|
||||
<button id="startBtn" class="btn btn-primary">Start</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row p-3">
|
||||
<div class="col-sm-6">
|
||||
<div class="col-sm-12">
|
||||
<h1 id="team-p"></h1>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
</div>
|
||||
<div class="row p-3">
|
||||
<div class="col-sm-12">
|
||||
<p hidden id="status-p"></p>
|
||||
<div hidden id="status-d" class="alert alert-danger" role="alert">
|
||||
A simple success alert—check it out!
|
||||
|
51
www/index.js
51
www/index.js
@ -35,23 +35,20 @@ const statusText = document.getElementById("status-p");
|
||||
const statusAlert = document.getElementById("status-d");
|
||||
const teamText = document.getElementById("team-p");
|
||||
|
||||
// const startBtn = document.getElementById("startBtn");
|
||||
// startBtn.onclick = start_game;
|
||||
const startBtn = document.getElementById("startBtn");
|
||||
startBtn.onclick = start_game;
|
||||
|
||||
let statusTimeout = null;
|
||||
let setStatus = setStatusAlert;
|
||||
|
||||
let current_state = GameState.HUMAN_TURN.THINKING;
|
||||
let painter = new Painter(CANVAS_WIDTH, CANVAS_HEIGHT, "game-canvas");
|
||||
// painter.draw(board);
|
||||
|
||||
let game = null;
|
||||
let painter = null;
|
||||
|
||||
let clicks = [];
|
||||
|
||||
let game = new Game(BOARD_WIDTH, BOARD_HEIGHT, PIECE_ROWS, Team.Black);
|
||||
game.set_painter(painter);
|
||||
game.draw();
|
||||
|
||||
updateTeamText();
|
||||
start_game();
|
||||
|
||||
/////////////////
|
||||
// CANVAS
|
||||
@ -75,6 +72,7 @@ canvas.addEventListener("click", (event) => {
|
||||
|
||||
function start_game() {
|
||||
game = new Game(BOARD_WIDTH, BOARD_HEIGHT, PIECE_ROWS, Team.Black);
|
||||
painter = new Painter(CANVAS_WIDTH, CANVAS_HEIGHT, "game-canvas");
|
||||
game.set_painter(painter);
|
||||
game.draw();
|
||||
|
||||
@ -85,6 +83,7 @@ function start_game() {
|
||||
function process_canvas_click(cell_coord) {
|
||||
|
||||
switch(current_state) {
|
||||
// first click of a move
|
||||
case GameState.HUMAN_TURN.THINKING:
|
||||
if (game.current_cell_state(cell_coord).state != SquareState.Occupied ) {
|
||||
return;
|
||||
@ -94,31 +93,36 @@ function process_canvas_click(cell_coord) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Your turn, first piece picked");
|
||||
// console.log("Your turn, first piece picked");
|
||||
|
||||
clicks.push(cell_coord);
|
||||
current_state = GameState.HUMAN_TURN.FROM_SELECTED;
|
||||
game.set_selected(cell_coord);
|
||||
game.draw();
|
||||
|
||||
break;
|
||||
|
||||
// second click of a move
|
||||
case GameState.HUMAN_TURN.FROM_SELECTED:
|
||||
if (game.current_cell_state(cell_coord).state != SquareState.Empty ) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Your turn, first piece already picked, picking second");
|
||||
// second click is different to first, process as move
|
||||
// otherwise, will skip straight to clean up (clear selected and clicks)
|
||||
if (!clicks[0].eq(cell_coord)) {
|
||||
|
||||
clicks.push(cell_coord);
|
||||
if (game.current_cell_state(cell_coord).state != SquareState.Empty ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (clicks.length != 2) {
|
||||
setStatus(`Error: wrong number of clicks to process ${clicks.length}`);
|
||||
console.error(`Error: wrong number of clicks to process ${clicks.length}`);
|
||||
// console.log("Your turn, first piece already picked, picking second");
|
||||
|
||||
return;
|
||||
}
|
||||
clicks.push(cell_coord);
|
||||
|
||||
if (clicks[0].eq(clicks[1])) {
|
||||
setStatus("Move Cancelled");
|
||||
} else {
|
||||
if (clicks.length != 2) {
|
||||
setStatus(`Error: wrong number of clicks to process ${clicks.length}`);
|
||||
console.error(`Error: wrong number of clicks to process ${clicks.length}`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let status = game.make_move(clicks[0], clicks[1]);
|
||||
|
||||
@ -153,6 +157,7 @@ function process_canvas_click(cell_coord) {
|
||||
|
||||
}
|
||||
|
||||
game.clear_selected();
|
||||
game.draw();
|
||||
clicks = [];
|
||||
current_state = GameState.HUMAN_TURN.THINKING;
|
||||
|
Loading…
Reference in New Issue
Block a user