Add AI tile count and total tile counts, other UI tweaks

This commit is contained in:
Joel Therrien 2023-09-16 21:08:10 -07:00
parent e7c46c8efd
commit 0d30ac0b46
6 changed files with 162 additions and 67 deletions

View file

@ -361,6 +361,23 @@ impl Game {
} }
pub fn get_remaining_tiles(&self) -> usize {
self.tile_pool.len()
}
pub fn get_player_tile_count(&self, player: &str) -> Result<usize, String> {
let tray = match self.player_states.get_tray(&player) {
None => {return Err(format!("Player {} not found", player))}
Some(x) => {x}
};
Ok(
tray.letters.iter()
.filter(|l| l.is_some())
.count()
)
}
} }
#[derive(Serialize, Deserialize, Tsify, Debug)] #[derive(Serialize, Deserialize, Tsify, Debug)]

View file

@ -155,4 +155,25 @@ impl GameWasm {
self.0.current_player_name() self.0.current_player_name()
} }
pub fn get_remaining_tiles(&self) -> usize {
self.0.get_remaining_tiles()
}
pub fn get_player_tile_count(&self, player: &str) -> Result<JsValue, Error> {
match self.0.get_player_tile_count(player) {
Ok(count) => {
Ok(serde_wasm_bindgen::to_value(&MyResult{
response_type: ResponseType::OK,
value: count,
})?)
},
Err(msg) => {
Ok(serde_wasm_bindgen::to_value(&MyResult{
response_type: ResponseType::OK,
value: msg,
})?)
}
}
}
} }

View file

