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