working AI, dynamic canvas size, UI input
This commit is contained in:
parent
6d2eb8ab0a
commit
d922c0f345
12
Cargo.toml
12
Cargo.toml
@ -3,7 +3,7 @@ name = "draught"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["aj <andrewjpack@gmail.com>"]
|
authors = ["aj <andrewjpack@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
repository = "https://github.com/Sarsoo/checkers"
|
repository = "https://github.com/Sarsoo/draught"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "rlib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
@ -15,6 +15,12 @@ default = ["console_error_panic_hook"]
|
|||||||
wasm-bindgen = "0.2.74"
|
wasm-bindgen = "0.2.74"
|
||||||
indextree = "4.3.1"
|
indextree = "4.3.1"
|
||||||
|
|
||||||
|
rand = {version = "0.7", features = ["wasm-bindgen"]}
|
||||||
|
|
||||||
|
# rand = {version = "0.8"}
|
||||||
|
# getrandom = { version = "0.2", features = ["js"] }
|
||||||
|
|
||||||
|
|
||||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||||
# logging them with `console.error`. This is great for development, but requires
|
# logging them with `console.error`. This is great for development, but requires
|
||||||
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
|
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
|
||||||
@ -47,6 +53,6 @@ wasm-bindgen-test = "0.3.24"
|
|||||||
# Tell `rustc` to optimize for small code size.
|
# Tell `rustc` to optimize for small code size.
|
||||||
opt-level = "s"
|
opt-level = "s"
|
||||||
|
|
||||||
[package.metadata.wasm-pack.profile.release]
|
# [package.metadata.wasm-pack.profile.release]
|
||||||
wasm-opt = false
|
# wasm-opt = false
|
||||||
|
|
||||||
|
@ -677,6 +677,9 @@ impl Board {
|
|||||||
|
|
||||||
Board::check_kinged(&mut new, to);
|
Board::check_kinged(&mut new, to);
|
||||||
|
|
||||||
|
// board has been changed, update player turn
|
||||||
|
new.current_turn = new.current_turn.opponent();
|
||||||
|
|
||||||
new
|
new
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -707,6 +710,9 @@ impl Board {
|
|||||||
|
|
||||||
Board::check_kinged(&mut new, to);
|
Board::check_kinged(&mut new, to);
|
||||||
|
|
||||||
|
// board has been changed, update player turn
|
||||||
|
new.current_turn = new.current_turn.opponent();
|
||||||
|
|
||||||
new
|
new
|
||||||
}
|
}
|
||||||
|
|
||||||
|
270
src/comp/mod.rs
270
src/comp/mod.rs
@ -1,17 +1,18 @@
|
|||||||
//! AI player logic
|
//! AI player logic
|
||||||
|
|
||||||
use indextree::{Arena, NodeId};
|
use indextree::{Arena, Node, NodeId, NodeEdge};
|
||||||
|
use rand::seq::SliceRandom;
|
||||||
|
|
||||||
extern crate wasm_bindgen;
|
extern crate wasm_bindgen;
|
||||||
// use wasm_bindgen::prelude::*;
|
// use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
// use crate::log;
|
use crate::log;
|
||||||
|
|
||||||
use crate::board::{Board, BrdIdx};
|
use crate::board::{Board, BrdIdx};
|
||||||
use crate::board::enums::{MoveType, Moveable, Team};
|
use crate::board::enums::{MoveType, Moveable, Team};
|
||||||
use crate::board::iter::{PieceIterator};
|
use crate::board::iter::{PieceIterator};
|
||||||
|
|
||||||
// use Team::*;
|
use Team::*;
|
||||||
// use Strength::*;
|
// use Strength::*;
|
||||||
// use SquareState::*;
|
// use SquareState::*;
|
||||||
|
|
||||||
@ -34,27 +35,45 @@ impl Move {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct BoardNode {
|
||||||
|
pub board: Board,
|
||||||
|
pub score: isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BoardNode {
|
||||||
|
pub fn new(board: Board, score: isize) -> BoardNode {
|
||||||
|
BoardNode {
|
||||||
|
board, score
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn brd(board: Board) -> BoardNode {
|
||||||
|
BoardNode {
|
||||||
|
board, score: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 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 root_node_id: NodeId,
|
|
||||||
pub search_depth: usize,
|
pub search_depth: usize,
|
||||||
pub team: Team,
|
pub team: Team,
|
||||||
|
pub last_node_count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Computer {
|
impl Computer {
|
||||||
pub fn new(initial_board: Board, search_depth: usize, team: Team) -> Computer {
|
pub fn new(search_depth: usize, team: Team) -> Computer {
|
||||||
let mut tree = Arena::new();
|
|
||||||
let root_node_id = tree.new_node(initial_board);
|
|
||||||
Computer {
|
Computer {
|
||||||
root_node_id,
|
|
||||||
search_depth,
|
search_depth,
|
||||||
team
|
team,
|
||||||
|
last_node_count: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get vector of available moves for a given board
|
/// Get vector of available moves for a given board
|
||||||
pub fn available_turns(&self, board: &Board) -> Vec<Move> {
|
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
|
||||||
// to reduce memory re-allocations
|
// to reduce memory re-allocations
|
||||||
@ -109,22 +128,158 @@ impl Computer {
|
|||||||
moves
|
moves
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn gen_tree(&mut self, tree: &mut Arena<Board>, board: Board) {
|
/// Generate tree of boards to given search depth, return root node
|
||||||
|
fn gen_tree(&mut self, tree: &mut Arena<BoardNode>, board: Board) -> NodeId {
|
||||||
// possible boards from given
|
|
||||||
let boards = self.get_move_boards(&board);
|
|
||||||
|
|
||||||
// root node of tree
|
// root node of tree
|
||||||
let root = tree.new_node(board);
|
let root = tree.new_node(BoardNode::brd(board));
|
||||||
|
|
||||||
|
let mut nodes = vec!(root);
|
||||||
|
|
||||||
|
for _ in 0..self.search_depth {
|
||||||
|
if nodes.len() == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes = self.expand_layer(tree, nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
root
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Propagate scores up the tree employing MiniMax algorithm
|
||||||
|
fn propagate_scores(&mut self, tree: Arena<BoardNode>, root: NodeId) -> Arena<BoardNode> {
|
||||||
|
|
||||||
|
// need to clone tree because we iterate over it and edit it at the same time
|
||||||
|
let mut new_tree = tree.clone();
|
||||||
|
|
||||||
|
for n in root.traverse(&tree) {
|
||||||
|
// only check end variant, checks nodes once
|
||||||
|
if let NodeEdge::End(node_id) = n {
|
||||||
|
|
||||||
|
// board current being looked at
|
||||||
|
let board_node = tree
|
||||||
|
.get(node_id) // get Node
|
||||||
|
.expect("No node returned for node id")
|
||||||
|
.get(); // get BoardNode from Node
|
||||||
|
|
||||||
|
// get scores of each nodes children
|
||||||
|
let children_scores: Vec<isize> = node_id // current node
|
||||||
|
.children(&tree)
|
||||||
|
.into_iter()
|
||||||
|
.map(
|
||||||
|
|n| tree
|
||||||
|
.get(n) // get Node
|
||||||
|
.expect("No node returned for node id") // unwrap, should always be fine
|
||||||
|
.get() // get BoardNode from Node
|
||||||
|
.score // get score from BoardNode
|
||||||
|
)
|
||||||
|
.collect(); // finalise
|
||||||
|
|
||||||
|
// only nodes which have children, ignores leaves
|
||||||
|
if children_scores.len() != 0 {
|
||||||
|
|
||||||
|
// calculate score to propagate up tree
|
||||||
|
new_tree
|
||||||
|
.get_mut(node_id) // get mutable Node to change score on
|
||||||
|
.expect("No node returned for node id") // should always be fine
|
||||||
|
.get_mut() // get mutable BoardNode from Node
|
||||||
|
.score = Computer::best_score(&board_node.board, children_scores);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new_tree
|
||||||
|
}
|
||||||
|
|
||||||
|
fn best_score(board: &Board, children_scores: Vec<isize>) -> isize {
|
||||||
|
match board.current_turn { // MiniMax algorithm here
|
||||||
|
// whether maximised or minimsed is based on current player
|
||||||
|
White => children_scores
|
||||||
|
.into_iter()
|
||||||
|
.min()
|
||||||
|
.expect("Couldn't unwrap min score value"),
|
||||||
|
Black => children_scores
|
||||||
|
.into_iter()
|
||||||
|
.max()
|
||||||
|
.expect("Couldn't unwrap max score value"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For given NodeIDs, insert score of board into tree
|
||||||
|
/// Used for leaf nodes ready for propagating up tree
|
||||||
|
fn insert_board_scores(&mut self, tree: &mut Arena<BoardNode>, nodes: Vec<NodeId>) {
|
||||||
|
for i in nodes.into_iter() {
|
||||||
|
match tree.get_mut(i) {
|
||||||
|
Some(node) => {
|
||||||
|
let board_node = node.get_mut();
|
||||||
|
board_node.score = board_node.board.score();
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
panic!("Failed to get node when inserting board scores");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Traverse tree from given root ID and get NodeIDs of leaf nodes (those without children)
|
||||||
|
fn get_leaf_nodes(&mut self, tree: &mut Arena<BoardNode>, root: NodeId) -> Vec<NodeId> {
|
||||||
|
|
||||||
|
let mut leaves = Vec::with_capacity(self.search_depth * 30);
|
||||||
|
|
||||||
|
for n in root.traverse(tree) {
|
||||||
|
|
||||||
|
// only check start variant, checks nodes once
|
||||||
|
if let NodeEdge::Start(node_id) = n {
|
||||||
|
|
||||||
|
let children: Vec<NodeId> = node_id.children(tree).collect();
|
||||||
|
|
||||||
|
if children.len() == 0 {
|
||||||
|
leaves.push(node_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
leaves
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expand all given nodes and return newly inserted layer of nodes
|
||||||
|
fn expand_layer(&mut self, tree: &mut Arena<BoardNode>, nodes: Vec<NodeId>) -> Vec<NodeId> {
|
||||||
|
|
||||||
|
nodes
|
||||||
|
.into_iter().map(
|
||||||
|
|n| self.expand_node(tree, n)
|
||||||
|
).flatten()
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert all possible next boards as children of given board node
|
||||||
|
fn expand_node(&mut self, tree: &mut Arena<BoardNode>, node: NodeId) -> Vec<NodeId> {
|
||||||
|
let node_board: Option<&Node<BoardNode>> = tree.get(node);
|
||||||
|
|
||||||
|
match node_board {
|
||||||
|
Some(node_ref) => {
|
||||||
|
|
||||||
|
let board: &BoardNode = &*node_ref.get();
|
||||||
|
|
||||||
|
// possible boards from given
|
||||||
|
let boards = self.get_move_boards(board);
|
||||||
|
|
||||||
// insert possible boards
|
// insert possible boards
|
||||||
let ids = self.insert_boards(tree, boards);
|
let ids = self.insert_boards(tree, boards);
|
||||||
// append ids to root node
|
// append ids to root node
|
||||||
ids.iter().for_each( |id| root.append(*id, tree) );
|
ids.iter().for_each( |id| node.append(*id, tree) );
|
||||||
|
|
||||||
|
return ids;
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
panic!("No board retrieved from tree, node: {:?}", node);
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_boards(&mut self, tree: &mut Arena<Board>, boards: Vec<Board>) -> Vec<NodeId> {
|
/// Insert given boards into tree
|
||||||
|
fn insert_boards(&mut self, tree: &mut Arena<BoardNode>, boards: Vec<BoardNode>) -> Vec<NodeId> {
|
||||||
|
|
||||||
boards
|
boards
|
||||||
.into_iter().map(
|
.into_iter().map(
|
||||||
@ -132,20 +287,91 @@ impl Computer {
|
|||||||
).collect()
|
).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_move_boards(&self, board: &Board) -> Vec<Board> {
|
/// Get all possible next boards from given board
|
||||||
|
fn get_move_boards(&self, board: &BoardNode) -> Vec<BoardNode> {
|
||||||
|
|
||||||
self.available_turns(board)
|
self.available_turns(&board.board)
|
||||||
.into_iter().map(
|
.into_iter().map(
|
||||||
|m| {
|
|m| {
|
||||||
match m.mv_type {
|
match m.mv_type {
|
||||||
MoveType::Move => board.apply_move(m.from, m.to),
|
MoveType::Move => BoardNode::brd(
|
||||||
MoveType::Jump => board.apply_jump(m.from, m.to),
|
board.board.apply_move(m.from, m.to)
|
||||||
|
),
|
||||||
|
MoveType::Jump => BoardNode::brd(
|
||||||
|
board.board.apply_jump(m.from, m.to)
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).collect()
|
).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_move(&self) {
|
pub fn get_move(&mut self, brd: Board) -> Option<Board> {
|
||||||
|
|
||||||
|
let mut tree = Arena::new();
|
||||||
|
|
||||||
|
// generate a tree to given depth for the given board
|
||||||
|
let root_node = self.gen_tree(&mut tree, brd);
|
||||||
|
|
||||||
|
self.last_node_count = tree.count() - 1;
|
||||||
|
|
||||||
|
// get the leaf nodes to insert the board scores with
|
||||||
|
let lowest_nodes = self.get_leaf_nodes(&mut tree, root_node);
|
||||||
|
|
||||||
|
// insert the board scores for the leaf nodes
|
||||||
|
self.insert_board_scores(&mut tree, lowest_nodes);
|
||||||
|
|
||||||
|
// propagate the scores up the tree, the root node has the best score
|
||||||
|
let tree = self.propagate_scores(tree, root_node);
|
||||||
|
|
||||||
|
// get root node to compare
|
||||||
|
let root_board_node = tree
|
||||||
|
.get(root_node) // get Node
|
||||||
|
.expect("No node returned for node id")
|
||||||
|
.get(); // get BoardNode from Node
|
||||||
|
|
||||||
|
log!("{}", root_board_node.score);
|
||||||
|
// log!("{:#?}", tree);
|
||||||
|
|
||||||
|
// when boards have equal scores, store for shuffling and selection
|
||||||
|
let mut equal_scores = Vec::with_capacity(10);
|
||||||
|
|
||||||
|
// DEBUG
|
||||||
|
let scores: Vec<NodeId> = root_node
|
||||||
|
.children(&tree)
|
||||||
|
.collect();
|
||||||
|
let scores: Vec<isize> = scores
|
||||||
|
.into_iter()
|
||||||
|
.map(|n| tree.get(n).unwrap().get().score)
|
||||||
|
.collect();
|
||||||
|
log!("SCORES: {:?}", scores);
|
||||||
|
// DEBUG
|
||||||
|
|
||||||
|
// search through root node's children for the same score
|
||||||
|
for n in root_node.children(&tree) {
|
||||||
|
|
||||||
|
// get each board
|
||||||
|
let iter_board_node = tree
|
||||||
|
.get(n) // get Node
|
||||||
|
.expect("No node returned for node id")
|
||||||
|
.get(); // get BoardNode from Node
|
||||||
|
|
||||||
|
if root_board_node.score == iter_board_node.score {
|
||||||
|
equal_scores.push(iter_board_node);
|
||||||
|
// return Some(iter_board_node.board.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if equal_scores.len() == 0 {
|
||||||
|
None
|
||||||
|
} else if equal_scores.len() == 1 {
|
||||||
|
Some(equal_scores[0].board.clone())
|
||||||
|
} else {
|
||||||
|
Some(equal_scores
|
||||||
|
.choose(&mut rand::thread_rng())
|
||||||
|
.unwrap()
|
||||||
|
.board
|
||||||
|
.clone()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,11 +1,12 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
// use wasm_bindgen::prelude::*;
|
||||||
use wasm_bindgen_test::*;
|
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 crate::log;
|
||||||
|
|
||||||
use Team::*;
|
// use Team::*;
|
||||||
|
|
||||||
wasm_bindgen_test_configure!(run_in_browser);
|
wasm_bindgen_test_configure!(run_in_browser);
|
||||||
|
|
||||||
@ -23,18 +24,17 @@ fn available_moves() {
|
|||||||
// . W .
|
// . W .
|
||||||
// _ . _
|
// _ . _
|
||||||
|
|
||||||
let brd = Board::new(3, 2, White);
|
let mut brd = Board::new(3, 2, White);
|
||||||
let mut brd2 = brd.clone();
|
let comp = Computer::new(3, White);
|
||||||
let comp = Computer::new(brd, 3, White);
|
|
||||||
|
|
||||||
// log!("{}", brd2);
|
// log!("{}", brd);
|
||||||
|
|
||||||
// can move left and right from central square
|
// can move left and right from central square
|
||||||
brd2.set_cell(brd2.cell_index(0, 1), Square::pc(White, Man));
|
brd.set_cell(brd.cell_index(0, 1), Square::pc(White, Man));
|
||||||
|
|
||||||
// log!("{}", brd2);
|
// log!("{}", brd);
|
||||||
|
|
||||||
let moves = comp.available_turns(&brd2);
|
let moves = comp.available_turns(&brd);
|
||||||
|
|
||||||
// log!("{:?}", moves);
|
// log!("{:?}", moves);
|
||||||
|
|
||||||
@ -49,18 +49,17 @@ fn available_moves_jumps() {
|
|||||||
// . _ . _
|
// . _ . _
|
||||||
// _ . _ .
|
// _ . _ .
|
||||||
|
|
||||||
let brd = Board::new(4, 4, White);
|
let mut brd = Board::new(4, 4, White);
|
||||||
let mut brd2 = brd.clone();
|
let comp = Computer::new(3, White);
|
||||||
let comp = Computer::new(brd, 3, White);
|
|
||||||
|
|
||||||
// log!("{}", brd2);
|
// log!("{}", brd);
|
||||||
|
|
||||||
brd2.set_cell(brd2.cell_index(0, 1), Square::pc(White, Man));
|
brd.set_cell(brd.cell_index(0, 1), Square::pc(White, Man));
|
||||||
brd2.set_cell(brd2.cell_index(1, 2), Square::pc(Black, Man));
|
brd.set_cell(brd.cell_index(1, 2), Square::pc(Black, Man));
|
||||||
|
|
||||||
// log!("{}", brd2);
|
// log!("{}", brd);
|
||||||
|
|
||||||
let moves = comp.available_turns(&brd2);
|
let moves = comp.available_turns(&brd);
|
||||||
|
|
||||||
// log!("{:?}", moves);
|
// log!("{:?}", moves);
|
||||||
|
|
||||||
@ -72,12 +71,11 @@ fn available_moves_jumps() {
|
|||||||
#[wasm_bindgen_test]
|
#[wasm_bindgen_test]
|
||||||
fn available_moves_std_brd() {
|
fn available_moves_std_brd() {
|
||||||
let brd = Board::init_game(Board::new(8, 8, White), 3);
|
let brd = Board::init_game(Board::new(8, 8, White), 3);
|
||||||
let brd2 = brd.clone();
|
let comp = Computer::new(3, White);
|
||||||
let comp = Computer::new(brd, 3, White);
|
|
||||||
|
|
||||||
// log!("{}", brd2);
|
// log!("{}", brd);
|
||||||
|
|
||||||
let moves = comp.available_turns(&brd2);
|
let moves = comp.available_turns(&brd);
|
||||||
|
|
||||||
// log!("{:?}", moves);
|
// log!("{:?}", moves);
|
||||||
|
|
||||||
@ -87,19 +85,204 @@ fn available_moves_std_brd() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
#[wasm_bindgen_test]
|
||||||
fn tree_insert() {
|
fn expand_node() {
|
||||||
let brd = Board::init_game(Board::new(8, 8, White), 3);
|
let brd = Board::init_game(Board::new(8, 8, White), 3);
|
||||||
let brd2 = brd.clone();
|
let mut comp = Computer::new(3, White);
|
||||||
let mut comp = Computer::new(brd, 3, White);
|
|
||||||
|
|
||||||
// log!("{}", brd2);
|
// log!("{}", brd);
|
||||||
|
|
||||||
let moves = comp.available_turns(&brd2);
|
|
||||||
log!("{}", moves.len());
|
|
||||||
|
|
||||||
let mut tree = Arena::new();
|
let mut tree = Arena::new();
|
||||||
comp.gen_tree(&mut tree, brd2);
|
let id = tree.new_node(BoardNode::brd(brd));
|
||||||
|
|
||||||
log!("{}", tree.count());
|
let moves = comp.expand_node(&mut tree, id);
|
||||||
|
|
||||||
|
assert_eq!(moves.len(), 7);
|
||||||
|
assert_eq!(tree.count(), 8);
|
||||||
|
|
||||||
|
let children: Vec<NodeId> = id.children(&mut tree).collect();
|
||||||
|
assert_eq!(children.len(), 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen_test]
|
||||||
|
fn expand_layer() {
|
||||||
|
let brd = Board::init_game(Board::new(8, 8, White), 3);
|
||||||
|
let mut comp = Computer::new(3, White);
|
||||||
|
|
||||||
|
// log!("{}", brd);
|
||||||
|
|
||||||
|
let mut tree = Arena::new();
|
||||||
|
let id = tree.new_node(BoardNode::brd(brd));
|
||||||
|
|
||||||
|
let moves = comp.expand_layer(&mut tree, vec!(id));
|
||||||
|
let moves = comp.expand_layer(&mut tree, moves);
|
||||||
|
|
||||||
|
assert_eq!(moves.len(), 49);
|
||||||
|
assert_eq!(tree.count(), 49 + 7 + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen_test]
|
||||||
|
fn leaf_nodes() {
|
||||||
|
let brd = Board::init_game(Board::new(8, 8, White), 3);
|
||||||
|
let mut comp = Computer::new(3, White);
|
||||||
|
|
||||||
|
let mut tree = Arena::new();
|
||||||
|
let id = tree.new_node(BoardNode::brd(brd));
|
||||||
|
|
||||||
|
let moves = comp.expand_node(&mut tree, id);
|
||||||
|
let children: Vec<NodeId> = id.children(&mut tree).collect();
|
||||||
|
|
||||||
|
let moves2 = comp.expand_node(&mut tree, children[0]);
|
||||||
|
|
||||||
|
assert_eq!(comp.get_leaf_nodes(&mut tree, id).len(), moves.len() + moves2.len() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen_test]
|
||||||
|
fn best_scores() {
|
||||||
|
let brd = Board::new(1, 1, White);
|
||||||
|
assert_eq!(Computer::best_score(&brd, vec!(-5, -1, 2, 3, 4)), -5);
|
||||||
|
|
||||||
|
let brd = Board::new(1, 1, Black);
|
||||||
|
assert_eq!(Computer::best_score(&brd, vec!(-1, 2, 3, 4)), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen_test]
|
||||||
|
fn propagate_scores() {
|
||||||
|
let brd = Board::init_game(Board::new(8, 8, White), 3);
|
||||||
|
let mut comp = Computer::new(3, White);
|
||||||
|
|
||||||
|
// log!("{}", brd);
|
||||||
|
|
||||||
|
let mut tree = Arena::new();
|
||||||
|
let root = tree.new_node(BoardNode::brd(brd));
|
||||||
|
|
||||||
|
comp.expand_layer(&mut tree, vec!(root));
|
||||||
|
|
||||||
|
let moves = comp.propagate_scores(tree, root);
|
||||||
|
// log!("{}", moves.len());
|
||||||
|
|
||||||
|
// log!("{}", tree.count());
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[wasm_bindgen_test]
|
||||||
|
// fn tree_2_depth() {
|
||||||
|
// // log!("{}", performance.timing().request_start());
|
||||||
|
|
||||||
|
// let iter = 3;
|
||||||
|
// let mut times = Vec::with_capacity(iter);
|
||||||
|
|
||||||
|
// for _ in 0..iter {
|
||||||
|
// times.push(time_tree_gen(6));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// log!("{:?}", times);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn time_tree_gen(depth: usize) {
|
||||||
|
// web_sys::console::time_with_label("tree_timer");
|
||||||
|
|
||||||
|
// let mut comp = Computer::new(depth, White);
|
||||||
|
|
||||||
|
// let mut tree = Arena::new();
|
||||||
|
// let brd = Board::init_game(Board::new(8, 8, White), 3);
|
||||||
|
|
||||||
|
// comp.gen_tree(&mut tree, brd);
|
||||||
|
|
||||||
|
// web_sys::console::time_end_with_label("tree_timer");
|
||||||
|
// log!("{}", tree.count());
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[wasm_bindgen_test]
|
||||||
|
// fn tree_last_nodes() {
|
||||||
|
// let mut brd = Board::new(2, 2, White);
|
||||||
|
// brd.set_cell(1, Square::pc(White, King));
|
||||||
|
// let mut comp = Computer::new(3, White);
|
||||||
|
|
||||||
|
// // log!("{}", brd);
|
||||||
|
// // log!("{}", brd.score());
|
||||||
|
|
||||||
|
// // let moves = comp.available_turns(&brd);
|
||||||
|
// // log!("{}", moves.len());
|
||||||
|
|
||||||
|
// let mut tree = Arena::new();
|
||||||
|
// let root_node = comp.gen_tree(&mut tree, brd);
|
||||||
|
|
||||||
|
// let lowest_nodes = comp.get_leaf_nodes(&mut tree, root_node);
|
||||||
|
|
||||||
|
// // log!("{:#?}", lowest_nodes);
|
||||||
|
|
||||||
|
// comp.insert_board_scores(&mut tree, lowest_nodes);
|
||||||
|
|
||||||
|
// // log!("{:#?}", tree);
|
||||||
|
// // log!("{}", tree.count());
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[wasm_bindgen_test]
|
||||||
|
// fn tree_score_propagation() {
|
||||||
|
// let mut brd = Board::new(4, 4, White);
|
||||||
|
// brd.set_cell(brd.cell_idx(BrdIdx::from(1, 2)), Square::pc(White, Man));
|
||||||
|
// brd.set_cell(brd.cell_idx(BrdIdx::from(1, 0)), Square::pc(White, Man));
|
||||||
|
// brd.set_cell(brd.cell_idx(BrdIdx::from(2, 1)), Square::pc(Black, Man));
|
||||||
|
// let mut comp = Computer::new(1, White);
|
||||||
|
|
||||||
|
// // log!("{}", brd);
|
||||||
|
// // log!("{}", brd.score());
|
||||||
|
|
||||||
|
// // let moves = comp.available_turns(&brd);
|
||||||
|
// // log!("{}", moves.len());
|
||||||
|
|
||||||
|
// let mut tree = Arena::new();
|
||||||
|
// let root_node = comp.gen_tree(&mut tree, brd);
|
||||||
|
// // log!("{}", root_node);
|
||||||
|
|
||||||
|
// let lowest_nodes = comp.get_leaf_nodes(&mut tree, root_node);
|
||||||
|
|
||||||
|
// // log!("{:#?}", lowest_nodes);
|
||||||
|
|
||||||
|
// comp.insert_board_scores(&mut tree, lowest_nodes);
|
||||||
|
|
||||||
|
// let tree = comp.propagate_scores(tree, root_node);
|
||||||
|
|
||||||
|
// let scores: Vec<NodeId> = root_node
|
||||||
|
// .children(&tree)
|
||||||
|
// .collect();
|
||||||
|
// let scores: Vec<isize> = scores.into_iter().map(|n| tree.get(n).unwrap().get().score).collect();
|
||||||
|
// // log!("SCORES: {:?}", scores);
|
||||||
|
|
||||||
|
// // log!("{:#?}", tree);
|
||||||
|
// // log!("{}", tree.count());
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[wasm_bindgen_test]
|
||||||
|
// fn tree_get_move() {
|
||||||
|
// let mut brd = Board::new(5, 4, White);
|
||||||
|
// brd.set_cell(brd.cell_idx(BrdIdx::from(1, 2)), Square::pc(White, Man));
|
||||||
|
// brd.set_cell(brd.cell_idx(BrdIdx::from(1, 0)), Square::pc(White, Man));
|
||||||
|
// brd.set_cell(brd.cell_idx(BrdIdx::from(1, 4)), Square::pc(White, Man));
|
||||||
|
// brd.set_cell(brd.cell_idx(BrdIdx::from(2, 1)), Square::pc(Black, Man));
|
||||||
|
// brd.set_cell(brd.cell_idx(BrdIdx::from(2, 3)), Square::pc(Black, Man));
|
||||||
|
// let mut comp = Computer::new(2, White);
|
||||||
|
|
||||||
|
// // log!("{}", brd);
|
||||||
|
|
||||||
|
// // log!("{:?}", comp.get_move(brd).unwrap());
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[wasm_bindgen_test]
|
||||||
|
// fn tree_get_move() {
|
||||||
|
// let mut brd = Board::new(5, 5, White);
|
||||||
|
// brd.set_cell(brd.cell_idx(BrdIdx::from(1, 2)), Square::pc(White, Man));
|
||||||
|
// brd.set_cell(brd.cell_idx(BrdIdx::from(1, 0)), Square::pc(White, Man));
|
||||||
|
// brd.set_cell(brd.cell_idx(BrdIdx::from(2, 1)), Square::pc(Black, Man));
|
||||||
|
// brd.set_cell(brd.cell_idx(BrdIdx::from(4, 1)), Square::pc(Black, Man));
|
||||||
|
// let mut comp = Computer::new(2, White);
|
||||||
|
|
||||||
|
// log!("{}", brd);
|
||||||
|
|
||||||
|
// let next = comp.get_move(brd).unwrap();
|
||||||
|
|
||||||
|
// log!("{}", next);
|
||||||
|
// let mut comp = Computer::new(2, White);
|
||||||
|
|
||||||
|
// let next = comp.get_move(next);
|
||||||
|
// // log!("{}", next);
|
||||||
|
// }
|
@ -10,6 +10,7 @@ use crate::log;
|
|||||||
use crate::board::{Square, BrdIdx};
|
use crate::board::{Square, BrdIdx};
|
||||||
use crate::board::enums::{SquareState, Moveable, Team};
|
use crate::board::enums::{SquareState, Moveable, Team};
|
||||||
use crate::paint::Painter;
|
use crate::paint::Painter;
|
||||||
|
use crate::comp::Computer;
|
||||||
|
|
||||||
// use Team::*;
|
// use Team::*;
|
||||||
use SquareState::*;
|
use SquareState::*;
|
||||||
@ -25,7 +26,9 @@ pub struct Game {
|
|||||||
current: Board,
|
current: Board,
|
||||||
selected_piece: Option<BrdIdx>,
|
selected_piece: Option<BrdIdx>,
|
||||||
previous_boards: Vec<Board>,
|
previous_boards: Vec<Board>,
|
||||||
painter: Option<Painter>
|
painter: Option<Painter>,
|
||||||
|
search_depth: usize,
|
||||||
|
pub last_node_count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Game {
|
impl Game {
|
||||||
@ -57,11 +60,20 @@ impl Game {
|
|||||||
self.current.current_turn
|
self.current.current_turn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Current board's score
|
||||||
|
pub fn score(&self) -> isize {
|
||||||
|
self.current.score()
|
||||||
|
}
|
||||||
|
|
||||||
/// Get square on current board for given index
|
/// 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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_search_depth(&mut self, search_depth: usize) {
|
||||||
|
self.search_depth = search_depth;
|
||||||
|
}
|
||||||
|
|
||||||
/// Set given index as selected piece
|
/// Set given index as selected piece
|
||||||
/// TODO: Check whether valid square?
|
/// TODO: Check whether valid square?
|
||||||
pub fn set_selected(&mut self, idx: &BrdIdx) {
|
pub fn set_selected(&mut self, idx: &BrdIdx) {
|
||||||
@ -101,9 +113,6 @@ impl Game {
|
|||||||
self.execute_jump(from, to);
|
self.execute_jump(from, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
// board has been changed, update player turn
|
|
||||||
self.current.current_turn = self.current.current_turn.opponent();
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
log!("Unable to make move, {:?}", able);
|
log!("Unable to make move, {:?}", able);
|
||||||
}
|
}
|
||||||
@ -135,7 +144,7 @@ impl Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen(constructor)]
|
#[wasm_bindgen(constructor)]
|
||||||
pub fn new(width: usize, height: usize, piece_rows: usize, first_turn: Team) -> Game {
|
pub fn new(width: usize, height: usize, piece_rows: usize, first_turn: Team, search_depth: usize) -> Game {
|
||||||
Game {
|
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,
|
||||||
@ -143,10 +152,12 @@ impl Game {
|
|||||||
selected_piece: None,
|
selected_piece: None,
|
||||||
previous_boards: Vec::with_capacity(10),
|
previous_boards: Vec::with_capacity(10),
|
||||||
painter: None,
|
painter: None,
|
||||||
|
search_depth,
|
||||||
|
last_node_count: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_with_canvas(width: usize, height: usize, piece_rows: usize, first_turn: Team, canvas_id: &str, canvas_width: u32, canvas_height: u32) -> Game {
|
pub fn new_with_canvas(width: usize, height: usize, piece_rows: usize, first_turn: Team, search_depth: usize, canvas_id: &str, canvas_width: u32, canvas_height: u32) -> Game {
|
||||||
Game {
|
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,
|
||||||
@ -156,6 +167,8 @@ impl Game {
|
|||||||
painter: Some(
|
painter: Some(
|
||||||
Painter::new(canvas_width, canvas_height, canvas_id)
|
Painter::new(canvas_width, canvas_height, canvas_id)
|
||||||
),
|
),
|
||||||
|
search_depth,
|
||||||
|
last_node_count: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,6 +182,19 @@ impl Game {
|
|||||||
None => log!("No painter to draw board with")
|
None => log!("No painter to draw board with")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ai_move(&mut self) {
|
||||||
|
|
||||||
|
let mut comp = Computer::new(self.search_depth, self.current.current_turn);
|
||||||
|
let new_brd = comp.get_move(self.current.clone());
|
||||||
|
|
||||||
|
self.last_node_count = comp.last_node_count;
|
||||||
|
|
||||||
|
match new_brd {
|
||||||
|
Some(brd) => self.push_new_board(brd),
|
||||||
|
None => panic!("No AI move returned"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Game {
|
impl Display for Game {
|
||||||
|
@ -11,7 +11,7 @@ use crate::board::enums::Team::*;
|
|||||||
|
|
||||||
#[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, 3);
|
||||||
// log!("{}", game);
|
// log!("{}", game);
|
||||||
// log!("{:?}", game);
|
// log!("{:?}", game);
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ fn make_move() {
|
|||||||
|
|
||||||
#[wasm_bindgen_test]
|
#[wasm_bindgen_test]
|
||||||
fn make_jump() {
|
fn make_jump() {
|
||||||
let mut game = Game::new(8, 8, 3, Black);
|
let mut game = Game::new(8, 8, 3, Black, 3);
|
||||||
// log!("{}", game);
|
// log!("{}", game);
|
||||||
// log!("{:?}", game);
|
// log!("{:?}", game);
|
||||||
|
|
||||||
|
@ -19,9 +19,9 @@ pub use paint::Painter;
|
|||||||
|
|
||||||
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
|
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
|
||||||
// allocator.
|
// allocator.
|
||||||
// #[cfg(feature = "wee_alloc")]
|
#[cfg(feature = "wee_alloc")]
|
||||||
// #[global_allocator]
|
#[global_allocator]
|
||||||
// static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||||
|
|
||||||
/// Wrap the [`web_sys`] access to the browser console in a macro for easy logging
|
/// Wrap the [`web_sys`] access to the browser console in a macro for easy logging
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
@ -47,10 +47,9 @@ const KING_OUTLINE: &str = "#ffea00";
|
|||||||
/// Whether to outline pieces
|
/// Whether to outline pieces
|
||||||
const DRAW_PIECE_OUTLINES: bool = true;
|
const DRAW_PIECE_OUTLINES: bool = true;
|
||||||
/// Line width for outlining pieces
|
/// Line width for outlining pieces
|
||||||
const PIECE_OUTLINE_WIDTH: f64 = 3.0;
|
const PIECE_OUTLINE_WIDTH: f64 = 6.0;
|
||||||
|
|
||||||
/// Margin from square to define piece radius
|
/// Margin from square to define piece radius
|
||||||
const PIECE_MARGIN: f64 = 10.0;
|
const PIECE_MARGIN: f64 = 14.0;
|
||||||
|
|
||||||
/// Used to paint boards onto HTML canvases
|
/// Used to paint boards onto HTML canvases
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
|
@ -25,6 +25,46 @@
|
|||||||
h1 {
|
h1 {
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#game-canvas {
|
||||||
|
width: 1000px;
|
||||||
|
height: 1000px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 1000px) {
|
||||||
|
#game-canvas {
|
||||||
|
width: 700px;
|
||||||
|
height: 700px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 800px) {
|
||||||
|
#game-canvas {
|
||||||
|
width: 600px;
|
||||||
|
height: 600px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
#game-canvas {
|
||||||
|
width: 400px;
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 400px) {
|
||||||
|
#game-canvas {
|
||||||
|
width: 300px;
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 300px) {
|
||||||
|
#game-canvas {
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@ -42,10 +82,55 @@
|
|||||||
<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" target="_blank">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-success">Start</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row p-3">
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<input type="number"
|
||||||
|
id="width"
|
||||||
|
name="width"
|
||||||
|
min="3" max="40" value="8"
|
||||||
|
class="form-control">
|
||||||
|
<label for="width">width</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<input type="number"
|
||||||
|
id="height"
|
||||||
|
name="height"
|
||||||
|
min="3" max="40" value="8"
|
||||||
|
class="form-control">
|
||||||
|
<label for="height">height</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<input type="number"
|
||||||
|
id="play_rows"
|
||||||
|
name="play_rows"
|
||||||
|
min="1" max="3" value="3"
|
||||||
|
class="form-control">
|
||||||
|
<label for="play_rows">playable rows</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row p-3">
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<input class="form-check-input" type="checkbox" value="" id="ai-checkbox" checked="checked">
|
||||||
|
<label class="form-check-label" for="ai-checkbox">
|
||||||
|
AI Player
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<input type="number"
|
||||||
|
id="ai_search_depth"
|
||||||
|
name="ai_search_depth"
|
||||||
|
min="1" max="10" value="4"
|
||||||
|
class="form-control">
|
||||||
|
<label for="ai_search_depth">ai difficulty</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<p class="text-muted" id="node-count"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row p-3">
|
<div class="row p-3">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<h1 id="team-p"></h1>
|
<h1 id="team-p"></h1>
|
||||||
|
93
www/index.js
93
www/index.js
@ -5,13 +5,14 @@ import { Game, Board, BrdIdx, Painter, Team, init_wasm, Moveable, SquareState, S
|
|||||||
// CONSTS
|
// CONSTS
|
||||||
///////////////////
|
///////////////////
|
||||||
|
|
||||||
const CANVAS_WIDTH = 480;
|
const CANVAS_WIDTH = 720;
|
||||||
const CANVAS_HEIGHT = 480;
|
const CANVAS_HEIGHT = 720;
|
||||||
|
|
||||||
const BOARD_WIDTH = 8;
|
var BOARD_WIDTH = 8;
|
||||||
const BOARD_HEIGHT = 8;
|
var BOARD_HEIGHT = 8;
|
||||||
|
|
||||||
const PIECE_ROWS = 3;
|
var PIECE_ROWS = 3;
|
||||||
|
var SEARCH_DEPTH = 4;
|
||||||
|
|
||||||
const STATUS_TIMEOUT = 3000;
|
const STATUS_TIMEOUT = 3000;
|
||||||
|
|
||||||
@ -34,6 +35,7 @@ init_wasm();
|
|||||||
const statusText = document.getElementById("status-p");
|
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 nodeCountText = document.getElementById("node-count");
|
||||||
|
|
||||||
const startBtn = document.getElementById("startBtn");
|
const startBtn = document.getElementById("startBtn");
|
||||||
startBtn.onclick = start_game;
|
startBtn.onclick = start_game;
|
||||||
@ -59,24 +61,25 @@ canvas.addEventListener("click", (event) => {
|
|||||||
var mousepos = getMousePos(canvas, event);
|
var mousepos = getMousePos(canvas, event);
|
||||||
// console.log(mousepos);
|
// console.log(mousepos);
|
||||||
var cell = new BrdIdx(
|
var cell = new BrdIdx(
|
||||||
Math.floor((mousepos.y / CANVAS_HEIGHT) * BOARD_HEIGHT),
|
Math.floor((mousepos.y / canvas.clientHeight) * BOARD_HEIGHT),
|
||||||
Math.floor((mousepos.x / CANVAS_WIDTH) * BOARD_WIDTH),
|
Math.floor((mousepos.x / canvas.clientWidth) * BOARD_WIDTH),
|
||||||
);
|
);
|
||||||
// console.log(cell);
|
// console.log(cell);
|
||||||
process_canvas_click(cell);
|
process_canvas_click(cell);
|
||||||
})
|
});
|
||||||
|
|
||||||
////////////////
|
////////////////
|
||||||
// FUNCS
|
// FUNCS
|
||||||
////////////////
|
////////////////
|
||||||
|
|
||||||
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, SEARCH_DEPTH);
|
||||||
painter = new Painter(CANVAS_WIDTH, CANVAS_HEIGHT, "game-canvas");
|
painter = new Painter(CANVAS_WIDTH, CANVAS_HEIGHT, "game-canvas");
|
||||||
game.set_painter(painter);
|
game.set_painter(painter);
|
||||||
game.draw();
|
game.draw();
|
||||||
|
|
||||||
updateTeamText();
|
updateTeamText();
|
||||||
|
clicks = [];
|
||||||
current_state = GameState.HUMAN_TURN.THINKING;
|
current_state = GameState.HUMAN_TURN.THINKING;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,6 +131,14 @@ function process_canvas_click(cell_coord) {
|
|||||||
|
|
||||||
switch(status) {
|
switch(status) {
|
||||||
case Moveable.Allowed:
|
case Moveable.Allowed:
|
||||||
|
console.log(`Score after your turn: ${game.score()}`);
|
||||||
|
|
||||||
|
if (aiCheckBox.checked) {
|
||||||
|
game.ai_move();
|
||||||
|
nodeCountText.innerText = `searched ${game.last_node_count.toLocaleString("en-GB")} possible moves`;
|
||||||
|
console.log(`Score after the AI's turn: ${game.score()}`);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case Moveable.IllegalTrajectory:
|
case Moveable.IllegalTrajectory:
|
||||||
setStatus("You can't move like that!");
|
setStatus("You can't move like that!");
|
||||||
@ -220,3 +231,67 @@ function updateTeamText(){
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////
|
||||||
|
// UI
|
||||||
|
////////////////
|
||||||
|
|
||||||
|
const widthBox = document.getElementById("width");
|
||||||
|
/**
|
||||||
|
* Handler for width input box change, start a new game
|
||||||
|
*/
|
||||||
|
const onWidth = () => {
|
||||||
|
|
||||||
|
BOARD_WIDTH = parseInt(widthBox.value);
|
||||||
|
start_game();
|
||||||
|
}
|
||||||
|
widthBox.onchange = onWidth;
|
||||||
|
widthBox.value = 8;
|
||||||
|
|
||||||
|
const heightBox = document.getElementById("height");
|
||||||
|
/**
|
||||||
|
* Handler for height input box change, start a new game
|
||||||
|
*/
|
||||||
|
const onHeight = () => {
|
||||||
|
|
||||||
|
BOARD_HEIGHT = parseInt(heightBox.value);
|
||||||
|
pieceRowsBox.max = (BOARD_HEIGHT / 2) - 1;
|
||||||
|
start_game();
|
||||||
|
}
|
||||||
|
heightBox.onchange = onHeight;
|
||||||
|
heightBox.value = 8;
|
||||||
|
|
||||||
|
const pieceRowsBox = document.getElementById("play_rows");
|
||||||
|
/**
|
||||||
|
* Handler for piece rows input box change, start a new game
|
||||||
|
*/
|
||||||
|
const onPieceRows = () => {
|
||||||
|
|
||||||
|
PIECE_ROWS = parseInt(pieceRowsBox.value);
|
||||||
|
console.log(typeof(PIECE_ROWS));
|
||||||
|
start_game();
|
||||||
|
}
|
||||||
|
pieceRowsBox.onchange = onPieceRows;
|
||||||
|
pieceRowsBox.value = 3;
|
||||||
|
|
||||||
|
const aiSearchDepthBox = document.getElementById("ai_search_depth");
|
||||||
|
/**
|
||||||
|
* Handler for AI search depth input box change, start a new game
|
||||||
|
*/
|
||||||
|
const onAISearchDepth = () => {
|
||||||
|
|
||||||
|
SEARCH_DEPTH = parseInt(aiSearchDepthBox.value);
|
||||||
|
game.set_search_depth(SEARCH_DEPTH);
|
||||||
|
}
|
||||||
|
aiSearchDepthBox.onchange = onAISearchDepth;
|
||||||
|
aiSearchDepthBox.value = 4;
|
||||||
|
|
||||||
|
const aiCheckBox = document.getElementById("ai-checkbox");
|
||||||
|
/**
|
||||||
|
* Handler for height input box change, get a new universe of given size
|
||||||
|
*/
|
||||||
|
const onAICheck = () => {
|
||||||
|
console.log(aiCheckBox.checked);
|
||||||
|
}
|
||||||
|
aiCheckBox.onchange = onAICheck;
|
||||||
|
// aiCheckBox.checked = true;
|
Loading…
Reference in New Issue
Block a user