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