Integrate score keeping and individual trays
This commit is contained in:
parent
deedc1aee4
commit
c7399b2b99
5 changed files with 172 additions and 51 deletions
53
src/game.rs
53
src/game.rs
|
@ -1,3 +1,4 @@
|
||||||
|
use rand::prelude::SliceRandom;
|
||||||
use rand::rngs::SmallRng;
|
use rand::rngs::SmallRng;
|
||||||
use rand::SeedableRng;
|
use rand::SeedableRng;
|
||||||
use crate::board::{Board, Letter};
|
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 {
|
pub struct PlayerState {
|
||||||
player: Player,
|
pub player: Player,
|
||||||
score: u32,
|
pub score: u32,
|
||||||
tray: Tray
|
tray: Tray
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Game{
|
pub struct Game{
|
||||||
tile_pool: Vec<Letter>,
|
tile_pool: Vec<Letter>,
|
||||||
board: Board,
|
board: Board,
|
||||||
player: PlayerState,
|
pub player_states: Vec<PlayerState>,
|
||||||
dictionary: DictionaryImpl,
|
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.
|
// This will later work out well when I build it out as an API for multiplayer.
|
||||||
|
|
||||||
impl Game {
|
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 rng = SmallRng::seed_from_u64(seed);
|
||||||
|
|
||||||
let mut letters = standard_tile_pool(Some(&mut rng));
|
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);
|
let mut tray = Tray::new(TRAY_LENGTH);
|
||||||
tray.fill(&mut letters);
|
tray.fill(&mut letters);
|
||||||
|
PlayerState {
|
||||||
|
player: Player::Human(name.clone()),
|
||||||
let player = PlayerState {
|
|
||||||
player: Player::Human("Joel".to_string()),
|
|
||||||
score: 0,
|
score: 0,
|
||||||
tray,
|
tray,
|
||||||
};
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
Game {
|
Game {
|
||||||
tile_pool: letters,
|
tile_pool: letters,
|
||||||
board: Board::new(),
|
board: Board::new(),
|
||||||
player,
|
player_states,
|
||||||
dictionary: DictionaryImpl::create_from_str(dictionary_text)
|
dictionary: DictionaryImpl::create_from_str(dictionary_text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_tray(&self) -> &Tray {
|
pub fn get_tray(&self, name: &str) -> Option<&Tray> {
|
||||||
&self.player.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 {
|
pub fn get_tray_mut(&mut self, name: &str) -> Option<&mut Tray> {
|
||||||
&mut self.player.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}
|
pub fn get_board(&self) -> &Board {&self.board}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
|
|
||||||
|
|
||||||
pub struct Difficulty {
|
pub struct Difficulty {
|
||||||
proportion: f64,
|
proportion: f64,
|
||||||
randomness: f64,
|
randomness: f64,
|
||||||
|
|
34
src/wasm.rs
34
src/wasm.rs
|
@ -35,13 +35,13 @@ impl GameWasm {
|
||||||
|
|
||||||
#[wasm_bindgen(constructor)]
|
#[wasm_bindgen(constructor)]
|
||||||
pub fn new(seed: u64, dictionary_text: &str) -> GameWasm {
|
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> {
|
pub fn get_tray(&self, name: &str) -> Result<JsValue, Error> {
|
||||||
let tray = self.0.get_tray();
|
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> {
|
pub fn get_board_cell_types(&self) -> Result<JsValue, Error> {
|
||||||
|
@ -54,11 +54,11 @@ impl GameWasm {
|
||||||
serde_wasm_bindgen::to_value(&cell_types)
|
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 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().clone();
|
let 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() {
|
||||||
|
@ -134,4 +134,26 @@ impl GameWasm {
|
||||||
Ok(result)
|
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)?)
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {GameWasm, Letter as LetterData, MyResult, PlayedTile, Tray, WordResult} from "word_grid";
|
import {GameWasm, Letter as LetterData, MyResult, PlayedTile, PlayerAndScore, Tray, WordResult} from "word_grid";
|
||||||
import {createRoot} from "react-dom/client";
|
import {useMemo, useReducer, useState} from "react";
|
||||||
import {Children, useMemo, useReducer, useState} from "react";
|
|
||||||
|
|
||||||
export enum LocationType {
|
export enum LocationType {
|
||||||
GRID,
|
GRID,
|
||||||
|
@ -75,6 +74,20 @@ function matchCoordinate(playerLetters: PlayableLetterData[], coords: Coordinate
|
||||||
|
|
||||||
type TileDispatch = React.Dispatch<{start: CoordinateData, end: CoordinateData}>;
|
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}) {
|
function movePlayableLetters(playerLetters: PlayableLetterData[], update: {start: CoordinateData, end: CoordinateData}) {
|
||||||
|
|
||||||
let startIndex = matchCoordinate(playerLetters, update.start);
|
let startIndex = matchCoordinate(playerLetters, update.start);
|
||||||
|
@ -92,17 +105,13 @@ function movePlayableLetters(playerLetters: PlayableLetterData[], update: {start
|
||||||
endLetter.index = update.start.index;
|
endLetter.index = update.start.index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setCheckPerformed(false);
|
||||||
|
|
||||||
return playerLetters.slice();
|
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, (_) => {
|
const [playerLetters, dispatch] = useReducer(movePlayableLetters, null, (_) => {
|
||||||
let tray: Tray = props.wasm.get_tray();
|
let tray: Tray = props.wasm.get_tray("Player");
|
||||||
|
|
||||||
// initial state
|
// initial state
|
||||||
let letters: PlayableLetterData[] = tray.letters.map((ld, i) => {
|
let letters: PlayableLetterData[] = tray.letters.map((ld, i) => {
|
||||||
|
@ -112,13 +121,28 @@ export function Game(props: {wasm: GameWasm}) {
|
||||||
})
|
})
|
||||||
|
|
||||||
return letters;
|
return letters;
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const [logInfo, logDispatch] = useReducer(addLogInfo, []);
|
||||||
|
|
||||||
|
const [turnCount, setTurnCount] = useState<number>(0);
|
||||||
|
const playerAndScores: PlayerAndScore[] = useMemo(() => {
|
||||||
|
return props.wasm.get_scores();
|
||||||
|
}, [turnCount])
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
|
<div className="board-log">
|
||||||
<Grid cellTypes={cellTypes} playerLetters={playerLetters} dispatch={dispatch}/>
|
<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}/>
|
<TileTray letters={playerLetters} trayLength={7} dispatch={dispatch}/>
|
||||||
<button onClick={(e) => {
|
<button onClick={(e) => {
|
||||||
const playedTiles = playerLetters.map((i) => {
|
const playedTiles = playerLetters.map((i) => {
|
||||||
|
@ -141,11 +165,11 @@ export function Game(props: {wasm: GameWasm}) {
|
||||||
return null;
|
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});
|
console.log({result});
|
||||||
|
|
||||||
if(result.response_type === "ERR") {
|
if(result.response_type === "ERR") {
|
||||||
alert(result.value);
|
logDispatch(<div><em>{(result.value as string)}</em></div>);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
let total_points = 0;
|
let total_points = 0;
|
||||||
|
@ -153,12 +177,15 @@ export function Game(props: {wasm: GameWasm}) {
|
||||||
total_points += word_result.score;
|
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}
|
{elements}
|
||||||
</div>
|
</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>
|
||||||
|
}
|
|
@ -21,6 +21,7 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
grid-gap: 1px;
|
grid-gap: 1px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
flex: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-spot {
|
.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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue