diff --git a/Cargo.toml b/Cargo.toml index 48156c7..0bcd009 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,16 +9,10 @@ repository = "https://github.com/Sarsoo/checkers" crate-type = ["cdylib", "rlib"] [features] -default = ["console_error_panic_hook", - "random_init" - ] - -random_init = ["rand", "rand_pcg"] +default = ["console_error_panic_hook"] [dependencies] wasm-bindgen = "0.2.74" -rand = {version = "0.8.4", optional = true } -rand_pcg = {version = "0.3.1", optional = true } indextree = "4.3.1" # The `console_error_panic_hook` crate provides better debugging of panics by @@ -38,10 +32,13 @@ wee_alloc = { version = "0.4.5", optional = true } version = "0.3.45" features = [ "console", -] -[dependencies.getrandom] -features = ["js"] + 'CanvasRenderingContext2d', + 'Document', + 'Element', + 'HtmlCanvasElement', + 'Window', +] [dev-dependencies] wasm-bindgen-test = "0.3.24" diff --git a/src/board/enums.rs b/src/board/enums.rs index 7cf29dc..25d8033 100644 --- a/src/board/enums.rs +++ b/src/board/enums.rs @@ -1,7 +1,7 @@ extern crate wasm_bindgen; use wasm_bindgen::prelude::*; -use std::fmt::{Display, Write}; +use std::fmt::{Display}; #[wasm_bindgen] #[repr(u8)] diff --git a/src/board/iter.rs b/src/board/iter.rs index a67c9eb..f44dcce 100644 --- a/src/board/iter.rs +++ b/src/board/iter.rs @@ -1,4 +1,4 @@ -use crate::board::{Board, Piece, Square}; +use crate::board::{Board, Square}; use crate::board::enums::*; pub struct RowIndexIterator<'a> { @@ -104,9 +104,10 @@ impl<'a> Iterator for PieceIterator<'a> { pub mod tests { use super::*; use crate::board::enums::SquareState; + use crate::board::Piece; use wasm_bindgen_test::*; - use crate::log; + // use crate::log; wasm_bindgen_test_configure!(run_in_browser); diff --git a/src/board/mod.rs b/src/board/mod.rs index 8896bbe..1b10084 100644 --- a/src/board/mod.rs +++ b/src/board/mod.rs @@ -14,6 +14,8 @@ use iter::*; use std::fmt::{Display, Write}; use std::option::Option; +use crate::log; + extern crate wasm_bindgen; use wasm_bindgen::prelude::*; @@ -119,8 +121,8 @@ impl Square { #[wasm_bindgen] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct BrdIdx { - row: usize, - col: usize + pub row: usize, + pub col: usize } #[wasm_bindgen] @@ -781,10 +783,13 @@ impl Display for Board { for j in 0..self.width { let idx = self.cell_index(i, j); - match self.cell_state(idx) { - Empty => { write!(string, "_ "); }, - Occupied => { write!(string, "{} ", self.cell(idx).occupant.unwrap().team); }, - Unplayable => { write!(string, ". "); }, + let result = match self.cell_state(idx) { + Empty => write!(string, "_ "), + Occupied => write!(string, "{} ", self.cell(idx).occupant.unwrap().team), + Unplayable => write!(string, ". "), + }; + if let Err(err) = result { + log!("Error printing cell state, ({}, {}), {}", i, j, err); } } string.push('\n'); diff --git a/src/board/tests.rs b/src/board/tests.rs index d76871d..4fa3e26 100644 --- a/src/board/tests.rs +++ b/src/board/tests.rs @@ -1,6 +1,6 @@ use super::*; use wasm_bindgen_test::*; -use crate::log; +// use crate::log; wasm_bindgen_test_configure!(run_in_browser); @@ -13,7 +13,7 @@ wasm_bindgen_test_configure!(run_in_browser); #[wasm_bindgen_test] fn create() { - let board = Board::new(STD_WIDTH, STD_HEIGHT, Black); + let _ = Board::new(STD_WIDTH, STD_HEIGHT, Black); assert!(true); } diff --git a/src/comp/mod.rs b/src/comp/mod.rs index 2a9702d..7fd81c4 100644 --- a/src/comp/mod.rs +++ b/src/comp/mod.rs @@ -1,21 +1,21 @@ //! AI player logic -use indextree::{Arena, NodeId, Node}; +use indextree::{Arena, NodeId}; extern crate wasm_bindgen; -use wasm_bindgen::prelude::*; +// use wasm_bindgen::prelude::*; -use crate::log; +// use crate::log; -use crate::board::{Square, Board, BrdIdx}; -use crate::board::enums::{SquareState, MoveType, Moveable, Team, Strength}; +use crate::board::{Board, BrdIdx}; +use crate::board::enums::{MoveType, Moveable, Team}; use crate::board::iter::{PieceIterator}; -use Team::*; -use Strength::*; -use SquareState::*; +// use Team::*; +// use Strength::*; +// use SquareState::*; -use std::fmt::{Display, Write}; +// use std::fmt::{Display, Write}; #[cfg(test)] pub mod tests; @@ -116,9 +116,23 @@ impl Computer { moves } - pub fn gen_tree(&mut self, board: Board) { + // pub fn gen_tree(&mut self, tree: &mut Arena, board: Board) { - } + // let boards = self.get_move_boards(&board); + + // let root_id = vec!(tree.new_node(board)); + // let ids = self.insert_boards(boards); + + // for d in 0..self.search_depth { + + // for root in root_id.iter(){ + // for id in ids.into_iter() { + // root.append(id, tree); + // } + // } + // } + + // } pub fn insert_boards(&mut self, boards: Vec) -> Vec { diff --git a/src/comp/tests.rs b/src/comp/tests.rs index 8d13ccd..244cb89 100644 --- a/src/comp/tests.rs +++ b/src/comp/tests.rs @@ -1,6 +1,11 @@ use super::*; use wasm_bindgen_test::*; +use crate::board::Square; +use crate::board::enums::Strength::*; + +use Team::*; + wasm_bindgen_test_configure!(run_in_browser); #[wasm_bindgen_test] @@ -66,7 +71,7 @@ fn available_moves_jumps() { #[wasm_bindgen_test] fn available_moves_std_brd() { let brd = Board::init_game(Board::new(8, 8, White), 3); - let mut brd2 = brd.clone(); + let brd2 = brd.clone(); let comp = Computer::new(brd, 3, White); // log!("{}", brd2); diff --git a/src/game/mod.rs b/src/game/mod.rs index 9b7ffc8..61e4fbb 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -1,3 +1,5 @@ +//! Top-level object for managing [`Board`]s, applying and managing turns + use crate::board::Board; extern crate wasm_bindgen; @@ -6,12 +8,13 @@ use wasm_bindgen::prelude::*; use crate::log; use crate::board::{Square, BrdIdx}; -use crate::board::enums::{SquareState, Moveable, Team}; +use crate::board::enums::{Moveable, Team}; +use crate::paint::Painter; -use Team::*; -use SquareState::*; +// use Team::*; +// use SquareState::*; -use std::fmt::{Display, Write}; +use std::fmt::{Display}; #[cfg(test)] pub mod tests; @@ -21,6 +24,7 @@ use std::fmt::{Display, Write}; pub struct Game { current: Board, previous_boards: Vec, + painter: Option } impl Game { @@ -37,6 +41,16 @@ impl Game { #[wasm_bindgen] impl Game { + /// Get pointer to current board's squares + pub fn current_board_cells(&self) -> *const Square { + self.current.cells() + } + + /// Get pointer to current board's squares + pub fn current_board_len(&self) -> usize { + self.current.num_cells() + } + /// Current turn's team pub fn current_turn(&self) -> Team { self.current.current_turn @@ -94,6 +108,30 @@ impl Game { Board::new(width, height, first_turn), piece_rows, ), previous_boards: Vec::with_capacity(10), + painter: None, + } + } + + 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 { + Game { + current: Board::init_game( + Board::new(width, height, first_turn), piece_rows, + ), + previous_boards: Vec::with_capacity(10), + painter: Some( + Painter::new(canvas_width, canvas_height, canvas_id) + ), + } + } + + pub fn set_painter(&mut self, value: Painter) { + self.painter = Some(value); + } + + pub fn draw(&self) { + match &self.painter { + Some(p) => p.draw(&self.current), + None => log!("No painter to draw board with") } } } @@ -102,4 +140,4 @@ impl Display for Game { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result{ write!(f, "{}", self.current) } -} \ No newline at end of file +} diff --git a/src/game/tests.rs b/src/game/tests.rs index 51ebf4a..9a6a9a7 100644 --- a/src/game/tests.rs +++ b/src/game/tests.rs @@ -1,11 +1,12 @@ use super::*; use wasm_bindgen_test::*; -use crate::log; +// use crate::log; wasm_bindgen_test_configure!(run_in_browser); -use crate::board::{Piece}; +// use crate::board::{Piece}; use crate::board::enums::Strength::*; +use crate::board::enums::Team::*; #[wasm_bindgen_test] diff --git a/src/lib.rs b/src/lib.rs index 9398d4e..3b3742b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ pub mod board; pub mod utils; pub mod game; pub mod player; +pub mod paint; pub mod comp; extern crate wasm_bindgen; @@ -13,6 +14,8 @@ use wasm_bindgen::prelude::*; pub use board::Board; pub use game::Game; +pub use comp::Computer; +pub use paint::Painter; // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global // allocator. @@ -30,9 +33,6 @@ macro_rules! log { #[wasm_bindgen] pub fn init_game() { - log!("initialising wasm"); + log!("Initialising WebAssembly"); utils::set_panic_hook(); - - #[cfg(feature = "random_init")] - log!("random layout enabled"); } \ No newline at end of file diff --git a/src/paint.rs b/src/paint.rs new file mode 100644 index 0000000..9f2258e --- /dev/null +++ b/src/paint.rs @@ -0,0 +1,330 @@ +//! Components for painting board states onto HTML canvases + +extern crate wasm_bindgen; +use wasm_bindgen::prelude::*; +use wasm_bindgen::{JsCast, JsValue}; + +use web_sys::HtmlCanvasElement; +use web_sys::CanvasRenderingContext2d; + +use std::f64; + +use crate::log; +use crate::board::{Board}; +use crate::board::iter::PieceIterator; + +use crate::board::enums::Team::*; + +/// Default hex colour value for white square background +const WHITE_SQUARE: &str = "#FFFFFF"; +/// Default hex colour value for black square background +const BLACK_SQUARE: &str = "#000000"; + +/// Default hex colour value for outline of black squares +const SQUARE_OUTLINE: &str = "#9c9c9c"; +/// Line width when outlining black squares +const OUTLINE_WIDTH: f64 = 3.0; +/// Whether to outline black squares +const DRAW_OUTLINE: bool = true; + +/// Default hex colour value for white pieces +const WHITE_PIECE: &str = "#dbdbdb"; +/// Default hex colour value for black pieces +const BLACK_PIECE: &str = "#ed0000"; + +/// 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"; +/// Whether to outline pieces +const DRAW_PIECE_OUTLINES: bool = true; +/// Line width for outlining pieces +const PIECE_OUTLINE_WIDTH: f64 = 3.0; + +/// Margin from square to define piece radius +const PIECE_MARGIN: f64 = 10.0; + +/// Used to paint boards onto HTML canvases +#[wasm_bindgen] +#[derive(Debug)] +pub struct Painter { + canvas: HtmlCanvasElement, + context: CanvasRenderingContext2d, + + white_square: JsValue, + black_square: JsValue, + + white_piece: JsValue, + black_piece: JsValue, + + white_piece_line: JsValue, + black_piece_line: JsValue, + + piece_lines: bool, + piece_line_width: f64, + + square_outline: JsValue, + outline_width: f64, + draw_outline: bool, + + width: u32, + height: u32, +} + +impl Painter { + /// Get a canvas by element ID + fn get_canvas(canvas_id: &str) -> HtmlCanvasElement { + // JS WINDOW + let window = match web_sys::window(){ + Some(win) => win, + None => panic!("No Js window returned"), + }; + // JS DOCUMENT + let document = match window.document() { + Some(doc) => doc, + None => panic!("No Js window document returned"), + }; + + // CANVAS + let canvas = match document.get_element_by_id(canvas_id) { + Some(el) => el, + None => panic!("No element found for {}", canvas_id), + }; + let canvas = match canvas.dyn_into::() { + Ok(el) => el, + Err(err) => panic!("Failed to cast canvas {:?}", err), + }; + + canvas + } + + /// Get a 2D canvas context for a given canvas + fn get_canvas_context(canvas: &HtmlCanvasElement) -> CanvasRenderingContext2d { + + // CANVAS CONTEXT + let context = match canvas.get_context("2d") { + Ok(op) => match op { + // UNWRAP OPTION + Some(object) => object, + None => panic!("Nothing found when unwrapping canvas context"), + }, + Err(err) => panic!("Error when getting canvas context: {:?}", err), + }; + // CAST CONTEXT + let context = match context.dyn_into::() { + Ok(dyn_cast) => dyn_cast, + Err(cast_err) => panic!("Error when casting canvas context: {:?}", cast_err) + }; + + context + } +} + +#[wasm_bindgen] +impl Painter { + + /// Default constructor which queries for canvas by ID + #[wasm_bindgen(constructor)] + pub fn new(width: u32, height: u32, canvas_id: &str) -> Painter { + + let canvas = Painter::get_canvas(canvas_id); + + canvas.set_width(width); + canvas.set_height(height); + + let context = Painter::get_canvas_context(&canvas); + + Painter { + canvas, + context, + width, height, + + 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), + + white_piece_line: JsValue::from_str(WHITE_PIECE_OUTLINE), + black_piece_line: JsValue::from_str(BLACK_PIECE_OUTLINE), + piece_lines: DRAW_PIECE_OUTLINES, + piece_line_width: PIECE_OUTLINE_WIDTH, + + square_outline: JsValue::from_str(SQUARE_OUTLINE), + outline_width: OUTLINE_WIDTH, + draw_outline: DRAW_OUTLINE, + } + } + + /// Constructor with given canvas element + pub fn new_with_canvas(width: u32, height: u32, canvas: HtmlCanvasElement) -> Painter { + canvas.set_width(width); + canvas.set_height(height); + + let context = Painter::get_canvas_context(&canvas); + + Painter { + canvas, + context, + width, height, + + 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), + + white_piece_line: JsValue::from_str(WHITE_PIECE_OUTLINE), + black_piece_line: JsValue::from_str(BLACK_PIECE_OUTLINE), + piece_lines: DRAW_PIECE_OUTLINES, + piece_line_width: PIECE_OUTLINE_WIDTH, + + square_outline: JsValue::from_str(SQUARE_OUTLINE), + outline_width: OUTLINE_WIDTH, + draw_outline: DRAW_OUTLINE, + } + } + + /// Set new square outline colour value + pub fn set_square_outline(&mut self, value: JsValue) { + self.square_outline = value; + } + + /// Set new line width for outlining squares + pub fn set_outline_width(&mut self, value: f64) { + self.outline_width = value; + } + + /// Set whether squares are outlined + pub fn set_draw_outline(&mut self, value: bool) { + self.draw_outline = value; + } + + /// Reset the canvas dimensions to the given width and height + pub fn reset_dimensions(&self) { + self.canvas.set_width(self.width); + self.canvas.set_height(self.height); + } + + /// Check whether given canvas dimensions divide evenly by given board dimenions + pub fn validate_board_dim(&self, board: &Board) -> bool { + let mut ans = true; + + if self.height as usize % board.height != 0 { + log!("Canvas and board heights do not evenly divide, Canvas({}) / Board({}) = {} px/cell", self.height, board.height, self.height as f32 / board.height as f32); + ans = false; + } + + if self.width as usize % board.width != 0 { + log!("Canvas and board widths do not evenly divide, Canvas({}) / Board({}) = {} px/cell", self.width, board.width, self.width as f32 / board.width as f32); + ans = false; + } + + ans + } + + /// Draw a board onto the canvas + pub fn draw(&self, board: &Board) { + + self.validate_board_dim(board); + + let cell_height = self.height as usize / board.height; + let cell_width = self.width as usize / board.width; + + 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.set_fill_style(&self.black_square); + self.context.set_stroke_style(&self.square_outline); + self.context.set_line_width(self.outline_width); + + // Draw black squares onto canvas + for i in 0..board.height { + for j in 0..board.width { + + if i % 2 == 0 { + if j % 2 == 1 { + self.context.fill_rect( + (j * cell_width) as f64, + (i * cell_height) as f64, + cell_width as f64, + cell_height as f64 + ); + + if self.draw_outline { + self.context.stroke_rect( + (j * cell_width) as f64, + (i * cell_height) as f64, + cell_width as f64, + cell_height as f64 + ); + } + } + } + else { + if j % 2 == 0 { + self.context.fill_rect( + (j * cell_width) as f64, + (i * cell_height) as f64, + cell_width as f64, + cell_height as f64 + ); + + if self.draw_outline { + self.context.stroke_rect( + (j * cell_width) as f64, + (i * cell_height) as f64, + cell_width as f64, + cell_height as f64 + ); + } + } + } + } + } + + // Draw pieces onto canvas + for (idx, square) in PieceIterator::new(board) { + match square.occupant { + Some(piece) => { + + let brd_idx = board.board_index(idx); + + match piece.team { + Black => { + self.context.set_fill_style(&self.black_piece); + self.context.set_stroke_style(&self.black_piece_line); + }, + White => { + self.context.set_fill_style(&self.white_piece); + self.context.set_stroke_style(&self.white_piece_line); + }, + } + + let center_x: f64 = (brd_idx.col as f64 * cell_width as f64) + (cell_width as f64) / 2.0; + let center_y: f64 = (brd_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 draw piece, idx: {}, square: {:?}, {:?}", idx, square, err), + }; + self.context.fill(); + + if self.piece_lines { + self.context.set_line_width(self.piece_line_width); + self.context.stroke() + } + + }, + None => panic!("No piece found when attempting to draw, idx: {}, square: {:?}", idx, square), + } + } + } +} \ No newline at end of file diff --git a/tests/board.rs b/tests/board.rs index 425c799..ff59d96 100644 --- a/tests/board.rs +++ b/tests/board.rs @@ -4,6 +4,6 @@ extern crate wasm_bindgen_test; // use wasm_bindgen_test::*; -use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; +// use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; -wasm_bindgen_test_configure!(run_in_browser); +// wasm_bindgen_test_configure!(run_in_browser); diff --git a/www/index.html b/www/index.html index 47ce3d6..6a09245 100644 --- a/www/index.html +++ b/www/index.html @@ -6,7 +6,7 @@ - game of life + draught