Integrate score keeping and individual trays

This commit is contained in:
Joel Therrien 2023-08-15 19:35:23 -07:00
parent deedc1aee4
commit c7399b2b99
5 changed files with 172 additions and 51 deletions

View file

@ -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<Letter>,
board: Board,
player: PlayerState,
pub player_states: Vec<PlayerState>,
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<String>) -> Self {
let mut rng = SmallRng::seed_from_u64(seed);
let mut letters = standard_tile_pool(Some(&mut rng));
player_names.shuffle(&mut rng);
let player_states: Vec<PlayerState> = player_names.iter()
.map(|name| {
let mut tray = Tray::new(TRAY_LENGTH);
tray.fill(&mut letters);
let player = PlayerState {
player: Player::Human("Joel".to_string()),
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}

View file

@ -1,4 +1,5 @@
pub struct Difficulty {
proportion: f64,
randomness: f64,

View file

@ -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<JsValue, Error> {
let tray = self.0.get_tray();
pub fn get_tray(&self, name: &str) -> Result<JsValue, Error> {
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<JsValue, Error> {
@ -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<JsValue, JsValue> {
pub fn receive_play(&mut self, player: &str, tray_tile_locations: JsValue, commit_move: bool) -> Result<JsValue, JsValue> {
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 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<JsValue, JsValue> {
#[derive(Serialize, Deserialize, Tsify)]
#[tsify(from_wasm_abi)]
pub struct PlayerAndScore {
name: String,
score: u32,
}
let scores: Vec<PlayerAndScore> = 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)?)
}
}

View file

@ -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,6 +74,20 @@ function matchCoordinate(playerLetters: PlayableLetterData[], coords: Coordinate
type TileDispatch = React.Dispatch<{start: CoordinateData, end: CoordinateData}>;
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}) {
const cellTypes = useMemo(() => {
return props.wasm.get_board_cell_types();
}, []);
const [checkPerformed, setCheckPerformed] = useState<boolean>(false);
function movePlayableLetters(playerLetters: PlayableLetterData[], update: {start: CoordinateData, end: CoordinateData}) {
let startIndex = matchCoordinate(playerLetters, update.start);
@ -92,17 +105,13 @@ function movePlayableLetters(playerLetters: PlayableLetterData[], update: {start
endLetter.index = update.start.index;
}
setCheckPerformed(false);
return playerLetters.slice();
}
export function Game(props: {wasm: GameWasm}) {
const cellTypes = useMemo(() => {
return props.wasm.get_board_cell_types();
}, []);
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<number>(0);
const playerAndScores: PlayerAndScore[] = useMemo(() => {
return props.wasm.get_scores();
}, [turnCount])
return <>
<div className="board-log">
<Grid cellTypes={cellTypes} playerLetters={playerLetters} dispatch={dispatch}/>
<div className="message-log">
<Scores playerScores={playerAndScores}/>
<div className="log">
{logInfo}
</div>
</div>
</div>
<TileTray letters={playerLetters} trayLength={7} dispatch={dispatch}/>
<button onClick={(e) => {
const playedTiles = playerLetters.map((i) => {
@ -141,11 +165,11 @@ export function Game(props: {wasm: GameWasm}) {
return null;
});
const result: MyResult<Array<WordResult> | string> = props.wasm.receive_play(playedTiles, false);
const result: MyResult<Array<WordResult> | string> = props.wasm.receive_play("Player", playedTiles, false);
console.log({result});
if(result.response_type === "ERR") {
alert(result.value);
logDispatch(<div><em>{(result.value as string)}</em></div>);
} else {
let total_points = 0;
@ -153,12 +177,15 @@ export function Game(props: {wasm: GameWasm}) {
total_points += word_result.score;
}
alert(`You would score ${total_points} points.`);
const msg = `You would score ${total_points} points.`;
logDispatch(<div><em>{msg}</em></div>);
setCheckPerformed(true);
}
}}>Check Submission</button>
}}>{checkPerformed ? "Submit ✅" : "Check"}</button>
</>;
@ -272,3 +299,17 @@ function Grid(props: {cellTypes: CellType[], playerLetters: Array<PlayableLetter
{elements}
</div>
}
function Scores(props: {playerScores: Array<PlayerAndScore>}){
let elements = props.playerScores.map((ps, i) => {
return <div key={ps.name}>
<h3>{ps.name}</h3>
<span>{ps.score}</span>
</div>;
});
return <div className="scoring">
{elements}
</div>
}

View file

@ -21,6 +21,7 @@
justify-content: center;
grid-gap: 1px;
user-select: none;
flex: initial;
}
.grid-spot {
@ -105,3 +106,34 @@
}
}
.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;
}
}