Bug and animations fixes

This commit is contained in:
Simon Cambier 2022-02-22 21:56:18 +01:00
parent cc86729dbc
commit d31e17e3be
13 changed files with 114 additions and 80 deletions

View File

@ -21,7 +21,7 @@
<script setup lang="ts">
import { onMounted } from 'vue'
import { darkMode, isDarkModeDefault } from '@/composables/settings'
import { darkMode } from '@/composables/settings'
import AppHeader from './components/AppHeader.vue'
import SideMenu from './components/SideMenu.vue'
@ -29,6 +29,6 @@ import SideMenu from './components/SideMenu.vue'
onMounted(() => {
darkMode.value = localStorage.getItem('n0_dark')
? localStorage.getItem('n0_dark') === 'true'
: isDarkModeDefault()
: true
})
</script>

View File

@ -141,7 +141,7 @@ export function isSolvable(result: number, plaquettes: number[]): boolean {
}
if (histories.length) {
// printHistory(histories[0])
printHistory(histories[0])
}
return found

View File

@ -12,7 +12,7 @@
</h1>
<button @click="isSideMenuVisible = true">
<IconMenu
class="text-xl hover:text-cyan-500 dark:text-stone-400 transition-opacity" />
class="text-xl btn" />
</button>
</div>
</template>

View File

@ -2,63 +2,61 @@
<div class="relative font-mono text-xl text-center">
<TransitionGroup name="slide_up">
<div
class=""
class="inline-block"
v-for="(op, i) in operations"
:style="{
transitionDelay: `${transDelay * (operations.length - i)}ms`,
}"
:key="i">
<div class="inline-block">
<!-- OPERATION LINE -->
<div
class="flex items-center pb-1 border-b border-stone-600 border-dashed md:py-2"
:class="{ 'text-red-500': isOperationInvalid(op) }">
<!-- LEFT OPERAND -->
<div class="w-[2.5em] transition-all">
{{ op.left?.value ?? '&nbsp;' }}
</div>
<!-- OPERATOR -->
<div class="flex justify-center w-[2.5em] transition-all">
<Component
class="text-lg text-stone-400"
v-if="op.operator"
:is="operatorIcons[op.operator]" />
<span v-else>&nbsp;</span>
</div>
<!-- RIGHT OPERAND -->
<div class="w-[2.5em] transition-all">
{{ op.right?.value ?? '&nbsp;' }}
</div>
<!-- EQUALS -->
<div class="mx-4 text-stone-400 border-none">
<IconEquals />
</div>
<!-- RESULT -->
<div class="w-[3.2em] transition-all">
<IconSad v-if="isOperationInvalid(op)" />
<span
:class="{ 'text-cyan-500': op.result.value === result }"
v-else-if="op.result">
{{ op.result?.value }}
</span>
<span v-else>?</span>
</div>
<!-- UNDO -->
<button
class="ml-4"
@click="undoOperation(i)">
<IconUndo
class="text-stone-600 hover:text-cyan-500"
:class="{
invisible: !canOperationBeDeleted(op),
}" />
</button>
<!-- OPERATION LINE -->
<div
class="flex items-center pb-1 border-b border-stone-600 border-dashed md:py-2"
:class="{ 'text-red-500': isOperationInvalid(op) }">
<!-- LEFT OPERAND -->
<div class="w-[2.5em] transition-all">
{{ op.left?.value ?? '&nbsp;' }}
</div>
<!-- OPERATOR -->
<div class="flex justify-center w-[2.5em] transition-all">
<Component
class="text-lg text-stone-400"
v-if="op.operator"
:is="operatorIcons[op.operator]" />
<span v-else>&nbsp;</span>
</div>
<!-- RIGHT OPERAND -->
<div class="w-[2.5em] transition-all">
{{ op.right?.value ?? '&nbsp;' }}
</div>
<!-- EQUALS -->
<div class="mx-4 text-stone-400 border-none">
<IconEquals />
</div>
<!-- RESULT -->
<div class="w-[3.2em] transition-all">
<IconSad v-if="isOperationInvalid(op)" />
<span
:class="{ 'text-cyan-500': op.result.value === result }"
v-else-if="op.result">
{{ op.result?.value }}
</span>
<span v-else>?</span>
</div>
<!-- UNDO -->
<button
class="ml-4"
@click="undoOperation(i)">
<IconUndo
class="text-stone-600 hover:text-cyan-500"
:class="{
invisible: !canOperationBeDeleted(op),
}" />
</button>
</div>
</div>
</TransitionGroup>
@ -93,9 +91,7 @@ function undoOperation(index: number): void {
let popped: Operation
if (i === index) {
popped = operations[index]
setTimeout(() => {
operations[index] = getEmptyOperation()
}, transDelay * l)
operations[index] = getEmptyOperation()
}
else {
popped = operations.pop()!

View File

@ -2,7 +2,7 @@
<div class="flex gap-2 justify-center my-4">
<PlaquetteBox
is="button"
class="aspect-square w-[1.5em] text-2xl border btn"
class="aspect-square w-[1.5em] text-2xl border btn-border"
@click="emit('click', item)"
v-for="(item, i) in operators"
:key="i">

View File

@ -9,7 +9,8 @@
@click="click(item)"
class="h-11 border"
:class="{
'dark:text-stone-600 dark:border-stone-600 text-stone-500 border-stone-400': !item.free,
'dark:text-stone-600 dark:border-stone-600 text-stone-500 border-stone-400':
!item.free,
'hover:border-cyan-500': item.free,
}"
:style="{ transitionDelay: `${initDelay * i}ms` }"
@ -40,4 +41,6 @@ function click(item: Plaquette): void {
initDelay.value = 0
emit('clickNumber', item)
}
defineExpose({ initDelay })
</script>

View File

@ -6,7 +6,7 @@
<div class="h-12">
<button @click="isSideMenuVisible = false">
<IconClose
class="absolute top-0 right-1 h-12 text-xl hover:text-cyan-500" />
class="absolute top-0 right-1 h-12 text-xl btn" />
</button>
</div>
<div class="flex-1">

View File

@ -36,3 +36,8 @@ export function numberOfGamesSinceStart(): number {
)
return differenceInDays(startDate, getCurrentDate())
}
export function clearOperationsList(): void {
operations.splice(0)
operations.push(getEmptyOperation())
}

