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 { pub fn jumpee_idx(&self, from: BrdIdx, to: BrdIdx) -> usize {
let (row_diff, col_diff) = Board::idx_diffs(from, to); let (row_diff, col_diff) = Board::idx_diffs(from, to);
self.cell_idx( self.cell_idx(
@ -628,13 +629,20 @@ impl Board {
let mut white: isize = 0; let mut white: isize = 0;
for (_, square) in PieceIterator::new(self) { for (_, square) in PieceIterator::new(self) {
if let Some(x) = square.occupant { if let Some(piece) = square.occupant {
match x.team {
// kings are move valuable than men
let increment = match piece.strength {
Man => 1,
King => 2,
};
match piece.team {
Black => { Black => {
black += 1; black += increment;
}, },
White => { White => {
white += 1; white += increment;
}, },
} }
} }
@ -648,6 +656,7 @@ impl Board {
self.cell(idx).state self.cell(idx).state
} }
/// Get new board derived from current with given move applied
pub fn apply_move(&self, from: BrdIdx, to: BrdIdx) -> Board { pub fn apply_move(&self, from: BrdIdx, to: BrdIdx) -> Board {
let mut new = self.clone(); let mut new = self.clone();
@ -666,9 +675,12 @@ impl Board {
Square::empty() // empty piece Square::empty() // empty piece
); );
Board::check_kinged(&mut new, to);
new new
} }
/// Get new board derived from current with given jump applied
pub fn apply_jump(&self, from: BrdIdx, to: BrdIdx) -> Board { pub fn apply_jump(&self, from: BrdIdx, to: BrdIdx) -> Board {
let mut new = self.clone(); let mut new = self.clone();
@ -693,9 +705,18 @@ impl Board {
Square::empty() // empty piece Square::empty() // empty piece
); );
Board::check_kinged(&mut new, to);
new 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 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 /// Initialise a game board without game pieces
#[wasm_bindgen(constructor)] #[wasm_bindgen(constructor)]
pub fn new(width: usize, height: usize, current_turn: Team) -> Board { 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 /// Root-level structure for managing the game as a collection of board states
#[derive(Debug)] #[derive(Debug)]
pub struct Computer { pub struct Computer {
pub tree: Arena<Board>,
pub root_node_id: NodeId, pub root_node_id: NodeId,
pub search_depth: usize, pub search_depth: usize,
pub team: Team, pub team: Team,
@ -48,19 +47,13 @@ impl Computer {
let mut tree = Arena::new(); let mut tree = Arena::new();
let root_node_id = tree.new_node(initial_board); let root_node_id = tree.new_node(initial_board);
Computer { Computer {
tree,
root_node_id, root_node_id,
search_depth, search_depth,
team team
} }
} }
pub fn update_board(&mut self, new_board: Board) { /// Get vector of available moves for a given board
let mut tree = Arena::new();
tree.new_node(new_board);
self.tree = tree;
}
pub fn available_turns(&self, board: &Board) -> Vec<Move> { pub fn available_turns(&self, board: &Board) -> Vec<Move> {
// allocate capacity for 2 moves per piece, likely too much but will be shrunk // 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); 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 { if let Some(jump) = jump_op {
for i in jump { for i in jump {
let to_brd_idx = board.board_index(i); let to_brd_idx = board.board_index(i);
@ -116,29 +109,26 @@ impl Computer {
moves 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)); // root node of tree
// let ids = self.insert_boards(boards); 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, tree: &mut Arena<Board>, boards: Vec<Board>) -> Vec<NodeId> {
pub fn insert_boards(&mut self, boards: Vec<Board>) -> Vec<NodeId> {
boards boards
.into_iter().map( .into_iter().map(
|b| self.tree.new_node(b) |b| tree.new_node(b)
).collect() ).collect()
} }

View File

@ -3,19 +3,20 @@ use wasm_bindgen_test::*;
use crate::board::Square; use crate::board::Square;
use crate::board::enums::Strength::*; use crate::board::enums::Strength::*;
use crate::log;
use Team::*; use Team::*;
wasm_bindgen_test_configure!(run_in_browser); wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen_test] // #[wasm_bindgen_test]
fn initial_tree_size() { // fn initial_tree_size() {
let brd = Board::new(3, 2, White); // let brd = Board::new(3, 2, White);
let comp = Computer::new(brd, 3, White); // let comp = Computer::new(brd, 3, White);
assert!(!comp.tree.is_empty()); // assert!(!comp.tree.is_empty());
assert_eq!(comp.tree.count(), 1); // assert_eq!(comp.tree.count(), 1);
} // }
#[wasm_bindgen_test] #[wasm_bindgen_test]
fn available_moves() { fn available_moves() {
@ -84,3 +85,21 @@ fn available_moves_std_brd() {
assert_eq!(moves.len(), 7); assert_eq!(moves.len(), 7);
assert!(moves.into_iter().all(|m| m.mv_type == MoveType::Move)); 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::log;
use crate::board::{Square, BrdIdx}; use crate::board::{Square, BrdIdx};
use crate::board::enums::{Moveable, Team}; use crate::board::enums::{SquareState, Moveable, Team};
use crate::paint::Painter; use crate::paint::Painter;
// use Team::*; // use Team::*;
// use SquareState::*; use SquareState::*;
use std::fmt::{Display}; use std::fmt::{Display};
@ -23,6 +23,7 @@ use std::fmt::{Display};
#[derive(Debug)] #[derive(Debug)]
pub struct Game { pub struct Game {
current: Board, current: Board,
selected_piece: Option<BrdIdx>,
previous_boards: Vec<Board>, previous_boards: Vec<Board>,
painter: Option<Painter> painter: Option<Painter>
} }
@ -56,10 +57,35 @@ impl Game {
self.current.current_turn self.current.current_turn
} }
/// Get square on current board for given index
pub fn current_cell_state(&self, idx: &BrdIdx) -> Square { pub fn current_cell_state(&self, idx: &BrdIdx) -> Square {
self.current.cell(self.current.cell_idx(*idx)) 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 /// Attempt to make a move given a source and destination index
pub fn make_move(&mut self, from: BrdIdx, to: BrdIdx) -> Moveable { pub fn make_move(&mut self, from: BrdIdx, to: BrdIdx) -> Moveable {
let able = self.current.can_move(from, to); let able = self.current.can_move(from, to);
@ -114,6 +140,7 @@ impl Game {
current: Board::init_game( current: Board::init_game(
Board::new(width, height, first_turn), piece_rows, Board::new(width, height, first_turn), piece_rows,
), ),
selected_piece: None,
previous_boards: Vec::with_capacity(10), previous_boards: Vec::with_capacity(10),
painter: None, painter: None,
} }
@ -124,6 +151,7 @@ impl Game {
current: Board::init_game( current: Board::init_game(
Board::new(width, height, first_turn), piece_rows, Board::new(width, height, first_turn), piece_rows,
), ),
selected_piece: None,
previous_boards: Vec::with_capacity(10), previous_boards: Vec::with_capacity(10),
painter: Some( painter: Some(
Painter::new(canvas_width, canvas_height, canvas_id) Painter::new(canvas_width, canvas_height, canvas_id)

View File

@ -10,7 +10,7 @@ use web_sys::CanvasRenderingContext2d;
use std::f64; use std::f64;
use crate::log; use crate::log;
use crate::board::{Board}; use crate::board::{Board, BrdIdx};
use crate::board::iter::PieceIterator; use crate::board::iter::PieceIterator;
use crate::board::enums::Team::*; use crate::board::enums::Team::*;
@ -32,11 +32,16 @@ const DRAW_OUTLINE: bool = true;
const WHITE_PIECE: &str = "#dbdbdb"; const WHITE_PIECE: &str = "#dbdbdb";
/// Default hex colour value for black pieces /// Default hex colour value for black pieces
const BLACK_PIECE: &str = "#ed0000"; 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 /// Default hex colour value for white piece outline
const WHITE_PIECE_OUTLINE: &str = "#9c9c9c"; const WHITE_PIECE_OUTLINE: &str = "#9c9c9c";
/// Default hex colour value for black piece outline /// Default hex colour value for black piece outline
const BLACK_PIECE_OUTLINE: &str = "#a60000"; 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 /// Default hex colour value for black piece outline
const KING_OUTLINE: &str = "#ffea00"; const KING_OUTLINE: &str = "#ffea00";
/// Whether to outline pieces /// Whether to outline pieces
@ -53,15 +58,18 @@ const PIECE_MARGIN: f64 = 10.0;
pub struct Painter { pub struct Painter {
canvas: HtmlCanvasElement, canvas: HtmlCanvasElement,
context: CanvasRenderingContext2d, context: CanvasRenderingContext2d,
selected_idx: Option<BrdIdx>,
white_square: JsValue, white_square: JsValue,
black_square: JsValue, black_square: JsValue,
white_piece: JsValue, white_piece: JsValue,
black_piece: JsValue, black_piece: JsValue,
selected_piece: JsValue,
white_piece_line: JsValue, white_piece_line: JsValue,
black_piece_line: JsValue, black_piece_line: JsValue,
selected_piece_line: JsValue,
king_line: JsValue, king_line: JsValue,
piece_lines: bool, piece_lines: bool,
@ -76,6 +84,11 @@ pub struct Painter {
} }
impl 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 /// Get a canvas by element ID
fn get_canvas(canvas_id: &str) -> HtmlCanvasElement { fn get_canvas(canvas_id: &str) -> HtmlCanvasElement {
// JS WINDOW // JS WINDOW
@ -142,15 +155,18 @@ impl Painter {
canvas, canvas,
context, context,
width, height, width, height,
selected_idx: None,
white_square: JsValue::from_str(WHITE_SQUARE), white_square: JsValue::from_str(WHITE_SQUARE),
black_square: JsValue::from_str(BLACK_SQUARE), black_square: JsValue::from_str(BLACK_SQUARE),
white_piece: JsValue::from_str(WHITE_PIECE), white_piece: JsValue::from_str(WHITE_PIECE),
black_piece: JsValue::from_str(BLACK_PIECE), black_piece: JsValue::from_str(BLACK_PIECE),
selected_piece: JsValue::from_str(SELECTED_PIECE),
white_piece_line: JsValue::from_str(WHITE_PIECE_OUTLINE), white_piece_line: JsValue::from_str(WHITE_PIECE_OUTLINE),
black_piece_line: JsValue::from_str(BLACK_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), king_line: JsValue::from_str(KING_OUTLINE),
piece_lines: DRAW_PIECE_OUTLINES, piece_lines: DRAW_PIECE_OUTLINES,
piece_line_width: PIECE_OUTLINE_WIDTH, piece_line_width: PIECE_OUTLINE_WIDTH,
@ -172,15 +188,18 @@ impl Painter {
canvas, canvas,
context, context,
width, height, width, height,
selected_idx: None,
white_square: JsValue::from_str(WHITE_SQUARE), white_square: JsValue::from_str(WHITE_SQUARE),
black_square: JsValue::from_str(BLACK_SQUARE), black_square: JsValue::from_str(BLACK_SQUARE),
white_piece: JsValue::from_str(WHITE_PIECE), white_piece: JsValue::from_str(WHITE_PIECE),
black_piece: JsValue::from_str(BLACK_PIECE), black_piece: JsValue::from_str(BLACK_PIECE),
selected_piece: JsValue::from_str(SELECTED_PIECE),
white_piece_line: JsValue::from_str(WHITE_PIECE_OUTLINE), white_piece_line: JsValue::from_str(WHITE_PIECE_OUTLINE),
black_piece_line: JsValue::from_str(BLACK_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), king_line: JsValue::from_str(KING_OUTLINE),
piece_lines: DRAW_PIECE_OUTLINES, piece_lines: DRAW_PIECE_OUTLINES,
piece_line_width: PIECE_OUTLINE_WIDTH, 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), 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>
<div class="row p-3"> <div class="row p-3">
<div class="col-sm-12"> <div class="col-sm-12">
<a href="doc/draught" class="btn btn-secondary">Docs</a> <a href="doc/draught" class="btn btn-secondary" target="_blank">Docs</a>
<!-- <button id="startBtn" class="btn btn-primary">Start</button> --> <button id="startBtn" class="btn btn-primary">Start</button>
</div> </div>
</div> </div>
<div class="row p-3"> <div class="row p-3">
<div class="col-sm-6"> <div class="col-sm-12">
<h1 id="team-p"></h1> <h1 id="team-p"></h1>
</div> </div>
<div class="col-sm-6"> </div>
<div class="row p-3">
<div class="col-sm-12">
<p hidden id="status-p"></p> <p hidden id="status-p"></p>
<div hidden id="status-d" class="alert alert-danger" role="alert"> <div hidden id="status-d" class="alert alert-danger" role="alert">
A simple success alert—check it out! 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 statusAlert = document.getElementById("status-d");
const teamText = document.getElementById("team-p"); const teamText = document.getElementById("team-p");
// const startBtn = document.getElementById("startBtn"); const startBtn = document.getElementById("startBtn");
// startBtn.onclick = start_game; startBtn.onclick = start_game;
let statusTimeout = null; let statusTimeout = null;
let setStatus = setStatusAlert; let setStatus = setStatusAlert;
let current_state = GameState.HUMAN_TURN.THINKING; 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 clicks = [];
let game = new Game(BOARD_WIDTH, BOARD_HEIGHT, PIECE_ROWS, Team.Black); start_game();
game.set_painter(painter);
game.draw();
updateTeamText();
///////////////// /////////////////
// CANVAS // CANVAS
@ -75,6 +72,7 @@ canvas.addEventListener("click", (event) => {
function start_game() { function start_game() {
game = new Game(BOARD_WIDTH, BOARD_HEIGHT, PIECE_ROWS, Team.Black); 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.set_painter(painter);
game.draw(); game.draw();
@ -85,6 +83,7 @@ function start_game() {
function process_canvas_click(cell_coord) { function process_canvas_click(cell_coord) {
switch(current_state) { switch(current_state) {
// first click of a move
case GameState.HUMAN_TURN.THINKING: case GameState.HUMAN_TURN.THINKING:
if (game.current_cell_state(cell_coord).state != SquareState.Occupied ) { if (game.current_cell_state(cell_coord).state != SquareState.Occupied ) {
return; return;
@ -94,18 +93,27 @@ function process_canvas_click(cell_coord) {
return; return;
} }
console.log("Your turn, first piece picked"); // console.log("Your turn, first piece picked");
clicks.push(cell_coord); clicks.push(cell_coord);
current_state = GameState.HUMAN_TURN.FROM_SELECTED; current_state = GameState.HUMAN_TURN.FROM_SELECTED;
game.set_selected(cell_coord);
game.draw();
break; break;
// second click of a move
case GameState.HUMAN_TURN.FROM_SELECTED: case GameState.HUMAN_TURN.FROM_SELECTED:
// 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)) {
if (game.current_cell_state(cell_coord).state != SquareState.Empty ) { if (game.current_cell_state(cell_coord).state != SquareState.Empty ) {
return; return;
} }
console.log("Your turn, first piece already picked, picking second"); // console.log("Your turn, first piece already picked, picking second");
clicks.push(cell_coord); clicks.push(cell_coord);
@ -116,10 +124,6 @@ function process_canvas_click(cell_coord) {
return; return;
} }
if (clicks[0].eq(clicks[1])) {
setStatus("Move Cancelled");
} else {
let status = game.make_move(clicks[0], clicks[1]); let status = game.make_move(clicks[0], clicks[1]);
switch(status) { switch(status) {
@ -153,6 +157,7 @@ function process_canvas_click(cell_coord) {
} }
game.clear_selected();
game.draw(); game.draw();
clicks = []; clicks = [];
current_state = GameState.HUMAN_TURN.THINKING; current_state = GameState.HUMAN_TURN.THINKING;