Add turn counting and score persistence

This commit is contained in:
Joel Therrien 2023-08-16 19:37:32 -07:00
parent c7399b2b99
commit fd7cf2d6af
5 changed files with 117 additions and 43 deletions

View file

@ -1,11 +1,13 @@
use std::collections::HashSet; use std::collections::HashSet;
use std::fmt; use std::fmt;
use std::fmt::{Formatter, Write}; use std::fmt::{Formatter, Write};
use std::borrow::BorrowMut;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tsify::Tsify; use tsify::Tsify;
use crate::constants::{ALL_LETTERS_BONUS, GRID_LENGTH, TRAY_LENGTH}; use crate::constants::{ALL_LETTERS_BONUS, GRID_LENGTH, TRAY_LENGTH};
use crate::dictionary::DictionaryImpl; use crate::dictionary::DictionaryImpl;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
enum Direction { enum Direction {
Row, Column Row, Column
@ -458,6 +460,16 @@ impl Board {
Ok(()) Ok(())
} }
pub fn fix_tiles(&mut self) {
for cell in self.cells.iter_mut() {
match cell.value.borrow_mut() {
None => {}
Some(x) => {
x.ephemeral = false;
}
}
}
}
} }
impl fmt::Display for Board { impl fmt::Display for Board {

View file

@ -28,11 +28,11 @@ impl Player {
pub struct PlayerState { pub struct PlayerState {
pub player: Player, pub player: Player,
pub score: u32, pub score: u32,
tray: Tray pub tray: Tray
} }
pub struct Game{ pub struct Game{
tile_pool: Vec<Letter>, pub tile_pool: Vec<Letter>,
board: Board, board: Board,
pub player_states: Vec<PlayerState>, pub player_states: Vec<PlayerState>,
dictionary: DictionaryImpl, dictionary: DictionaryImpl,
@ -75,19 +75,29 @@ impl Game {
} }
} }
pub fn get_tray(&self, name: &str) -> Option<&Tray> { pub fn get_player_state(&self, name: &str) -> Option<&PlayerState> {
let player = self.player_states.iter() self.player_states.iter()
.filter(|state| state.player.get_name().eq(name)) .filter(|state| state.player.get_name().eq(name))
.nth(0)?; .nth(0)
}
pub fn get_player_state_mut(&mut self, name: &str) -> Option<&mut PlayerState> {
self.player_states.iter_mut()
.filter(|state| state.player.get_name().eq(name))
.nth(0)
}
pub fn get_tray(&self, name: &str) -> Option<&Tray> {
let player = self.get_player_state(name)?;
Some(&player.tray) Some(&player.tray)
} }
pub fn get_tray_mut(&mut self, name: &str) -> Option<&mut Tray> { pub fn get_tray_mut(&mut self, name: &str) -> Option<&mut Tray> {
let player = self.player_states.iter_mut() let player = self.get_player_state_mut(name)?;
.filter(|state| state.player.get_name().eq(name))
.nth(0)?;
Some(&mut player.tray) Some(&mut player.tray)
@ -95,8 +105,15 @@ impl Game {
pub fn get_board(&self) -> &Board {&self.board} pub fn get_board(&self) -> &Board {&self.board}
pub fn get_board_mut(&mut self) -> &mut Board { pub fn set_board(&mut self, new_board: Board) {
&mut self.board self.board = new_board;
}
pub fn fill_trays(&mut self){
for state in self.player_states.iter_mut() {
let tray = &mut state.tray;
tray.fill(&mut self.tile_pool);
}
} }
pub fn get_dictionary(&self) -> &DictionaryImpl { pub fn get_dictionary(&self) -> &DictionaryImpl {
@ -104,5 +121,4 @@ impl Game {
} }
} }

View file

@ -5,7 +5,7 @@ use crate::board::Letter;
pub mod ai; pub mod ai;
#[derive(Debug, Serialize, Deserialize, Tsify)] #[derive(Debug, Serialize, Deserialize, Tsify, Clone)]
#[tsify(from_wasm_abi)] #[tsify(from_wasm_abi)]
pub struct Tray { pub struct Tray {
pub letters: Vec<Option<Letter>> pub letters: Vec<Option<Letter>>

View file

@ -58,13 +58,14 @@ impl GameWasm {
let tray_tile_locations: Vec<Option<PlayedTile>> = serde_wasm_bindgen::from_value(tray_tile_locations)?; let tray_tile_locations: Vec<Option<PlayedTile>> = serde_wasm_bindgen::from_value(tray_tile_locations)?;
let mut board_instance = self.0.get_board().clone(); let mut board_instance = self.0.get_board().clone();
let tray = self.0.get_tray(player).unwrap().clone(); let mut tray = self.0.get_tray(player).unwrap().clone();
let mut played_letters: Vec<(Letter, Coordinates)> = Vec::new(); let mut played_letters: Vec<(Letter, Coordinates)> = Vec::new();
for (i, played_tile) in tray_tile_locations.iter().enumerate() { for (i, played_tile) in tray_tile_locations.iter().enumerate() {
if played_tile.is_some() { if played_tile.is_some() {
let played_tile = played_tile.unwrap(); let played_tile = played_tile.unwrap();
let mut letter: Letter = tray.letters.get(i).unwrap().unwrap(); let mut letter: Letter = tray.letters.get(i).unwrap().unwrap();
*tray.letters.get_mut(i).unwrap() = None;
let coord = Coordinates::new_from_index(played_tile.index); let coord = Coordinates::new_from_index(played_tile.index);
if letter.is_blank { if letter.is_blank {
@ -113,7 +114,16 @@ impl GameWasm {
}) })
.collect(); .collect();
//let total_score = x.1;
if commit_move {
let mut player_state = self.0.get_player_state_mut(player).unwrap();
player_state.score += x.1;
player_state.tray = tray;
board_instance.fix_tiles();
self.0.set_board(board_instance);
self.0.fill_trays()
}
serde_wasm_bindgen::to_value( serde_wasm_bindgen::to_value(
&MyResult { &MyResult {

View file

@ -1,6 +1,6 @@
import * as React from "react"; import * as React from "react";
import {GameWasm, Letter as LetterData, MyResult, PlayedTile, PlayerAndScore, Tray, WordResult} from "word_grid"; import {GameWasm, Letter as LetterData, MyResult, PlayedTile, PlayerAndScore, Tray, WordResult} from "word_grid";
import {useMemo, useReducer, useState} from "react"; import {useEffect, useMemo, useReducer, useState} from "react";
export enum LocationType { export enum LocationType {
GRID, GRID,
@ -72,10 +72,15 @@ function matchCoordinate(playerLetters: PlayableLetterData[], coords: Coordinate
} }
type TileDispatch = React.Dispatch<{start: CoordinateData, end: CoordinateData}>; enum TileDispatchActionType {
MOVE,
RETRIEVE,
}
type TileDispatchAction = {action: TileDispatchActionType, start?: CoordinateData, end?: CoordinateData};
type TileDispatch = React.Dispatch<TileDispatchAction>;
function addLogInfo(existingLog: React.JSX.Element[], newItem: React.JSX.Element) { function addLogInfo(existingLog: React.JSX.Element[], newItem: React.JSX.Element) {
newItem.key = existingLog.length; newItem = React.cloneElement(newItem, { key: existingLog.length })
existingLog.push(newItem); existingLog.push(newItem);
return existingLog.slice(); return existingLog.slice();
} }
@ -86,31 +91,48 @@ export function Game(props: {wasm: GameWasm}) {
return props.wasm.get_board_cell_types(); return props.wasm.get_board_cell_types();
}, []); }, []);
const [checkPerformed, setCheckPerformed] = useState<boolean>(false); const [confirmedScorePoints, setConfirmedScorePoints] = useState<number>(-1);
function movePlayableLetters(playerLetters: PlayableLetterData[], update: {start: CoordinateData, end: CoordinateData}) { function movePlayableLetters(playerLetters: PlayableLetterData[], update: TileDispatchAction) {
let startIndex = matchCoordinate(playerLetters, update.start); if(update.action === TileDispatchActionType.RETRIEVE) {
let endIndex = matchCoordinate(playerLetters, update.end); let tray: Tray = props.wasm.get_tray("Player");
if(startIndex != null) { // initial state
let startLetter = playerLetters[startIndex]; let letters: PlayableLetterData[] = tray.letters.map((ld, i) => {
startLetter.location = update.end.location; ld["location"] = LocationType.TRAY;
startLetter.index = update.end.index; ld["index"] = i;
return ld as PlayableLetterData;
})
return letters;
} else if (update.action === TileDispatchActionType.MOVE) {
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;
}
setConfirmedScorePoints(-1);
return playerLetters.slice();
} else {
console.error("Unknown tray update");
console.error({update});
} }
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, (_) => { const [playerLetters, trayDispatch] = useReducer(movePlayableLetters, null, (_) => {
let tray: Tray = props.wasm.get_tray("Player"); let tray: Tray = props.wasm.get_tray("Player");
// initial state // initial state
@ -127,14 +149,22 @@ export function Game(props: {wasm: GameWasm}) {
const [logInfo, logDispatch] = useReducer(addLogInfo, []); const [logInfo, logDispatch] = useReducer(addLogInfo, []);
const [turnCount, setTurnCount] = useState<number>(0); const [turnCount, setTurnCount] = useState<number>(1);
const playerAndScores: PlayerAndScore[] = useMemo(() => { const playerAndScores: PlayerAndScore[] = useMemo(() => {
return props.wasm.get_scores(); return props.wasm.get_scores();
}, [turnCount]) }, [turnCount])
useEffect(() => {
logDispatch(<h4>Turn {turnCount}</h4>);
setConfirmedScorePoints(-1);
trayDispatch({action: TileDispatchActionType.RETRIEVE});
}, [turnCount]);
return <> return <>
<div className="board-log"> <div className="board-log">
<Grid cellTypes={cellTypes} playerLetters={playerLetters} dispatch={dispatch}/> <Grid cellTypes={cellTypes} playerLetters={playerLetters} dispatch={trayDispatch}/>
<div className="message-log"> <div className="message-log">
<Scores playerScores={playerAndScores}/> <Scores playerScores={playerAndScores}/>
<div className="log"> <div className="log">
@ -143,7 +173,7 @@ export function Game(props: {wasm: GameWasm}) {
</div> </div>
</div> </div>
<TileTray letters={playerLetters} trayLength={7} dispatch={dispatch}/> <TileTray letters={playerLetters} trayLength={7} dispatch={trayDispatch}/>
<button onClick={(e) => { <button onClick={(e) => {
const playedTiles = playerLetters.map((i) => { const playedTiles = playerLetters.map((i) => {
if (i === undefined) { if (i === undefined) {
@ -165,7 +195,7 @@ export function Game(props: {wasm: GameWasm}) {
return null; return null;
}); });
const result: MyResult<Array<WordResult> | string> = props.wasm.receive_play("Player", playedTiles, false); const result: MyResult<Array<WordResult> | string> = props.wasm.receive_play("Player", playedTiles, confirmedScorePoints > -1);
console.log({result}); console.log({result});
if(result.response_type === "ERR") { if(result.response_type === "ERR") {
@ -177,15 +207,21 @@ export function Game(props: {wasm: GameWasm}) {
total_points += word_result.score; total_points += word_result.score;
} }
const msg = `You would score ${total_points} points.`; let msg: string;
if (confirmedScorePoints > -1) {
msg = `You scored ${total_points} points.`;
setTurnCount(turnCount + 1);
}
logDispatch(<div><em>{msg}</em></div>); logDispatch(<div><em>{msg}</em></div>);
setCheckPerformed(true); setConfirmedScorePoints(total_points);
} }
}}>{checkPerformed ? "Submit ✅" : "Check"}</button> }}>{confirmedScorePoints > -1 ? `Score ${confirmedScorePoints} points ✅` : "Check"}</button>
</>; </>;
@ -205,7 +241,7 @@ export function TileSlot(props: { tile: React.JSX.Element | undefined, location:
const startLocation: CoordinateData = JSON.parse(e.dataTransfer.getData("wordGrid/coords")); const startLocation: CoordinateData = JSON.parse(e.dataTransfer.getData("wordGrid/coords"));
const thisLocation = props.location; const thisLocation = props.location;
props.dispatch({start: startLocation, end: thisLocation}); props.dispatch({action: TileDispatchActionType.MOVE, start: startLocation, end: thisLocation});
} }
let className = "tileSpot"; let className = "tileSpot";