MULTI PUZZLE DRIFTING
This commit is contained in:
parent
72b45087b6
commit
0ab57ddd65
9 changed files with 2084 additions and 201 deletions
1053
optimle/puzzles.txt
Normal file
1053
optimle/puzzles.txt
Normal file
File diff suppressed because it is too large
Load diff
|
@ -18,7 +18,7 @@ pub struct Puzzle<const N: usize> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> Puzzle<N> {
|
impl<const N: usize> Puzzle<N> {
|
||||||
pub fn generate(dictionary: &Dictionary<N>, seed: u64) -> Self {
|
pub fn generate(dictionary: &Dictionary<N>, seed: u64, heres: usize) -> Self {
|
||||||
let mut rng: ChaCha8Rng = ChaCha8Rng::seed_from_u64(seed);
|
let mut rng: ChaCha8Rng = ChaCha8Rng::seed_from_u64(seed);
|
||||||
let mut history: Option<GuessHistory<N>> = None;
|
let mut history: Option<GuessHistory<N>> = None;
|
||||||
let mut solutions: usize = 0;
|
let mut solutions: usize = 0;
|
||||||
|
@ -37,7 +37,7 @@ impl<const N: usize> Puzzle<N> {
|
||||||
game_history.push(result);
|
game_history.push(result);
|
||||||
if game_history.len() > 2 {
|
if game_history.len() > 2 {
|
||||||
let possible_words = game.guesser.get_possible_words();
|
let possible_words = game.guesser.get_possible_words();
|
||||||
let interesting_solutions = Puzzle::interesting(dictionary, &game_history, possible_words);
|
let interesting_solutions = Puzzle::interesting(dictionary, &game_history, possible_words, heres);
|
||||||
if interesting_solutions.is_some() {
|
if interesting_solutions.is_some() {
|
||||||
history = Some(game_history);
|
history = Some(game_history);
|
||||||
solutions = interesting_solutions.unwrap();
|
solutions = interesting_solutions.unwrap();
|
||||||
|
@ -55,7 +55,12 @@ impl<const N: usize> Puzzle<N> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn interesting(dictionary: &Dictionary<N>, history: &GuessHistory<N>, possible_words: Dictionary<N>) -> Option<usize> {
|
fn interesting(
|
||||||
|
dictionary: &Dictionary<N>,
|
||||||
|
history: &GuessHistory<N>,
|
||||||
|
possible_words: Dictionary<N>,
|
||||||
|
wanted_heres: usize,
|
||||||
|
) -> Option<usize> {
|
||||||
let count = possible_words.len();
|
let count = possible_words.len();
|
||||||
if count < 3 || count > 6 {
|
if count < 3 || count > 6 {
|
||||||
return None;
|
return None;
|
||||||
|
@ -65,7 +70,7 @@ impl<const N: usize> Puzzle<N> {
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|&m| *m == LetterMatch::HERE)
|
.filter(|&m| *m == LetterMatch::HERE)
|
||||||
.count();
|
.count();
|
||||||
if heres < 3 {
|
if heres != wanted_heres {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,13 +99,9 @@ impl<const N: usize> Puzzle<N> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let max_solutions = match heres {
|
const MAX_SOLUTIONS: usize = 100;
|
||||||
4 => 20,
|
|
||||||
3 => 100,
|
|
||||||
_ => panic!(),
|
|
||||||
};
|
|
||||||
let solution_count = solutions.len();
|
let solution_count = solutions.len();
|
||||||
let interesting = solution_count > 0 && solution_count <= max_solutions;
|
let interesting = solution_count > 0 && solution_count <= MAX_SOLUTIONS;
|
||||||
|
|
||||||
if interesting {
|
if interesting {
|
||||||
Some(solution_count)
|
Some(solution_count)
|
||||||
|
|
|
@ -21,25 +21,28 @@ impl PuzzleOutput {
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let common_words: Vec<Word<5>> = wordlelike::load::load_words(include_str!("../../wordlelike/src/words/common.txt"));
|
let common_words: Vec<Word<5>> = wordlelike::load::load_words(include_str!("../../wordlelike/src/words/common.txt"));
|
||||||
for seed in 11..=365 {
|
for heres in 2..=4 {
|
||||||
let puzzle = Puzzle::generate(&common_words, seed);
|
println!("heres: {}", heres);
|
||||||
println!("{}", serde_json::to_string(&PuzzleOutput {
|
for seed in 16..=365 {
|
||||||
history: puzzle.history
|
let puzzle = Puzzle::generate(&common_words, seed, heres);
|
||||||
.iter()
|
println!("{}", serde_json::to_string(&PuzzleOutput {
|
||||||
.map(|res| ResultOutput {
|
history: puzzle.history
|
||||||
word: res.word.iter().collect(),
|
.iter()
|
||||||
matches: res.matches
|
.map(|res| ResultOutput {
|
||||||
.iter()
|
word: res.word.iter().collect(),
|
||||||
.map(|m| match m {
|
matches: res.matches
|
||||||
LetterMatch::NOWHERE => 0,
|
.iter()
|
||||||
LetterMatch::ELSEWHERE => 1,
|
.map(|m| match m {
|
||||||
LetterMatch::HERE => 2,
|
LetterMatch::NOWHERE => 0,
|
||||||
})
|
LetterMatch::ELSEWHERE => 1,
|
||||||
.collect(),
|
LetterMatch::HERE => 2,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
seed: puzzle.seed,
|
})
|
||||||
solutions: puzzle.solutions,
|
.collect(),
|
||||||
}).unwrap());
|
seed: puzzle.seed,
|
||||||
|
solutions: puzzle.solutions,
|
||||||
|
}).unwrap());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,6 +137,10 @@ table.Game-rows > tbody {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.puzzle-type {
|
||||||
|
padding: 0 .25em;
|
||||||
|
}
|
||||||
|
|
||||||
.letter-correct {
|
.letter-correct {
|
||||||
border: 2px solid rgba(0, 0, 0, 0.3);
|
border: 2px solid rgba(0, 0, 0, 0.3);
|
||||||
background-color: rgb(87, 172, 120);
|
background-color: rgb(87, 172, 120);
|
||||||
|
@ -266,6 +270,17 @@ a:active {
|
||||||
margin-inline-end: 8px;
|
margin-inline-end: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.top-left {
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pick-puzzle-type {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
.top-right {
|
.top-right {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 5px;
|
top: 5px;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { h, JSX } from 'preact';
|
import { h, JSX, Fragment } from 'preact';
|
||||||
import { useState, useRef, useMemo, useCallback, useEffect } from 'preact/hooks';
|
import { useState, useRef, useMemo, useCallback, useEffect } from 'preact/hooks';
|
||||||
|
|
||||||
import * as wasm from 'web-optimle';
|
import * as wasm from 'web-optimle';
|
||||||
|
@ -6,14 +6,13 @@ import { clue, describeClue, type Clue, type GuessResult } from './clue';
|
||||||
import { gameName, speak } from './util';
|
import { gameName, speak } from './util';
|
||||||
|
|
||||||
import { COMMON_WORDS, UNCOMMON_WORDS } from './dictionary';
|
import { COMMON_WORDS, UNCOMMON_WORDS } from './dictionary';
|
||||||
import * as PUZZLES from './puzzles.json';
|
|
||||||
import { Row, RowState } from './Row';
|
import { Row, RowState } from './Row';
|
||||||
import { Keyboard } from './Keyboard';
|
import { Keyboard } from './Keyboard';
|
||||||
import { useLocalStorage } from './localstorage';
|
import { useLocalStorage } from './localstorage';
|
||||||
|
import { PuzzleType, PuzzleTypeDisplay } from './PuzzleType';
|
||||||
|
|
||||||
const MAX_GUESSES = 2;
|
const MAX_GUESSES = 2;
|
||||||
const WORD_LENGTH = 5;
|
const WORD_LENGTH = 5;
|
||||||
const EPOCH = new Date(2022, 1, 27);
|
|
||||||
|
|
||||||
enum GameState {
|
enum GameState {
|
||||||
Playing,
|
Playing,
|
||||||
|
@ -21,65 +20,110 @@ enum GameState {
|
||||||
Lost,
|
Lost,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type History = GuessResult[];
|
||||||
|
|
||||||
|
|
||||||
|
type Puzzle = {
|
||||||
|
history: History,
|
||||||
|
seed: number,
|
||||||
|
solutions: number,
|
||||||
|
}
|
||||||
|
|
||||||
interface GameProps {
|
interface GameProps {
|
||||||
|
puzzleType: PuzzleType;
|
||||||
|
puzzle: Puzzle;
|
||||||
|
day: number;
|
||||||
hidden: boolean;
|
hidden: boolean;
|
||||||
colorBlind: boolean;
|
colorBlind: boolean;
|
||||||
keyboardLayout: string;
|
keyboardLayout: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type History = GuessResult[];
|
|
||||||
|
|
||||||
type DailyStats = {
|
type PuzzleStats = {
|
||||||
tries: number,
|
tries: number,
|
||||||
won: boolean,
|
won: boolean,
|
||||||
winningGuesses?: GuessResult[],
|
guesses?: GuessResult[],
|
||||||
|
};
|
||||||
|
|
||||||
|
function defaultPuzzleStats() {
|
||||||
|
return { tries: 1, won: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
type DailyStats = {
|
||||||
|
[key in PuzzleType]: PuzzleStats
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Game(props: GameProps) {
|
export function Game(props: GameProps) {
|
||||||
const day = useMemo(() => {
|
const { puzzleType, puzzle, day } = props;
|
||||||
let date = new Date();
|
|
||||||
date.setHours(date.getHours() - 6); // treat 6 AM as the start of the day
|
|
||||||
date.setHours(0); // now set it to midnight of the day
|
|
||||||
return Math.round((date.getTime() - EPOCH.getTime()) / (24 * 60 * 60 * 1000));
|
|
||||||
}, []);
|
|
||||||
const puzzle = PUZZLES[day];
|
|
||||||
const [gameState, setGameState] = useState(GameState.Playing);
|
const [gameState, setGameState] = useState(GameState.Playing);
|
||||||
const [guesses, setGuesses] = useState<GuessResult[]>([...puzzle.history]);
|
const [guesses, setGuesses] = useState<GuessResult[]>([]);
|
||||||
const [currentGuess, setCurrentGuess] = useState<string>("");
|
const [currentGuess, setCurrentGuess] = useState<string>("");
|
||||||
const [dailyStats, setDailyStats] = useLocalStorage<{ [key: number]: DailyStats }>("dailyTries", {});
|
const [dailyStats, setDailyStats] = useLocalStorage<{ [key: number]: DailyStats }>("dailyStats", {});
|
||||||
const [todayStats, setTodayStats] = useState<DailyStats>({ tries: 1, won: false });
|
const [todayStats, setTodayStats] = useState<DailyStats>({
|
||||||
|
2: defaultPuzzleStats(),
|
||||||
|
3: defaultPuzzleStats(),
|
||||||
|
4: defaultPuzzleStats(),
|
||||||
|
});
|
||||||
const [hint, setHint] = useState<JSX.Element | string>(
|
const [hint, setHint] = useState<JSX.Element | string>(
|
||||||
<p>
|
<p>
|
||||||
Try to guarantee a win in <strong>2 guesses</strong>!
|
Try to guarantee a win in <strong>2 guesses</strong>!
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
const tableRef = useRef<HTMLTableElement>(null);
|
const tableRef = useRef<HTMLTableElement>(null);
|
||||||
const game = useMemo(() => wasm.Game.new(COMMON_WORDS, puzzle.history), []);
|
const game = useMemo(
|
||||||
|
() => wasm.Game.new(COMMON_WORDS, puzzle.history),
|
||||||
|
[puzzle]
|
||||||
|
);
|
||||||
const [gameTree, setGameTree] = useState<GuessResult[][]>();
|
const [gameTree, setGameTree] = useState<GuessResult[][]>();
|
||||||
const reset = useCallback(() => {
|
const reset = useCallback(() => {
|
||||||
game.reset();
|
game.reset();
|
||||||
setGuesses([...puzzle.history]);
|
setGuesses([...puzzle.history]);
|
||||||
setCurrentGuess("");
|
setCurrentGuess("");
|
||||||
setGameState(GameState.Playing);
|
setGameState(GameState.Playing);
|
||||||
}, []);
|
setTodayStats((oldStats) => ({
|
||||||
|
...oldStats,
|
||||||
|
[puzzleType]: {
|
||||||
|
...oldStats[puzzleType],
|
||||||
|
guesses: [],
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}, [puzzle]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const selectedPuzzle = todayStats[puzzleType];
|
||||||
|
setGuesses([...puzzle.history, ...(selectedPuzzle.guesses || [])]);
|
||||||
|
game.reset();
|
||||||
|
setGameTree(undefined);
|
||||||
|
selectedPuzzle.guesses?.forEach(
|
||||||
|
(result) => game.guess(result.word)
|
||||||
|
);
|
||||||
|
if (selectedPuzzle.won) {
|
||||||
|
setGameState(GameState.Won);
|
||||||
|
if (selectedPuzzle.tries === 1) {
|
||||||
|
setHint(<>
|
||||||
|
You won today's <PuzzleTypeDisplay type={puzzleType} />{' '}
|
||||||
|
puzzle on your first try! Amazing job.
|
||||||
|
</>);
|
||||||
|
} else {
|
||||||
|
setHint(<>
|
||||||
|
You won today's <PuzzleTypeDisplay type={puzzleType} />{' '}
|
||||||
|
puzzle in {selectedPuzzle.tries} tries! Great job.
|
||||||
|
</>);
|
||||||
|
}
|
||||||
|
} else if ((selectedPuzzle.guesses?.length || 0) >= 2) {
|
||||||
|
setGameState(GameState.Lost);
|
||||||
|
setHint(<p>Not quite! The answer could've been <strong>{game.possible_word().toUpperCase()}</strong>. (Enter to try again)</p>);
|
||||||
|
} else {
|
||||||
|
setGameState(GameState.Playing);
|
||||||
|
setHint("");
|
||||||
|
}
|
||||||
|
}, [puzzle, puzzleType, todayStats]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const storedTodayStats = dailyStats[day];
|
const storedTodayStats = dailyStats[day];
|
||||||
if (storedTodayStats !== undefined) {
|
if (storedTodayStats !== undefined) {
|
||||||
setTodayStats(storedTodayStats);
|
setTodayStats(storedTodayStats);
|
||||||
if (storedTodayStats.won) {
|
|
||||||
setGameState(GameState.Won);
|
|
||||||
setHint("You already won today's puzzle!");
|
|
||||||
if (storedTodayStats.winningGuesses) {
|
|
||||||
setGuesses([
|
|
||||||
...puzzle.history,
|
|
||||||
...storedTodayStats.winningGuesses,
|
|
||||||
]);
|
|
||||||
storedTodayStats.winningGuesses.forEach(
|
|
||||||
(result) => game.guess(result.word)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -157,29 +201,37 @@ export function Game(props: GameProps) {
|
||||||
matches: matches,
|
matches: matches,
|
||||||
}
|
}
|
||||||
setGuesses((guesses) => guesses.concat([result]));
|
setGuesses((guesses) => guesses.concat([result]));
|
||||||
|
setTodayStats((oldStats) => ({
|
||||||
|
...oldStats,
|
||||||
|
[puzzleType]: {
|
||||||
|
...oldStats[puzzleType],
|
||||||
|
guesses: [
|
||||||
|
...(oldStats[puzzleType].guesses || []),
|
||||||
|
result,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}));
|
||||||
setCurrentGuess("");
|
setCurrentGuess("");
|
||||||
|
|
||||||
if (matches.every((m) => m === 2)) {
|
if (matches.every((m) => m === 2)) {
|
||||||
if (todayStats.tries === 1) {
|
|
||||||
setHint("You won on your first try! Amazing job.");
|
|
||||||
} else {
|
|
||||||
setHint(`You won in ${todayStats.tries} tries! Great job.`);
|
|
||||||
}
|
|
||||||
setGameState(GameState.Won);
|
|
||||||
setTodayStats((oldStats) => ({
|
setTodayStats((oldStats) => ({
|
||||||
...oldStats,
|
...oldStats,
|
||||||
won: true,
|
[puzzleType]: {
|
||||||
winningGuesses: [
|
...oldStats[puzzleType],
|
||||||
...guesses.slice(puzzle.history.length),
|
won: true,
|
||||||
result,
|
guesses: [
|
||||||
],
|
...(oldStats[puzzleType].guesses || []),
|
||||||
}))
|
result,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}));
|
||||||
} else if (guesses.length + 1 === puzzle.history.length + MAX_GUESSES) {
|
} else if (guesses.length + 1 === puzzle.history.length + MAX_GUESSES) {
|
||||||
setHint(<p>Not quite! The answer could've been <strong>{game.possible_word().toUpperCase()}</strong>. (Enter to try again)</p>);
|
|
||||||
setGameState(GameState.Lost);
|
|
||||||
setTodayStats((oldStats) => ({
|
setTodayStats((oldStats) => ({
|
||||||
...oldStats,
|
...oldStats,
|
||||||
tries: oldStats.tries + 1,
|
[puzzleType]: {
|
||||||
|
...oldStats[puzzleType],
|
||||||
|
tries: oldStats[puzzleType].tries + 1,
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
speak(describeClue(clue(result)));
|
speak(describeClue(clue(result)));
|
||||||
|
@ -271,10 +323,13 @@ export function Game(props: GameProps) {
|
||||||
const emoji = props.colorBlind
|
const emoji = props.colorBlind
|
||||||
? ["⬛", "🟦", "🟧"]
|
? ["⬛", "🟦", "🟧"]
|
||||||
: ["⬛", "🟨", "🟩"];
|
: ["⬛", "🟨", "🟩"];
|
||||||
const score = todayStats.tries === 1 ? "my first try!" : `try #${todayStats.tries}!`;
|
const score = (todayStats[puzzleType].tries === 1
|
||||||
|
? "my first try!"
|
||||||
|
: `try #${todayStats[puzzleType].tries}!`
|
||||||
|
);
|
||||||
share(
|
share(
|
||||||
"Result copied to clipboard!",
|
"Result copied to clipboard!",
|
||||||
`I solved ${gameName} #${puzzle.seed} on ${score}\n` +
|
`I solved ${gameName} #${puzzle.seed} (${puzzleType}🟩) on ${score}\n` +
|
||||||
guesses
|
guesses
|
||||||
.slice(puzzle.history.length)
|
.slice(puzzle.history.length)
|
||||||
.map((result) =>
|
.map((result) =>
|
||||||
|
|
9
web-optimle/www/src/PuzzleType.tsx
Normal file
9
web-optimle/www/src/PuzzleType.tsx
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { h } from 'preact';
|
||||||
|
|
||||||
|
export type PuzzleType = 2 | 3 | 4;
|
||||||
|
|
||||||
|
export function PuzzleTypeDisplay(props: { type: PuzzleType, active?: boolean }) {
|
||||||
|
const { type, active } = props;
|
||||||
|
const isActive = active === undefined ? true : active;
|
||||||
|
return <span class={`puzzle-type ${isActive ? 'letter-correct' : ''}`}>{type}</span>;
|
||||||
|
}
|
|
@ -1,15 +1,39 @@
|
||||||
import { h, render, Fragment } from 'preact';
|
import { h, render, Fragment } from 'preact';
|
||||||
import { useState, useEffect } from 'preact/hooks';
|
import { useState, useEffect, useMemo } from 'preact/hooks';
|
||||||
import { Game } from './Game';
|
import { Game } from './Game';
|
||||||
import { gameName, urlParam } from "./util";
|
import { gameName, urlParam } from "./util";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
import { About } from './About';
|
import { About } from './About';
|
||||||
import { useLocalStorage } from './localstorage';
|
import { useLocalStorage } from './localstorage';
|
||||||
|
import { PuzzleType, PuzzleTypeDisplay } from './PuzzleType';
|
||||||
|
|
||||||
|
import * as PUZZLES from './puzzles.json';
|
||||||
|
|
||||||
|
const EPOCH = new Date(2022, 1, 27);
|
||||||
|
|
||||||
|
type PickPuzzleTypeProps = {
|
||||||
|
puzzleType: PuzzleType,
|
||||||
|
setPuzzleType: (p: PuzzleType) => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
function PickPuzzleType(props: PickPuzzleTypeProps) {
|
||||||
|
const { puzzleType, setPuzzleType } = props;
|
||||||
|
const PUZZLE_TYPES: PuzzleType[] = [2, 3, 4];
|
||||||
|
return (
|
||||||
|
<div class="pick-puzzle-type">
|
||||||
|
{PUZZLE_TYPES.map((type) => (
|
||||||
|
<div style={{ cursor: 'pointer' }} onClick={() => setPuzzleType(type)}>
|
||||||
|
<PuzzleTypeDisplay type={type} active={type === puzzleType} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
type Page = "game" | "about" | "settings";
|
type Page = "game" | "about" | "settings";
|
||||||
const [page, setPage] = useState<Page>("game");
|
const [page, setPage] = useState<Page>("game");
|
||||||
|
|
||||||
const prefersDark =
|
const prefersDark =
|
||||||
window.matchMedia &&
|
window.matchMedia &&
|
||||||
window.matchMedia("(prefers-color-scheme: dark)").matches;
|
window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||||
|
@ -21,6 +45,15 @@ export function App() {
|
||||||
);
|
);
|
||||||
const [enterLeft, setEnterLeft] = useLocalStorage<boolean>("enter-left", false);
|
const [enterLeft, setEnterLeft] = useLocalStorage<boolean>("enter-left", false);
|
||||||
|
|
||||||
|
const day = useMemo(() => {
|
||||||
|
let date = new Date();
|
||||||
|
date.setHours(date.getHours() - 6); // treat 6 AM as the start of the day
|
||||||
|
date.setHours(0); // now set it to midnight of the day
|
||||||
|
return Math.round((date.getTime() - EPOCH.getTime()) / (24 * 60 * 60 * 1000));
|
||||||
|
}, []);
|
||||||
|
const [puzzleType, setPuzzleType] = useLocalStorage<2 | 3 | 4>("puzzleType", 4);
|
||||||
|
const puzzle = useMemo(() => PUZZLES[puzzleType][day], [puzzleType]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.body.className = dark ? "dark" : "";
|
document.body.className = dark ? "dark" : "";
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -44,6 +77,11 @@ export function App() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={"App-container" + (colorBlind ? " color-blind" : "")}>
|
<div className={"App-container" + (colorBlind ? " color-blind" : "")}>
|
||||||
|
<div className="top-left">
|
||||||
|
{page === "game" && (
|
||||||
|
<PickPuzzleType puzzleType={puzzleType} setPuzzleType={setPuzzleType} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<h1>
|
<h1>
|
||||||
{gameName}
|
{gameName}
|
||||||
</h1>
|
</h1>
|
||||||
|
@ -103,14 +141,25 @@ export function App() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Game
|
{puzzle ? (
|
||||||
hidden={page !== "game"}
|
<Game
|
||||||
colorBlind={colorBlind}
|
puzzleType={puzzleType}
|
||||||
keyboardLayout={keyboard.replaceAll(
|
puzzle={puzzle}
|
||||||
/[BE]/g,
|
day={day}
|
||||||
(x) => (enterLeft ? "EB" : "BE")["BE".indexOf(x)]
|
hidden={page !== "game"}
|
||||||
)}
|
colorBlind={colorBlind}
|
||||||
/>
|
keyboardLayout={keyboard.replaceAll(
|
||||||
|
/[BE]/g,
|
||||||
|
(x) => (enterLeft ? "EB" : "BE")["BE".indexOf(x)]
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
puzzle === null ? (
|
||||||
|
<p>Hang tight! Something new is in store for us...</p>
|
||||||
|
) : (
|
||||||
|
<p>Optimle has concluded. Take a deep breath and relax :)</p>
|
||||||
|
)
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -34,4 +34,9 @@ module.exports = {
|
||||||
meta: {viewport: 'width=device-width, initial-scale=1, shrink-to-fit=no'},
|
meta: {viewport: 'width=device-width, initial-scale=1, shrink-to-fit=no'},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
performance: {
|
||||||
|
hints: false,
|
||||||
|
maxEntrypointSize: 512000,
|
||||||
|
maxAssetSize: 512000
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue