diff --git a/package.json b/package.json
index 075aa8f..d3218aa 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
"dependencies": {
"date-fns": "^2.28.0",
"date-fns-tz": "^1.2.2",
+ "lodash-es": "^4.17.21",
"vue": "^3.2.31",
"vue-i18n": "^9.1.9",
"vue-router": "^4.0.12"
@@ -21,6 +22,7 @@
"@iconify-json/ph": "^1.1.0",
"@intlify/vite-plugin-vue-i18n": "^3.3.0",
"@rushstack/eslint-patch": "^1.1.0",
+ "@types/lodash-es": "^4.17.6",
"@types/node": "^16.11.25",
"@typescript-eslint/eslint-plugin": "^5.12.0",
"@typescript-eslint/parser": "5.0.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index dde484b..ea28e7b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -4,6 +4,7 @@ specifiers:
'@iconify-json/ph': ^1.1.0
'@intlify/vite-plugin-vue-i18n': ^3.3.0
'@rushstack/eslint-patch': ^1.1.0
+ '@types/lodash-es': ^4.17.6
'@types/node': ^16.11.25
'@typescript-eslint/eslint-plugin': ^5.12.0
'@typescript-eslint/parser': 5.0.0
@@ -23,6 +24,7 @@ specifiers:
eslint-plugin-tailwindcss: ^3.4.4
eslint-plugin-vue: ^8.4.1
jsdom: ^19.0.0
+ lodash-es: ^4.17.21
postcss: ^8.4.6
prettier: ^2.5.1
prettier-eslint: ^13.0.0
@@ -41,6 +43,7 @@ specifiers:
dependencies:
date-fns: 2.28.0
date-fns-tz: 1.2.2_date-fns@2.28.0
+ lodash-es: 4.17.21
vue: 3.2.31
vue-i18n: 9.1.9_vue@3.2.31
vue-router: 4.0.12_vue@3.2.31
@@ -49,6 +52,7 @@ devDependencies:
'@iconify-json/ph': 1.1.0
'@intlify/vite-plugin-vue-i18n': 3.3.0_vite@2.8.4+vue-i18n@9.1.9
'@rushstack/eslint-patch': 1.1.0
+ '@types/lodash-es': 4.17.6
'@types/node': 16.11.25
'@typescript-eslint/eslint-plugin': 5.12.0_ae020354c3da76ce329e71c9084ef5bf
'@typescript-eslint/parser': 5.0.0_eslint@7.32.0+typescript@4.5.5
@@ -1574,6 +1578,16 @@ packages:
resolution: {integrity: sha1-7ihweulOEdK4J7y+UnC86n8+ce4=}
dev: true
+ /@types/lodash-es/4.17.6:
+ resolution: {integrity: sha512-R+zTeVUKDdfoRxpAryaQNRKk3105Rrgx2CFRClIgRGaqDTdjsm8h6IYA8ir584W3ePzkZfst5xIgDwYrlh9HLg==}
+ dependencies:
+ '@types/lodash': 4.14.178
+ dev: true
+
+ /@types/lodash/4.14.178:
+ resolution: {integrity: sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==}
+ dev: true
+
/@types/node/16.11.25:
resolution: {integrity: sha512-NrTwfD7L1RTc2qrHQD4RTTy4p0CO2LatKBEKEds3CaVuhoM/+DJzmWZl5f+ikR8cm8F5mfJxK+9rQq07gRiSjQ==}
dev: true
@@ -4056,6 +4070,10 @@ packages:
p-locate: 5.0.0
dev: true
+ /lodash-es/4.17.21:
+ resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
+ dev: false
+
/lodash.debounce/4.0.8:
resolution: {integrity: sha1-gteb/zCmfEAF/9XiUVMArZyk168=}
dev: true
diff --git a/src/App.vue b/src/App.vue
index 69d2808..bec75e1 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -21,14 +21,13 @@
diff --git a/src/composables/settings.ts b/src/composables/settings.ts
index 8bc30f7..1e5e15b 100644
--- a/src/composables/settings.ts
+++ b/src/composables/settings.ts
@@ -1,5 +1,8 @@
import { ref, watch } from 'vue'
+import { LSK_DARKMODE } from '@/globals'
+import { setItem } from '@/storage'
+
export const darkMode = ref(true)
watch(darkMode, val => {
@@ -9,7 +12,7 @@ watch(darkMode, val => {
else {
document.documentElement.classList.remove('dark')
}
- localStorage.setItem('n0_dark', val ? 'true' : 'false')
+ setItem(LSK_DARKMODE, val ? 'true' : 'false')
})
export function isDarkModeDefault(): boolean {
diff --git a/src/composables/statistics.ts b/src/composables/statistics.ts
new file mode 100644
index 0000000..e13e5e2
--- /dev/null
+++ b/src/composables/statistics.ts
@@ -0,0 +1,76 @@
+import { merge } from 'lodash-es'
+import { reactive, watch } from 'vue'
+
+// import { plausible } from '@/analytics'
+import { LSK_STATS } from '@/globals'
+import * as storage from '@/storage'
+import { GameStats } from '@/types'
+// import { getCurrentSessionKey } from '@/utils'
+
+// import { countTotalGuesses, isWinner } from './game-state'
+
+export const gameStats = reactive(loadStats())
+
+// Triggered when the list of played games has changed
+watch(
+ () => gameStats.games,
+ games => {
+ const keys = Object.keys(games).sort()
+
+ // Recompute games count
+ gameStats.bestStreak = 0
+ gameStats.currentStreak = 0
+ gameStats.nbGames = keys.length
+ for (const key of keys) {
+ if (gameStats.games[key].won) {
+ if (++gameStats.currentStreak > gameStats.bestStreak) {
+ gameStats.bestStreak = gameStats.currentStreak
+ }
+ }
+ else {
+ gameStats.currentStreak = 0
+ }
+ }
+
+ // Automatically save stats in storage when updated
+ storage.setItem(LSK_STATS, JSON.stringify(gameStats))
+ },
+ { deep: true, immediate: true },
+)
+
+function loadStats(): GameStats {
+ const stats: GameStats = {
+ bestStreak: 0,
+ currentStreak: 0,
+ nbGames: 0,
+ games: {},
+ }
+ const loaded = (() => {
+ try {
+ return JSON.parse(storage.getItem(LSK_STATS)!)
+ }
+ catch (e) {
+ return {}
+ }
+ })()
+ merge(stats, loaded)
+
+ return stats
+}
+
+function setScore(seed: string, won: boolean, score: number): void {
+ // Don't overwrite an existing score
+ if (!gameStats.games[seed]) {
+ gameStats.games[seed] = { score, won }
+ // plausible.trackEvent(won ? 'win_game' : 'lose_game')
+ // plausible.trackEvent('end_game')
+ }
+}
+
+export function hasPlayed(seed:string): boolean {
+ return !!gameStats.games[seed]
+}
+
+// export function saveScore(): void {
+// setScore(getCurrentSessionKey(), isWinner.value, countTotalGuesses.value)
+// }
diff --git a/src/globals.ts b/src/globals.ts
index c01e8c3..8307564 100644
--- a/src/globals.ts
+++ b/src/globals.ts
@@ -1,6 +1,9 @@
export const GAME_STARTING_DATE = '2022-02-18'
export const BXL_TZ = 'Europe/Brussels'
+export const LSK_DARKMODE = 'n0_dark'
+export const LSK_STATS = 'n0_stats'
+
export const operators = ['+', '-', '*', '/'] as const
export const pools = {
diff --git a/src/locales/fr.json b/src/locales/fr.json
index 5ffccd2..d5695ca 100644
--- a/src/locales/fr.json
+++ b/src/locales/fr.json
@@ -12,5 +12,6 @@
"gameDescription": "Combinez les nombres imposés afin d'atteindre le résultat, ou de vous en approcher le plus possible.",
"dailyDescription": "Le défi quotidien change chaque jour à minuit, et est commun à tous les joueurs.",
"randomDescription": "Une partie au hasard, pour le plaisir.",
- "share": "Partager"
+ "share": "Partager",
+ "finishDailyToPlayRandom": "Terminez le défi quotidien pour débloquer."
}
diff --git a/src/storage.ts b/src/storage.ts
new file mode 100644
index 0000000..3519970
--- /dev/null
+++ b/src/storage.ts
@@ -0,0 +1,16 @@
+import { hasPlayed } from './composables/statistics'
+import { getCurrentSessionKey } from './utils'
+
+export function setItem(k: string, v: string): void {
+ return localStorage.setItem(k, v)
+}
+export function getItem(k: string): string | null
+export function getItem(k: string, defaultValue: any): string
+export function getItem(k: string, defaultValue?: any): string | null {
+ try {
+ return localStorage.getItem(k) ?? null
+ }
+ catch (e) {
+ return defaultValue ?? null
+ }
+}
diff --git a/src/types.ts b/src/types.ts
index 5413f88..a6bce34 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -11,3 +11,10 @@ export type Operation = {
operator: OperatorType | null
result: Plaquette | null
}
+
+export type GameStats = {
+ bestStreak: number
+ currentStreak: number
+ nbGames: number
+ games: { [key: string]: { score: number; won: boolean } }
+}
diff --git a/src/utils.ts b/src/utils.ts
index 6a5ea75..a7d0bbe 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -6,6 +6,10 @@ export function getCurrentDate(): Date {
return utcToZonedTime(new Date(), BXL_TZ)
}
+export function getCurrentSessionKey(): string {
+ return getCurrentDate().toISOString().slice(0, 10)
+}
+
// #region RNG
export let random = Math.random
@@ -60,7 +64,7 @@ export function shuffle(array: T[]): T[] {
function initDailyPRNG(): () => number {
// Prefix the seed when in dev to avoid spoiling myself while working on it
const prefix = import.meta.env.DEV ? 'dev-' : ''
- const hashed = hashStr(prefix + getCurrentDate().toISOString().slice(0, 10))
+ const hashed = hashStr(prefix + getCurrentSessionKey())
return mulberry32(hashed)
}
diff --git a/src/views/GameView.vue b/src/views/GameView.vue
index f93b68c..e06cbdb 100644
--- a/src/views/GameView.vue
+++ b/src/views/GameView.vue
@@ -71,7 +71,7 @@