From 5029d1da9c36cd8ac5b21bd1de0dc3eab9aebcf2 Mon Sep 17 00:00:00 2001 From: Simon Cambier Date: Fri, 18 Feb 2022 19:28:52 +0100 Subject: [PATCH] Daily challenge --- package.json | 2 ++ pnpm-lock.yaml | 17 ++++++++++ src/components/AppHeader.vue | 4 +-- src/composables/game-state.ts | 17 ++++++++-- src/globals.ts | 3 ++ src/utils.ts | 63 ++++++++++++++++++++++++++++++++--- src/views/GameView.vue | 31 ++++++++++++----- 7 files changed, 119 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 0890c65..e747158 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,8 @@ "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" }, "dependencies": { + "date-fns": "^2.28.0", + "date-fns-tz": "^1.2.2", "vue": "^3.2.31", "vue-i18n": "^9.1.9", "vue-router": "^4.0.12" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7e7f6cf..4076265 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,6 +12,8 @@ specifiers: '@vue/eslint-config-typescript': ^10.0.0 '@vue/test-utils': ^2.0.0-rc.18 autoprefixer: 10.0.2 + date-fns: ^2.28.0 + date-fns-tz: ^1.2.2 eslint: ^7.32.0 eslint-config-standard: ^16.0.3 eslint-plugin-import: 2.22.1 @@ -35,6 +37,8 @@ specifiers: vue-tsc: ^0.29.8 dependencies: + date-fns: 2.28.0 + date-fns-tz: 1.2.2_date-fns@2.28.0 vue: 3.2.31 vue-i18n: 9.1.9_vue@3.2.31 vue-router: 4.0.12_vue@3.2.31 @@ -1255,6 +1259,19 @@ packages: whatwg-url: 10.0.0 dev: true + /date-fns-tz/1.2.2_date-fns@2.28.0: + resolution: {integrity: sha512-vWtn44eEqnLbkACb7T5G5gPgKR4nY8NkNMOCyoY49NsRGHrcDmY2aysCyzDeA+u+vcDBn/w6nQqEDyouRs4m8w==} + peerDependencies: + date-fns: '>=2.0.0' + dependencies: + date-fns: 2.28.0 + dev: false + + /date-fns/2.28.0: + resolution: {integrity: sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==} + engines: {node: '>=0.11'} + dev: false + /debug/2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} dependencies: diff --git a/src/components/AppHeader.vue b/src/components/AppHeader.vue index b7a696d..3dde71f 100644 --- a/src/components/AppHeader.vue +++ b/src/components/AppHeader.vue @@ -3,13 +3,13 @@ + class="text-xl text-stone-400 hover:text-cyan-500 transition-opacity duration-200">

Numbers

