import * as React from "react"; import { GameWasm, Letter as LetterData, MyResult, PlayedTile, PlayerAndScore, ScoreResult, Tray, TurnAction, TurnAdvanceResult } from "../../pkg/word_grid"; import { HighlightableLetterData, 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, }) { const cellTypes = useMemo(() => { return props.wasm.get_board_cell_types(); }, []); const [confirmedScorePoints, setConfirmedScorePoints] = useState(-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}); } } function exchangeFunction(selectedArray: Array) { const result: MyResult = props.wasm.exchange_tiles(selectedArray); console.log({result}); if(result.response_type === "ERR") { logDispatch(
{(result.value as string)}
); } else { handlePlayerAction(result.value as TurnAction, props.settings.playerName); setTurnCount(turnCount + 1); } } function addWordFn(word: string) { props.wasm.add_word(word); logDispatch(
{word} was added to dictionary.
); } function runAI() { const result: TurnAdvanceResult = props.wasm.advance_turn(); if(result.type == "AIMove"){ handlePlayerAction(result.action, props.settings.aiName); } else { // this would be quite surprising console.error({result}); } setTurnCount(turnCount + 1); } const [playerLetters, trayDispatch] = useReducer(movePlayableLetters, []); const [logInfo, logDispatch] = useReducer(addLogInfo, []); const [turnCount, setTurnCount] = useState(1); const playerAndScores: PlayerAndScore[] = useMemo(() => { return props.wasm.get_scores(); }, [turnCount]); const [boardLetters, setBoardLetters] = useState(() => { const newLetterData = [] as HighlightableLetterData[]; for(let i=0; i<15*15; i++) { newLetterData.push(undefined); } return newLetterData; }); useEffect(() => { const newLetterData = props.wasm.get_board_letters() as HighlightableLetterData[]; // we need to go through and set 'highlight' field to either true or false // it will always be false if the player that just went was the AI // TODO - build in support for multiple other players for(let i=0; i { return props.wasm.get_current_player(); }, [turnCount]); useEffect(() => { logDispatch(

Turn {turnCount}

); logDispatch(
{playerTurnName}'s turn
) setConfirmedScorePoints(-1); trayDispatch({action: TileDispatchActionType.RETRIEVE}); if(playerTurnName != props.settings.playerName) { runAI(); } }, [turnCount]); const logDivRef = useRef(null); const [isTileExchangeOpen, setIsTileExchangeOpen] = useState(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]); 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; if(result.response_type == "OK") { return result.value as number; } else { console.error(result.value); return -1; } }, [turnCount]); function handlePlayerAction(action: TurnAction, playerName: string) { if (action.type == "PlayTiles"){ const result = action.result; result.words.sort((a, b) => b.score - a.score); for(let word of result.words) { logDispatch(
{playerName} received {word.score} points for playing '{word.word}.'
); } logDispatch(
{playerName} received a total of {result.total} points for their turn.
); } else if(action.type == "ExchangeTiles") { logDispatch(
{playerName} exchanged {action.tiles_exchanged} tile{action.tiles_exchanged > 1 ? 's' : ''} for their turn.
); } else if(action.type == "Pass"){ logDispatch(
{playerName} passed.
); } } return <>
{logInfo}
{remainingTiles} letters remaining
{props.settings.aiName} has {remainingAITiles} tiles
; } function AddWordButton(props: {word: string, addWordFn: (x: string) => void}) { const [isClicked, setIsClicked] = useState(false); return }