diff --git a/.gitignore b/.gitignore index ea8c4bf..4a471a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/rdraught-wasm/dist diff --git a/Cargo.toml b/Cargo.toml index 2aa388f..bde2c2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,13 @@ [workspace] -members = ["rdraught", "rdraught-cli", "rdraught-gtk", "rdraught-w4"] +members = [ + "rdraught", + "rdraught-cli", + "rdraught-gtk", + "rdraught-w4", + "rdraught-wasm", + "rdraught-sdl", + "rdraught-ui-common", +] resolver = "3" [workspace.package] @@ -12,17 +20,24 @@ edition = "2024" rust-version = "1.87" [workspace.dependencies] -rdraught = { path = "rdraught", version = "0.1.0" } +rdraught = { path = "rdraught", version = "0.1" } +rdraught-ui-common = { path = "rdraught-ui-common", version = "0.1" } +base64 = "0.22" cairo-rs = "0.20" cairo-sys-rs = "0.20" librsvg = "2.60" gtk4 = {version = "0.9", features = ["v4_10"] } gdk4 = "0.9" -gio = "0.20.12" -glib = "0.20.12" -wasm4 = "0.2.0" -wasm4-sys = "0.1.3" -strum = "0.27.1" -strum_macros = "0.27.1" -heapless = "0.8.0" - +gio = "0.20" +glib = "0.20" +wasm4 = "0.2" +wasm4-sys = "0.1" +strum = "0.27" +strum_macros = "0.27" +heapless = "0.8" +wasm-bindgen = "0.2" +web-sys = "0.3" +console_error_panic_hook = "0.1" +sdl3 = { version = "0.14.35" } +sdl3-sys = { version = "0.5" } +rmath = {version = "0.1.0", registry = "gitea"} diff --git a/rdraught-gtk/Cargo.toml b/rdraught-gtk/Cargo.toml index 7a5188a..d8965fc 100644 --- a/rdraught-gtk/Cargo.toml +++ b/rdraught-gtk/Cargo.toml @@ -12,6 +12,7 @@ version.workspace = true gtk4.workspace = true gdk4.workspace = true rdraught = {workspace = true, features = ["std"]} +rdraught-ui-common = { workspace = true } librsvg.workspace = true cairo-rs.workspace = true gio.workspace = true diff --git a/rdraught-gtk/examples/ai_debugger.rs b/rdraught-gtk/examples/ai_debugger.rs index 93a1c86..5ad2a6e 100644 --- a/rdraught-gtk/examples/ai_debugger.rs +++ b/rdraught-gtk/examples/ai_debugger.rs @@ -1,19 +1,35 @@ use glib::ExitCode; -use rdraught::draughts::DraughtsGame; -use rdraught::{draughts::Piece, draughts::Player, position::Position}; -use rdraught_ui::run; +use rdraught::{DraughtsBoard, DraughtsGame, Move, Piece, Player, Position}; +use rdraught_gtk::run; use std::collections::HashMap; fn main() -> ExitCode { - let mut pieces = HashMap::::new(); - pieces.insert(Position::new(2, 4), Piece::CrownedRedPawn); - pieces.insert(Position::new(5, 5), Piece::CrownedWhitePawn); - let game = DraughtsGame::new( - |p| match pieces.get(&p) { - None => Piece::NoPiece, - Some(piece) => *piece, - }, - Player::Red, - ); - run(game) + println!("move: {}", size_of::()); + println!("game: {}", size_of::()); + + // let boards = [[(Position::new(2, 4), Piece::CrownedRedPawn)]]; + + // for pieces in boards.into_iter() { + // let map = pieces.into_iter().collect::>(); + // let board = DraughtsBoard::new(|p| match map.get(&p) { + // None => Piece::NoPiece, + // Some(piece) => *piece, + // }); + // println!("{:?}", board); + // for (pos, piece) in map.iter() { + // assert_eq!(*piece, board.get(*pos)); + // } + // } + ExitCode::SUCCESS + // let mut pieces = HashMap::::new(); + // pieces.insert(Position::new(2, 4), Piece::CrownedRedPawn); + // pieces.insert(Position::new(5, 5), Piece::CrownedWhitePawn); + // let game = DraughtsGame::new( + // |p| match pieces.get(&p) { + // None => Piece::NoPiece, + // Some(piece) => *piece, + // }, + // Player::Red, + // ); + // run(game) } diff --git a/rdraught-gtk/src/geo2d.rs b/rdraught-gtk/src/geo2d.rs deleted file mode 100644 index 3ca6ac2..0000000 --- a/rdraught-gtk/src/geo2d.rs +++ /dev/null @@ -1,96 +0,0 @@ -use std::clone::Clone; -use std::cmp::Eq; -use std::cmp::PartialEq; -use std::fmt::Display; -use std::marker::Copy; -use std::ops::Add; -use std::ops::Div; -use std::ops::Mul; -use std::ops::Neg; -use std::ops::Sub; - -#[derive(Debug)] -pub struct Point { - x: f64, - y: f64, -} - -impl Point { - pub fn x(&self) -> f64 { - self.x - } - - pub fn y(&self) -> f64 { - self.y - } - - pub fn new(x: f64, y: f64) -> Point { - Point { x, y } - } -} - -impl Add<&Point> for &Point { - fn add(self, rhs: &Point) -> Self::Output { - Point { - x: self.x() + rhs.x(), - y: self.y() + rhs.y(), - } - } - type Output = Point; -} - -impl Sub<&Point> for &Point { - fn sub(self, rhs: &Point) -> Self::Output { - Point { - x: self.x() - rhs.x(), - y: self.y() - rhs.y(), - } - } - type Output = Point; -} - -impl Mul for &Point { - fn mul(self, rhs: f64) -> Self::Output { - Point::new(self.x * rhs, self.y * rhs) - } - type Output = Point; -} - -impl Div for &Point { - fn div(self, rhs: f64) -> Self::Output { - Point::new(self.x / rhs, self.y / rhs) - } - type Output = Point; -} - -impl Neg for &Point { - fn neg(self) -> Self::Output { - Point::new(-self.x(), -self.y()) - } - - type Output = Point; -} - -impl PartialEq for Point { - fn eq(&self, other: &Self) -> bool { - self.x.eq(&other.x) && self.y.eq(&other.y) - } -} - -impl Eq for Point {} - -impl Clone for Point { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for Point {} - -impl Display for Point { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Point({}, {})", self.x, self.y) - } -} - -impl Point {} diff --git a/rdraught-gtk/src/greeting_dialog.rs b/rdraught-gtk/src/greeting_dialog.rs index 2afaa6a..dcd3828 100644 --- a/rdraught-gtk/src/greeting_dialog.rs +++ b/rdraught-gtk/src/greeting_dialog.rs @@ -1,7 +1,7 @@ use gtk4::{Align, Application, Box, CheckButton, Label, Orientation, Window, prelude::*}; -use crate::types::SharedMutable; use rdraught::Player; +use rdraught_ui_common::SharedMutable; pub(crate) fn create(application: &Application, current_player: SharedMutable) -> Window { let label = Label::builder().label("Main player:").build(); diff --git a/rdraught-gtk/src/lib.rs b/rdraught-gtk/src/lib.rs index 84cca54..a64607c 100644 --- a/rdraught-gtk/src/lib.rs +++ b/rdraught-gtk/src/lib.rs @@ -1,8 +1,5 @@ -mod geo2d; - mod final_dialog; mod greeting_dialog; mod rdraught_application; -mod types; pub use rdraught_application::run; diff --git a/rdraught-gtk/src/rdraught_application.rs b/rdraught-gtk/src/rdraught_application.rs index c980f44..39255c2 100644 --- a/rdraught-gtk/src/rdraught_application.rs +++ b/rdraught-gtk/src/rdraught_application.rs @@ -1,4 +1,3 @@ -use super::geo2d::Point; use core::f64::consts::PI; use gdk4::cairo::{Context as CairoContext, Matrix, Rectangle}; use glib::ExitCode; @@ -14,9 +13,9 @@ const SQUARE_SIZE: f64 = 1.0; use super::final_dialog; use super::greeting_dialog; -use super::types; - -use types::{SharedMutable, SharedMutableRef, new_shared_mut, new_shared_mut_ref}; +use rdraught_ui_common::{ + Point, SharedMutable, SharedMutableRef, new_shared_mut, new_shared_mut_ref, +}; const CROWN_RED: &[u8] = include_bytes!("crown_red.svg"); const CROWN_WHITE: &[u8] = include_bytes!("crown_white.svg"); @@ -494,7 +493,7 @@ fn create_game_window( }; let p = transform_point(&Point::new(x, y), &inverse); if board_clone.contains(&p) { - let p = &p - &board_clone.tl(); + let p = p - board_clone.tl(); // println!("Point: {:?}", p); let position = match current_player { Player::White => Position::new( diff --git a/rdraught-sdl/Cargo.toml b/rdraught-sdl/Cargo.toml new file mode 100644 index 0000000..4a6e4a6 --- /dev/null +++ b/rdraught-sdl/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "rdraught-sdl" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true +version.workspace = true + +# [lib] +# crate-type = ["cdylib"] + +[dependencies] + +[dependencies.sdl3] +workspace = true +features = ["build-from-source-static"] diff --git a/rdraught-sdl/src/main.rs b/rdraught-sdl/src/main.rs new file mode 100644 index 0000000..3b8c67f --- /dev/null +++ b/rdraught-sdl/src/main.rs @@ -0,0 +1,75 @@ +use core::error::Error; +use sdl3::event::Event; +use sdl3::keyboard::Keycode; +use sdl3::pixels::Color; +use sdl3::rect::Rect; +use std::time::Duration; + +const WINDOW_WIDTH: u32 = 640; +const WINDOW_HEIGHT: u32 = 640; +const BOARD_SIZE: usize = 8; +const SQUARE_SIZE: u32 = WINDOW_WIDTH / BOARD_SIZE as u32; + +fn main() -> Result<(), Box> { + // Initialize SDL + let sdl_context = sdl3::init()?; + let video_subsystem = sdl_context.video()?; + + // Create window + let window = video_subsystem + .window("Chessboard", WINDOW_WIDTH, WINDOW_HEIGHT) + .position_centered() + .build() + .map_err(|e| e.to_string())?; + + // Create canvas + let mut canvas = window.into_canvas(); + + // Set default color + canvas.set_draw_color(Color::RGB(255, 255, 255)); + canvas.clear(); + canvas.present(); + + // Main loop + let mut event_pump = sdl_context.event_pump()?; + 'running: loop { + // Handle events + for event in event_pump.poll_iter() { + match event { + Event::Quit { .. } + | Event::KeyDown { + keycode: Some(Keycode::Escape), + .. + } => break 'running, + _ => {} + } + } + + // Draw chessboard + canvas.set_draw_color(Color::RGB(255, 255, 255)); + canvas.clear(); + + for row in 0..BOARD_SIZE { + for col in 0..BOARD_SIZE { + let color = if (row + col) % 2 == 0 { + Color::RGB(240, 217, 181) // Light squares + } else { + Color::RGB(181, 136, 99) // Dark squares + }; + + canvas.set_draw_color(color); + canvas.fill_rect(Rect::new( + (col as u32 * SQUARE_SIZE) as i32, + (row as u32 * SQUARE_SIZE) as i32, + SQUARE_SIZE, + SQUARE_SIZE, + ))?; + } + } + + canvas.present(); + std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 60)); + } + + Ok(()) +} diff --git a/rdraught-ui-common/Cargo.toml b/rdraught-ui-common/Cargo.toml new file mode 100644 index 0000000..4d718fb --- /dev/null +++ b/rdraught-ui-common/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "rdraught-ui-common" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true +version.workspace = true + +[lib] +crate-type = ["lib"] + +[dependencies] +rdraught.workspace = true +rmath.workspace = true diff --git a/rdraught-ui-common/src/geo2d.rs b/rdraught-ui-common/src/geo2d.rs new file mode 100644 index 0000000..b255777 --- /dev/null +++ b/rdraught-ui-common/src/geo2d.rs @@ -0,0 +1,261 @@ +use rmath::NumericalMatrix; +use rmath::SMatrix; +use std::clone::Clone; +use std::cmp::Eq; +use std::cmp::PartialEq; +use std::fmt::Display; +use std::marker::Copy; +use std::ops::Add; +use std::ops::Div; +use std::ops::Mul; +use std::ops::Neg; +use std::ops::Sub; + +#[derive(Debug)] +pub struct Point { + x: f64, + y: f64, +} + +impl Point { + pub fn x(&self) -> f64 { + self.x + } + + pub fn y(&self) -> f64 { + self.y + } + + pub fn new(x: f64, y: f64) -> Point { + Point { x, y } + } +} + +impl Add for Point { + fn add(self, rhs: Point) -> Self::Output { + Point { + x: self.x() + rhs.x(), + y: self.y() + rhs.y(), + } + } + type Output = Point; +} + +impl Sub for Point { + fn sub(self, rhs: Point) -> Self::Output { + Point { + x: self.x() - rhs.x(), + y: self.y() - rhs.y(), + } + } + type Output = Point; +} + +impl Mul for Point { + fn mul(self, rhs: f64) -> Self::Output { + Point::new(self.x * rhs, self.y * rhs) + } + type Output = Point; +} + +impl Div for Point { + fn div(self, rhs: f64) -> Self::Output { + Point::new(self.x / rhs, self.y / rhs) + } + type Output = Point; +} + +impl Neg for Point { + fn neg(self) -> Self::Output { + Point::new(-self.x(), -self.y()) + } + + type Output = Point; +} + +impl PartialEq for Point { + fn eq(&self, other: &Self) -> bool { + self.x.eq(&other.x) && self.y.eq(&other.y) + } +} + +impl Eq for Point {} + +impl Clone for Point { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Point {} + +impl Display for Point { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Point({}, {})", self.x, self.y) + } +} + +impl Point {} + +#[derive(Clone)] +pub struct Rect { + tl: Point, + br: Point, +} + +impl Rect { + pub fn new(tl: Point, br: Point) -> Rect { + Rect { tl, br } + } + + pub fn from_size(tl: Point, width: f64, height: f64) -> Rect { + Rect { + tl, + br: tl + Point::new(width, height), + } + } + + pub fn width(&self) -> f64 { + self.br.x - self.tl.x + } + + pub fn height(&self) -> f64 { + self.br.y - self.tl.y + } + + pub fn tl(&self) -> Point { + self.tl + } + + pub fn br(&self) -> Point { + self.br + } + + pub fn center(&self) -> Point { + (self.tl + self.br) / 2.0 + } +} + +impl Display for Rect { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Rect({}, {})", self.tl, self.br) + } +} + +#[derive(Clone)] +pub struct Xform(SMatrix); + +impl Xform { + pub fn new(xx: f64, xy: f64, yx: f64, yy: f64, tx: f64, ty: f64) -> Xform { + Xform(SMatrix::::new(|pos| match pos { + (0, 0) => xx, + (0, 1) => yx, + (1, 0) => xy, + (1, 1) => yy, + (2, 0) => tx, + (2, 1) => ty, + (2, 2) => 1f64, + _ => 0f64, + })) + } + + pub fn rot(alpha: f64) -> Xform { + let sa = alpha.sin(); + let ca = alpha.cos(); + Xform(SMatrix::::new(|position| match position { + (0, 0) => ca, + (1, 1) => ca, + (1, 0) => -sa, + (0, 1) => sa, + (2, 2) => 1f64, + _ => 0f64, + })) + } + + pub fn scale(x: f64, y: f64) -> Xform { + Xform(SMatrix::::new(|position| match position { + (0, 0) => x, + (1, 1) => y, + (2, 2) => 1f64, + _ => 0f64, + })) + } + + pub fn xlate(x: f64, y: f64) -> Xform { + Xform(SMatrix::::new(|position| match position { + (0, 0) => 1f64, + (1, 1) => 1f64, + (2, 2) => 1f64, + (2, 0) => x, + (2, 1) => y, + _ => 0f64, + })) + } + + pub fn xx(&self) -> f64 { + self.0[(0, 0)] + } + + pub fn xy(&self) -> f64 { + self.0[(1, 0)] + } + + pub fn tx(&self) -> f64 { + self.0[(2, 0)] + } + + pub fn yx(&self) -> f64 { + self.0[(1, 0)] + } + + pub fn yy(&self) -> f64 { + self.0[(1, 1)] + } + + pub fn ty(&self) -> f64 { + self.0[(2, 1)] + } + + pub fn invert(&self) -> Xform { + Xform(self.0.clone().invert()) + } +} + +impl Default for Xform { + fn default() -> Self { + Xform(SMatrix::::identity(3)) + } +} + +impl Mul<&Xform> for Point { + fn mul(self, rhs: &Xform) -> Self::Output { + Point::new( + self.x * rhs.0[(0, 0)] + self.y * rhs.0[(1, 0)] + rhs.0[(2, 0)], + self.x * rhs.0[(0, 1)] + self.y * rhs.0[(1, 1)] + rhs.0[(2, 1)], + ) + } + + type Output = Point; +} + +impl Mul for Xform { + type Output = Xform; + + fn mul(self, rhs: Xform) -> Self::Output { + Xform(self.0 * rhs.0) + } +} + +impl Mul<&Xform> for &Xform { + type Output = Xform; + + fn mul(self, rhs: &Xform) -> Self::Output { + Xform(&self.0 * &rhs.0) + } +} + +impl Display for Xform { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} diff --git a/rdraught-ui-common/src/lib.rs b/rdraught-ui-common/src/lib.rs new file mode 100644 index 0000000..b23a848 --- /dev/null +++ b/rdraught-ui-common/src/lib.rs @@ -0,0 +1,5 @@ +mod geo2d; +mod types; + +pub use geo2d::{Point, Rect, Xform}; +pub use types::{SharedMutable, SharedMutableRef, new_shared_mut, new_shared_mut_ref}; diff --git a/rdraught-ui-common/src/types.rs b/rdraught-ui-common/src/types.rs new file mode 100644 index 0000000..54d7545 --- /dev/null +++ b/rdraught-ui-common/src/types.rs @@ -0,0 +1,12 @@ +use std::cell::{Cell, RefCell}; +use std::rc::Rc; + +pub type SharedMutable = Rc>; +pub type SharedMutableRef = Rc>; +pub fn new_shared_mut_ref(obj: T) -> SharedMutableRef { + Rc::new(RefCell::new(obj)) +} + +pub fn new_shared_mut(obj: T) -> SharedMutable { + Rc::new(Cell::new(obj)) +} diff --git a/rdraught-w4/.cargo/config.toml b/rdraught-w4/.cargo/config.toml new file mode 100644 index 0000000..77e4c64 --- /dev/null +++ b/rdraught-w4/.cargo/config.toml @@ -0,0 +1,20 @@ +[build] +target = "wasm32-unknown-unknown" + +[target.wasm32-unknown-unknown] +rustflags = [ + "-A", "dead_code", + + # Import memory from WASM-4 + "-C", "link-arg=--import-memory", + "-C", "link-arg=--initial-memory=65536", + "-C", "link-arg=--max-memory=65536", + # "-C", "link-args=--relocatable", + + # Reserve 2044 bytes of stack space, offset from 6580. + # Bump this value, 16-byte aligned, if the framebuffer gets corrupted. + "-C", "link-arg=-zstack-size=14752", + + # Not working? https://github.com/rust-lang/rust/issues/46645#issuecomment-423912553 + # "-C", "link-arg=--global-base=14752", +] diff --git a/rdraught-w4/Cargo.toml b/rdraught-w4/Cargo.toml index eb7040a..08c4cbc 100644 --- a/rdraught-w4/Cargo.toml +++ b/rdraught-w4/Cargo.toml @@ -13,7 +13,10 @@ wasm4.workspace = true wasm4-sys.workspace = true rdraught.workspace = true -[package.metadata.docs.rs] -all-features = true -default-target = "wasm32-unknown-unknown" -targets = [] +[lib] +crate-type = ["cdylib"] + +[profile.release] +opt-level = "z" +lto = true + diff --git a/rdraught-w4/src/lib.rs b/rdraught-w4/src/lib.rs new file mode 100644 index 0000000..cf97fc5 --- /dev/null +++ b/rdraught-w4/src/lib.rs @@ -0,0 +1,223 @@ +#![no_main] +#![no_std] + +use rdraught::{DraughtsBoard, DraughtsGame, Piece, Position}; +use wasm4::draw::{Color, DrawIndex, DrawIndices, Framebuffer}; +use wasm4::rt::{Resources, Runtime}; +// use wasm4 as w4; +use core::arch::wasm32::unreachable; +use wasm4::sys::DRAW_COLORS; +use wasm4::trace; +// mod wasm4; + +// use wasm4::{DRAW_COLORS, oval, rect}; + +// mod palette; + +// static mut GAME: DraughtsGame = DraughtsGame::default(); +struct Rdraught_W4 { + framebuffer: Framebuffer, + game: DraughtsGame, +} + +impl Runtime for Rdraught_W4 { + fn start(rs: Resources) -> Self { + rs.framebuffer.replace_palette([ + Color(0x00edeada), + Color(0x00c74634), + Color(0x005d8d60), + Color(0x00100f24), + ]); + Rdraught_W4 { + framebuffer: rs.framebuffer, + game: DraughtsGame::default(), + } + } + + fn update(&mut self) { + let indices = DrawIndices::from_array([ + DrawIndex::Third, + DrawIndex::Fourth, + DrawIndex::Fourth, + DrawIndex::Transparent, + ]); + unsafe { + *DRAW_COLORS = indices.into_u16(); + } + + self.framebuffer.rect([10, 10], [50, 50]); + self.framebuffer.oval([50, 50], [10, 10]); + let SQUARE_SIZE = 20u32; + for i in 0..8i32 { + for j in 0..8i32 { + let indices = if (i + j) % 2 == 0 { + DrawIndices::from_array([ + DrawIndex::Third, + DrawIndex::Transparent, + DrawIndex::Fourth, + DrawIndex::Transparent, + ]) + } else { + DrawIndices::from_array([ + DrawIndex::Fourth, + DrawIndex::Transparent, + DrawIndex::Fourth, + DrawIndex::Transparent, + ]) + }; + unsafe { + *DRAW_COLORS = indices.into_u16(); + } + let tl = [i as i32 * SQUARE_SIZE as i32, j as i32 * SQUARE_SIZE as i32]; + self.framebuffer.rect(tl, [SQUARE_SIZE, SQUARE_SIZE]); + + let score = self.game.score_for_player(rdraught::Player::Red); + // let msg = format!("{}", score); + // w4::trace(msg.as_str()); + + let piece = self.game.piece_at(Position::new(i as u8, j as u8).unwrap()); + match piece { + Piece::SimpleWhitePawn => { + let indices = DrawIndices::from_array([ + DrawIndex::First, + DrawIndex::Transparent, + DrawIndex::Transparent, + DrawIndex::Transparent, + ]); + unsafe { + *DRAW_COLORS = indices.into_u16(); + } + self.framebuffer.oval(tl, [SQUARE_SIZE, SQUARE_SIZE]); + } + Piece::SimpleRedPawn => { + let indices = DrawIndices::from_array([ + DrawIndex::Second, + DrawIndex::Transparent, + DrawIndex::Transparent, + DrawIndex::Transparent, + ]); + unsafe { + *DRAW_COLORS = indices.into_u16(); + } + self.framebuffer.oval(tl, [SQUARE_SIZE, SQUARE_SIZE]); + } + Piece::CrownedRedPawn => {} + Piece::CrownedWhitePawn => {} + Piece::NoPiece => {} + } + } + } + } +} + +wasm4::main! { Rdraught_W4 } + +// use wasm4::*; +// use wasm4_sys; + +// #[unsafe(no_mangle)] +// fn update() { +// unsafe { +// wasm4_sys::rect(10, 10, 32, 32); +// } +// } +// #[unsafe(no_mangle)] +// fn start() { +// palette::change_palette(0usize); +// palette::set_draw_color(2u16); +// } + +// #[unsafe(no_mangle)] +// fn update() { +// // if self.count % 60 == 0 { +// // w4::trace("tick"); +// // self.count = 0; +// // } +// // self.count += 1; +// // let indices = DrawIndices::from_array([ +// // DrawIndex::Third, +// // DrawIndex::Fourth, +// // DrawIndex::Fourth, +// // DrawIndex::Transparent, +// // ]); +// // unsafe { +// // *DRAW_COLORS = 0x; +// // } +// palette::set_draw_color([3, 4, 4, 0]); + +// rect(10, 10, 50, 50); +// oval(50, 50, 10, 10); +// let SQUARE_SIZE = 20u32; +// for i in 0..8i32 { +// for j in 0..8i32 { +// // let indices = if (i + j) % 2 == 0 { +// // DrawIndices::from_array([ +// // DrawIndex::Third, +// // DrawIndex::Transparent, +// // DrawIndex::Fourth, +// // DrawIndex::Transparent, +// // ]) +// // } else { +// // DrawIndices::from_array([ +// // DrawIndex::Fourth, +// // DrawIndex::Transparent, +// // DrawIndex::Fourth, +// // DrawIndex::Transparent, +// // ]) +// // }; +// // unsafe { +// // *DRAW_COLORS = indices.into_u16(); +// // } +// let tl = [i as i32 * SQUARE_SIZE as i32, j as i32 * SQUARE_SIZE as i32]; +// rect(tl.0, tl.1, SQUARE_SIZE, SQUARE_SIZE); + +// // let score = self.game.score_for_player(rdraught::Player::Red); +// // let msg = format!("{}", score); +// // w4::trace(msg.as_str()); +// unsafe { +// let piece = GAME.piece_at(Position::new(i as u8, j as u8).unwrap()); +// match piece { +// Piece::SimpleWhitePawn => { +// // let indices = DrawIndices::from_array([ +// // DrawIndex::First, +// // DrawIndex::Transparent, +// // DrawIndex::Transparent, +// // DrawIndex::Transparent, +// // ]); +// // unsafe { +// // *DRAW_COLORS = indices.into_u16(); +// // } +// oval(tl, [SQUARE_SIZE, SQUARE_SIZE]); +// } +// Piece::SimpleRedPawn => { +// // let indices = DrawIndices::from_array([ +// // DrawIndex::Second, +// // DrawIndex::Transparent, +// // DrawIndex::Transparent, +// // DrawIndex::Transparent, +// // ]); +// // unsafe { +// // *DRAW_COLORS = indices.into_u16(); +// // } +// oval(tl, [SQUARE_SIZE, SQUARE_SIZE]); +// } +// Piece::CrownedRedPawn => {} +// Piece::CrownedWhitePawn => {} +// Piece::NoPiece => {} +// } +// } +// } +// } +// } + +#[panic_handler] +fn panic_handler(_panic_info: &core::panic::PanicInfo<'_>) -> ! { + trace("panic error"); + + #[cfg(debug_assertions)] + if let Some(cause) = _panic_info.payload().downcast_ref::<&str>() { + trace(cause); + } + + unreachable() +} diff --git a/rdraught-w4/src/main.rs b/rdraught-w4/src/main.rs deleted file mode 100644 index 25e723a..0000000 --- a/rdraught-w4/src/main.rs +++ /dev/null @@ -1,45 +0,0 @@ -#![no_main] - -use w4::draw::{Color, Framebuffer}; -use w4::rt::Resources; -use wasm4 as w4; - -struct Rdraught_wasm4 { - framebuffer: Framebuffer, -} - -impl w4::rt::Runtime for Rdraught_wasm4 { - fn start(rs: Resources) -> Self { - rs.framebuffer.replace_palette([ - Color(0xff000000), - Color(0x00ff0000), - Color(0x0000ff00), - Color(0xffff0000), - ]); - Rdraught_wasm4 { - framebuffer: rs.framebuffer, - } - } - - fn update(&mut self) { - // if self.count % 60 == 0 { - // w4::trace("tick"); - // self.count = 0; - // } - // self.count += 1; - self.framebuffer.rect([10, 10], [50, 50]); - self.framebuffer.oval([50, 50], [10, 10]); - } -} - -w4::main! { Rdraught_wasm4 } - -// use wasm4::*; -// use wasm4_sys; - -// #[unsafe(no_mangle)] -// fn update() { -// unsafe { -// wasm4_sys::rect(10, 10, 32, 32); -// } -// } diff --git a/rdraught-w4/src/palette.rs b/rdraught-w4/src/palette.rs new file mode 100644 index 0000000..d8f4fdd --- /dev/null +++ b/rdraught-w4/src/palette.rs @@ -0,0 +1,29 @@ +use super::wasm4::DRAW_COLORS; +use super::wasm4::PALETTE; + +type Palette = [u32; 4]; + +pub const W4_DEFAULT: Palette = [0xe0f8cf, 0x86c06c, 0x306850, 0x071821]; + +pub const PONG_OG: Palette = [0x1b1b1b, 0xdcdcdc, 0x1b1b1b, 0xdcdcdc]; + +pub const ICE_CREAM_GB: Palette = [0xfff6d3, 0xf9a875, 0xeb6b6f, 0x7c3f58]; + +pub const HOLLOW: Palette = [0x0f0f1b, 0x565a75, 0xc5b7be, 0xf9fbf5]; + +const PALETTES: [Palette; 4] = [PONG_OG, ICE_CREAM_GB, HOLLOW, W4_DEFAULT]; + +#[inline] +fn palette_by_idx>(idx: T) -> Palette { + PALETTES[idx.into() % PALETTES.len()] +} + +pub fn change_palette>(idx: T) { + unsafe { + *PALETTE = palette_by_idx(idx); + } +} + +pub fn set_draw_color>(idx: T) { + unsafe { *DRAW_COLORS = idx.into() } +} diff --git a/rdraught-w4/src/wasm4.rs b/rdraught-w4/src/wasm4.rs new file mode 100644 index 0000000..2c559b6 --- /dev/null +++ b/rdraught-w4/src/wasm4.rs @@ -0,0 +1,196 @@ +// +// WASM-4: https://wasm4.org/docs + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Platform Constants │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +pub const SCREEN_SIZE: u32 = 160; +pub const FONT_SIZE: u32 = 8; + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Memory Addresses │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +pub static mut PALETTE: *mut [u32; 4] = 0x04 as *mut [u32; 4]; +pub const DRAW_COLORS: *mut u16 = 0x14 as *mut u16; +pub const GAMEPAD1: *const u8 = 0x16 as *const u8; +pub const GAMEPAD2: *const u8 = 0x17 as *const u8; +pub const GAMEPAD3: *const u8 = 0x18 as *const u8; +pub const GAMEPAD4: *const u8 = 0x19 as *const u8; +pub const MOUSE_X: *const i16 = 0x1a as *const i16; +pub const MOUSE_Y: *const i16 = 0x1c as *const i16; +pub const MOUSE_BUTTONS: *const u8 = 0x1e as *const u8; +pub static mut FRAMEBUFFER: *mut [u8; 6400] = 0xa0 as *mut [u8; 6400]; + +pub const BUTTON_1: u8 = 1; +pub const BUTTON_2: u8 = 2; +pub const BUTTON_LEFT: u8 = 16; +pub const BUTTON_RIGHT: u8 = 32; +pub const BUTTON_UP: u8 = 64; +pub const BUTTON_DOWN: u8 = 128; + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Drawing Functions │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +/// Copies pixels to the framebuffer. +pub fn blit(sprite: &[u8], x: i32, y: i32, width: u32, height: u32, flags: u32) { + unsafe { extern_blit(sprite.as_ptr(), x, y, width, height, flags) } +} +extern "C" { + #[link_name = "blit"] + fn extern_blit(sprite: *const u8, x: i32, y: i32, width: u32, height: u32, flags: u32); +} + +/// Copies a subregion within a larger sprite atlas to the framebuffer. +pub fn blit_sub( + sprite: &[u8], + x: i32, + y: i32, + width: u32, + height: u32, + src_x: u32, + src_y: u32, + stride: u32, + flags: u32, +) { + unsafe { + extern_blit_sub( + sprite.as_ptr(), + x, + y, + width, + height, + src_x, + src_y, + stride, + flags, + ) + } +} +extern "C" { + #[link_name = "blitSub"] + fn extern_blit_sub( + sprite: *const u8, + x: i32, + y: i32, + width: u32, + height: u32, + src_x: u32, + src_y: u32, + stride: u32, + flags: u32, + ); +} + +pub const BLIT_2BPP: u32 = 1; +pub const BLIT_1BPP: u32 = 0; +pub const BLIT_FLIP_X: u32 = 2; +pub const BLIT_FLIP_Y: u32 = 4; +pub const BLIT_ROTATE: u32 = 8; + +/// Draws a line between two points. +pub fn line(x1: i32, y1: i32, x2: i32, y2: i32) { + unsafe { extern_line(x1, y1, x2, y2) } +} +extern "C" { + #[link_name = "line"] + fn extern_line(x1: i32, y1: i32, x2: i32, y2: i32); +} + +/// Draws an oval (or circle). +pub fn oval(x: i32, y: i32, width: u32, height: u32) { + unsafe { extern_oval(x, y, width, height) } +} +extern "C" { + #[link_name = "oval"] + fn extern_oval(x: i32, y: i32, width: u32, height: u32); +} + +/// Draws a rectangle. +pub fn rect(x: i32, y: i32, width: u32, height: u32) { + unsafe { extern_rect(x, y, width, height) } +} +extern "C" { + #[link_name = "rect"] + fn extern_rect(x: i32, y: i32, width: u32, height: u32); +} + +/// Draws text using the built-in system font. +pub fn text(text: &str, x: i32, y: i32) { + unsafe { extern_text(text.as_ptr(), text.len(), x, y) } +} +extern "C" { + #[link_name = "textUtf8"] + fn extern_text(text: *const u8, length: usize, x: i32, y: i32); +} + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Sound Functions │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +/// Plays a sound tone. +pub fn tone(frequency: u32, duration: u32, volume: u32, flags: u32) { + unsafe { extern_tone(frequency, duration, volume, flags) } +} +extern "C" { + #[link_name = "tone"] + fn extern_tone(frequency: u32, duration: u32, volume: u32, flags: u32); +} + +pub const TONE_PULSE1: u32 = 0; +pub const TONE_PULSE2: u32 = 1; +pub const TONE_TRIANGLE: u32 = 2; +pub const TONE_NOISE: u32 = 3; +pub const TONE_MODE1: u32 = 0; +pub const TONE_MODE2: u32 = 4; +pub const TONE_MODE3: u32 = 8; +pub const TONE_MODE4: u32 = 12; + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Storage Functions │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +extern "C" { + /// Reads up to `size` bytes from persistent storage into the pointer `dest`. + pub fn diskr(dest: *mut u8, size: u32) -> u32; + + /// Writes up to `size` bytes from the pointer `src` into persistent storage. + pub fn diskw(src: *const u8, size: u32) -> u32; +} + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Other Functions │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +extern "C" { + /// Copies `size` bytes from `srcPtr` into `destPtr`. + #[link_name = "memcpy"] + pub fn memcpy(dest: *mut u8, src: *const u8, size: usize) -> usize; + + /// Fills memory at `destPtr` with `size` bytes of the fixed value `value`. + #[link_name = "memset"] + pub fn memset(dest: *mut u8, byte: u8, size: usize) -> usize; +} + +/// Prints a message to the debug console. +pub fn trace(text: &str) { + unsafe { extern_trace(text.as_ptr(), text.len()) } +} +extern "C" { + #[link_name = "traceUtf8"] + fn extern_trace(trace: *const u8, length: usize); +} diff --git a/rdraught-wasm/.cargo/config.toml b/rdraught-wasm/.cargo/config.toml new file mode 100644 index 0000000..aa59d2e --- /dev/null +++ b/rdraught-wasm/.cargo/config.toml @@ -0,0 +1,3 @@ +[build] +target = "wasm32-unknown-unknown" + diff --git a/rdraught-wasm/Cargo.toml b/rdraught-wasm/Cargo.toml new file mode 100644 index 0000000..08f880a --- /dev/null +++ b/rdraught-wasm/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "rdraught-wasm" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true +version.workspace = true + +# [lib] +# crate-type = ["cdylib"] + +[dependencies] +rdraught.workspace = true +rdraught-ui-common.workspace = true +wasm-bindgen.workspace = true +console_error_panic_hook.workspace = true +rmath.workspace = true +base64.workspace = true + + +[dependencies.web-sys] +workspace = true +features = [ + 'DomMatrix', + 'CanvasRenderingContext2d', + 'CssStyleDeclaration', + 'Document', + 'Element', + 'EventTarget', + 'HtmlCanvasElement', + 'HtmlElement', + 'MouseEvent', + 'Node', + 'HtmlImageElement', + 'SvgImageElement', + 'Window', +] diff --git a/rdraught-wasm/dist/index.html b/rdraught-wasm/dist/index.html new file mode 100644 index 0000000..9cf2726 --- /dev/null +++ b/rdraught-wasm/dist/index.html @@ -0,0 +1,27 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/rdraught-wasm/index.html b/rdraught-wasm/index.html new file mode 100644 index 0000000..96444ee --- /dev/null +++ b/rdraught-wasm/index.html @@ -0,0 +1,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/rdraught-wasm/src/crown_red.svg b/rdraught-wasm/src/crown_red.svg new file mode 100644 index 0000000..07cf3f0 --- /dev/null +++ b/rdraught-wasm/src/crown_red.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + diff --git a/rdraught-wasm/src/crown_white.svg b/rdraught-wasm/src/crown_white.svg new file mode 100644 index 0000000..4ac5db8 --- /dev/null +++ b/rdraught-wasm/src/crown_white.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + diff --git a/rdraught-wasm/src/lib.rs b/rdraught-wasm/src/lib.rs new file mode 100644 index 0000000..2c1587a --- /dev/null +++ b/rdraught-wasm/src/lib.rs @@ -0,0 +1,61 @@ +use std::cell::Cell; +use std::rc::Rc; +use wasm_bindgen::prelude::*; + +use web_sys::CanvasRenderingContext2d; +use web_sys::MouseEvent; +extern crate console_error_panic_hook; + +// #[wasm_bindgen(start)] +fn main() -> Result<(), JsValue> { + let document = web_sys::window().unwrap().document().unwrap(); + let canvas = document + .create_element("canvas")? + .dyn_into::()?; + document.body().unwrap().append_child(&canvas)?; + canvas.set_width(640); + canvas.set_height(480); + canvas.style().set_property("border", "solid")?; + let context = canvas + .get_context("2d")? + .unwrap() + .dyn_into::()?; + let context = Rc::new(context); + let pressed = Rc::new(Cell::new(false)); + { + let context = context.clone(); + let pressed = pressed.clone(); + let closure = Closure::::new(move |event: web_sys::MouseEvent| { + context.begin_path(); + context.move_to(event.offset_x() as f64, event.offset_y() as f64); + pressed.set(true); + }); + canvas.add_event_listener_with_callback("mousedown", closure.as_ref().unchecked_ref())?; + closure.forget(); + } + { + let context = context.clone(); + let pressed = pressed.clone(); + let closure = Closure::::new(move |event: web_sys::MouseEvent| { + if pressed.get() { + context.line_to(event.offset_x() as f64, event.offset_y() as f64); + context.stroke(); + context.begin_path(); + context.move_to(event.offset_x() as f64, event.offset_y() as f64); + } + }); + canvas.add_event_listener_with_callback("mousemove", closure.as_ref().unchecked_ref())?; + closure.forget(); + } + { + let closure = Closure::::new(move |event: web_sys::MouseEvent| { + pressed.set(false); + context.line_to(event.offset_x() as f64, event.offset_y() as f64); + context.stroke(); + }); + canvas.add_event_listener_with_callback("mouseup", closure.as_ref().unchecked_ref())?; + closure.forget(); + } + + Ok(()) +} diff --git a/rdraught-wasm/src/main.rs b/rdraught-wasm/src/main.rs new file mode 100644 index 0000000..f44e2e3 --- /dev/null +++ b/rdraught-wasm/src/main.rs @@ -0,0 +1,446 @@ +use base64::Engine; +use base64::engine::general_purpose::STANDARD as b64; +use core::f64::consts::PI; +use rdraught::{DraughtsBoard, DraughtsGame, Move, Piece, Player, Position, RectangularBoard}; +use rdraught_ui_common::{ + Point, Rect, SharedMutable, SharedMutableRef, Xform, new_shared_mut, new_shared_mut_ref, +}; +use wasm_bindgen::prelude::*; +use web_sys::{CanvasRenderingContext2d, Document, HtmlImageElement, MouseEvent}; + +extern crate console_error_panic_hook; + +#[wasm_bindgen] +extern "C" { + // Use `js_namespace` here to bind `console.log(..)` instead of just + // `log(..)` + #[wasm_bindgen(js_namespace = console)] + fn log(s: &str); + + // The `console.log` is quite polymorphic, so we can bind it with multiple + // signatures. Note that we need to use `js_name` to ensure we always call + // `log` in JS. + #[wasm_bindgen(js_namespace = console, js_name = log)] + fn log_u32(a: u32); + + // Multiple arguments too! + #[wasm_bindgen(js_namespace = console, js_name = log)] + fn log_many(a: &str, b: &str); +} + +macro_rules! console_log { + // Note that this is using the `log` function imported above during + // `bare_bones` + ($($t:tt)*) => (log(&format_args!($($t)*).to_string())) +} + +struct PieceDrawer { + red_crown: HtmlImageElement, + white_crown: HtmlImageElement, +} + +impl PieceDrawer { + fn new(document: &Document) -> Result { + const CROWN_RED: &[u8] = include_bytes!("crown_red.svg"); + const CROWN_WHITE: &[u8] = include_bytes!("crown_white.svg"); + let red_crown = document + .create_element("img")? + .dyn_into::()?; + let src = b64.encode(CROWN_RED); + let src = format!("data:image/svg+xml;base64,{}", src); + red_crown.set_src(&src); + let white_crown = document + .create_element("img")? + .dyn_into::()?; + let src = b64.encode(CROWN_WHITE); + let src = format!("data:image/svg+xml;base64,{}", src); + white_crown.set_src(&src); + Ok(PieceDrawer { + red_crown, + white_crown, + }) + } + + fn draw_piece( + &self, + ctx: &CanvasRenderingContext2d, + xform: &Xform, + cell: Rect, + piece: Piece, + ) -> Result<(), JsValue> { + ctx.save(); + let center = cell.center(); + let outer_radius = cell.width() * 0.3; + let thickness = outer_radius * 0.5; + let vertical_scale_factor = 0.7; + { + let p = Point::new(0.0, cell.center().y()) * xform; + let xform = xform.clone() + * Xform::xlate(-p.x(), -p.y()) + * Xform::scale(1.0, vertical_scale_factor) + * Xform::xlate(p.x(), p.y()); + apply_xform(ctx, &xform)?; + } + + ctx.set_fill_style_str("#000"); + ctx.begin_path(); + + ctx.arc( + center.x(), + center.y() + thickness / 2.0, + outer_radius, + 0.0, + PI, + )?; + ctx.line_to(center.x() - outer_radius, center.y() - thickness / 2.0); + ctx.arc( + center.x(), + center.y() - thickness / 2.0, + outer_radius, + PI, + 2.0 * PI, + )?; + ctx.line_to(center.x() + outer_radius, center.y() + thickness / 2.0); + ctx.fill(); + + match piece.player() { + Some(Player::Red) => { + ctx.set_fill_style_str("#ff0000"); + } + Some(Player::White) => { + ctx.set_fill_style_str("#ffffff"); + } + None => {} + } + let radius = cell.width() * 0.275; + ctx.begin_path(); + ctx.arc( + center.x(), + center.y() - thickness / 2.0, + radius, + 0.0, + 2.0 * PI, + )?; + ctx.fill(); + ctx.restore(); + ctx.save(); + if let Some(player) = piece.player() { + if piece.is_crowned() { + { + let p = Point::new(cell.center().x(), cell.center().y()) * xform; + let f = 0.55; + let xform = xform.clone() + * Xform::xlate(-p.x(), -p.y()) + * Xform::scale(1.0, vertical_scale_factor) + * Xform::scale(f, f) + * Xform::xlate(p.x(), p.y()); + apply_xform(ctx, &xform)?; + } + let image = match player { + Player::White => self.white_crown.clone(), + Player::Red => self.red_crown.clone(), + }; + ctx.draw_image_with_html_image_element_and_dw_and_dh( + &image, + cell.tl().x(), + cell.tl().y() - thickness / 1.0, + cell.width(), + cell.height(), + )?; + } + } + ctx.restore(); + Ok(()) + } +} + +fn apply_xform(ctx: &CanvasRenderingContext2d, xform: &Xform) -> Result<(), JsValue> { + ctx.set_transform( + xform.xx(), + xform.xy(), + xform.yx(), + xform.yy(), + xform.tx(), + xform.ty(), + ) +} + +fn from_ctx(ctx: &CanvasRenderingContext2d) -> Result { + let m = ctx.get_transform()?; + Ok(Xform::new(m.a(), m.c(), m.b(), m.d(), m.e(), m.f())) +} + +fn map_row(current_player: Player, row: usize) -> usize { + match current_player { + Player::White => DraughtsBoard::rows() - 1 - row, + Player::Red => row, + } +} + +trait Agent {} + +struct App { + game: DraughtsGame, + board: Rect, + screen: Rect, + xform: Xform, + selected_piece: Option, + available_moves: Vec, + main_player: Player, + red_agent: Box, + white_agent: Box, +} + +struct Human {} + +impl Human { + fn new() -> Human { + Human {} + } +} + +impl Agent for Human {} + +impl Default for App { + fn default() -> Self { + let board = Rect::from_size( + Point::new(100.0, 400.0), + App::SQUARE_SIZE * DraughtsBoard::columns() as f64, + App::SQUARE_SIZE * DraughtsBoard::rows() as f64, + ); + App { + game: DraughtsGame::default(), + board, + screen: Rect::new(Point::new(0.0, 0.0), Point::new(0.0, 0.0)), + xform: Xform::default(), + selected_piece: None, + available_moves: Vec::new(), + main_player: Player::White, + red_agent: Box::new(Human::new()), + white_agent: Box::new(Human::new()), + } + } +} + +impl App { + const SQUARE_SIZE: f64 = 100.0; + fn draw( + &mut self, + ctx: &CanvasRenderingContext2d, + piece_drawer: &PieceDrawer, + ) -> Result<(), JsValue> { + ctx.clear_rect(0.0, 0.0, self.screen.width(), self.screen.height()); + let board = &self.board; + let f = f64::min( + self.screen.width() / board.width(), + self.screen.height() / board.height(), + ); + ctx.save(); + self.xform = Xform::default() + * Xform::xlate(-board.center().x(), -board.center().y()) + * Xform::scale(f, f) + * Xform::xlate(self.screen.center().x(), self.screen.center().y()); + apply_xform(ctx, &self.xform)?; + // let board_center = board.center(); + // let screen_center = screen.center(); + // let xlate = -board_center; + // ctx.restore(); + // ctx.translate(-xlate.x(), -xlate.y())?; + // ctx.scale(f, f)?; + // ctx.translate(screen_center.x(), screen_center.y())?; + // console_log!("xform2: {}", from_ctx(ctx)?); + let available_moves: Vec = self + .available_moves + .iter() + .map(Move::get_end_position) + .collect(); + for i in 0..DraughtsBoard::rows() { + for j in 0..DraughtsBoard::columns() { + let cell = Rect::new( + Point::new( + board.tl().x() + i as f64 * App::SQUARE_SIZE, + board.tl().y() + j as f64 * App::SQUARE_SIZE, + ), + Point::new( + board.tl().x() + (i + 1) as f64 * App::SQUARE_SIZE, + board.tl().y() + (j + 1) as f64 * App::SQUARE_SIZE, + ), + ); + if (i + j) % 2 == 0 { + ctx.set_fill_style_str("#cccc99"); + } else { + ctx.set_fill_style_str("#666633"); + } + ctx.fill_rect(cell.tl().x(), cell.tl().y(), cell.width(), cell.height()); + if (i + j) % 2 != 0 { + let current_player = Player::White; + let position = Position::from_index((map_row(current_player, j), i)); + let piece = self.game.piece_at(position); + if piece.is_present() { + piece_drawer.draw_piece(ctx, &self.xform, cell.clone(), piece)?; + } + if let Some(selected_piece) = self.selected_piece { + if selected_piece == position { + // console_log!("Selected: {}", selected_piece); + ctx.save(); + ctx.set_shadow_blur(300.0); + ctx.set_shadow_color("#ffffff"); + ctx.begin_path(); + ctx.move_to(cell.tl().x(), cell.tl().y()); + ctx.line_to(cell.br().x(), cell.tl().y()); + ctx.line_to(cell.br().x(), cell.br().y()); + ctx.line_to(cell.tl().x(), cell.br().y()); + ctx.line_to(cell.tl().x(), cell.tl().y()); + ctx.clip(); + ctx.set_stroke_style_str("#fff"); + ctx.set_line_width(4.0); + ctx.begin_path(); + ctx.move_to(board.tl().x(), board.tl().y()); + ctx.line_to(board.br().x(), board.tl().y()); + ctx.line_to(board.br().x(), board.br().y()); + ctx.line_to(board.tl().x(), board.br().y()); + ctx.line_to(board.tl().x(), board.tl().y()); + ctx.move_to(cell.tl().x(), cell.tl().y()); + ctx.line_to(cell.br().x(), cell.tl().y()); + ctx.line_to(cell.br().x(), cell.br().y()); + ctx.line_to(cell.tl().x(), cell.br().y()); + ctx.line_to(cell.tl().x(), cell.tl().y()); + ctx.stroke(); + ctx.restore(); + } + } + if available_moves.contains(&position) { + ctx.save(); + ctx.set_shadow_blur(300.0); + ctx.set_shadow_color("#0000ff"); + ctx.begin_path(); + ctx.move_to(cell.tl().x(), cell.tl().y()); + ctx.line_to(cell.br().x(), cell.tl().y()); + ctx.line_to(cell.br().x(), cell.br().y()); + ctx.line_to(cell.tl().x(), cell.br().y()); + ctx.line_to(cell.tl().x(), cell.tl().y()); + ctx.clip(); + ctx.set_stroke_style_str("#0f0"); + ctx.set_line_width(4.0); + ctx.begin_path(); + ctx.move_to(board.tl().x(), board.tl().y()); + ctx.line_to(board.br().x(), board.tl().y()); + ctx.line_to(board.br().x(), board.br().y()); + ctx.line_to(board.tl().x(), board.br().y()); + ctx.line_to(board.tl().x(), board.tl().y()); + ctx.move_to(cell.tl().x(), cell.tl().y()); + ctx.line_to(cell.br().x(), cell.tl().y()); + ctx.line_to(cell.br().x(), cell.br().y()); + ctx.line_to(cell.tl().x(), cell.br().y()); + ctx.line_to(cell.tl().x(), cell.tl().y()); + ctx.stroke(); + ctx.restore(); + } + } + } + } + ctx.restore(); + Ok(()) + } +} + +fn main() -> Result<(), JsValue> { + let document = web_sys::window().unwrap().document().unwrap(); + let canvas = document + .create_element("canvas")? + .dyn_into::()?; + canvas.style().set_property("background-color", "black")?; + document.body().unwrap().append_child(&canvas)?; + // canvas.style().set_property("border", "solid")?; + let window = web_sys::window().expect("should have a window in this context"); + let width = window.inner_width().unwrap().as_f64().unwrap(); + let height = window.inner_height().unwrap().as_f64().unwrap(); + canvas.set_width(width as u32); + canvas.set_height(height as u32); + let app = new_shared_mut_ref(App::default()); + let ctx = canvas + .get_context("2d")? + .unwrap() + .dyn_into::()?; + let piece_drawer = new_shared_mut_ref(PieceDrawer::new(&document)?); + { + let piece_drawer = piece_drawer.clone(); + let screen = Rect::new(Point::new(0.0, 0.0), Point::new(width, height)); + app.borrow_mut().screen = screen; + app.borrow_mut().draw(&ctx, &piece_drawer.borrow())?; + } + { + let app = app.clone(); + let canvas = canvas.clone(); + let ctx = ctx.clone(); + let piece_drawer = piece_drawer.clone(); + let closure = Closure::::new(move || { + let window = web_sys::window().expect("should have a window in this context"); + let width = window.inner_width().unwrap().as_f64().unwrap() as u32; + let height = window.inner_height().unwrap().as_f64().unwrap() as u32; + canvas.set_width(width); + canvas.set_height(height); + app.borrow_mut().screen = Rect::new( + Point::new(0.0, 0.0), + Point::new(canvas.width() as f64, canvas.height() as f64), + ); + app.borrow_mut().draw(&ctx, &piece_drawer.borrow()).unwrap(); + }); + let window = web_sys::window().expect("should have a window in this context"); + window.set_onresize(Some(closure.as_ref().unchecked_ref())); + closure.forget(); + } + + { + let app = app.clone(); + let piece_drawer = piece_drawer.clone(); + let mouse_click_handler = Closure::wrap(Box::new(move |event: MouseEvent| { + let mut app = app.borrow_mut(); + let xform = app.xform.invert(); + let board = &app.board; + let position = Point::new(event.x() as f64, event.y() as f64) * &xform - board.tl(); + if position.x() > 0.0 + && position.x() < board.width() + && position.y() > 0.0 + && position.y() < board.height() + { + let col = f64::floor(position.x() / App::SQUARE_SIZE) as usize; + let row = f64::floor(position.y() / App::SQUARE_SIZE) as usize; + let row = map_row(app.main_player, row); + if (row + col) % 2 == 0 { + let position = Position::new(row as u8, col as u8).unwrap(); + let selected_move = app + .available_moves + .iter() + .find(|mv| mv.get_end_position() == position) + .map(Move::clone); + if app.selected_piece.is_some() && selected_move.is_some() { + let mv = selected_move.unwrap(); + app.game.check_and_apply_move(&mv).unwrap(); + app.selected_piece = None; + app.available_moves.clear(); + } else { + app.selected_piece = Some(position); + let mut available_moves = Vec::::new(); + for mv in app.game.moves_for_piece(position) { + available_moves.push(mv); + } + app.available_moves = available_moves; + // console_log!("Clicked at: ({}, {})", col, row); + } + } else { + app.selected_piece = None; + app.available_moves.clear(); + } + } else { + app.selected_piece = None; + app.available_moves.clear(); + } + app.draw(&ctx, &piece_drawer.borrow()).unwrap(); + }) as Box); + canvas.set_onclick(Some(mouse_click_handler.as_ref().unchecked_ref())); + mouse_click_handler.forget(); + } + + Ok(()) +} diff --git a/rdraught-gtk/src/types.rs b/rdraught-wasm/src/types.rs similarity index 100% rename from rdraught-gtk/src/types.rs rename to rdraught-wasm/src/types.rs diff --git a/rdraught/examples/checkers.rs b/rdraught/examples/checkers.rs index 7f58c20..8addac8 100644 --- a/rdraught/examples/checkers.rs +++ b/rdraught/examples/checkers.rs @@ -1,6 +1,5 @@ use heapless::Vec; -use rdraught::draughts::{DraughtsBoard, Piece}; -use rdraught::position::Position; +use rdraught::{DraughtsBoard, Piece, Position}; fn main() { let board = DraughtsBoard::default(); diff --git a/rdraught/src/movement.rs b/rdraught/src/movement.rs index 87e58e3..379692f 100644 --- a/rdraught/src/movement.rs +++ b/rdraught/src/movement.rs @@ -118,7 +118,7 @@ impl LoggedMove { } } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum Move { Movement { start: Position,