Integrate score keeping and individual trays
This commit is contained in:
parent
deedc1aee4
commit
c7399b2b99
5 changed files with 172 additions and 51 deletions
59
src/game.rs
59
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<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));
|
||||
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<PlayerState> = 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}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
|
||||
|
||||
pub struct Difficulty {
|
||||
proportion: f64,
|
||||
randomness: f64,
|
||||
|
|
34
src/wasm.rs
34
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<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)?)
|
||||
|
||||
}
|
||||
}
|
|
@ -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<boolean>(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<number>(0);
|
||||
const playerAndScores: PlayerAndScore[] = useMemo(() => {
|
||||
return props.wasm.get_scores();
|
||||
}, [turnCount])
|
||||
|
||||
return <>
|
||||
<Grid cellTypes={cellTypes} playerLetters={playerLetters} dispatch={dispatch}/>
|
||||
<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>
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue