diff --git a/demo/src/four_winning.ts b/demo/src/four_winning.ts new file mode 100644 index 0000000..56f7c67 --- /dev/null +++ b/demo/src/four_winning.ts @@ -0,0 +1,106 @@ +import init, { FourWinning } from "@swingalytica/gust"; +import wasmUrl from "../../pkg/gust_bg.wasm?url"; +import "./style.css"; + +let game: FourWinning; +let board: any[][]; + +const PLAYERS = [ + { id: "1", pos: 0, name: "Player 1", color: "#e74c3c", data: [] }, + { id: "2", pos: 1, name: "Player 2", color: "#f1c40f", data: [] }, +]; + +async function setup() { + await init({ module_or_path: wasmUrl }); + game = new FourWinning(); + PLAYERS.forEach((p) => game.add_player(p)); + board = game.generate_game_board(10, 276, "m"); + game.start(new Date().toISOString()); + render(); +} + +function render() { + const players = game.get_players() as typeof PLAYERS; + + document.querySelector("#app")!.innerHTML = ` +
+

4Winning

+

G[olf Games in R]ust — Demo

+
+ +
+ ${players + .map( + (p) => ` +
+ + ${p.name} + ${p.data.length} shots +
+ `, + ) + .join("")} +
+ +
+
+ ${board[0] + .filter((c: any) => c.col) + .map((c: any) => `
${c.col.toUpperCase()}
`) + .join("")} +
+
+ ${board + .map( + (row: any[], rowIdx: number) => ` +
+
${rowIdx + 1}
+ ${row + .filter((c: any) => c.col) + .map( + (cell: any) => ` +
+ ${cell.text} +
+ `, + ) + .join("")} +
+ `, + ) + .join("")} +
+
+ + + `; + + document.querySelectorAll(".cell.empty").forEach((el) => { + el.addEventListener("click", () => { + const coord = (el as HTMLElement).dataset.coord!; + game.click_cell(coord); + board = game.get_board(); + + const winner_id = game.get_winner_id(); + if (winner_id) { + const players = game.get_players() as typeof PLAYERS; + const winner = players.find((p) => p.id === winner_id); + alert(`${winner?.name} wins!`); + } + + render(); + }); + }); + + document.querySelector("#reset")!.addEventListener("click", () => { + game = new FourWinning(); + PLAYERS.forEach((p) => game.add_player({ ...p, data: [] as string[] })); + board = game.generate_game_board(10, 276, "m"); + game.start(new Date().toISOString()); + render(); + }); +} + +await setup(); diff --git a/demo/src/main.ts b/demo/src/main.ts index 56f7c67..581fdd6 100644 --- a/demo/src/main.ts +++ b/demo/src/main.ts @@ -1,106 +1,14 @@ -import init, { FourWinning } from "@swingalytica/gust"; +import init, { Exact } from "@swingalytica/gust"; import wasmUrl from "../../pkg/gust_bg.wasm?url"; -import "./style.css"; -let game: FourWinning; +let game: any; let board: any[][]; -const PLAYERS = [ - { id: "1", pos: 0, name: "Player 1", color: "#e74c3c", data: [] }, - { id: "2", pos: 1, name: "Player 2", color: "#f1c40f", data: [] }, -]; - async function setup() { await init({ module_or_path: wasmUrl }); - game = new FourWinning(); - PLAYERS.forEach((p) => game.add_player(p)); - board = game.generate_game_board(10, 276, "m"); - game.start(new Date().toISOString()); - render(); -} - -function render() { - const players = game.get_players() as typeof PLAYERS; - - document.querySelector("#app")!.innerHTML = ` -
-

4Winning

-

G[olf Games in R]ust — Demo

-
- -
- ${players - .map( - (p) => ` -
- - ${p.name} - ${p.data.length} shots -
- `, - ) - .join("")} -
- -
-
- ${board[0] - .filter((c: any) => c.col) - .map((c: any) => `
${c.col.toUpperCase()}
`) - .join("")} -
-
- ${board - .map( - (row: any[], rowIdx: number) => ` -
-
${rowIdx + 1}
- ${row - .filter((c: any) => c.col) - .map( - (cell: any) => ` -
- ${cell.text} -
- `, - ) - .join("")} -
- `, - ) - .join("")} -
-
- - - `; - - document.querySelectorAll(".cell.empty").forEach((el) => { - el.addEventListener("click", () => { - const coord = (el as HTMLElement).dataset.coord!; - game.click_cell(coord); - board = game.get_board(); - - const winner_id = game.get_winner_id(); - if (winner_id) { - const players = game.get_players() as typeof PLAYERS; - const winner = players.find((p) => p.id === winner_id); - alert(`${winner?.name} wins!`); - } - - render(); - }); - }); - - document.querySelector("#reset")!.addEventListener("click", () => { - game = new FourWinning(); - PLAYERS.forEach((p) => game.add_player({ ...p, data: [] as string[] })); - board = game.generate_game_board(10, 276, "m"); - game.start(new Date().toISOString()); - render(); - }); + game = new Exact(); + board = game.get_game_board(); + console.log(board); } await setup(); diff --git a/src/games/exact/consts.rs b/src/games/exact/consts.rs new file mode 100644 index 0000000..a356fb8 --- /dev/null +++ b/src/games/exact/consts.rs @@ -0,0 +1,18 @@ +pub const DOUBLES: [i32; 9] = [11, 22, 33, 44, 55, 66, 77, 88, 99]; +pub const TENS: [i32; 9] = [10, 20, 30, 40, 50, 60, 70, 80, 90]; +pub const BULLSEYE: [i32; 1] = [100]; +pub const PENALTIES: [&str; 2] = ["<5", ">100"]; + +// Numbers from 1 to 100, excluding SPECIAL_NUMBERS +pub const REGULAR_NUMBERS: [i32; 81] = [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, + 12, 13, 14, 15, 16, 17, 18, 19, + 21, 23, 24, 25, 26, 27, 28, 29, + 31, 32, 34, 35, 36, 37, 38, 39, + 41, 42, 43, 45, 46, 47, 48, 49, + 51, 52, 53, 54, 56, 57, 58, 59, + 61, 62, 63, 64, 65, 67, 68, 69, + 71, 72, 73, 74, 75, 76, 78, 79, + 81, 82, 83, 84, 85, 86, 87, 89, + 91, 92, 93, 94, 95, 96, 97, 98 +]; \ No newline at end of file diff --git a/src/games/exact/generate_game_board.rs b/src/games/exact/generate_game_board.rs new file mode 100644 index 0000000..55d6756 --- /dev/null +++ b/src/games/exact/generate_game_board.rs @@ -0,0 +1,43 @@ +use super::consts::*; +use serde::{Serialize, Deserialize}; + +#[derive(Serialize, Deserialize)] +#[serde(untagged)] +pub enum Distance { + Number(i32), + Text(String), +} + +#[derive(Serialize, Deserialize)] +pub struct ExactGameBoardCell { + pub points: i32, + pub distances: Vec, +} + +pub fn create_chunks(arr: &[T], chunk_size: i32) -> Vec> +where + T: Clone, +{ + if chunk_size == 0 { + return Vec::new(); + } + + arr.chunks(chunk_size as usize).map(|chunk: &[T]| chunk.to_vec()).collect() +} + +pub fn rows() -> Vec { + let mut rows: Vec = create_chunks(®ULAR_NUMBERS, 14) + .into_iter() + .map(|distances: Vec| { + let distances: Vec = distances.into_iter().map(Distance::Number).collect(); + ExactGameBoardCell { points: 1, distances } + }) + .collect(); + + rows.push(ExactGameBoardCell { points: 2, distances: DOUBLES.iter().cloned().map(Distance::Number).collect() }); + rows.push(ExactGameBoardCell { points: 3, distances: TENS.iter().cloned().map(Distance::Number).collect() }); + rows.push(ExactGameBoardCell { points: 5, distances: BULLSEYE.iter().cloned().map(Distance::Number).collect() }); + rows.push(ExactGameBoardCell { points: -1, distances: PENALTIES.iter().cloned().map(|s: &str| Distance::Text(s.to_string())).collect() }); + + rows +} \ No newline at end of file diff --git a/src/games/exact/mod.rs b/src/games/exact/mod.rs new file mode 100644 index 0000000..aca932b --- /dev/null +++ b/src/games/exact/mod.rs @@ -0,0 +1,108 @@ +mod consts; +mod generate_game_board; + +use serde::{Deserialize, Serialize}; +use serde_wasm_bindgen::{to_value, Error}; +use wasm_bindgen::prelude::*; +use generate_game_board::{rows, ExactGameBoardCell}; + +#[wasm_bindgen(typescript_custom_section)] +const TS_TYPES: &'static str = r#" +export interface Player { + id: string; + pos: number; + name: string; + color: string; + shots: number; + points: number; +} + +export interface PlayerUpdate { + pos?: number; + name?: string; + color?: string; + shots?: number; + points?: number; +} +"#; + +#[wasm_bindgen]extern "C" { + #[wasm_bindgen(typescript_type = "Player")] + pub type PlayerJS; + #[wasm_bindgen(typescript_type = "PlayerUpdate")] + pub type PlayerUpdateJS; +} + +#[derive(Deserialize, Serialize)] +pub struct Player { + pub id: String, + pub pos: i32, + pub name: String, + pub color: String, + pub shots: i32, + pub points: i64 +} + +#[derive(Deserialize)] +pub struct PlayerUpdate { + pub pos: Option, + pub name: Option, + pub color: Option, + pub shots: Option, + pub points: Option +} + +#[wasm_bindgen] +pub struct Exact { + players: Vec, +} + +#[wasm_bindgen] +impl Exact { + #[wasm_bindgen(constructor)] + pub fn new() -> Exact { + Exact { + players: Vec::new(), + } + } + + #[wasm_bindgen] + pub fn get_game_board(&self) -> JsValue { + let game_board: Vec = rows(); + to_value(&game_board).unwrap_or_else(|e: Error| JsValue::from_str(&e.to_string())) + } + + #[wasm_bindgen] + pub fn add_player(&mut self, player: PlayerJS) -> Result<(), JsValue> { + let player: Player = serde_wasm_bindgen::from_value(player.into()) + .map_err(|e: Error| JsValue::from_str(&e.to_string()))?; + self.players.push(player); + Ok(()) + } + + #[wasm_bindgen] + pub fn update_player(&mut self, id: String, update: PlayerUpdateJS) -> Result<(), JsValue> { + let update: PlayerUpdate = serde_wasm_bindgen::from_value(update.into()) + .map_err(|e: Error| JsValue::from_str(&e.to_string()))?; + + if let Some(p) = self.players.iter_mut().find(|p: &&mut Player| p.id == id) { + if let Some(pos) = update.pos { p.pos = pos; } + if let Some(name) = update.name { p.name = name; } + if let Some(color) = update.color { p.color = color; } + if let Some(shots) = update.shots { p.shots = shots; } + if let Some(points) = update.points { p.points = points; } + } + + Ok(()) + } + + #[wasm_bindgen] + pub fn remove_player(&mut self, id: String) { + self.players.retain(|p: &Player| p.id != id); + } + + #[wasm_bindgen] + pub fn get_players(&self) -> Result { + to_value(&self.players).map_err(|e: Error| JsValue::from_str(&e.to_string())) + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 74f245e..1f2cbb0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,8 +2,10 @@ pub mod shot; pub mod game; pub mod games { pub mod four_winning; + pub mod exact; } pub use shot::Shot; pub use game::{Game, GameState, ShotResult}; -// pub use games::four_winning::FourWinning; \ No newline at end of file +pub use games::four_winning::FourWinning; +pub use games::exact::Exact; \ No newline at end of file