From c7399b2b99cad98ddfd637b00596a5dc2b306cd8 Mon Sep 17 00:00:00 2001 From: Joel Therrien Date: Tue, 15 Aug 2023 19:35:23 -0700 Subject: [PATCH] Integrate score keeping and individual trays --- src/game.rs | 59 +++++++++++++++------- src/player_interaction/ai.rs | 1 + src/wasm.rs | 34 ++++++++++--- ui/src/elements.tsx | 97 +++++++++++++++++++++++++----------- ui/src/style.less | 32 ++++++++++++ 5 files changed, 172 insertions(+), 51 deletions(-) diff --git a/src/game.rs b/src/game.rs index 8ab8814..1752d37 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,3 +1,4 @@ +use rand::prelude::SliceRandom; use rand::rngs::SmallRng; use rand::SeedableRng; use crate::board::{Board, Letter}; @@ -14,16 +15,26 @@ pub enum Player { } } +impl Player { + + pub fn get_name(&self) -> &str { + match &self { + Player::Human(name) => {name} + Player::AI { name, .. } => {name} + } + } +} + pub struct PlayerState { - player: Player, - score: u32, + pub player: Player, + pub score: u32, tray: Tray } pub struct Game{ tile_pool: Vec, board: Board, - player: PlayerState, + pub player_states: Vec, dictionary: DictionaryImpl, } @@ -38,34 +49,48 @@ pub struct Game{ // This will later work out well when I build it out as an API for multiplayer. impl Game { - pub fn new(seed: u64, dictionary_text: &str) -> Self { + pub fn new(seed: u64, dictionary_text: &str, mut player_names: Vec) -> Self { let mut rng = SmallRng::seed_from_u64(seed); let mut letters = standard_tile_pool(Some(&mut rng)); - let mut tray = Tray::new(TRAY_LENGTH); - tray.fill(&mut letters); - - let player = PlayerState { - player: Player::Human("Joel".to_string()), - score: 0, - tray, - }; + player_names.shuffle(&mut rng); + let player_states: Vec = player_names.iter() + .map(|name| { + let mut tray = Tray::new(TRAY_LENGTH); + tray.fill(&mut letters); + PlayerState { + player: Player::Human(name.clone()), + score: 0, + tray, + } + }) + .collect(); Game { tile_pool: letters, board: Board::new(), - player, + player_states, dictionary: DictionaryImpl::create_from_str(dictionary_text) } } - pub fn get_tray(&self) -> &Tray { - &self.player.tray + pub fn get_tray(&self, name: &str) -> Option<&Tray> { + let player = self.player_states.iter() + .filter(|state| state.player.get_name().eq(name)) + .nth(0)?; + + Some(&player.tray) + } - pub fn get_tray_mut(&mut self) -> &mut Tray { - &mut self.player.tray + pub fn get_tray_mut(&mut self, name: &str) -> Option<&mut Tray> { + let player = self.player_states.iter_mut() + .filter(|state| state.player.get_name().eq(name)) + .nth(0)?; + + Some(&mut player.tray) + } pub fn get_board(&self) -> &Board {&self.board} diff --git a/src/player_interaction/ai.rs b/src/player_interaction/ai.rs index 38177e8..bc03dea 100644 --- a/src/player_interaction/ai.rs +++ b/src/player_interaction/ai.rs @@ -1,4 +1,5 @@ + pub struct Difficulty { proportion: f64, randomness: f64, diff --git a/src/wasm.rs b/src/wasm.rs index 985bcb6..fe9d9ef 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -35,13 +35,13 @@ impl GameWasm { #[wasm_bindgen(constructor)] pub fn new(seed: u64, dictionary_text: &str) -> GameWasm { - GameWasm(Game::new(seed, dictionary_text)) + GameWasm(Game::new(seed, dictionary_text, vec!["Player".to_string(), "AI".to_string()])) } - pub fn get_tray(&self) -> Result { - let tray = self.0.get_tray(); + pub fn get_tray(&self, name: &str) -> Result { + let tray = self.0.get_tray(name); - serde_wasm_bindgen::to_value(tray) + serde_wasm_bindgen::to_value(&tray) } pub fn get_board_cell_types(&self) -> Result { @@ -54,11 +54,11 @@ impl GameWasm { serde_wasm_bindgen::to_value(&cell_types) } - pub fn receive_play(&mut self, tray_tile_locations: JsValue, commit_move: bool) -> Result { + pub fn receive_play(&mut self, player: &str, tray_tile_locations: JsValue, commit_move: bool) -> Result { let tray_tile_locations: Vec> = serde_wasm_bindgen::from_value(tray_tile_locations)?; let mut board_instance = self.0.get_board().clone(); - let tray = self.0.get_tray().clone(); + let tray = self.0.get_tray(player).unwrap().clone(); let mut played_letters: Vec<(Letter, Coordinates)> = Vec::new(); for (i, played_tile) in tray_tile_locations.iter().enumerate() { @@ -134,4 +134,26 @@ impl GameWasm { Ok(result) } + + pub fn get_scores(&self) -> Result { + + #[derive(Serialize, Deserialize, Tsify)] + #[tsify(from_wasm_abi)] + pub struct PlayerAndScore { + name: String, + score: u32, + } + + let scores: Vec = self.0.player_states.iter() + .map(|player_state| { + PlayerAndScore { + name: player_state.player.get_name().to_string(), + score: player_state.score, + } + }) + .collect(); + + Ok(serde_wasm_bindgen::to_value(&scores)?) + + } } \ No newline at end of file diff --git a/ui/src/elements.tsx b/ui/src/elements.tsx index 60ab153..f66ae2f 100644 --- a/ui/src/elements.tsx +++ b/ui/src/elements.tsx @@ -1,7 +1,6 @@ import * as React from "react"; -import {GameWasm, Letter as LetterData, MyResult, PlayedTile, Tray, WordResult} from "word_grid"; -import {createRoot} from "react-dom/client"; -import {Children, useMemo, useReducer, useState} from "react"; +import {GameWasm, Letter as LetterData, MyResult, PlayedTile, PlayerAndScore, Tray, WordResult} from "word_grid"; +import {useMemo, useReducer, useState} from "react"; export enum LocationType { GRID, @@ -75,24 +74,10 @@ function matchCoordinate(playerLetters: PlayableLetterData[], coords: Coordinate type TileDispatch = React.Dispatch<{start: CoordinateData, end: CoordinateData}>; -function movePlayableLetters(playerLetters: PlayableLetterData[], update: {start: CoordinateData, end: CoordinateData}) { - - let startIndex = matchCoordinate(playerLetters, update.start); - let endIndex = matchCoordinate(playerLetters, update.end); - - if(startIndex != null) { - let startLetter = playerLetters[startIndex]; - startLetter.location = update.end.location; - startLetter.index = update.end.index; - } - - if(endIndex != null) { - let endLetter = playerLetters[endIndex]; - endLetter.location = update.start.location; - endLetter.index = update.start.index; - } - - return playerLetters.slice(); +function addLogInfo(existingLog: React.JSX.Element[], newItem: React.JSX.Element) { + newItem.key = existingLog.length; + existingLog.push(newItem); + return existingLog.slice(); } export function Game(props: {wasm: GameWasm}) { @@ -101,8 +86,32 @@ export function Game(props: {wasm: GameWasm}) { return props.wasm.get_board_cell_types(); }, []); + const [checkPerformed, setCheckPerformed] = useState(false); + + function movePlayableLetters(playerLetters: PlayableLetterData[], update: {start: CoordinateData, end: CoordinateData}) { + + let startIndex = matchCoordinate(playerLetters, update.start); + let endIndex = matchCoordinate(playerLetters, update.end); + + if(startIndex != null) { + let startLetter = playerLetters[startIndex]; + startLetter.location = update.end.location; + startLetter.index = update.end.index; + } + + if(endIndex != null) { + let endLetter = playerLetters[endIndex]; + endLetter.location = update.start.location; + endLetter.index = update.start.index; + } + + setCheckPerformed(false); + + return playerLetters.slice(); + } + const [playerLetters, dispatch] = useReducer(movePlayableLetters, null, (_) => { - let tray: Tray = props.wasm.get_tray(); + let tray: Tray = props.wasm.get_tray("Player"); // initial state let letters: PlayableLetterData[] = tray.letters.map((ld, i) => { @@ -112,13 +121,28 @@ export function Game(props: {wasm: GameWasm}) { }) return letters; - }) + }); + const [logInfo, logDispatch] = useReducer(addLogInfo, []); + + const [turnCount, setTurnCount] = useState(0); + const playerAndScores: PlayerAndScore[] = useMemo(() => { + return props.wasm.get_scores(); + }, [turnCount]) return <> - +
+ +
+ +
+ {logInfo} +
+
+
+ + }}>{checkPerformed ? "Submit ✅" : "Check"} ; @@ -271,4 +298,18 @@ function Grid(props: {cellTypes: CellType[], playerLetters: Array {elements} +} + + +function Scores(props: {playerScores: Array}){ + let elements = props.playerScores.map((ps, i) => { + return
+

{ps.name}

+ {ps.score} +
; + }); + + return
+ {elements} +
} \ No newline at end of file diff --git a/ui/src/style.less b/ui/src/style.less index ed43bee..152ddd5 100644 --- a/ui/src/style.less +++ b/ui/src/style.less @@ -21,6 +21,7 @@ justify-content: center; grid-gap: 1px; user-select: none; + flex: initial; } .grid-spot { @@ -104,4 +105,35 @@ padding-right: 5px; } +} + +.board-log { + display: flex; + justify-content: start; + flex-flow: row wrap; + align-items: stretch; +} + +.message-log { + border-color: black; + border-style: solid; + border-width: 2px; + width: max-content; + margin: 5px +} + +.scoring { + text-align: center; + display: flex; + grid-template-columns: 1fr 1fr; + grid-template-rows: none; + + span { + font-size: 20px; + } + + div { + margin-left: 10px; + margin-right: 10px; + } } \ No newline at end of file