extract settings stuff to localStorage, store daily game stats & preserve try count
This commit is contained in:
parent
65f20beaf7
commit
e0e2f3b2ef
3 changed files with 92 additions and 35 deletions
|
@ -9,6 +9,8 @@ import { COMMON_WORDS, UNCOMMON_WORDS } from './dictionary';
|
||||||
import * as PUZZLES from './puzzles.json';
|
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';
|
||||||
|
|
||||||
const MAX_GUESSES = 2;
|
const MAX_GUESSES = 2;
|
||||||
const WORD_LENGTH = 5;
|
const WORD_LENGTH = 5;
|
||||||
const EPOCH = new Date(2022, 1, 27);
|
const EPOCH = new Date(2022, 1, 27);
|
||||||
|
@ -27,18 +29,25 @@ interface GameProps {
|
||||||
|
|
||||||
type History = GuessResult[];
|
type History = GuessResult[];
|
||||||
|
|
||||||
|
type DailyStats = {
|
||||||
|
tries: number,
|
||||||
|
won: boolean,
|
||||||
|
winningGuesses?: GuessResult[],
|
||||||
|
};
|
||||||
|
|
||||||
export function Game(props: GameProps) {
|
export function Game(props: GameProps) {
|
||||||
const puzzle = useMemo(() => {
|
const day = useMemo(() => {
|
||||||
let date = new Date();
|
let date = new Date();
|
||||||
date.setHours(date.getHours() - 6); // treat 6 AM as the start of the day
|
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
|
date.setHours(0); // now set it to midnight of the day
|
||||||
const day = Math.round((date.getTime() - EPOCH.getTime()) / (24 * 60 * 60 * 1000));
|
return Math.round((date.getTime() - EPOCH.getTime()) / (24 * 60 * 60 * 1000));
|
||||||
return PUZZLES[day];
|
|
||||||
}, []);
|
}, []);
|
||||||
|
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[]>([...puzzle.history]);
|
||||||
const [currentGuess, setCurrentGuess] = useState<string>("");
|
const [currentGuess, setCurrentGuess] = useState<string>("");
|
||||||
const [tryNumber, setTryNumber] = useState(1);
|
const [dailyStats, setDailyStats] = useLocalStorage<{ [key: number]: DailyStats }>("dailyTries", {});
|
||||||
|
const [todayStats, setTodayStats] = useState<DailyStats>({ tries: 1, won: false });
|
||||||
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>!
|
||||||
|
@ -51,12 +60,38 @@ export function Game(props: GameProps) {
|
||||||
setGuesses([...puzzle.history]);
|
setGuesses([...puzzle.history]);
|
||||||
setCurrentGuess("");
|
setCurrentGuess("");
|
||||||
setGameState(GameState.Playing);
|
setGameState(GameState.Playing);
|
||||||
setTryNumber((x) => x + 1);
|
setTodayStats((oldStats) => ({
|
||||||
|
...oldStats,
|
||||||
|
tries: oldStats.tries + 1,
|
||||||
|
}));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const storedTodayStats = dailyStats[day];
|
||||||
|
if (storedTodayStats !== undefined) {
|
||||||
|
setTodayStats(storedTodayStats);
|
||||||
|
if (storedTodayStats.won) {
|
||||||
|
setGameState(GameState.Won);
|
||||||
|
setHint("You already won today's puzzle!");
|
||||||
|
if (storedTodayStats.winningGuesses) {
|
||||||
|
setGuesses([
|
||||||
|
...puzzle.history,
|
||||||
|
...storedTodayStats.winningGuesses,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
useEffect(() => {
|
||||||
|
setDailyStats({
|
||||||
|
...dailyStats,
|
||||||
|
[day]: todayStats,
|
||||||
|
});
|
||||||
|
}, [todayStats]);
|
||||||
|
|
||||||
async function share(copiedHint: string, text?: string) {
|
async function share(copiedHint: string, text?: string) {
|
||||||
const url = window.location.origin + window.location.pathname;
|
const url = window.location.origin + window.location.pathname;
|
||||||
const body = `${text}\n\n${url}`;
|
const body = `${text}\n${url}`;
|
||||||
if (
|
if (
|
||||||
/android|iphone|ipad|ipod|webos/i.test(navigator.userAgent) &&
|
/android|iphone|ipad|ipod|webos/i.test(navigator.userAgent) &&
|
||||||
!/firefox/i.test(navigator.userAgent)
|
!/firefox/i.test(navigator.userAgent)
|
||||||
|
@ -123,12 +158,20 @@ export function Game(props: GameProps) {
|
||||||
setCurrentGuess("");
|
setCurrentGuess("");
|
||||||
|
|
||||||
if (matches.every((m) => m === 2)) {
|
if (matches.every((m) => m === 2)) {
|
||||||
if (tryNumber === 1) {
|
if (todayStats.tries === 1) {
|
||||||
setHint("You won on your first try! Amazing job.");
|
setHint("You won on your first try! Amazing job.");
|
||||||
} else {
|
} else {
|
||||||
setHint(`You won in ${tryNumber} tries! Great job.`);
|
setHint(`You won in ${todayStats.tries} tries! Great job.`);
|
||||||
}
|
}
|
||||||
setGameState(GameState.Won);
|
setGameState(GameState.Won);
|
||||||
|
setTodayStats((oldStats) => ({
|
||||||
|
...oldStats,
|
||||||
|
won: true,
|
||||||
|
winningGuesses: [
|
||||||
|
...guesses.slice(puzzle.history.length),
|
||||||
|
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>);
|
setHint(<p>Not quite! The answer could've been <strong>{game.possible_word().toUpperCase()}</strong>. (Enter to try again)</p>);
|
||||||
setGameState(GameState.Lost);
|
setGameState(GameState.Lost);
|
||||||
|
@ -216,10 +259,21 @@ export function Game(props: GameProps) {
|
||||||
{gameState === GameState.Won && (
|
{gameState === GameState.Won && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const score = tryNumber === 1 ? "my first try!" : `try #${tryNumber}.`;
|
const emoji = props.colorBlind
|
||||||
|
? ["⬛", "🟦", "🟧"]
|
||||||
|
: ["⬛", "🟨", "🟩"];
|
||||||
|
const score = todayStats.tries === 1 ? "my first try!" : `try #${todayStats.tries}!`;
|
||||||
share(
|
share(
|
||||||
"Result copied to clipboard!",
|
"Result copied to clipboard!",
|
||||||
`I solved ${gameName} #${puzzle.seed} on ${score}`
|
`I solved ${gameName} #${puzzle.seed} on ${score}\n` +
|
||||||
|
guesses
|
||||||
|
.slice(puzzle.history.length)
|
||||||
|
.map((result) =>
|
||||||
|
clue(result)
|
||||||
|
.map((c) => emoji[c.clue ?? 0])
|
||||||
|
.join("")
|
||||||
|
)
|
||||||
|
.join("\n")
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -4,28 +4,8 @@ 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';
|
||||||
|
|
||||||
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);
|
|
||||||
return item ? JSON.parse(item) : initial;
|
|
||||||
} catch (e) {
|
|
||||||
return initial;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const setSetting = (value: T | ((t: T) => T)) => {
|
|
||||||
try {
|
|
||||||
const v = value instanceof Function ? value(current) : value;
|
|
||||||
setCurrent(v);
|
|
||||||
window.localStorage.setItem(key, JSON.stringify(v));
|
|
||||||
} catch (e) { }
|
|
||||||
};
|
|
||||||
return [current, setSetting];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
type Page = "game" | "about" | "settings";
|
type Page = "game" | "about" | "settings";
|
||||||
|
@ -33,13 +13,13 @@ export function App() {
|
||||||
const prefersDark =
|
const prefersDark =
|
||||||
window.matchMedia &&
|
window.matchMedia &&
|
||||||
window.matchMedia("(prefers-color-scheme: dark)").matches;
|
window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||||
const [dark, setDark] = useSetting<boolean>("dark", prefersDark);
|
const [dark, setDark] = useLocalStorage<boolean>("dark", prefersDark);
|
||||||
const [colorBlind, setColorBlind] = useSetting<boolean>("colorblind", false);
|
const [colorBlind, setColorBlind] = useLocalStorage<boolean>("colorblind", false);
|
||||||
const [keyboard, setKeyboard] = useSetting<string>(
|
const [keyboard, setKeyboard] = useLocalStorage<string>(
|
||||||
"keyboard",
|
"keyboard",
|
||||||
"qwertyuiop-asdfghjkl-BzxcvbnmE"
|
"qwertyuiop-asdfghjkl-BzxcvbnmE"
|
||||||
);
|
);
|
||||||
const [enterLeft, setEnterLeft] = useSetting<boolean>("enter-left", false);
|
const [enterLeft, setEnterLeft] = useLocalStorage<boolean>("enter-left", false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.body.className = dark ? "dark" : "";
|
document.body.className = dark ? "dark" : "";
|
||||||
|
|
23
web-optimle/www/src/localstorage.ts
Normal file
23
web-optimle/www/src/localstorage.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
|
|
||||||
|
export function useLocalStorage<T>(
|
||||||
|
key: string,
|
||||||
|
initial: T
|
||||||
|
): [T, (value: T | ((t: T) => T)) => void] {
|
||||||
|
const [current, setCurrent] = useState<T>(() => {
|
||||||
|
try {
|
||||||
|
const item = window.localStorage.getItem(key);
|
||||||
|
return item ? JSON.parse(item) : initial;
|
||||||
|
} catch (e) {
|
||||||
|
return initial;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const setSetting = (value: T | ((t: T) => T)) => {
|
||||||
|
try {
|
||||||
|
const v = value instanceof Function ? value(current) : value;
|
||||||
|
setCurrent(v);
|
||||||
|
window.localStorage.setItem(key, JSON.stringify(v));
|
||||||
|
} catch (e) { }
|
||||||
|
};
|
||||||
|
return [current, setSetting];
|
||||||
|
}
|
Loading…
Reference in a new issue