From d7e2f52f11d4944074a7bc7acd803e58c42df33f Mon Sep 17 00:00:00 2001 From: Lynn Date: Mon, 17 Jan 2022 23:25:44 +0100 Subject: [PATCH] Add hard mode --- src/App.css | 71 +++++++++++++++++++++++++----------------------- src/App.tsx | 23 ++++++++++++---- src/Game.tsx | 16 ++++++++++- src/clue.ts | 24 ++++++++++++++++ src/targets.json | 3 -- src/util.ts | 4 +++ 6 files changed, 97 insertions(+), 44 deletions(-) diff --git a/src/App.css b/src/App.css index ce59cf2..b81621f 100644 --- a/src/App.css +++ b/src/App.css @@ -5,6 +5,7 @@ body { text-align: center; background-color: #eeeeee; + transition: 0.3s background-color ease-out; } .Row { @@ -14,7 +15,7 @@ body { .Row-letter { margin: 2px; - border: 2px solid rgba(0, 0, 0, 0.4); + border: 2px solid rgba(128, 128, 128, 0.8); flex: 1; max-width: 40px; height: 40px; @@ -39,10 +40,14 @@ body { margin-top: 0; } +.Game, +h1 { + user-select: none; +} + .Game { display: flex; flex-direction: column; - user-select: none; } table.Game-rows { @@ -99,51 +104,29 @@ table.Game-rows > tbody { .letter-correct { border: 2px solid rgba(0, 0, 0, 0.3); - background-color: rgb(87, 172, 87); - color: white; + background-color: rgb(87, 172, 120); + color: white !important; } .letter-elsewhere { border: 2px dotted rgba(0, 0, 0, 0.3); background-color: #e9c601; - color: white; + color: white !important; } .letter-absent { border: 2px solid transparent; background-color: rgb(162, 162, 162); - color: white; + color: white !important; } -@media (prefers-color-scheme: dark) { - body { - background-color: #404040; - color: #e0e0e0; - } +body.dark { + background-color: #404040; + color: #e0e0e0; +} - .Game-keyboard-button { - color: #404040; - } - - .Row-letter { - border: 2px solid rgba(255, 255, 255, 0.4); - } - - .letter-correct { - border: 2px solid rgba(0, 0, 0, 0.3); - color: white; - } - - .letter-elsewhere { - border: 2px dotted rgba(0, 0, 0, 0.3); - color: white; - } - - .letter-absent { - border: 2px solid transparent; - background-color: rgb(142, 142, 142); - color: white; - } +body.dark .Game-keyboard-button { + color: #404040; } a, @@ -196,3 +179,23 @@ a:active { height: 1px; overflow: hidden; } + +.Settings { + text-align: left; + font-size: 18px; +} + +.Settings-setting { + margin: 8px; + display: flex; + align-items: center; +} + +.Settings-setting input { + width: 18px; + height: 18px; +} + +.Settings-setting label { + margin-inline-start: 8px; +} diff --git a/src/App.tsx b/src/App.tsx index 498fb45..245fc85 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,13 @@ import "./App.css"; import { maxGuesses, seed } from "./util"; import Game from "./Game"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { About } from "./About"; -function useSetting(key: string, initial: T): [T, (value: T | ((t: T) => T)) => void] { +function useSetting( + key: string, + initial: T +): [T, (value: T | ((t: T) => T)) => void] { const [current, setCurrent] = useState(() => { try { const item = window.localStorage.getItem(key); @@ -26,13 +29,20 @@ function useSetting(key: string, initial: T): [T, (value: T | ((t: T) => T)) function App() { const [page, setPage] = useState<"game" | "about" | "settings">("game"); const prefersDark = - window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches; + window.matchMedia && + window.matchMedia("(prefers-color-scheme: dark)").matches; const [dark, setDark] = useSetting("dark", prefersDark); const [hard, setHard] = useSetting("hard", false); + useEffect(() => { + document.body.className = dark ? "dark" : ""; + }, [dark]); + return ( )} diff --git a/src/Game.tsx b/src/Game.tsx index a8d085e..7ce94db 100644 --- a/src/Game.tsx +++ b/src/Game.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import { Row, RowState } from "./Row"; import dictionary from "./dictionary.json"; -import { Clue, clue, describeClue } from "./clue"; +import { Clue, clue, describeClue, violation } from "./clue"; import { Keyboard } from "./Keyboard"; import targetList from "./targets.json"; import { dictionarySet, pick, resetRng, seed, speak } from "./util"; @@ -58,6 +58,11 @@ function Game(props: GameProps) { setCurrentGuess((guess) => (guess + key.toLowerCase()).slice(0, wordLength) ); + // When typing a guess, make sure a later "Enter" press won't activate a link or button. + const active = document.activeElement as HTMLElement; + if (active && ["A", "BUTTON"].includes(active.tagName)) { + active.blur(); + } setHint(""); } else if (key === "Backspace") { setCurrentGuess((guess) => guess.slice(0, -1)); @@ -71,6 +76,15 @@ function Game(props: GameProps) { setHint("Not a valid word"); return; } + if (props.hard) { + for (const g of guesses) { + const feedback = violation(clue(g, target), currentGuess); + if (feedback) { + setHint(feedback); + return; + } + } + } setGuesses((guesses) => guesses.concat([currentGuess])); setCurrentGuess((guess) => ""); if (currentGuess === target) { diff --git a/src/clue.ts b/src/clue.ts index 29d063e..4f283ac 100644 --- a/src/clue.ts +++ b/src/clue.ts @@ -1,3 +1,5 @@ +import { ordinal } from "./util"; + export enum Clue { Absent, Elsewhere, @@ -55,3 +57,25 @@ export function describeClue(clue: CluedLetter[]): string { .map(({ letter, clue }) => letter.toUpperCase() + " " + clueWord(clue!)) .join(", "); } + +export function violation( + clues: CluedLetter[], + guess: string +): string | undefined { + let i = 0; + for (const { letter, clue } of clues) { + if (clue === Clue.Absent) { + // Apparently Wordle doesn't enforce this? + // if (guess.includes(letter)) + // return "Guess can't contain " + letter.toUpperCase(); + } else if (clue === Clue.Correct) { + if (guess[i] !== letter) + return ordinal(i + 1) + " letter must be " + letter.toUpperCase(); + } else if (clue === Clue.Elsewhere) { + if (!guess.includes(letter)) + return "Guess must contain " + letter.toUpperCase(); + } + ++i; + } + return undefined; +} diff --git a/src/targets.json b/src/targets.json index ad4e5bc..6b4069c 100644 --- a/src/targets.json +++ b/src/targets.json @@ -6714,8 +6714,6 @@ "reminder", "exodus", "clash", - "bobby", - "syphilis", "demon", "authorization", "richness", @@ -20795,7 +20793,6 @@ "nicety", "intrepidity", "shopper", - "empyema", "whitewash", "uncontrollably", "atelectasis", diff --git a/src/util.ts b/src/util.ts index d7fcdc6..ae11274 100644 --- a/src/util.ts +++ b/src/util.ts @@ -47,3 +47,7 @@ export function speak( document.body.removeChild(document.getElementById(id)!); }, 1000); } + +export function ordinal(n: number): string { + return n + ([, "st", "nd", "rd"][(n % 100 >> 3) ^ 1 && n % 10] || "th"); +}