Add UI for tile exchange

This commit is contained in:
Joel Therrien 2023-08-21 19:42:22 -07:00
parent 4ba4c15e23
commit b790e48bc4
5 changed files with 154 additions and 6 deletions

3
ui/package-lock.json generated
View file

@ -19,7 +19,8 @@
}, },
"../pkg": { "../pkg": {
"name": "word_grid", "name": "word_grid",
"version": "0.1.0" "version": "0.1.0",
"license": "AGPL-3"
}, },
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
"version": "7.22.5", "version": "7.22.5",

View file

@ -2,7 +2,7 @@ import {useEffect, useRef} from "react";
export function Modal(props: { export function Modal(props: {
isOpen: boolean; isOpen: boolean;
setClosed: () => void; setOpen: (isOpen: boolean) => void;
children: any; children: any;
}){ }){
@ -27,7 +27,7 @@ export function Modal(props: {
// we manually trigger a close anyway because the children can get updated before the dialog gets closed otherwise // we manually trigger a close anyway because the children can get updated before the dialog gets closed otherwise
// @ts-ignore // @ts-ignore
ref.current.close(); ref.current.close();
props.setClosed(); props.setOpen(false);
}}></button> }}></button>
</div> </div>
<div> <div>

View file

@ -1,6 +1,8 @@
import * as React from "react"; import * as React from "react";
import {GameWasm, Letter as LetterData, MyResult, PlayedTile, PlayerAndScore, Tray, WordResult} from "word_grid"; import {GameWasm, Letter as LetterData, MyResult, PlayedTile, PlayerAndScore, Tray, WordResult} from "word_grid";
import {ChangeEvent, JSX, useEffect, useMemo, useReducer, useRef, useState} from "react"; import {ChangeEvent, JSX, useEffect, useMemo, useReducer, useRef, useState} from "react";
import {Modal} from "./Modal";
import {addNTimes} from "./utils";
export interface Settings { export interface Settings {
trayLength: number; trayLength: number;
@ -181,8 +183,6 @@ export function Game(props: {wasm: GameWasm, settings: Settings}) {
} }
const [playerLetters, trayDispatch] = useReducer(movePlayableLetters, []); const [playerLetters, trayDispatch] = useReducer(movePlayableLetters, []);
const [logInfo, logDispatch] = useReducer(addLogInfo, []); const [logInfo, logDispatch] = useReducer(addLogInfo, []);
const [turnCount, setTurnCount] = useState<number>(1); const [turnCount, setTurnCount] = useState<number>(1);
@ -202,6 +202,7 @@ export function Game(props: {wasm: GameWasm, settings: Settings}) {
const logDivRef = useRef(null); const logDivRef = useRef(null);
const [isTileExchangeOpen, setIsTileExchangeOpen] = useState<boolean>(false);
useEffect(() => { useEffect(() => {
// Effect is to keep the log window scrolled down // Effect is to keep the log window scrolled down
@ -214,6 +215,7 @@ export function Game(props: {wasm: GameWasm, settings: Settings}) {
return <> return <>
<TileExchangeModal playerLetters={playerLetters} isOpen={isTileExchangeOpen} setOpen={setIsTileExchangeOpen} />
<div className="board-log"> <div className="board-log">
<Grid cellTypes={cellTypes} playerLetters={playerLetters} boardLetters={boardLetters} dispatch={trayDispatch}/> <Grid cellTypes={cellTypes} playerLetters={playerLetters} boardLetters={boardLetters} dispatch={trayDispatch}/>
<div className="message-log"> <div className="message-log">
@ -273,6 +275,7 @@ export function Game(props: {wasm: GameWasm, settings: Settings}) {
}}>{confirmedScorePoints > -1 ? `Score ${confirmedScorePoints} points ✅` : "Check"}</button> }}>{confirmedScorePoints > -1 ? `Score ${confirmedScorePoints} points ✅` : "Check"}</button>
<button onClick={(e) => {setIsTileExchangeOpen(true);}}>Open Tile Exchange</button>
</>; </>;
@ -466,3 +469,114 @@ function Scores(props: {playerScores: Array<PlayerAndScore>}){
{elements} {elements}
</div> </div>
} }
function TileExchangeModal(props: {playerLetters: PlayableLetterData[], isOpen: boolean, setOpen: (isOpen: boolean) => void}) {
function clearExchangeTiles() {
const array: boolean[] = [];
addNTimes(array, false, props.playerLetters.length);
return array;
}
const [tilesToExchange, setTilesToExchange] = useState<boolean[]>(clearExchangeTiles);
let tilesExchangedSelected = 0;
for (let i of tilesToExchange) {
if(i) {
tilesExchangedSelected++;
}
}
return <Modal isOpen={props.isOpen} setOpen={(isOpen) => {
setTilesToExchange(clearExchangeTiles());
props.setOpen(isOpen);
}}>
<div className="tile-exchange-dialog">
<div className="instructions">
Click on each tile you'd like to exchange. You currently have {tilesExchangedSelected} tiles selected.
</div>
<div className="selection-buttons">
<button onClick={(e) => {
const array: boolean[] = [];
addNTimes(array, true, props.playerLetters.length);
setTilesToExchange(array);
}}>Select All</button>
<button onClick={(e) => {
setTilesToExchange(clearExchangeTiles());
}}>Select None</button>
</div>
<TilesExchangedTray
tray={props.playerLetters}
selectedArray={tilesToExchange}
setSelectedArray={setTilesToExchange}
trayLength={props.playerLetters.length}
/>
<div className="finish-buttons">
<button onClick={() => {
setTilesToExchange(clearExchangeTiles());
props.setOpen(false);
}}>Cancel</button>
<button disabled = {tilesExchangedSelected == 0} onClick={(e) => {
// TODO exchange code
}}>Exchange</button>
</div>
</div>
</Modal>;
}
function TilesExchangedTray(props: {
tray: Array<PlayableLetterData>,
trayLength: number,
selectedArray: Array<boolean>,
setSelectedArray: (x: Array<boolean>) => void,
}){
const divContent = [];
for(let i = 0; i<props.trayLength; i++){
const tileData = props.tray[i];
const toggleFunction = () => {
props.selectedArray[i] = !props.selectedArray[i];
props.setSelectedArray(props.selectedArray.slice());
}
if(tileData != null){
divContent.push(
<DummyExchangeTile key={i} letter={tileData} isSelected={props.selectedArray[i]} onClick={toggleFunction}/>
);
} else{
divContent.push(<span key={i} />);
}
}
return <div className="tray">
{divContent}
</div>;
}
function DummyExchangeTile(props: {
letter: LetterData,
isSelected: boolean,
onClick: () => void,
}){
let textClassName = "text";
if(props.letter.is_blank) {
textClassName += " prev-blank";
}
let letterClassName = "letter";
if(props.isSelected){
letterClassName += ' selected-tile';
}
return <div className={letterClassName} onClick={props.onClick}>
<div className={textClassName}>{props.letter.text}</div>
<div className="letter-points">{props.letter.points}</div>
</div>;
}

View file

@ -188,4 +188,31 @@ dialog {
} }
} }
.tile-exchange-dialog {
.selection-buttons {
display: flex;
justify-content: space-around;
button {
width: 40%;
}
}
.finish-buttons {
.selection-buttons();
}
.tray-container {
display: flex;
justify-content: center;
margin: 20px;
}
.selected-tile {
bottom: 10px;
}
}
} }

6
ui/src/utils.ts Normal file
View file

@ -0,0 +1,6 @@
export function addNTimes<T>(array: T[], toAdd: T, times: number) {
for (let i=0; i<times; i++) {
array.push(toAdd);
}
}