Daily challenge
This commit is contained in:
parent
9de2e42c42
commit
5029d1da9c
|
@ -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"
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
<RouterLink
|
||||
:class="{ 'opacity-0': isHome }"
|
||||
to="/"
|
||||
class="text-xl text-stone-400 transition-opacity">
|
||||
class="text-xl text-stone-400 hover:text-cyan-500 transition-opacity duration-200">
|
||||
<IconHouse />
|
||||
</RouterLink>
|
||||
<h1 class="py-2 text-3xl text-center">
|
||||
Numbers
|
||||
</h1>
|
||||
<IconMenu class="text-xl" />
|
||||
<IconMenu class="text-xl text-stone-400 hover:text-cyan-500 transition-opacity" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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 = {
|
||||
|
|
63
src/utils.ts
63
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<T>(items: T[], rnd = Math.random): T {
|
||||
export function randItem<T>(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<T>(items: T[], rnd = Math.random): T {
|
|||
*/
|
||||
export function shuffle<T>(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<T>(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
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<Transition name="zero_height">
|
||||
<div
|
||||
class="text-center"
|
||||
v-if="gameState === GameState.Waiting">
|
||||
v-if="gameState <= GameState.Waiting">
|
||||
<div v-if="isDaily">
|
||||
Le défi quotidien vous propose un résultat différent chaque jour,
|
||||
commun à tous les joueurs.<br>
|
||||
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user