@ -108,7 +108,7 @@ export function Game(props: {
function runAI() { function runAI() {
const result: TurnAdvanceResult = props.wasm.advance_turn(); const result: TurnAdvanceResult = props.wasm.advance_turn();
if(result.type == "AIMove"){ if(result.type == "AIMove"){
handlePlayerAction(result.action, "AI"); handlePlayerAction(result.action, props.settings.aiName);
} else { } else {
// this would be quite surprising // this would be quite surprising
console.error({result}); console.error({result});
@ -179,6 +179,21 @@ export function Game(props: {
} }
}, [logInfo]); }, [logInfo]);
const remainingTiles = useMemo(() => {
return props.wasm.get_remaining_tiles();
}, [turnCount]);
const remainingAITiles = useMemo(() => {
let result = props.wasm.get_player_tile_count(props.settings.aiName) as MyResult<number | String>;
if(result.response_type == "OK") {
return result.value as number;
} else {
console.error(result.value);
return -1;
}
}, [turnCount]);
function handlePlayerAction(action: TurnAction, playerName: string) { function handlePlayerAction(action: TurnAction, playerName: string) {
if (action.type == "PlayTiles"){ if (action.type == "PlayTiles"){
@ -213,73 +228,85 @@ export function Game(props: {
</div> </div>
</div> </div>
</div> </div>
<div className="tray-row">
<div>
<div>
{remainingTiles} letters remaining
</div>
<div>
{props.settings.aiName} has {remainingAITiles} tiles
</div>
<button onClick={() => {
trayDispatch({action: TileDispatchActionType.RETURN}); // want all tiles back on tray for tile exchange
setIsTileExchangeOpen(true);
}}>Open Tile Exchange</button>
</div>
<TileTray letters={playerLetters} trayLength={props.settings.trayLength} dispatch={trayDispatch}/>
<div className="player-controls">
<button className="check" onClick={() => {
const playedTiles = playerLetters.map((i) => {
if (i === undefined) {
return null;
}
<TileTray letters={playerLetters} trayLength={props.settings.trayLength} dispatch={trayDispatch}/> if (i.location === LocationType.GRID) {
<button onClick={() => { let result: PlayedTile = {
const playedTiles = playerLetters.map((i) => { index: i.index,
if (i === undefined) { character: undefined
return null; };
} if (i.is_blank) {
result.character = i.text;
}
if (i.location === LocationType.GRID) { return result;
let result: PlayedTile = { }
index: i.index,
character: undefined return null;
}; });
if (i.is_blank) {
result.character = i.text; const result: MyResult<{ type: "PlayTiles"; result: ScoreResult } | string> = props.wasm.receive_play(playedTiles, confirmedScorePoints > -1);
console.log({result});
if(result.response_type === "ERR") {
const message = result.value as string;
if (message.endsWith("is not a valid word")) {
// extract out word
const word = message.split(" ")[0];
logDispatch(<div><em>{message}</em><AddWordButton word={word} addWordFn={addWordFn} /></div>);
} else {
logDispatch(<div><em>{message}</em></div>);
}
} else {
const score_result = (result.value as { type: "PlayTiles"; result: ScoreResult }).result;
const total_points = score_result.total;
if (confirmedScorePoints > -1) {
handlePlayerAction({type: "PlayTiles", result: score_result}, props.settings.playerName);
setTurnCount(turnCount + 1);
}
setConfirmedScorePoints(total_points);
} }
return result;
}
return null;
});
const result: MyResult<{ type: "PlayTiles"; result: ScoreResult } | string> = props.wasm.receive_play(playedTiles, confirmedScorePoints > -1);
console.log({result});
if(result.response_type === "ERR") {
const message = result.value as string;
if (message.endsWith("is not a valid word")) {
// extract out word
const word = message.split(" ")[0];
logDispatch(<div><em>{message}</em><AddWordButton word={word} addWordFn={addWordFn} /></div>);
} else {
logDispatch(<div><em>{message}</em></div>);
}
} else {
const score_result = (result.value as { type: "PlayTiles"; result: ScoreResult }).result;
const total_points = score_result.total;
if (confirmedScorePoints > -1) { }}>{confirmedScorePoints > -1 ? `Score ${confirmedScorePoints} points` : "Check"}</button>
handlePlayerAction({type: "PlayTiles", result: score_result}, props.settings.playerName); <button className="return" onClick={() => {
trayDispatch({action: TileDispatchActionType.RETURN});
}}>Return Tiles</button>
<button className="pass" onClick={() => {
props.wasm.skip_turn();
handlePlayerAction({type: "Pass"}, props.settings.playerName);
setTurnCount(turnCount + 1); setTurnCount(turnCount + 1);
} }}>Pass</button>
</div>
setConfirmedScorePoints(total_points); </div>
}
}}>{confirmedScorePoints > -1 ? `Score ${confirmedScorePoints} points ✅` : "Check"}</button>
<button
onClick={() => {
trayDispatch({action: TileDispatchActionType.RETURN}); // want all tiles back on tray for tile exchange
setIsTileExchangeOpen(true);
}}
>Open Tile Exchange</button>
<button onClick={() => {
trayDispatch({action: TileDispatchActionType.RETURN});
}}>Return Tiles</button>
<button onClick={() => {
props.wasm.skip_turn();
handlePlayerAction({type: "Pass"}, props.settings.playerName);
setTurnCount(turnCount + 1);
}}>Skip Turn</button>
</>; </>;
} }

View file

@ -51,6 +51,7 @@ async function run() {
root.render(<Menu dictionary_text={dictionary_text} settings={{ root.render(<Menu dictionary_text={dictionary_text} settings={{
trayLength: 7, trayLength: 7,
playerName: 'Player', playerName: 'Player',
aiName: 'AI',
}}/>); }}/>);

View file

@ -3,16 +3,44 @@
@board-length: 15; @board-length: 15;
@tile-star-size: 45px; @tile-star-size: 45px;
.tray { .tray-row {
display: grid; display: grid;
grid-template-columns: repeat(7, @tile-width); grid-template-columns: 1fr 1fr 1fr;
grid-gap: 5px; width: @board-length*@tile-width;
height: @tile-width;
width: fit-content; .tray {
background-color: #bbb59d; display: grid;
margin: 10px; grid-template-columns: repeat(7, @tile-width);
grid-gap: 5px;
height: @tile-width;
width: fit-content;
background-color: #bbb59d;
margin: 10px;
}
.player-controls {
display: grid;
grid-template-areas: "check return"
"check pass";
grid-template-rows: 1fr 1fr;
grid-template-columns: 1fr 1fr;
.check {
grid-area: check;
}
.return {
grid-area: return;
}
.pass {
grid-area: pass;
}
}
} }
.board-grid { .board-grid {
//grid-area: grid; //grid-area: grid;
display: grid; display: grid;

View file

@ -1,4 +1,4 @@
import {Letter as LetterData, Letter} from "word_grid"; import {Letter as LetterData, Letter} from "../../pkg/word_grid";
import * as React from "react"; import * as React from "react";
export enum CellType { export enum CellType {
@ -14,6 +14,7 @@ export enum CellType {
export interface Settings { export interface Settings {
trayLength: number; trayLength: number;
playerName: string; playerName: string;
aiName: string;
} }
export enum LocationType { export enum LocationType {