Ultra Hard mode

This commit is contained in:
Lynn 2022-01-22 17:46:14 +01:00
parent f2cf40c4b9
commit 806eb9fe41
5 changed files with 109 additions and 54 deletions

View file

@ -209,7 +209,7 @@ a:active {
.Settings-setting {
margin: 8px;
display: flex;
align-items: center;
align-items: top;
}
.Settings-setting input {
@ -217,6 +217,11 @@ a:active {
height: 18px;
}
.Settings-setting input[type=range] {
width: 50px;
height: 18px;
}
.Settings-setting label {
margin-inline-start: 8px;
}

View file

@ -27,12 +27,13 @@ function useSetting<T>(
}
function App() {
const [page, setPage] = useState<"game" | "about" | "settings">("game");
type Page = "game" | "about" | "settings";
const [page, setPage] = useState<Page>("game");
const prefersDark =
window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches;
const [dark, setDark] = useSetting<boolean>("dark", prefersDark);
const [hard, setHard] = useSetting<boolean>("hard", false);
const [difficulty, setDifficulty] = useSetting<number>("difficulty", 0);
useEffect(() => {
document.body.className = dark ? "dark" : "";
@ -41,42 +42,42 @@ function App() {
}, 1);
}, [dark]);
const link = (emoji: string, label: string, page: Page) => (
<a
className="emoji-link"
href="#"
onClick={() => setPage(page)}
title={label}
aria-label={label}
>
{emoji}
</a>
);
return (
<div className="App-container">
<h1>
<span style={hard ? { color: "#e66" } : {}}>hell</span>o wordl
<span
style={
difficulty > 0
? {
color: "#e66",
textShadow: difficulty > 1 ? "0px 0px 5px #e66" : "none",
}
: {}
}
>
hell
</span>
o wordl
</h1>
<div className="top-right">
{page !== "game" ? (
<a
className="emoji-link"
href="#"
onClick={() => setPage("game")}
title="Close"
aria-label="Close"
>
</a>
link("❌", "Close", "game")
) : (
<>
<a
className="emoji-link"
href="#"
onClick={() => setPage("about")}
title="About"
aria-label="About"
>
</a>
<a
className="emoji-link"
href="#"
onClick={() => setPage("settings")}
title="Settings"
aria-label="Settings"
>
</a>
{link("❓", "About", "about")}
{link("⚙️", "Settings", "settings")}
</>
)}
</div>
@ -114,16 +115,42 @@ function App() {
</div>
<div className="Settings-setting">
<input
id="hard-setting"
type="checkbox"
checked={hard}
onChange={() => setHard((x: boolean) => !x)}
id="difficulty-setting"
type="range"
min="0"
max="2"
value={difficulty}
onChange={(e) => setDifficulty(+e.target.value)}
/>
<label htmlFor="hard-setting">Hard mode (must use all clues)</label>
<div>
<label htmlFor="difficulty-setting">Difficulty:</label>
&nbsp;
<strong>{["Normal", "Hard", "Ultra Hard"][difficulty]}</strong>
<div
style={{
fontSize: 14,
height: 40,
marginLeft: 8,
marginTop: 8,
}}
>
{
[
`No restrictions on guesses.`,
`Wordle's "Hard Mode". Green letters must stay fixed, and yellow letters must be reused.`,
`An even stricter Hard Mode. Yellow letters must move away, and gray letters can't be reused.`,
][difficulty]
}
</div>
</div>
</div>
</div>
)}
<Game maxGuesses={maxGuesses} hidden={page !== "game"} hard={hard} />
<Game
maxGuesses={maxGuesses}
hidden={page !== "game"}
difficulty={difficulty}
/>
</div>
);
}

View file

@ -4,7 +4,15 @@ import dictionary from "./dictionary.json";
import { Clue, clue, describeClue, violation } from "./clue";
import { Keyboard } from "./Keyboard";
import targetList from "./targets.json";
import { dictionarySet, pick, resetRng, seed, speak, urlParam } from "./util";
import {
dictionarySet,
Difficulty,
pick,
resetRng,
seed,
speak,
urlParam,
} from "./util";
import { decode, encode } from "./base64";
enum GameState {
@ -16,7 +24,7 @@ enum GameState {
interface GameProps {
maxGuesses: number;
hidden: boolean;
hard: boolean;
difficulty: Difficulty;
}
const targets = targetList.slice(0, targetList.indexOf("murky") + 1); // Words no rarer than this one
@ -111,13 +119,12 @@ 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;
}
for (const g of guesses) {
const c = clue(g, target);
const feedback = violation(props.difficulty, c, currentGuess);
if (feedback) {
setHint(feedback);
return;
}
}
setGuesses((guesses) => guesses.concat([currentGuess]));

View file

@ -1,4 +1,4 @@
import { ordinal } from "./util";
import { Difficulty, ordinal } from "./util";
export enum Clue {
Absent,
@ -59,21 +59,31 @@ export function describeClue(clue: CluedLetter[]): string {
}
export function violation(
difficulty: Difficulty,
clues: CluedLetter[],
guess: string
): string | undefined {
if (difficulty === Difficulty.Normal) {
return undefined;
}
let i = 0;
for (const { letter, clue } of clues) {
const upper = letter.toUpperCase();
const nth = ordinal(i + 1);
if (clue === Clue.Absent) {
// Apparently Wordle doesn't enforce this?
// if (guess.includes(letter))
// return "Guess can't contain " + letter.toUpperCase();
if (difficulty === Difficulty.UltraHard && guess.includes(letter)) {
return "Guess can't contain " + upper;
}
} else if (clue === Clue.Correct) {
if (guess[i] !== letter)
return ordinal(i + 1) + " letter must be " + letter.toUpperCase();
if (guess[i] !== letter) {
return nth + " letter must be " + upper;
}
} else if (clue === Clue.Elsewhere) {
if (!guess.includes(letter))
return "Guess must contain " + letter.toUpperCase();
if (!guess.includes(letter)) {
return "Guess must contain " + upper;
} else if (difficulty === Difficulty.UltraHard && guess[i] === letter) {
return nth + " letter can't be " + upper;
}
}
++i;
}

View file

@ -1,5 +1,11 @@
import dictionary from "./dictionary.json";
export enum Difficulty {
Normal,
Hard,
UltraHard,
}
export const maxGuesses = 6;
export const dictionarySet: Set<string> = new Set(dictionary);