Skip to content
106 changes: 106 additions & 0 deletions demo/src/four_winning.ts
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement>("#app")!.innerHTML = `
<header>
<h1>4Winning</h1>
<p class="subtitle">G[olf Games in R]ust &mdash; Demo</p>
</header>

<div class="players">
${players
.map(
(p) => `
<div class="player-card" data-id="${p.id}">
<span class="dot" style="background:${p.color}"></span>
<span class="player-name">${p.name}</span>
<span class="shot-count">${p.data.length} shots</span>
</div>
`,
)
.join("")}
</div>

<div class="board-wrapper">
<div class="col-labels">
${board[0]
.filter((c: any) => c.col)
.map((c: any) => `<div class="col-label">${c.col.toUpperCase()}</div>`)
.join("")}
</div>
<div class="board">
${board
.map(
(row: any[], rowIdx: number) => `
<div class="board-row">
<div class="row-label">${rowIdx + 1}</div>
${row
.filter((c: any) => c.col)
.map(
(cell: any) => `
<div class="cell ${cell.player_id ? "taken" : "empty"}"
data-coord="${cell.col}${cell.row}"
style="--cell-color: ${cell.player_id ? cell.color : "transparent"}">
<span class="cell-text">${cell.text}</span>
</div>
`,
)
.join("")}
</div>
`,
)
.join("")}
</div>
</div>

<button id="reset">Reset Game</button>
`;

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();
102 changes: 5 additions & 97 deletions demo/src/main.ts
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement>("#app")!.innerHTML = `
<header>
<h1>4Winning</h1>
<p class="subtitle">G[olf Games in R]ust &mdash; Demo</p>
</header>

<div class="players">
${players
.map(
(p) => `
<div class="player-card" data-id="${p.id}">
<span class="dot" style="background:${p.color}"></span>
<span class="player-name">${p.name}</span>
<span class="shot-count">${p.data.length} shots</span>
</div>
`,
)
.join("")}
</div>

<div class="board-wrapper">
<div class="col-labels">
${board[0]
.filter((c: any) => c.col)
.map((c: any) => `<div class="col-label">${c.col.toUpperCase()}</div>`)
.join("")}
</div>
<div class="board">
${board
.map(
(row: any[], rowIdx: number) => `
<div class="board-row">
<div class="row-label">${rowIdx + 1}</div>
${row
.filter((c: any) => c.col)
.map(
(cell: any) => `
<div class="cell ${cell.player_id ? "taken" : "empty"}"
data-coord="${cell.col}${cell.row}"
style="--cell-color: ${cell.player_id ? cell.color : "transparent"}">
<span class="cell-text">${cell.text}</span>
</div>
`,
)
.join("")}
</div>
`,
)
.join("")}
</div>
</div>

<button id="reset">Reset Game</button>
`;

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();
18 changes: 18 additions & 0 deletions src/games/exact/consts.rs
Original file line number Diff line number Diff line change
@@ -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
];
43 changes: 43 additions & 0 deletions src/games/exact/generate_game_board.rs
Original file line number Diff line number Diff line change
@@ -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<Distance>,
}

pub fn create_chunks<T>(arr: &[T], chunk_size: i32) -> Vec<Vec<T>>
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<ExactGameBoardCell> {
let mut rows: Vec<ExactGameBoardCell> = create_chunks(&REGULAR_NUMBERS, 14)
.into_iter()
.map(|distances: Vec<i32>| {
let distances: Vec<Distance> = 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
}
108 changes: 108 additions & 0 deletions src/games/exact/mod.rs
Original file line number Diff line number Diff line change
@@ -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<i32>,
pub name: Option<String>,
pub color: Option<String>,
pub shots: Option<i32>,
pub points: Option<i64>
}

#[wasm_bindgen]
pub struct Exact {
players: Vec<Player>,
}

#[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<ExactGameBoardCell> = 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<JsValue, JsValue> {
to_value(&self.players).map_err(|e: Error| JsValue::from_str(&e.to_string()))
}
}
Loading