- + diff --git a/src/composables/game-state.ts b/src/composables/game-state.ts index 4a1ce82..1d3a4c5 100644 --- a/src/composables/game-state.ts +++ b/src/composables/game-state.ts @@ -1,8 +1,11 @@ +import { differenceInDays } from 'date-fns' +import { utcToZonedTime } from 'date-fns-tz' import { computed, reactive, ref } from 'vue' -import { getEmptyOperation, isOperationReady } from '../algo' -import { GameState } from '../globals' -import { Operation, Plaquette } from '../types' +import { getEmptyOperation, isOperationReady } from '@/algo' +import { BXL_TZ, GAME_STARTING_DATE, GameState } from '@/globals' +import { Operation, Plaquette } from '@/types' +import { getCurrentDate } from '@/utils' export const gameState = ref(GameState.Undefined) @@ -25,3 +28,11 @@ export const isEndGame = computed( export const isResultPerfect = computed( () => !!operations.some(o => o.result?.value === result.value), ) + +export function numberOfGamesSinceStart(): number { + const startDate = utcToZonedTime( + new Date(GAME_STARTING_DATE as string), + BXL_TZ, + ) + return differenceInDays(startDate, getCurrentDate()) +} diff --git a/src/globals.ts b/src/globals.ts index ab4157d..c01e8c3 100644 --- a/src/globals.ts +++ b/src/globals.ts @@ -1,3 +1,6 @@ +export const GAME_STARTING_DATE = '2022-02-18' +export const BXL_TZ = 'Europe/Brussels' + export const operators = ['+', '-', '*', '/'] as const export const pools = { diff --git a/src/utils.ts b/src/utils.ts index 3d20c8d..d3f5c7b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,17 +1,37 @@ +import { utcToZonedTime } from 'date-fns-tz' + +import { BXL_TZ, GAME_STARTING_DATE } from '@/globals' + +export function getCurrentDate(): Date { + return utcToZonedTime(new Date(), BXL_TZ) +} + +// #region RNG + +export let random = Math.random + /** * * @param min inclusive * @param max exclusive * @returns */ -export function randRange(min: number, max: number, rnd = Math.random): number { +export function randRange(min: number, max: number, rnd = random): number { return Math.floor(rnd() * (max - min) + min) } -export function randItem(items: T[], rnd = Math.random): T { +export function randItem(items: T[], rnd = random): T { return items[Math.floor(rnd() * items.length)] } +export function setMathPRNG(): void { + random = Math.random +} + +export function setDailyPRNG(): void { + random = initDailyPRNG() +} + /** * Shuffles an array in place and returns it * @param array @@ -19,12 +39,12 @@ export function randItem(items: T[], rnd = Math.random): T { */ export function shuffle(array: T[]): T[] { let currentIndex = array.length - let randomIndex + let randomIndex: number // While there remain elements to shuffle... while (currentIndex !== 0) { // Pick a remaining element... - randomIndex = Math.floor(Math.random() * currentIndex) + randomIndex = Math.floor(random() * currentIndex) currentIndex-- // And swap it with the current element. @@ -36,3 +56,38 @@ export function shuffle(array: T[]): T[] { return array } + +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 + GAME_STARTING_DATE) + return mulberry32(hashed) +} + +function mulberry32(a: number) { + return function () { + let t = (a += 0x6d2b79f5) + t = Math.imul(t ^ (t >>> 15), t | 1) + t ^= t + Math.imul(t ^ (t >>> 7), t | 61) + return ((t ^ (t >>> 14)) >>> 0) / 4294967296 + } +} + +/** + * https://stackoverflow.com/a/7616484 + * @param val + * @returns + */ +function hashStr(val: string): number { + let hash = 0 + if (val.length === 0) { + return 0 + } + for (let i = 0; i < val.length; ++i) { + hash = (hash << 5) - hash + val[i].charCodeAt(0) + hash |= 0 + } + return hash +} + +// #endregion RNG diff --git a/src/views/GameView.vue b/src/views/GameView.vue index 6fd1d25..c568231 100644 --- a/src/views/GameView.vue +++ b/src/views/GameView.vue @@ -10,7 +10,7 @@
+ v-if="gameState <= GameState.Waiting">
Le défi quotidien vous propose un résultat différent chaque jour, commun à tous les joueurs.
@@ -89,7 +89,7 @@ import { } from '@/composables/game-state' import { GameState, pools } from '@/globals' import { OperatorType, Plaquette } from '@/types' -import { randItem, randRange } from '@/utils' +import { randItem, random, randRange, setDailyPRNG, setMathPRNG } from '@/utils' const { t } = useI18n() // call `useI18n`, and spread `t` from `useI18n` returning @@ -99,7 +99,7 @@ const { t } = useI18n() // call `useI18n`, and spread `t` from `useI18n` return const difficultyLevel = ref<1 | 2>(1) -const isDaily = computed(() => useRoute().name === 'daily') +const isDaily = computed(() => useRoute()?.name === 'daily') const shownPlaquettes = computed(() => gameIsRunning.value ? plaquettes.value : [], ) @@ -157,6 +157,10 @@ function selectOperator(o: OperatorType): void { function reboot(): void { gameState.value = GameState.Playing + + // The daily number is >= 500 to have a minimum challenge + const minValue = isDaily.value ? 500 : 101 + do { // Find a problem // result.value = randRange(101, 1000) @@ -164,9 +168,9 @@ function reboot(): void { result.value = (() => { switch (difficultyLevel.value) { case 1: - return randRange(101, 1000) + return randRange(minValue, 1000) case 2: - return randRange(101, 1000) + return randRange(minValue, 1000) } })() // result.value = 29 @@ -178,8 +182,8 @@ function reboot(): void { plaquettes.value = [] const poolCopy = [...pools[difficultyLevel.value]] for (let i = 0; i < 6; ++i) { - const random = Math.floor(Math.random() * poolCopy.length) - const el = poolCopy.splice(random, 1)[0] + const rndItem = Math.floor(random() * poolCopy.length) + const el = poolCopy.splice(rndItem, 1)[0] plaquettes.value.push({ value: el, free: true }) } // plaquettes.value = [4, 8, 10, 25, 50, 100].map(value => ({ @@ -201,9 +205,18 @@ function reboot(): void { */ onMounted(() => { - // reboot() - // gameState.value = GameState.Waiting + if (isDaily.value) { + console.log('daily rng') + setDailyPRNG() + } + else { + console.log('normal rng') + setMathPRNG() + } + result.value = 0 + + // Wait until after the page transion to generate the number setTimeout(() => { reboot() gameState.value = GameState.Waiting