260 lines
6.0 KiB
Vue
260 lines
6.0 KiB
Vue
<template>
|
|
<div class="w-full">
|
|
<!-- Number to find -->
|
|
<div class="p-1 mx-auto mb-4 w-fit text-cyan-500">
|
|
<span class="font-mono text-4xl">{{ result ? result : '???' }}</span>
|
|
</div>
|
|
|
|
<!-- Start button -->
|
|
<!-- TODO: fix animation -->
|
|
<Transition name="zero_height">
|
|
<div
|
|
class="text-center"
|
|
v-if="gameState <= GameState.Waiting">
|
|
<div v-if="isDaily">
|
|
{{ t('dailyDescription') }}<br>
|
|
</div>
|
|
|
|
<button
|
|
@click="startGame"
|
|
class="py-2 px-4 mt-4 btn-border">
|
|
{{ t('startGame') }}
|
|
</button>
|
|
</div>
|
|
</Transition>
|
|
|
|
<!-- PLAQUETTES -->
|
|
<PlaquettesList
|
|
ref="cmpPlaquettes"
|
|
:plaquettes="shownPlaquettes"
|
|
@click-number="selectNumber" />
|
|
|
|
<Transition name="slide_up">
|
|
<div v-if="gameIsRunning">
|
|
<!-- OPERATORS -->
|
|
<OperatorsList @click="selectOperator" />
|
|
|
|
<!-- Divider -->
|
|
<div class="my-4 mx-auto max-w-sm border-b border-cyan-500/50" />
|
|
|
|
<!-- List of Operations -->
|
|
<OperationsList v-show="gameIsRunning" />
|
|
</div>
|
|
</Transition>
|
|
|
|
<Transition name="slide_up">
|
|
<div
|
|
v-if="isEndGame"
|
|
class="flex flex-row justify-evenly items-center mx-auto mt-8 max-w-sm">
|
|
<span
|
|
v-if="isResultPerfect"
|
|
v-html="t('endGame.victoryLabel')" />
|
|
<span
|
|
v-else
|
|
v-html="t('endGame.failureLabel')" />
|
|
<button
|
|
v-if="!isDaily"
|
|
class="btn-border"
|
|
@click="reboot">
|
|
{{ t('playAgain') }}
|
|
</button>
|
|
<button
|
|
v-else
|
|
class="btn-border">
|
|
{{ t('share') }}
|
|
</button>
|
|
</div>
|
|
</Transition>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
|
|
import {
|
|
getEmptyOperation,
|
|
isOperationReady,
|
|
isOperationResultValid,
|
|
isSolvable,
|
|
operate,
|
|
} from '@/algo'
|
|
import OperationsList from '@/components/OperationsList.vue'
|
|
import OperatorsList from '@/components/OperatorsList.vue'
|
|
import PlaquettesList from '@/components/PlaquettesList.vue'
|
|
import {
|
|
clearOperationsList,
|
|
currentOperation,
|
|
gameIsRunning,
|
|
gameState,
|
|
isEndGame,
|
|
isResultPerfect,
|
|
operations,
|
|
plaquettes,
|
|
result,
|
|
} from '@/composables/game-state'
|
|
import { hasPlayed } from '@/composables/statistics'
|
|
import { GameState, pools } from '@/globals'
|
|
import { OperatorType, Plaquette } from '@/types'
|
|
import {
|
|
getCurrentSessionKey,
|
|
randItem,
|
|
random,
|
|
randRange,
|
|
setDailyPRNG,
|
|
setMathPRNG,
|
|
} from '@/utils'
|
|
|
|
const { t } = useI18n() // call `useI18n`, and spread `t` from `useI18n` returning
|
|
|
|
/*
|
|
* Computed & refs
|
|
*/
|
|
|
|
const difficultyLevel = ref<1 | 2>(1)
|
|
const cmpPlaquettes = ref<InstanceType<typeof PlaquettesList>>()
|
|
|
|
const isDaily = computed(() => useRoute()?.name === 'daily')
|
|
const shownPlaquettes = computed(() =>
|
|
gameIsRunning.value ? plaquettes.value : [],
|
|
)
|
|
|
|
/*
|
|
* Watchers
|
|
*/
|
|
|
|
watch(
|
|
currentOperation,
|
|
op => {
|
|
if (isOperationReady(op) && isOperationResultValid(op) && !op.result) {
|
|
op.result = {
|
|
free: true,
|
|
value: operate(op.operator!, op.left!.value, op.right!.value),
|
|
}
|
|
if (operations.length < 5 && !isEndGame.value) {
|
|
plaquettes.value.push(op.result)
|
|
operations.push(getEmptyOperation())
|
|
}
|
|
}
|
|
},
|
|
{ deep: true },
|
|
)
|
|
|
|
function startGame(): void {
|
|
gameState.value = GameState.Loading
|
|
setTimeout(() => (gameState.value = GameState.Playing), 500)
|
|
setTimeout(() => {
|
|
if (cmpPlaquettes.value) {
|
|
// Remove the animation delay for the plaquettes,
|
|
// or else the mouse hover effect is delayed until the first click
|
|
cmpPlaquettes.value.initDelay = 0
|
|
}
|
|
}, 2000)
|
|
}
|
|
|
|
function selectNumber(p: Plaquette): void {
|
|
if (isEndGame.value) return
|
|
|
|
const op = currentOperation.value
|
|
if (!p.free) return
|
|
|
|
if (!op.left) {
|
|
op.left = p
|
|
p.free = false
|
|
}
|
|
else if (!op.right) {
|
|
op.right = p
|
|
p.free = false
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Functions
|
|
*/
|
|
|
|
function selectOperator(o: OperatorType): void {
|
|
if (isEndGame.value) return
|
|
currentOperation.value.operator = o
|
|
}
|
|
|
|
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)
|
|
difficultyLevel.value = randItem([1, 1, 1, 1, 2])
|
|
result.value = (() => {
|
|
switch (difficultyLevel.value) {
|
|
case 1:
|
|
return randRange(minValue, 1000)
|
|
case 2:
|
|
return randRange(minValue, 1000)
|
|
}
|
|
})()
|
|
// result.value = 29
|
|
// Reset Operations list
|
|
clearOperationsList()
|
|
|
|
// Generate result and plaquettes
|
|
plaquettes.value = []
|
|
const poolCopy = [...pools[difficultyLevel.value]]
|
|
for (let i = 0; i < 6; ++i) {
|
|
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 => ({
|
|
// free: true,
|
|
// value,
|
|
// }))
|
|
// Solve it
|
|
} while (
|
|
!isSolvable(
|
|
result.value,
|
|
plaquettes.value.map(p => p.value),
|
|
)
|
|
)
|
|
plaquettes.value.sort((a, b) => a.value - b.value)
|
|
}
|
|
|
|
/*
|
|
* Hooks
|
|
*/
|
|
|
|
onMounted(() => {
|
|
if (!isDaily.value && !hasPlayed(getCurrentSessionKey())) {
|
|
const router = useRouter()
|
|
router.replace({ name: 'home' })
|
|
return
|
|
}
|
|
|
|
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
|
|
}, 800)
|
|
// But make sure the operations list is empty asap
|
|
clearOperationsList()
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
gameState.value = GameState.Waiting
|
|
})
|
|
</script>
|