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"
|
"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"
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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())
|
||||||
|
}
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
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 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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user