Ultra Hard mode
This commit is contained in:
parent
f2cf40c4b9
commit
806eb9fe41
|
@ -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;
|
||||
}
|
||||
|
|
99
src/App.tsx
99
src/App.tsx
|
@ -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>
|
||||
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
|
25
src/Game.tsx
25
src/Game.tsx
|
@ -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]));
|
||||
|
|
26
src/clue.ts
26
src/clue.ts
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue