Add hard mode

This commit is contained in:
Lynn 2022-01-17 23:25:44 +01:00
parent 83dc909cc4
commit d7e2f52f11
6 changed files with 97 additions and 44 deletions

View file

@ -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;
}

View file

@ -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<T>(key: string, initial: T): [T, (value: T | ((t: T) => T)) => void] {
function useSetting<T>(
key: string,
initial: T
): [T, (value: T | ((t: T) => T)) => void] {
const [current, setCurrent] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
@ -26,13 +29,20 @@ function useSetting<T>(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<boolean>("dark", prefersDark);
const [hard, setHard] = useSetting<boolean>("hard", false);
useEffect(() => {
document.body.className = dark ? "dark" : "";
}, [dark]);
return (
<div className="App-container">
<h1>hello wordl</h1>
<h1>
<span style={hard ? { color: "#e66" } : {}}>hell</span>o wordl
</h1>
<div style={{ position: "absolute", right: 5, top: 5 }}>
{page !== "game" ? (
<a href="#" onClick={() => setPage("game")}>
@ -63,7 +73,8 @@ function App() {
onClick={() =>
(document.location = seed
? "?"
: "?seed=" + new Date().toISOString().replace(/-/g, "").slice(0, 8))
: "?seed=" +
new Date().toISOString().replace(/-/g, "").slice(0, 8))
}
>
{seed ? "Random" : "Today's"}
@ -88,7 +99,7 @@ function App() {
checked={hard}
onChange={() => setHard((x: boolean) => !x)}
/>
<label htmlFor="hard-setting">Hard mode (must use clues)</label>
<label htmlFor="hard-setting">Hard mode (must use all clues)</label>
</div>
</div>
)}

View file

@ -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) {

View file

@ -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;
}

View file

@ -6714,8 +6714,6 @@
"reminder",
"exodus",
"clash",
"bobby",
"syphilis",
"demon",
"authorization",
"richness",
@ -20795,7 +20793,6 @@
"nicety",
"intrepidity",
"shopper",
"empyema",
"whitewash",
"uncontrollably",
"atelectasis",

View file

@ -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");
}