Refactor, "Give up" button

This commit is contained in:
Lynn
2022-01-03 00:08:23 +01:00
parent 49221d965a
commit f2524f6dae
3 changed files with 69 additions and 53 deletions

View File

@@ -134,15 +134,19 @@ a:active {
color: #cc77ff; color: #cc77ff;
} }
.App-option { .Game-options {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.App-option input { .Game-options > * + * {
margin-left: 0.5rem; margin-inline-start: 0.5rem;
}
.Game-options button {
min-width: 4rem;
} }
.App-footer { .App-footer {

View File

@@ -5,54 +5,17 @@ import Game from "./Game";
import { names } from "./names"; import { names } from "./names";
import { useState } from "react"; import { useState } from "react";
const targets = common
.slice(0, 20000) // adjust for max target freakiness
.filter((word) => dictionarySet.has(word) && !names.has(word));
function randomTarget(wordLength: number) {
const eligible = targets.filter((word) => word.length === wordLength);
return pick(eligible);
}
function App() { function App() {
const [wordLength, setWordLength] = useState(5);
const [target, setTarget] = useState(randomTarget(wordLength));
if (target.length !== wordLength) {
throw new Error("length mismatch");
}
return ( return (
<> <>
<h1>hello wordl</h1> <h1>hello wordl</h1>
<footer className="App-footer"> <footer className="App-footer">
by <a href="https://twitter.com/chordbug">@chordbug</a>, inspired by{" "} by <a href="https://twitter.com/chordbug">@chordbug</a>, inspired by{" "}
<a href="https://www.powerlanguage.co.uk/wordle/">wordle</a>. report <a href="https://www.powerlanguage.co.uk/wordle/">wordle</a>. report
issues <a href="https://github.com/lynn/hello-wordl">here</a> issues <a href="https://github.com/lynn/hello-wordl/issues">here</a>
</footer> </footer>
<div className="App-option">
<label htmlFor="wordLength">Letters:</label>
<input
type="range"
min="4"
max="11"
id="wordLength"
value={wordLength}
onChange={(e) => {
const length = Number(e.target.value);
setTarget(randomTarget(length));
setWordLength(length);
}}
></input>
</div>
<div className="App"> <div className="App">
<Game <Game maxGuesses={6} />
key={target}
wordLength={wordLength}
target={target}
maxGuesses={6}
restart={() => {
setTarget(randomTarget(wordLength));
}}
/>
</div> </div>
</> </>
); );

View File

@@ -3,6 +3,9 @@ import { Row, RowState } from "./Row";
import dictionary from "./dictionary.json"; import dictionary from "./dictionary.json";
import { Clue, clue } from "./clue"; import { Clue, clue } from "./clue";
import { Keyboard } from "./Keyboard"; import { Keyboard } from "./Keyboard";
import common from "./common.json";
import { dictionarySet, pick } from "./util";
import { names } from "./names";
enum GameState { enum GameState {
Playing, Playing,
@@ -11,34 +14,50 @@ enum GameState {
} }
interface GameProps { interface GameProps {
target: string;
wordLength: number;
maxGuesses: number; maxGuesses: number;
restart: () => void; }
const targets = common
.slice(0, 20000) // adjust for max target freakiness
.filter((word) => dictionarySet.has(word) && !names.has(word));
function randomTarget(wordLength: number) {
const eligible = targets.filter((word) => word.length === wordLength);
return pick(eligible);
} }
function Game(props: GameProps) { function Game(props: GameProps) {
const [gameState, setGameState] = useState(GameState.Playing); const [gameState, setGameState] = useState(GameState.Playing);
const [guesses, setGuesses] = useState<string[]>([]); const [guesses, setGuesses] = useState<string[]>([]);
const [currentGuess, setCurrentGuess] = useState<string>(""); const [currentGuess, setCurrentGuess] = useState<string>("");
const [hint, setHint] = useState<string>(`${props.wordLength} letters`); const [wordLength, setWordLength] = useState(5);
const [hint, setHint] = useState<string>(`Make your first guess!`);
const [target, setTarget] = useState(randomTarget(wordLength));
const reset = () => {
setTarget(randomTarget(wordLength));
setGuesses([]);
setCurrentGuess("");
setHint("");
setGameState(GameState.Playing);
};
const onKey = (key: string) => { const onKey = (key: string) => {
if (gameState !== GameState.Playing) { if (gameState !== GameState.Playing) {
if (key === "Enter") { if (key === "Enter") {
props.restart(); reset();
} }
return; return;
} }
if (guesses.length === props.maxGuesses) return; if (guesses.length === props.maxGuesses) return;
if (/^[a-z]$/.test(key)) { if (/^[a-z]$/.test(key)) {
setCurrentGuess((guess) => (guess + key).slice(0, props.wordLength)); setCurrentGuess((guess) => (guess + key).slice(0, wordLength));
setHint(""); setHint("");
} else if (key === "Backspace") { } else if (key === "Backspace") {
setCurrentGuess((guess) => guess.slice(0, -1)); setCurrentGuess((guess) => guess.slice(0, -1));
setHint(""); setHint("");
} else if (key === "Enter") { } else if (key === "Enter") {
if (currentGuess.length !== props.wordLength) { if (currentGuess.length !== wordLength) {
setHint("Too short"); setHint("Too short");
return; return;
} }
@@ -48,12 +67,12 @@ function Game(props: GameProps) {
} }
setGuesses((guesses) => guesses.concat([currentGuess])); setGuesses((guesses) => guesses.concat([currentGuess]));
setCurrentGuess((guess) => ""); setCurrentGuess((guess) => "");
if (currentGuess === props.target) { if (currentGuess === target) {
setHint("You won! (Enter to play again)"); setHint("You won! (Enter to play again)");
setGameState(GameState.Won); setGameState(GameState.Won);
} else if (guesses.length + 1 === props.maxGuesses) { } else if (guesses.length + 1 === props.maxGuesses) {
setHint( setHint(
`You lost! The answer was ${props.target.toUpperCase()}. (Enter to play again)` `You lost! The answer was ${target.toUpperCase()}. (Enter to play again)`
); );
setGameState(GameState.Lost); setGameState(GameState.Lost);
} else { } else {
@@ -79,7 +98,7 @@ function Game(props: GameProps) {
.fill(undefined) .fill(undefined)
.map((_, i) => { .map((_, i) => {
const guess = [...guesses, currentGuess][i] ?? ""; const guess = [...guesses, currentGuess][i] ?? "";
const cluedLetters = clue(guess, props.target); const cluedLetters = clue(guess, target);
const lockedIn = i < guesses.length; const lockedIn = i < guesses.length;
if (lockedIn) { if (lockedIn) {
for (const { clue, letter } of cluedLetters) { for (const { clue, letter } of cluedLetters) {
@@ -93,7 +112,7 @@ function Game(props: GameProps) {
return ( return (
<Row <Row
key={i} key={i}
wordLength={props.wordLength} wordLength={wordLength}
rowState={lockedIn ? RowState.LockedIn : RowState.Pending} rowState={lockedIn ? RowState.LockedIn : RowState.Pending}
cluedLetters={cluedLetters} cluedLetters={cluedLetters}
/> />
@@ -102,6 +121,36 @@ function Game(props: GameProps) {
return ( return (
<div className="Game"> <div className="Game">
<div className="Game-options">
<label htmlFor="wordLength">Letters:</label>
<input
type="range"
min="4"
max="11"
id="wordLength"
disabled={guesses.length > 0 || currentGuess !== ""}
value={wordLength}
onChange={(e) => {
const length = Number(e.target.value);
setTarget(randomTarget(length));
setWordLength(length);
setHint(`${length} letters`);
}}
></input>
<button
style={{ flex: "0" }}
disabled={gameState !== GameState.Playing || guesses.length === 0}
onClick={() => {
setHint(
`The answer was ${target.toUpperCase()}. (Enter to play again)`
);
setGameState(GameState.Lost);
(document.activeElement as HTMLElement)?.blur();
}}
>
Give up
</button>
</div>
{rowDivs} {rowDivs}
<p>{hint || `\u00a0`}</p> <p>{hint || `\u00a0`}</p>
<Keyboard letterInfo={letterInfo} onKey={onKey} /> <Keyboard letterInfo={letterInfo} onKey={onKey} />