AI perfect chance, rendering with proportions instead of absolute pixel values
This commit is contained in:
parent
ab0d0e5df3
commit
40d47f8042
104
src/comp/mod.rs
104
src/comp/mod.rs
@ -1,12 +1,15 @@
|
|||||||
//! AI player logic
|
//! AI player logic
|
||||||
|
|
||||||
use indextree::{Arena, Node, NodeId, NodeEdge};
|
use indextree::{Arena, Node, NodeId, NodeEdge};
|
||||||
|
|
||||||
|
use rand::prelude::*;
|
||||||
use rand::seq::SliceRandom;
|
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::log_error;
|
||||||
|
|
||||||
use crate::board::{Board, BrdIdx};
|
use crate::board::{Board, BrdIdx};
|
||||||
use crate::board::enums::{MoveType, Moveable, Team};
|
use crate::board::enums::{MoveType, Moveable, Team};
|
||||||
@ -63,13 +66,15 @@ pub struct Computer {
|
|||||||
pub search_depth: usize,
|
pub search_depth: usize,
|
||||||
pub team: Team,
|
pub team: Team,
|
||||||
pub last_node_count: usize,
|
pub last_node_count: usize,
|
||||||
|
pub perfect_chance: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Computer {
|
impl Computer {
|
||||||
pub fn new(search_depth: usize, team: Team) -> Computer {
|
pub fn new(search_depth: usize, team: Team, perfect_chance: f64) -> Computer {
|
||||||
Computer {
|
Computer {
|
||||||
search_depth,
|
search_depth,
|
||||||
team,
|
team,
|
||||||
|
perfect_chance,
|
||||||
last_node_count: 0,
|
last_node_count: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -333,49 +338,88 @@ impl Computer {
|
|||||||
.expect("No node returned for node id")
|
.expect("No node returned for node id")
|
||||||
.get(); // get BoardNode from Node
|
.get(); // get BoardNode from Node
|
||||||
|
|
||||||
// when boards have equal scores, store for shuffling and selection
|
// node ids of available next moves
|
||||||
let mut equal_scores = Vec::with_capacity(10);
|
let possible_moves: Vec<NodeId> = root_node.children(&tree).collect();
|
||||||
|
|
||||||
|
if possible_moves.len() == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
// DEBUG
|
// DEBUG
|
||||||
#[cfg(feature = "debug_logs")]
|
#[cfg(feature = "debug_logs")]
|
||||||
{
|
{
|
||||||
log!("Current root score: {}", root_board_node.score);
|
log!("Current root score: {}", root_board_node.score);
|
||||||
let scores: Vec<NodeId> = root_node
|
let scores: Vec<isize> = possible_moves
|
||||||
.children(&tree)
|
.iter()
|
||||||
.collect();
|
.map(|n| tree.get(*n).unwrap().get().score)
|
||||||
let scores: Vec<isize> = scores
|
|
||||||
.into_iter()
|
|
||||||
.map(|n| tree.get(n).unwrap().get().score)
|
|
||||||
.collect();
|
.collect();
|
||||||
log!("Next boards scores: {:?}", scores);
|
log!("Next boards scores: {:?}", scores);
|
||||||
}
|
}
|
||||||
|
|
||||||
// search through root node's children for the same score
|
let mut rng = rand::thread_rng();
|
||||||
for n in root_node.children(&tree) {
|
// random number to compare against threshold
|
||||||
|
let perfect_num: f64 = rng.gen();
|
||||||
|
|
||||||
// get each board
|
// make perfect move
|
||||||
let iter_board_node = tree
|
if perfect_num < self.perfect_chance {
|
||||||
.get(n) // get Node
|
#[cfg(feature = "debug_logs")]
|
||||||
.expect("No node returned for node id")
|
log!("Making perfect move");
|
||||||
.get(); // get BoardNode from Node
|
|
||||||
|
|
||||||
if root_board_node.score == iter_board_node.score {
|
// get boards of equal score that are perfect for the given player
|
||||||
equal_scores.push(iter_board_node);
|
let possible_perfect_moves: Vec<&BoardNode> = possible_moves
|
||||||
// return Some(iter_board_node.board.clone());
|
.iter()
|
||||||
|
.map(
|
||||||
|
// get immutable references to BoardNodes for possible moves
|
||||||
|
|n| tree
|
||||||
|
.get(*n) // get Node using NodeID
|
||||||
|
.expect("Unable to get perfect move data from tree node")
|
||||||
|
.get() // get *BoardNode from Node
|
||||||
|
)
|
||||||
|
.filter(
|
||||||
|
// filter for only scores of root node which are perfect moves
|
||||||
|
|b| b.score == root_board_node.score
|
||||||
|
)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// weird error, no child nodes have same score as root node
|
||||||
|
// this is odd because the root nodes score is either the max or min of it's children
|
||||||
|
if possible_perfect_moves.len() == 0 {
|
||||||
|
log_error!("No next moves matched the score of the root node, picking randomly instead");
|
||||||
|
|
||||||
|
Some(Computer::random_choice(&tree, possible_moves, &mut rng))
|
||||||
|
}
|
||||||
|
// only one possible move, use that
|
||||||
|
else if possible_perfect_moves.len() == 1 {
|
||||||
|
Some(possible_perfect_moves[0].board.clone())
|
||||||
|
}
|
||||||
|
// more than one possible perfect move to make, choose one randomly
|
||||||
|
else {
|
||||||
|
Some(
|
||||||
|
possible_perfect_moves
|
||||||
|
.choose(&mut rng) // random choice
|
||||||
|
.unwrap() // unwrap Option
|
||||||
|
.board
|
||||||
|
.clone()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// get random move
|
||||||
|
else {
|
||||||
|
#[cfg(feature = "debug_logs")]
|
||||||
|
log!("Making random move");
|
||||||
|
|
||||||
if equal_scores.len() == 0 {
|
Some(Computer::random_choice(&tree, possible_moves, &mut rng))
|
||||||
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()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a random board from possible node IDs and associated tree
|
||||||
|
fn random_choice(tree: &Arena<BoardNode>, possible_moves: Vec<NodeId>, rng: &mut ThreadRng) -> Board {
|
||||||
|
let chosen_move = possible_moves.choose(rng).unwrap();
|
||||||
|
tree
|
||||||
|
.get(*chosen_move)
|
||||||
|
.expect("Unable to get random move data from tree node")
|
||||||
|
.get()
|
||||||
|
.board
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
}
|
}
|
@ -4,7 +4,7 @@ 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::*;
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ fn available_moves() {
|
|||||||
// _ . _
|
// _ . _
|
||||||
|
|
||||||
let mut brd = Board::new(3, 2, White);
|
let mut brd = Board::new(3, 2, White);
|
||||||
let comp = Computer::new(3, White);
|
let comp = Computer::new(3, White, 0.5);
|
||||||
|
|
||||||
// log!("{}", brd);
|
// log!("{}", brd);
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ fn available_moves_jumps() {
|
|||||||
// _ . _ .
|
// _ . _ .
|
||||||
|
|
||||||
let mut brd = Board::new(4, 4, White);
|
let mut brd = Board::new(4, 4, White);
|
||||||
let comp = Computer::new(3, White);
|
let comp = Computer::new(3, White, 0.5);
|
||||||
|
|
||||||
// log!("{}", brd);
|
// log!("{}", brd);
|
||||||
|
|
||||||
@ -71,7 +71,7 @@ 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 comp = Computer::new(3, White);
|
let comp = Computer::new(3, White, 0.5);
|
||||||
|
|
||||||
// log!("{}", brd);
|
// log!("{}", brd);
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ fn available_moves_std_brd() {
|
|||||||
#[wasm_bindgen_test]
|
#[wasm_bindgen_test]
|
||||||
fn expand_node() {
|
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 mut comp = Computer::new(3, White);
|
let mut comp = Computer::new(3, White, 0.5);
|
||||||
|
|
||||||
// log!("{}", brd);
|
// log!("{}", brd);
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ fn expand_node() {
|
|||||||
#[wasm_bindgen_test]
|
#[wasm_bindgen_test]
|
||||||
fn expand_layer() {
|
fn expand_layer() {
|
||||||
let brd = Board::init_game(Board::new(8, 8, White), 3);
|
let brd = Board::init_game(Board::new(8, 8, White), 3);
|
||||||
let mut comp = Computer::new(3, White);
|
let mut comp = Computer::new(3, White, 0.5);
|
||||||
|
|
||||||
// log!("{}", brd);
|
// log!("{}", brd);
|
||||||
|
|
||||||
@ -123,7 +123,7 @@ fn expand_layer() {
|
|||||||
#[wasm_bindgen_test]
|
#[wasm_bindgen_test]
|
||||||
fn leaf_nodes() {
|
fn leaf_nodes() {
|
||||||
let brd = Board::init_game(Board::new(8, 8, White), 3);
|
let brd = Board::init_game(Board::new(8, 8, White), 3);
|
||||||
let mut comp = Computer::new(3, White);
|
let mut comp = Computer::new(3, White, 0.5);
|
||||||
|
|
||||||
let mut tree = Arena::new();
|
let mut tree = Arena::new();
|
||||||
let id = tree.new_node(BoardNode::brd(brd));
|
let id = tree.new_node(BoardNode::brd(brd));
|
||||||
@ -160,7 +160,7 @@ fn insert_scores_all_take() {
|
|||||||
brd.set_cell(brd.cell_idx(BrdIdx::from(1, 4)), 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, 1)), Square::pc(Black, Man));
|
||||||
brd.set_cell(brd.cell_idx(BrdIdx::from(2, 3)), Square::pc(Black, Man));
|
brd.set_cell(brd.cell_idx(BrdIdx::from(2, 3)), Square::pc(Black, Man));
|
||||||
let mut comp = Computer::new(1, White);
|
let mut comp = Computer::new(1, White, 0.5);
|
||||||
|
|
||||||
// log!("{}", brd);
|
// log!("{}", brd);
|
||||||
|
|
||||||
@ -203,7 +203,7 @@ fn insert_scores_one_take() {
|
|||||||
brd.set_cell(brd.cell_idx(BrdIdx::from(1, 4)), 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, 1)), Square::pc(Black, Man));
|
||||||
// brd.set_cell(brd.cell_idx(BrdIdx::from(2, 3)), Square::pc(Black, Man));
|
// brd.set_cell(brd.cell_idx(BrdIdx::from(2, 3)), Square::pc(Black, Man));
|
||||||
let mut comp = Computer::new(1, White);
|
let mut comp = Computer::new(1, White, 0.5);
|
||||||
|
|
||||||
// log!("{}", brd);
|
// log!("{}", brd);
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ pub struct Game {
|
|||||||
painter: Option<Painter>,
|
painter: Option<Painter>,
|
||||||
search_depth: usize,
|
search_depth: usize,
|
||||||
pub last_node_count: usize,
|
pub last_node_count: usize,
|
||||||
|
pub perfect_chance: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Game {
|
impl Game {
|
||||||
@ -95,6 +96,7 @@ impl Game {
|
|||||||
self.current.cell(self.current.cell_idx(*idx))
|
self.current.cell(self.current.cell_idx(*idx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set tree depth for AI to search to
|
||||||
pub fn set_search_depth(&mut self, search_depth: usize) {
|
pub fn set_search_depth(&mut self, search_depth: usize) {
|
||||||
self.search_depth = search_depth;
|
self.search_depth = search_depth;
|
||||||
}
|
}
|
||||||
@ -114,6 +116,11 @@ impl Game {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set proportion of perfect moves from AI
|
||||||
|
pub fn set_perfect_chance(&mut self, new_chance: f64) {
|
||||||
|
self.perfect_chance = new_chance;
|
||||||
|
}
|
||||||
|
|
||||||
/// Clear currently selected piece
|
/// Clear currently selected piece
|
||||||
pub fn clear_selected(&mut self) {
|
pub fn clear_selected(&mut self) {
|
||||||
self.selected_piece = None;
|
self.selected_piece = None;
|
||||||
@ -180,6 +187,7 @@ impl Game {
|
|||||||
painter: None,
|
painter: None,
|
||||||
search_depth,
|
search_depth,
|
||||||
last_node_count: 0,
|
last_node_count: 0,
|
||||||
|
perfect_chance: 0.5,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,6 +204,7 @@ impl Game {
|
|||||||
),
|
),
|
||||||
search_depth,
|
search_depth,
|
||||||
last_node_count: 0,
|
last_node_count: 0,
|
||||||
|
perfect_chance: 0.5,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,14 +224,22 @@ impl Game {
|
|||||||
/// Create computer, get move from current board and update current board
|
/// Create computer, get move from current board and update current board
|
||||||
pub fn ai_move(&mut self) {
|
pub fn ai_move(&mut self) {
|
||||||
|
|
||||||
let mut comp = Computer::new(self.search_depth, self.current.current_turn);
|
let mut comp = Computer::new(self.search_depth, self.current.current_turn, self.perfect_chance);
|
||||||
|
|
||||||
let new_brd = comp.get_move(self.current.clone());
|
let new_brd = comp.get_move(self.current.clone());
|
||||||
|
|
||||||
self.last_node_count = comp.last_node_count;
|
self.last_node_count = comp.last_node_count;
|
||||||
|
|
||||||
match new_brd {
|
match new_brd {
|
||||||
Some(brd) => self.push_new_board(brd),
|
Some(brd) => self.push_new_board(brd),
|
||||||
None => panic!("No AI move returned"),
|
None => {
|
||||||
|
log!("No possible moves, re-pushing current board");
|
||||||
|
|
||||||
|
let mut new_brd = self.current.clone();
|
||||||
|
new_brd.current_turn = new_brd.current_turn.opponent();
|
||||||
|
|
||||||
|
self.push_new_board(new_brd);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ wasm_bindgen_test_configure!(run_in_browser);
|
|||||||
|
|
||||||
// use crate::board::{Piece};
|
// use crate::board::{Piece};
|
||||||
use crate::board::enums::Strength::*;
|
use crate::board::enums::Strength::*;
|
||||||
use crate::board::enums::Team::*;
|
// use crate::board::enums::Team::*;
|
||||||
|
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
#[wasm_bindgen_test]
|
||||||
|
@ -29,6 +29,12 @@ macro_rules! log {
|
|||||||
web_sys::console::log_1(&format!( $( $t )* ).into());
|
web_sys::console::log_1(&format!( $( $t )* ).into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! log_error {
|
||||||
|
( $( $t:tt )* ) => {
|
||||||
|
web_sys::console::error_1(&format!( $( $t )* ).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn init_wasm() {
|
pub fn init_wasm() {
|
||||||
|
35
src/paint.rs
35
src/paint.rs
@ -23,8 +23,8 @@ const BLACK_SQUARE: &str = "#000000";
|
|||||||
|
|
||||||
/// Default hex colour value for outline of black squares
|
/// Default hex colour value for outline of black squares
|
||||||
const SQUARE_OUTLINE: &str = "#9c9c9c";
|
const SQUARE_OUTLINE: &str = "#9c9c9c";
|
||||||
/// Line width when outlining black squares
|
/// Line width when outlining black squares as proportion of min cell dimension
|
||||||
const OUTLINE_WIDTH: f64 = 3.0;
|
const OUTLINE_WIDTH: f64 = 0.05;
|
||||||
/// Whether to outline black squares
|
/// Whether to outline black squares
|
||||||
const DRAW_OUTLINE: bool = true;
|
const DRAW_OUTLINE: bool = true;
|
||||||
|
|
||||||
@ -46,10 +46,10 @@ const SELECTED_PIECE_OUTLINE: &str = "#d1cf45";
|
|||||||
const KING_OUTLINE: &str = "#ffea00";
|
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 as proportion of piece radius
|
||||||
const PIECE_OUTLINE_WIDTH: f64 = 6.0;
|
const PIECE_OUTLINE_PROPORTION: f64 = 0.25;
|
||||||
/// Margin from square to define piece radius
|
/// Proportion of square that piece fills as proportion of min cell dimension
|
||||||
const PIECE_MARGIN: f64 = 14.0;
|
const PIECE_PROPORTION: f64 = 0.6;
|
||||||
|
|
||||||
/// Used to paint boards onto HTML canvases
|
/// Used to paint boards onto HTML canvases
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
@ -72,7 +72,7 @@ pub struct Painter {
|
|||||||
king_line: JsValue,
|
king_line: JsValue,
|
||||||
|
|
||||||
piece_lines: bool,
|
piece_lines: bool,
|
||||||
piece_line_width: f64,
|
piece_line_proportion: f64,
|
||||||
|
|
||||||
square_outline: JsValue,
|
square_outline: JsValue,
|
||||||
outline_width: f64,
|
outline_width: f64,
|
||||||
@ -168,7 +168,7 @@ impl Painter {
|
|||||||
selected_piece_line: JsValue::from_str(SELECTED_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_proportion: PIECE_OUTLINE_PROPORTION,
|
||||||
|
|
||||||
square_outline: JsValue::from_str(SQUARE_OUTLINE),
|
square_outline: JsValue::from_str(SQUARE_OUTLINE),
|
||||||
outline_width: OUTLINE_WIDTH,
|
outline_width: OUTLINE_WIDTH,
|
||||||
@ -201,7 +201,7 @@ impl Painter {
|
|||||||
selected_piece_line: JsValue::from_str(SELECTED_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_proportion: PIECE_OUTLINE_PROPORTION,
|
||||||
|
|
||||||
square_outline: JsValue::from_str(SQUARE_OUTLINE),
|
square_outline: JsValue::from_str(SQUARE_OUTLINE),
|
||||||
outline_width: OUTLINE_WIDTH,
|
outline_width: OUTLINE_WIDTH,
|
||||||
@ -255,12 +255,19 @@ impl Painter {
|
|||||||
let cell_height = self.height as usize / board.height;
|
let cell_height = self.height as usize / board.height;
|
||||||
let cell_width = self.width as usize / board.width;
|
let cell_width = self.width as usize / board.width;
|
||||||
|
|
||||||
|
let min_dimension = usize::min(cell_width, cell_height) as f64;
|
||||||
|
|
||||||
|
let cell_radius = min_dimension * PIECE_PROPORTION / 2.0;
|
||||||
|
|
||||||
|
let piece_outline = cell_radius * self.piece_line_proportion;
|
||||||
|
let square_outline = min_dimension * self.outline_width;
|
||||||
|
|
||||||
self.context.set_fill_style(&self.white_square);
|
self.context.set_fill_style(&self.white_square);
|
||||||
self.context.fill_rect(0.0, 0.0, self.width as f64, self.height as f64);
|
self.context.fill_rect(0.0, 0.0, self.width as f64, self.height as f64);
|
||||||
|
|
||||||
self.context.set_fill_style(&self.black_square);
|
self.context.set_fill_style(&self.black_square);
|
||||||
self.context.set_stroke_style(&self.square_outline);
|
self.context.set_stroke_style(&self.square_outline);
|
||||||
self.context.set_line_width(self.outline_width);
|
self.context.set_line_width(square_outline);
|
||||||
|
|
||||||
// Draw black squares onto canvas
|
// Draw black squares onto canvas
|
||||||
for i in 0..board.height {
|
for i in 0..board.height {
|
||||||
@ -336,7 +343,7 @@ impl Painter {
|
|||||||
match self.context.arc(
|
match self.context.arc(
|
||||||
center_x,
|
center_x,
|
||||||
center_y,
|
center_y,
|
||||||
(cell_width as f64 / 2.0) - PIECE_MARGIN, // radius
|
cell_radius, // radius
|
||||||
0.0, // start angle
|
0.0, // start angle
|
||||||
f64::consts::PI * 2.0) // end angle
|
f64::consts::PI * 2.0) // end angle
|
||||||
{
|
{
|
||||||
@ -346,7 +353,7 @@ impl Painter {
|
|||||||
self.context.fill();
|
self.context.fill();
|
||||||
|
|
||||||
if self.piece_lines {
|
if self.piece_lines {
|
||||||
self.context.set_line_width(self.piece_line_width);
|
self.context.set_line_width(piece_outline);
|
||||||
self.context.stroke()
|
self.context.stroke()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -366,7 +373,7 @@ impl Painter {
|
|||||||
match self.context.arc(
|
match self.context.arc(
|
||||||
center_x,
|
center_x,
|
||||||
center_y,
|
center_y,
|
||||||
(cell_width as f64 / 2.0) - PIECE_MARGIN, // radius
|
cell_radius, // radius
|
||||||
0.0, // start angle
|
0.0, // start angle
|
||||||
f64::consts::PI * 2.0) // end angle
|
f64::consts::PI * 2.0) // end angle
|
||||||
{
|
{
|
||||||
@ -376,7 +383,7 @@ impl Painter {
|
|||||||
self.context.fill();
|
self.context.fill();
|
||||||
|
|
||||||
if self.piece_lines {
|
if self.piece_lines {
|
||||||
self.context.set_line_width(self.piece_line_width);
|
self.context.set_line_width(piece_outline);
|
||||||
self.context.stroke()
|
self.context.stroke()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row p-3">
|
<div class="row p-3">
|
||||||
<div class="col-sm-4" title="should the AI play?">
|
<div class="col-sm-3" title="should the AI play?">
|
||||||
<input class="form-check-input"
|
<input class="form-check-input"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
value=""
|
value=""
|
||||||
@ -129,15 +129,19 @@
|
|||||||
AI Player
|
AI Player
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-4" title="how many layers deep should the AI search (grows exponentially, be careful)">
|
<div class="col-sm-3" title="how many layers deep should the AI search (grows exponentially, be careful)">
|
||||||
<input type="number"
|
<input type="number"
|
||||||
id="ai_search_depth"
|
id="ai_search_depth"
|
||||||
name="ai_search_depth"
|
name="ai_search_depth"
|
||||||
min="1" max="10" value="4"
|
min="1" max="10" value="4"
|
||||||
class="form-control">
|
class="form-control">
|
||||||
<label for="ai_search_depth">ai difficulty <small class="text-muted">moves ahead</small></label>
|
<label for="ai_search_depth">ai clairvoyance <small class="text-muted">moves ahead</small></label>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-4" title="how many nodes were expanded in the search tree">
|
<div class="col-sm-3" title="what percentage of the AI's moves should be perfect?">
|
||||||
|
<label for="ai_difficulty" class="form-label">ai difficulty <small class="text-muted">%</small></label>
|
||||||
|
<input type="range" class="form-range" min="1" max="100" id="ai_difficulty">
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-3" title="how many nodes were expanded in the search tree">
|
||||||
<p class="text-muted" id="node-count"></p>
|
<p class="text-muted" id="node-count"></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
15
www/index.js
15
www/index.js
@ -13,6 +13,7 @@ var BOARD_HEIGHT = 8;
|
|||||||
|
|
||||||
var PIECE_ROWS = 3;
|
var PIECE_ROWS = 3;
|
||||||
var SEARCH_DEPTH = 4;
|
var SEARCH_DEPTH = 4;
|
||||||
|
var PERFECT_CHANCE = 0.5;
|
||||||
|
|
||||||
const STATUS_TIMEOUT = 3000;
|
const STATUS_TIMEOUT = 3000;
|
||||||
const WON_TIMEOUT = 3000;
|
const WON_TIMEOUT = 3000;
|
||||||
@ -137,7 +138,7 @@ function process_canvas_click(cell_coord) {
|
|||||||
switch(status) {
|
switch(status) {
|
||||||
case Moveable.Allowed:
|
case Moveable.Allowed:
|
||||||
|
|
||||||
if (aiCheckBox.checked) {
|
if (aiCheckBox.checked && game.has_won() === undefined) {
|
||||||
game.ai_move();
|
game.ai_move();
|
||||||
nodeCountText.innerText = `searched ${game.last_node_count.toLocaleString("en-GB")} possible moves`;
|
nodeCountText.innerText = `searched ${game.last_node_count.toLocaleString("en-GB")} possible moves`;
|
||||||
}
|
}
|
||||||
@ -338,3 +339,15 @@ const onAICheck = () => {
|
|||||||
}
|
}
|
||||||
aiCheckBox.onchange = onAICheck;
|
aiCheckBox.onchange = onAICheck;
|
||||||
// aiCheckBox.checked = true;
|
// aiCheckBox.checked = true;
|
||||||
|
|
||||||
|
const aiPerfectChance = document.getElementById("ai_difficulty");
|
||||||
|
/**
|
||||||
|
* Handler for piece rows input box change, start a new game
|
||||||
|
*/
|
||||||
|
const onPerfectChance = () => {
|
||||||
|
|
||||||
|
PERFECT_CHANCE = parseInt(aiPerfectChance.value) / 100;
|
||||||
|
game.set_perfect_chance(PERFECT_CHANCE);
|
||||||
|
}
|
||||||
|
aiPerfectChance.onchange = onPerfectChance;
|
||||||
|
aiPerfectChance.value = 50;
|
Loading…
Reference in New Issue
Block a user