View File

@ -59,17 +59,17 @@
.route-move,
.route-enter-active,
.route-leave-active {
transition: all .8s ease;
transition: all 1s ease;
}
.route-enter-from {
opacity: 0;
transform: translateX(100vw);
transform: translateX(100%);
}
.route-leave-to {
opacity: 0;
transform: translateX(-100vw);
transform: translateX(-100%);
}
.route-leave-active {
@ -82,17 +82,17 @@
.route_back-move,
.route_back-enter-active,
.route_back-leave-active {
transition: all .8s ease;
transition: all 1s ease;
}
.route_back-enter-from {
opacity: 0;
transform: translateX(-100vw);
transform: translateX(-100%);
}
.route_back-leave-to {
opacity: 0;
transform: translateX(100vw);
transform: translateX(100%);
}
.route_back-leave-active {
@ -128,9 +128,11 @@
.slide_up-enter-from,
.slide_up-leave-to {
opacity: 0;
transform: translateY(30px);
transform: translateY(60px);
}
/* ZERO HEIGHT */
.zero_height-enter-active,
.zero_height-leave-active {
transition: all 0.5s ease;
@ -144,10 +146,20 @@
@layer utils {
}
@layer components {
.btn {
@apply hover:text-cyan-500 dark:text-stone-400 dark:hover:text-cyan-500;
@apply transition-colors duration-200;
}
.btn-border {
@apply w-fit p-2;
@apply rounded border border-stone-600 transition-opacity;
@apply hover:border-cyan-500 hover:text-cyan-500;
@apply transition-colors duration-200;
}
.btn-disabled {
pointer-events: none;
opacity: 0.5;
}
}

View File

@ -2,12 +2,13 @@
"playAgain": "New number",
"easy": "easy",
"dailyGame": "Daily challenge\n",
"endGame.failureLabel": "Too bad...<br>Another game?",
"endGame.failureLabel": "Almost there...",
"endGame.victoryLabel": "Well done!",
"hard": "hard",
"impossible": "☠",
"medium": "medium",
"randomGame": "Random number",
"startGame": "Start",
"dailyDescription": "The daily challenge changes every day at midnight, and is common to all players."
"dailyDescription": "The daily challenge changes every day at midnight, and is common to all players.",
"share": "Share"
}

View File

@ -1,7 +1,7 @@
{
"playAgain": "Nouveau nombre",
"endGame.victoryLabel": "Bien joué !",
"endGame.failureLabel": "Dommage...<br>Une autre partie ?",
"endGame.failureLabel": "Presque...",
"startGame": "Démarrer",
"easy": "facile",
"medium": "moyen",
@ -11,5 +11,6 @@
"randomGame": "Nombre au hasard",
"gameDescription": "Combinez les nombres imposés afin d'atteindre le résultat, ou de vous en approcher le plus possible.",
"dailyDescription": "Le défi quotidien change chaque jour à minuit, et est commun à tous les joueurs.",
"randomDescription": "Une partie au hasard, pour le plaisir."
"randomDescription": "Une partie au hasard, pour le plaisir.",
"share": "Partager"
}

View File

@ -17,7 +17,7 @@
<button
@click="startGame"
class="py-2 px-4 mt-4 btn">
class="py-2 px-4 mt-4 btn-border">
{{ t('startGame') }}
</button>
</div>
@ -25,6 +25,7 @@
<!-- PLAQUETTES -->
<PlaquettesList
ref="cmpPlaquettes"
:plaquettes="shownPlaquettes"
@click-number="selectNumber" />
@ -34,8 +35,7 @@
<OperatorsList @click="selectOperator" />
<!-- Divider -->
<div
class="my-4 mx-auto max-w-sm border-b border-cyan-500/50" />
<div class="my-4 mx-auto max-w-sm border-b border-cyan-500/50" />
<!-- List of Operations -->
<OperationsList v-show="gameIsRunning" />
@ -53,10 +53,16 @@
v-else
v-html="t('endGame.failureLabel')" />
<button
class="btn"
v-if="!isDaily"
class="btn-border"
@click="reboot">
{{ t('playAgain') }}
</button>
<button
v-else
class="btn-border">
{{ t('share') }}
</button>
</div>
</Transition>
</div>
@ -78,6 +84,7 @@ import OperationsList from '@/components/OperationsList.vue'
import OperatorsList from '@/components/OperatorsList.vue'
import PlaquettesList from '@/components/PlaquettesList.vue'
import {
clearOperationsList,
currentOperation,
gameIsRunning,
gameState,
@ -98,6 +105,7 @@ const { t } = useI18n() // call `useI18n`, and spread `t` from `useI18n` return
*/
const difficultyLevel = ref<1 | 2>(1)
const cmpPlaquettes = ref<InstanceType<typeof PlaquettesList>>()
const isDaily = computed(() => useRoute()?.name === 'daily')
const shownPlaquettes = computed(() =>
@ -128,6 +136,13 @@ watch(
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 {
@ -175,8 +190,7 @@ function reboot(): void {
})()
// result.value = 29
// Reset Operations list
operations.splice(0)
operations.push(getEmptyOperation())
clearOperationsList()
// Generate result and plaquettes
plaquettes.value = []
@ -221,6 +235,8 @@ onMounted(() => {
reboot()
gameState.value = GameState.Waiting
}, 800)
// But make sure the operations list is empty asap
clearOperationsList()
})
onUnmounted(() => {

View File

@ -9,7 +9,7 @@
<div class="flex flex-col gap-4 items-center">
<RouterLink
to="/daily"
class="text-2xl btn">
class="text-2xl btn-border">
{{ t('dailyGame') }}
</RouterLink>
<span v-html="t('dailyDescription')" />
@ -19,7 +19,7 @@
<div class="flex flex-col gap-4 items-center">
<RouterLink
to="/random"
class="text-2xl btn">
class="text-2xl btn-border">
{{ t('randomGame') }}
</RouterLink>
{{ t('randomDescription') }}