added selected highlighting, king checking

This commit is contained in:
andy 2021-07-10 23:54:05 +01:00
parent 13a17d538b
commit 6d2eb8ab0a
7 changed files with 195 additions and 69 deletions

View File

@ -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 {

View File

@ -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()
}

View File

@ -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());
}

View File

@ -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)

View File

@ -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()
}
}
}
}

View File

@ -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!

View File

@ -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;