extract settings stuff to localStorage, store daily game stats & preserve try count

This commit is contained in:
ashastral 2022-02-27 23:52:20 -08:00
parent 65f20beaf7
commit e0e2f3b2ef
3 changed files with 92 additions and 35 deletions

View file

@ -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")
); );
}} }}
> >

View file

@ -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" : "";

View 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];
}