dim letters that are part of the puzzle; improve uncommon word handling; add example game to about
This commit is contained in:
parent
6fced58fb7
commit
8032a835c9
5 changed files with 136 additions and 21 deletions
|
@ -1,4 +1,6 @@
|
|||
import { h } from 'preact';
|
||||
import { Clue } from './clue';
|
||||
import { Row, RowState } from './Row';
|
||||
import { gameName } from "./util";
|
||||
|
||||
export function About() {
|
||||
|
@ -25,9 +27,11 @@ export function About() {
|
|||
<br />
|
||||
<br />
|
||||
Like Wordle & hello wordl, the game has two separate word lists: a
|
||||
"common" list for secret words, and a broader list including "uncommon"
|
||||
words for your guesses. The game will stop you from guessing an uncommon
|
||||
word for your second guess, since it has no chance of being the secret word.
|
||||
"secret list" containing only reasonably common words, and a larger list of
|
||||
words you're allowed to guess. The game will let you know when you've typed
|
||||
a word that doesn't appear in the secret list; you can still guess it if you
|
||||
want, but it can't be the secret word, so try something else if you're on your
|
||||
second guess!
|
||||
<br />
|
||||
<br />
|
||||
There's a new puzzle every day, starting at 6 AM in all timezones.
|
||||
|
@ -37,6 +41,99 @@ export function About() {
|
|||
forced to guess an uncommon word to win!
|
||||
</p>
|
||||
<hr />
|
||||
<Row
|
||||
rowState={RowState.LockedIn}
|
||||
wordLength={5}
|
||||
isPartOfPuzzle={true}
|
||||
cluedLetters={[
|
||||
{ clue: Clue.Absent, letter: "b" },
|
||||
{ clue: Clue.Absent, letter: "a" },
|
||||
{ clue: Clue.Elsewhere, letter: "t" },
|
||||
{ clue: Clue.Absent, letter: "c" },
|
||||
{ clue: Clue.Correct, letter: "h" },
|
||||
]}
|
||||
/>
|
||||
<Row
|
||||
rowState={RowState.LockedIn}
|
||||
wordLength={5}
|
||||
isPartOfPuzzle={true}
|
||||
cluedLetters={[
|
||||
{ clue: Clue.Absent, letter: "y" },
|
||||
{ clue: Clue.Correct, letter: "o" },
|
||||
{ clue: Clue.Absent, letter: "u" },
|
||||
{ clue: Clue.Correct, letter: "t" },
|
||||
{ clue: Clue.Correct, letter: "h" },
|
||||
]}
|
||||
/>
|
||||
<Row
|
||||
rowState={RowState.LockedIn}
|
||||
wordLength={5}
|
||||
isPartOfPuzzle={true}
|
||||
cluedLetters={[
|
||||
{ clue: Clue.Absent, letter: "t" },
|
||||
{ clue: Clue.Correct, letter: "o" },
|
||||
{ clue: Clue.Absent, letter: "o" },
|
||||
{ clue: Clue.Correct, letter: "t" },
|
||||
{ clue: Clue.Correct, letter: "h" },
|
||||
]}
|
||||
/>
|
||||
<p>
|
||||
We know the word has an <b class="green-bg">O</b> in the second position
|
||||
and ends with <b class="green-bg">TH</b>. What words could it be?
|
||||
</p>
|
||||
<Row
|
||||
rowState={RowState.LockedIn}
|
||||
wordLength={5}
|
||||
isPartOfPuzzle={false}
|
||||
cluedLetters={[
|
||||
{ clue: Clue.Absent, letter: "n" },
|
||||
{ clue: Clue.Correct, letter: "o" },
|
||||
{ clue: Clue.Correct, letter: "r" },
|
||||
{ clue: Clue.Correct, letter: "t" },
|
||||
{ clue: Clue.Correct, letter: "h" },
|
||||
]}
|
||||
/>
|
||||
<Row
|
||||
rowState={RowState.LockedIn}
|
||||
wordLength={5}
|
||||
isPartOfPuzzle={false}
|
||||
cluedLetters={[
|
||||
{ clue: Clue.Absent, letter: "f" },
|
||||
{ clue: Clue.Correct, letter: "o" },
|
||||
{ clue: Clue.Correct, letter: "r" },
|
||||
{ clue: Clue.Correct, letter: "t" },
|
||||
{ clue: Clue.Correct, letter: "h" },
|
||||
]}
|
||||
/>
|
||||
<p style={{ fontSize: "80%" }}>Not quite! The answer could've been <strong>WORTH</strong>.</p>
|
||||
<p>Looks like we didn't cover all our bases. Let's see if we can account for that <b>W</b>:</p>
|
||||
<Row
|
||||
rowState={RowState.LockedIn}
|
||||
wordLength={5}
|
||||
isPartOfPuzzle={false}
|
||||
cluedLetters={[
|
||||
{ clue: Clue.Absent, letter: "s" },
|
||||
{ clue: Clue.Absent, letter: "w" },
|
||||
{ clue: Clue.Elsewhere, letter: "o" },
|
||||
{ clue: Clue.Elsewhere, letter: "r" },
|
||||
{ clue: Clue.Absent, letter: "n" },
|
||||
]}
|
||||
/>
|
||||
<Row
|
||||
rowState={RowState.LockedIn}
|
||||
wordLength={5}
|
||||
isPartOfPuzzle={false}
|
||||
cluedLetters={[
|
||||
{ clue: Clue.Correct, letter: "f" },
|
||||
{ clue: Clue.Correct, letter: "o" },
|
||||
{ clue: Clue.Correct, letter: "r" },
|
||||
{ clue: Clue.Correct, letter: "t" },
|
||||
{ clue: Clue.Correct, letter: "h" },
|
||||
]}
|
||||
/>
|
||||
<p style={{ fontSize: "80%" }}>You won in 2 tries! Great job.</p>
|
||||
<p>There! The game had no choice but to narrow it down to one word.</p>
|
||||
<hr />
|
||||
<p>
|
||||
Like hello wordl, this game will be free and ad-free for as long as
|
||||
it's online.
|
||||
|
|
|
@ -142,18 +142,27 @@ table.Game-rows > tbody {
|
|||
background-color: rgb(87, 172, 120);
|
||||
color: white !important;
|
||||
}
|
||||
.letter-correct.dim {
|
||||
background-color: rgba(87, 172, 120, 0.75);
|
||||
}
|
||||
|
||||
.letter-elsewhere {
|
||||
border: 2px dotted rgba(0, 0, 0, 0.3);
|
||||
background-color: #e9c601;
|
||||
background-color: rgb(233, 198, 1);
|
||||
color: white !important;
|
||||
}
|
||||
.letter-elsewhere.dim {
|
||||
background-color: rgba(233, 198, 1, 0.75);
|
||||
}
|
||||
|
||||
.letter-absent {
|
||||
border: 2px solid transparent;
|
||||
background-color: hsla(190, 12.5%, 50%, 0.6);
|
||||
background-color: hsla(190, 12.5%, 40%, 0.6);
|
||||
color: white !important;
|
||||
}
|
||||
.letter-absent.dim {
|
||||
opacity: .75;
|
||||
}
|
||||
|
||||
body.dark {
|
||||
background-color: #0b3644;
|
||||
|
|
|
@ -122,11 +122,21 @@ export function Game(props: GameProps) {
|
|||
}
|
||||
if (guesses.length === MAX_GUESSES) return;
|
||||
if (/^[a-z]$/i.test(key)) {
|
||||
// We're about to update the current guess, but
|
||||
// we can't rely on the value updating in this render
|
||||
const newGuess = currentGuess + key.toLowerCase();
|
||||
setCurrentGuess((guess) =>
|
||||
(guess + key.toLowerCase()).slice(0, WORD_LENGTH)
|
||||
);
|
||||
tableRef.current?.focus();
|
||||
if (newGuess.length === WORD_LENGTH
|
||||
&& !COMMON_WORDS.includes(newGuess)
|
||||
&& UNCOMMON_WORDS.includes(newGuess)
|
||||
) {
|
||||
setHint(`(The word ${newGuess.toUpperCase()} isn't in the secret list)`);
|
||||
} else {
|
||||
setHint("");
|
||||
}
|
||||
} else if (key === "Backspace") {
|
||||
setCurrentGuess((guess) => guess.slice(0, -1));
|
||||
setHint("");
|
||||
|
@ -135,16 +145,11 @@ export function Game(props: GameProps) {
|
|||
setHint("Too short");
|
||||
return;
|
||||
}
|
||||
if (!COMMON_WORDS.includes(currentGuess)) {
|
||||
if (!UNCOMMON_WORDS.includes(currentGuess)) {
|
||||
if (!COMMON_WORDS.includes(currentGuess)
|
||||
&& !UNCOMMON_WORDS.includes(currentGuess)
|
||||
) {
|
||||
setHint("Not a valid word");
|
||||
return;
|
||||
} else if (guesses.length === puzzle.history.length) {
|
||||
setHint("(Uncommon word allowed for first guess)");
|
||||
} else {
|
||||
setHint("(Uncommon word not allowed for second guess)");
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (const g of guesses) {
|
||||
const c = clue(g);
|
||||
|
@ -200,6 +205,7 @@ export function Game(props: GameProps) {
|
|||
const tableRows = Array(puzzle.history.length + MAX_GUESSES)
|
||||
.fill(undefined)
|
||||
.map((_, i) => {
|
||||
const isPartOfPuzzle = i < puzzle.history.length;
|
||||
const result = [
|
||||
...guesses,
|
||||
{ word: currentGuess, matches: [0, 0, 0, 0, 0] },
|
||||
|
@ -227,6 +233,7 @@ export function Game(props: GameProps) {
|
|||
: RowState.Pending
|
||||
}
|
||||
cluedLetters={cluedLetters}
|
||||
isPartOfPuzzle={isPartOfPuzzle}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { h } from "preact";
|
||||
import { h, JSX } from "preact";
|
||||
import { Clue, clueClass, CluedLetter, clueWord } from "./clue";
|
||||
|
||||
export enum RowState {
|
||||
|
@ -11,7 +11,8 @@ interface RowProps {
|
|||
rowState: RowState;
|
||||
wordLength: number;
|
||||
cluedLetters: CluedLetter[];
|
||||
annotation?: string;
|
||||
annotation?: string | JSX.Element;
|
||||
isPartOfPuzzle: boolean;
|
||||
}
|
||||
|
||||
export function Row(props: RowProps) {
|
||||
|
@ -28,7 +29,7 @@ export function Row(props: RowProps) {
|
|||
return (
|
||||
<td
|
||||
key={i}
|
||||
className={letterClass}
|
||||
className={`${letterClass} ${props.isPartOfPuzzle ? "dim" : ""}`}
|
||||
aria-live={isEditing ? "assertive" : "off"}
|
||||
aria-label={
|
||||
isLockedIn
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
[
|
||||
{"history":[{"word":"batch","matches":[0,0,1,0,2]},{"word":"youth","matches":[0,2,0,2,2]},{"word":"tooth","matches":[0,2,0,2,2]}],"seed":0,"solutions":40},
|
||||
{"history":[{"word":"grope","matches":[0,1,0,0,1]},{"word":"teary","matches":[0,1,1,1,0]},{"word":"maker","matches":[0,2,0,2,2]},{"word":"waver","matches":[0,2,0,2,2]},{"word":"baler","matches":[0,2,0,2,2]}],"seed":1,"solutions":29},
|
||||
{"history":[{"word":"batch","matches":[0,0,1,0,2]},{"word":"youth","matches":[0,2,0,2,2]},{"word":"tooth","matches":[0,2,0,2,2]}],"seed":1,"solutions":40},
|
||||
{"history":[{"word":"batch","matches":[0,0,1,0,2]},{"word":"youth","matches":[0,2,0,2,2]},{"word":"tooth","matches":[0,2,0,2,2]}],"seed":1,"solutions":40},
|
||||
|
||||
{"history":[{"word":"decal","matches":[0,1,0,0,0]},{"word":"inner","matches":[1,1,0,1,0]},{"word":"ovine","matches":[0,0,2,2,2]}],"seed":2,"solutions":32},
|
||||
{"history":[{"word":"glean","matches":[2,0,1,1,0]},{"word":"grade","matches":[2,2,2,0,2]},{"word":"grace","matches":[2,2,2,0,2]}],"seed":3,"solutions":2},
|
||||
{"history":[{"word":"sassy","matches":[0,2,0,0,0]},{"word":"rabbi","matches":[0,2,0,0,0]},{"word":"naval","matches":[1,2,0,0,0]},{"word":"oaken","matches":[0,2,0,0,1]},{"word":"haunt","matches":[0,2,2,2,2]}],"seed":4,"solutions":1},
|
||||
|
|
Loading…
Reference in a new issue