207 lines
6.9 KiB
TypeScript
207 lines
6.9 KiB
TypeScript
|
import * as React from "react";
|
||
|
import {GameWasm, Letter as LetterData, MyResult, PlayedTile, PlayerAndScore, Tray, WordResult} from "word_grid";
|
||
|
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}) {
|
||
|
|
||
|
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>) {
|
||
|
|
||
|
let numSelected = 0;
|
||
|
selectedArray.forEach((x) => {
|
||
|
if (x){
|
||
|
numSelected++;
|
||
|
}
|
||
|
})
|
||
|
|
||
|
const result: MyResult<Tray | string> = props.wasm.exchange_tiles("Player", selectedArray);
|
||
|
console.log({result});
|
||
|
|
||
|
if(result.response_type === "ERR") {
|
||
|
logDispatch(<div><em>{(result.value as string)}</em></div>);
|
||
|
} else {
|
||
|
logDispatch(<div><em>You exchanged {numSelected} tiles.</em></div>);
|
||
|
setTurnCount(turnCount + 1);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
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}/>
|
||
|
<button onClick={(e) => {
|
||
|
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<Array<WordResult> | string> = props.wasm.receive_play("Player", playedTiles, confirmedScorePoints > -1);
|
||
|
console.log({result});
|
||
|
|
||
|
if(result.response_type === "ERR") {
|
||
|
logDispatch(<div><em>{(result.value as string)}</em></div>);
|
||
|
} else {
|
||
|
|
||
|
let total_points = 0;
|
||
|
for (let word_result of (result.value as Array<WordResult>)) {
|
||
|
total_points += word_result.score;
|
||
|
}
|
||
|
|
||
|
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
|
||
|
onClick={(e) => {
|
||
|
trayDispatch({action: TileDispatchActionType.RETURN}); // want all tiles back on tray for tile exchange
|
||
|
setIsTileExchangeOpen(true);
|
||
|
}}
|
||
|
>Open Tile Exchange</button>
|
||
|
<button onClick={(e) => {
|
||
|
trayDispatch({action: TileDispatchActionType.RETURN});
|
||
|
}}>Return Tiles</button>
|
||
|
</>;
|
||
|
|
||
|
|
||
|
}
|