WordGrid/ui/src/Game.tsx

245 lines
8.1 KiB
TypeScript
Raw Normal View History

2023-08-24 03:46:43 +00:00
import * as React from "react";
import {
GameWasm,
Letter as LetterData,
MyResult,
PlayedTile,
PlayerAndScore,
ScoreResult,
Tray, TurnAction, TurnAdvanceResult
} from "word_grid";
2023-08-24 03:46:43 +00:00
import {
LocationType,
matchCoordinate,
mergeTrays,
PlayableLetterData,
Settings,
TileDispatchAction,
TileDispatchActionType
} from "./utils";
import {useEffect, useMemo, useReducer, useRef, useState} from "react";
import {TileExchangeModal} from "./TileExchange";
import {Grid, Scores, TileTray} from "./UI";
function addLogInfo(existingLog: React.JSX.Element[], newItem: React.JSX.Element) {
newItem = React.cloneElement(newItem, { key: existingLog.length })
existingLog.push(newItem);
return existingLog.slice();
}
export function Game(props: {
wasm: GameWasm,
settings: Settings,
end_game_fn: () => void,
}) {
2023-08-24 03:46:43 +00:00
const cellTypes = useMemo(() => {
return props.wasm.get_board_cell_types();
}, []);
const [confirmedScorePoints, setConfirmedScorePoints] = useState<number>(-1);
function movePlayableLetters(playerLetters: PlayableLetterData[], update: TileDispatchAction) {
if(update.action === TileDispatchActionType.RETRIEVE) {
let tray: Tray = props.wasm.get_tray("Player");
return mergeTrays(playerLetters, tray.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();
} if (update.action === TileDispatchActionType.SET_BLANK) {
const blankLetter = playerLetters[update.blankIndex];
if(blankLetter.text !== update.newBlankValue) {
blankLetter.text = update.newBlankValue;
if (blankLetter.location == LocationType.GRID) {
setConfirmedScorePoints(-1);
}
}
return playerLetters.slice();
} else if (update.action === TileDispatchActionType.RETURN) {
return mergeTrays(playerLetters, playerLetters);
} else {
console.error("Unknown tray update");
console.error({update});
}
}
const [playerLetters, trayDispatch] = useReducer(movePlayableLetters, []);
const [logInfo, logDispatch] = useReducer(addLogInfo, []);
const [turnCount, setTurnCount] = useState<number>(1);
const playerAndScores: PlayerAndScore[] = useMemo(() => {
return props.wasm.get_scores();
}, [turnCount]);
const boardLetters: LetterData[] = useMemo(() => {
return props.wasm.get_board_letters();
}, [turnCount]);
useEffect(() => {
logDispatch(<h4>Turn {turnCount}</h4>);
setConfirmedScorePoints(-1);
trayDispatch({action: TileDispatchActionType.RETRIEVE});
}, [turnCount]);
const logDivRef = useRef(null);
const [isTileExchangeOpen, setIsTileExchangeOpen] = useState<boolean>(false);
useEffect(() => {
// Effect is to keep the log window scrolled down
if (logDivRef.current != null) {
logDivRef.current.scrollTo(0, logDivRef.current.scrollHeight); // scroll down
}
}, [logInfo])
function exchangeFunction(selectedArray: Array<boolean>) {
const result: MyResult<TurnAction | string> = props.wasm.exchange_tiles(selectedArray);
2023-08-24 03:46:43 +00:00
console.log({result});
if(result.response_type === "ERR") {
logDispatch(<div><em>{(result.value as string)}</em></div>);
} else {
const action = result.value as {type: "ExchangeTiles"; tiles_exchanged: number;};
logDispatch(<div><em>You exchanged {action.tiles_exchanged} tiles.</em></div>);
2023-08-24 03:46:43 +00:00
setTurnCount(turnCount + 1);
}
}
function addWordFn(word: string) {
props.wasm.add_word(word);
logDispatch(<div><em>{word} was added to dictionary.</em></div>);
}
2023-08-24 03:46:43 +00:00
return <>
<TileExchangeModal
playerLetters={playerLetters}
isOpen={isTileExchangeOpen}
setOpen={setIsTileExchangeOpen}
exchangeFunction={exchangeFunction}
/>
<div className="board-log">
<Grid cellTypes={cellTypes} playerLetters={playerLetters} boardLetters={boardLetters} dispatch={trayDispatch}/>
<div className="message-log">
<Scores playerScores={playerAndScores}/>
<div className="log" ref={logDivRef}>
{logInfo}
</div>
</div>
</div>
<TileTray letters={playerLetters} trayLength={props.settings.trayLength} dispatch={trayDispatch}/>
2023-08-24 04:58:34 +00:00
<button onClick={() => {
2023-08-24 03:46:43 +00:00
const playedTiles = playerLetters.map((i) => {
if (i === undefined) {
return null;
}
if (i.location === LocationType.GRID) {
let result: PlayedTile = {
index: i.index,
character: undefined
};
if (i.is_blank) {
result.character = i.text;
}
return result;
}
return null;
});
const result: MyResult<{ type: "PlayTiles"; result: ScoreResult } | string> = props.wasm.receive_play(playedTiles, confirmedScorePoints > -1);
2023-08-24 03:46:43 +00:00
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>);
}
2023-08-24 03:46:43 +00:00
} else {
const score_result = (result.value as { type: "PlayTiles"; result: ScoreResult }).result;
const total_points = score_result.total;
2023-08-24 03:46:43 +00:00
let msg: string;
if (confirmedScorePoints > -1) {
msg = `You scored ${total_points} points.`;
setTurnCount(turnCount + 1);
}
logDispatch(<div><em>{msg}</em></div>);
setConfirmedScorePoints(total_points);
}
}}>{confirmedScorePoints > -1 ? `Score ${confirmedScorePoints} points ✅` : "Check"}</button>
<button
2023-08-24 04:58:34 +00:00
onClick={() => {
2023-08-24 03:46:43 +00:00
trayDispatch({action: TileDispatchActionType.RETURN}); // want all tiles back on tray for tile exchange
setIsTileExchangeOpen(true);
}}
>Open Tile Exchange</button>
2023-08-24 04:58:34 +00:00
<button onClick={() => {
2023-08-24 03:46:43 +00:00
trayDispatch({action: TileDispatchActionType.RETURN});
}}>Return Tiles</button>
<button onClick={() => {
props.wasm.skip_turn();
logDispatch(<div><em>Player skipped their turn</em></div>);
setTurnCount(turnCount + 1);
}}>Skip Turn</button>
<button onClick={() => {
const result: TurnAdvanceResult = props.wasm.advance_turn();
console.info({result});
setTurnCount(turnCount + 1);
}}>Run AI</button>
2023-08-24 03:46:43 +00:00
</>;
}
function AddWordButton(props: {word: string, addWordFn: (x: string) => void}) {
const [isClicked, setIsClicked] = useState<boolean>(false);
return <button
disabled={isClicked}
onClick={() => {
setIsClicked(true);
props.addWordFn(props.word);
}}>
Add to dictionary
</button>
}