From 15f0d329d8149280f646ceddaff751ba71dc65f1 Mon Sep 17 00:00:00 2001 From: Josh Feinsilber Date: Mon, 3 Jun 2024 01:36:05 -0700 Subject: [PATCH] medal system --- src/const/medals.ts | 34 +++++++++++++++++++++ src/screens/game/WordCount.tsx | 21 +++++++++---- src/screens/results/App.tsx | 2 ++ src/screens/results/Medal.tsx | 43 +++++++++++++++++++++++++++ src/store/game.ts | 4 ++- src/util/game/handleSubmit.ts | 8 ++++- src/util/results/getMedal.ts | 16 ++++++++++ src/util/results/share.ts | 3 ++ src/util/solution/generateSolution.ts | 13 ++++++++ 9 files changed, 136 insertions(+), 8 deletions(-) create mode 100644 src/const/medals.ts create mode 100644 src/screens/results/Medal.tsx create mode 100644 src/util/results/getMedal.ts diff --git a/src/const/medals.ts b/src/const/medals.ts new file mode 100644 index 0000000..a2bfbde --- /dev/null +++ b/src/const/medals.ts @@ -0,0 +1,34 @@ +export interface IMedal { + name: string + emoji: string + differenceFromOptimal: number +} + +export const MEDALS: IMedal[] = [ + { + name: 'Perfection', + emoji: '🏆', + differenceFromOptimal: 0 + }, + { + name: 'Gold', + emoji: '🥇', + differenceFromOptimal: 1 + }, + { + name: 'Silver', + emoji: '🥈', + differenceFromOptimal: 2 + }, + { + name: 'Bronze', + emoji: '🥉', + differenceFromOptimal: 3 + } +] + +export const NONE_MEDAL: IMedal = { + name: 'No', + emoji: '🫤', + differenceFromOptimal: -1 +} diff --git a/src/screens/game/WordCount.tsx b/src/screens/game/WordCount.tsx index 9cf5873..03015ae 100644 --- a/src/screens/game/WordCount.tsx +++ b/src/screens/game/WordCount.tsx @@ -4,26 +4,35 @@ import { useThrottle } from '@uidotdev/usehooks' import { useMemo } from 'react' import { isWord } from '../../util/game/handleSubmit' import classNames from 'classnames' +import { generateSolution } from '../../util/solution/generateSolution' +import { lettersForToday } from '../../util/lottery/letters' export const WordCount = () => { const wordCount = useAtomValue(words).length const word = useAtomValue(currentWord) + const solutionNumberOfWords = useMemo(() => { + return generateSolution(lettersForToday()).length + }, []) + // Throttle the word so that we're not checking on every keystroke const throttledWord = useThrottle(word, 150) const isProperWord = useMemo(() => { return isWord(throttledWord) }, [throttledWord]) - console.log(isProperWord) - return (
- Word #{wordCount + 1} +

+ Word #{wordCount + 1} +

+
+

🏆 {solutionNumberOfWords}

+
) } diff --git a/src/screens/results/App.tsx b/src/screens/results/App.tsx index 30ab917..67893c3 100644 --- a/src/screens/results/App.tsx +++ b/src/screens/results/App.tsx @@ -7,6 +7,7 @@ import { lettersForToday } from '../../util/lottery/letters' import { useMemo } from 'react' import { decodeResults } from '../../util/results/encodedInfo' import { Bot } from './Bot' +import { Medal } from './Medal' export const Results = () => { const wordList = useAtomValue(words) @@ -28,6 +29,7 @@ export const Results = () => { className="w-full max-w-xl" > + {otherResults ? ( ) : null} diff --git a/src/screens/results/Medal.tsx b/src/screens/results/Medal.tsx new file mode 100644 index 0000000..33fe8a7 --- /dev/null +++ b/src/screens/results/Medal.tsx @@ -0,0 +1,43 @@ +import { useMemo } from 'react' +import { MEDALS, NONE_MEDAL } from '../../const/medals' +import { generateSolution } from '../../util/solution/generateSolution' +import { lettersForToday } from '../../util/lottery/letters' +import { getMedal } from '../../util/results/getMedal' +import { useAtomValue } from 'jotai' +import { words } from '../../store/game' + +export const Medal = () => { + const wordList = useAtomValue(words) + + const medal = getMedal(wordList.length) + + const perfectSolutionWordCount = useMemo(() => { + return generateSolution(lettersForToday()).length + }, []) + + return ( +
+
+

Medal

+
+
+

{medal.emoji}

+
+ +
+ {MEDALS.map((medal) => { + return ( +

+ {medal.emoji} {medal.differenceFromOptimal + perfectSolutionWordCount} Words +

+ ) + })} +

+ {NONE_MEDAL.emoji} {perfectSolutionWordCount + 4}+ Words +

+
+
+
+
+ ) +} diff --git a/src/store/game.ts b/src/store/game.ts index 9fe293d..7862d37 100644 --- a/src/store/game.ts +++ b/src/store/game.ts @@ -16,7 +16,9 @@ export const words = atomWithSeedStorage('words', [] as string[]) export const resultHistory = atomWithStorage< Array<{ + seed: string puzzleNum: number wordsUsed: number + perfectSolutionWordsUsed: number }> ->('history', []) +>('game-history', [], undefined, { getOnInit: true }) diff --git a/src/util/game/handleSubmit.ts b/src/util/game/handleSubmit.ts index 9d04568..8ee4500 100644 --- a/src/util/game/handleSubmit.ts +++ b/src/util/game/handleSubmit.ts @@ -8,6 +8,8 @@ import { SUCCESS_WORD_MESSAGES } from '../../const/messages' import { wordsByStartingLetter } from '../../const/wordList' import { Screen, screen } from '../../store/screen' import { PUZZLE_NUMBER } from '../../const/puzzleNumber' +import { generateSolution } from '../solution/generateSolution' +import { seed } from '../lottery/seed' export const isWord = (word: string) => { const firstLetter = word[0] @@ -56,15 +58,19 @@ export const handleSubmit = async () => { } const nextLetter = letters[lastLetterIndex + 1] + if (!nextLetter) { store.set(screen, Screen.results) + // Push to our result history store.set(resultHistory, (prev) => { return [ ...prev, { puzzleNum: PUZZLE_NUMBER, - wordsUsed: store.get(words).length + seed, + wordsUsed: store.get(words).length, + perfectSolutionWordsUsed: generateSolution(letters).length } ] }) diff --git a/src/util/results/getMedal.ts b/src/util/results/getMedal.ts new file mode 100644 index 0000000..7a9f258 --- /dev/null +++ b/src/util/results/getMedal.ts @@ -0,0 +1,16 @@ +import { MEDALS, NONE_MEDAL } from '../../const/medals' +import { lettersForToday } from '../lottery/letters' +import { generateSolution } from '../solution/generateSolution' + +export const getMedal = (wordsUsed: number) => { + const solutionWordsUsed = generateSolution(lettersForToday()).length + const difference = Math.abs(solutionWordsUsed - wordsUsed) + + let medal = MEDALS.find((medal) => { + return medal.differenceFromOptimal === difference + }) + if (!medal) { + medal = NONE_MEDAL + } + return medal +} diff --git a/src/util/results/share.ts b/src/util/results/share.ts index 4ffc10b..f772767 100644 --- a/src/util/results/share.ts +++ b/src/util/results/share.ts @@ -4,14 +4,17 @@ import { words } from '../../store/game' import { store } from '../../store/store' import { toast } from 'sonner' import { encodeResults } from './encodedInfo' +import { getMedal } from './getMedal' const colors = ['🟦', '🟧', '🟪', '🟨', '🟩', '🟫', '⬜'] export const resultText = () => { const wordsUsed = store.get(words) + const medal = getMedal(wordsUsed.length) let text = `🎰 Charlottery No. ${PUZZLE_NUMBER} ✅ Completed in ${wordsUsed.length} words +${medal.emoji} ${medal.name} Medal ` let colorIndex = 0 diff --git a/src/util/solution/generateSolution.ts b/src/util/solution/generateSolution.ts index 3c7b2c4..b63a0c5 100644 --- a/src/util/solution/generateSolution.ts +++ b/src/util/solution/generateSolution.ts @@ -61,7 +61,16 @@ const findWordsMatchingOrder = (letters: string[], options?: { maxLettersPerWord return matchingWords } +const solutionCache = new Map() + export const generateSolution = (letters: string[], options?: { maxLettersPerWord?: number }) => { + if (!options) { + const cacheKey = letters.join('') + if (solutionCache.has(cacheKey)) { + return solutionCache.get(cacheKey)! + } + } + // Create our root node which is an empty word at the start of the letters const root = new WordNode({ word: '', @@ -109,5 +118,9 @@ export const generateSolution = (letters: string[], options?: { maxLettersPerWor } } + if (!options) { + solutionCache.set(letters.join(''), solution) + } + return solution }