Daily challenge

This commit is contained in:
Simon Cambier 2022-02-18 19:28:52 +01:00
parent 9de2e42c42
commit 5029d1da9c
7 changed files with 119 additions and 18 deletions

View File

@ -11,6 +11,8 @@
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
}, },
"dependencies": { "dependencies": {
"date-fns": "^2.28.0",
"date-fns-tz": "^1.2.2",
"vue": "^3.2.31", "vue": "^3.2.31",
"vue-i18n": "^9.1.9", "vue-i18n": "^9.1.9",
"vue-router": "^4.0.12" "vue-router": "^4.0.12"

View File

@ -12,6 +12,8 @@ specifiers:
'@vue/eslint-config-typescript': ^10.0.0 '@vue/eslint-config-typescript': ^10.0.0
'@vue/test-utils': ^2.0.0-rc.18 '@vue/test-utils': ^2.0.0-rc.18
autoprefixer: 10.0.2 autoprefixer: 10.0.2
date-fns: ^2.28.0
date-fns-tz: ^1.2.2
eslint: ^7.32.0 eslint: ^7.32.0
eslint-config-standard: ^16.0.3 eslint-config-standard: ^16.0.3
eslint-plugin-import: 2.22.1 eslint-plugin-import: 2.22.1
@ -35,6 +37,8 @@ specifiers:
vue-tsc: ^0.29.8 vue-tsc: ^0.29.8
dependencies: dependencies:
date-fns: 2.28.0
date-fns-tz: 1.2.2_date-fns@2.28.0
vue: 3.2.31 vue: 3.2.31
vue-i18n: 9.1.9_vue@3.2.31 vue-i18n: 9.1.9_vue@3.2.31
vue-router: 4.0.12_vue@3.2.31 vue-router: 4.0.12_vue@3.2.31
@ -1255,6 +1259,19 @@ packages:
whatwg-url: 10.0.0 whatwg-url: 10.0.0
dev: true 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: /debug/2.6.9:
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
dependencies: dependencies:

View File

@ -3,13 +3,13 @@
<RouterLink <RouterLink
:class="{ 'opacity-0': isHome }" :class="{ 'opacity-0': isHome }"
to="/" to="/"
class="text-xl text-stone-400 transition-opacity"> class="text-xl text-stone-400 hover:text-cyan-500 transition-opacity duration-200">
<IconHouse /> <IconHouse />
</RouterLink> </RouterLink>
<h1 class="py-2 text-3xl text-center"> <h1 class="py-2 text-3xl text-center">
Numbers Numbers
</h1> </h1>
<IconMenu class="text-xl" /> <IconMenu class="text-xl text-stone-400 hover:text-cyan-500 transition-opacity" />
</div> </div>
</template> </template>

View File

@ -1,8 +1,11 @@
import { differenceInDays } from 'date-fns'
import { utcToZonedTime } from 'date-fns-tz'
import { computed, reactive, ref } from 'vue' import { computed, reactive, ref } from 'vue'
import { getEmptyOperation, isOperationReady } from '../algo' import { getEmptyOperation, isOperationReady } from '@/algo'
import { GameState } from '../globals' import { BXL_TZ, GAME_STARTING_DATE, GameState } from '@/globals'
import { Operation, Plaquette } from '../types' import { Operation, Plaquette } from '@/types'
import { getCurrentDate } from '@/utils'
export const gameState = ref(GameState.Undefined) export const gameState = ref(GameState.Undefined)
@ -25,3 +28,11 @@ export const isEndGame = computed(
export const isResultPerfect = computed( export const isResultPerfect = computed(
() => !!operations.some(o => o.result?.value === result.value), () => !!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())
}

View File

@ -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 operators = ['+', '-', '*', '/'] as const
export const pools = { export const pools = {

View File

@ -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 min inclusive
* @param max exclusive * @param max exclusive
* @returns * @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) return Math.floor(rnd() * (max - min) + min)
} }
export function randItem<T>(items: T[], rnd = Math.random): T { export function randItem<T>(items: T[], rnd = random): T {
return items[Math.floor(rnd() * items.length)] 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 * Shuffles an array in place and returns it
* @param array * @param array
@ -19,12 +39,12 @@ export function randItem<T>(items: T[], rnd = Math.random): T {
*/ */
export function shuffle<T>(array: T[]): T[] { export function shuffle<T>(array: T[]): T[] {
let currentIndex = array.length let currentIndex = array.length
let randomIndex let randomIndex: number
// While there remain elements to shuffle... // While there remain elements to shuffle...
while (currentIndex !== 0) { while (currentIndex !== 0) {
// Pick a remaining element... // Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex) randomIndex = Math.floor(random() * currentIndex)
currentIndex-- currentIndex--
// And swap it with the current element. // And swap it with the current element.
@ -36,3 +56,38 @@ export function shuffle<T>(array: T[]): T[] {
return array 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

View File

@ -10,7 +10,7 @@
<Transition name="zero_height"> <Transition name="zero_height">
<div <div
class="text-center" class="text-center"
v-if="gameState === GameState.Waiting"> v-if="gameState <= GameState.Waiting">
<div v-if="isDaily"> <div v-if="isDaily">
Le défi quotidien vous propose un résultat différent chaque jour, Le défi quotidien vous propose un résultat différent chaque jour,
commun à tous les joueurs.<br> commun à tous les joueurs.<br>
@ -89,7 +89,7 @@ import {
} from '@/composables/game-state' } from '@/composables/game-state'
import { GameState, pools } from '@/globals' import { GameState, pools } from '@/globals'
import { OperatorType, Plaquette } from '@/types' 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 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 difficultyLevel = ref<1 | 2>(1)
const isDaily = computed(() => useRoute().name === 'daily') const isDaily = computed(() => useRoute()?.name === 'daily')
const shownPlaquettes = computed(() => const shownPlaquettes = computed(() =>
gameIsRunning.value ? plaquettes.value : [], gameIsRunning.value ? plaquettes.value : [],
) )
@ -157,6 +157,10 @@ function selectOperator(o: OperatorType): void {
function reboot(): void { function reboot(): void {
gameState.value = GameState.Playing gameState.value = GameState.Playing
// The daily number is >= 500 to have a minimum challenge
const minValue = isDaily.value ? 500 : 101
do { do {
// Find a problem // Find a problem
// result.value = randRange(101, 1000) // result.value = randRange(101, 1000)
@ -164,9 +168,9 @@ function reboot(): void {
result.value = (() => { result.value = (() => {
switch (difficultyLevel.value) { switch (difficultyLevel.value) {
case 1: case 1:
return randRange(101, 1000) return randRange(minValue, 1000)
case 2: case 2:
return randRange(101, 1000) return randRange(minValue, 1000)
} }
})() })()
// result.value = 29 // result.value = 29
@ -178,8 +182,8 @@ function reboot(): void {
plaquettes.value = [] plaquettes.value = []
const poolCopy = [...pools[difficultyLevel.value]] const poolCopy = [...pools[difficultyLevel.value]]
for (let i = 0; i < 6; ++i) { for (let i = 0; i < 6; ++i) {
const random = Math.floor(Math.random() * poolCopy.length) const rndItem = Math.floor(random() * poolCopy.length)
const el = poolCopy.splice(random, 1)[0] const el = poolCopy.splice(rndItem, 1)[0]
plaquettes.value.push({ value: el, free: true }) plaquettes.value.push({ value: el, free: true })
} }
// plaquettes.value = [4, 8, 10, 25, 50, 100].map(value => ({ // plaquettes.value = [4, 8, 10, 25, 50, 100].map(value => ({
@ -201,9 +205,18 @@ function reboot(): void {
*/ */
onMounted(() => { onMounted(() => {
// reboot() if (isDaily.value) {
// gameState.value = GameState.Waiting console.log('daily rng')
setDailyPRNG()
}
else {
console.log('normal rng')
setMathPRNG()
}
result.value = 0 result.value = 0
// Wait until after the page transion to generate the number
setTimeout(() => { setTimeout(() => {
reboot() reboot()
gameState.value = GameState.Waiting gameState.value = GameState.Waiting