Normal and Hard modes
This commit is contained in:
parent
1909b75663
commit
80064a98e5
|
@ -147,7 +147,7 @@ export function isSolvable(result: number, plaquettes: number[]): boolean {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (histories.length) {
|
if (histories.length && import.meta.env.DEV) {
|
||||||
printHistory(histories[0])
|
printHistory(histories[0])
|
||||||
}
|
}
|
||||||
return found
|
return found
|
||||||
|
|
|
@ -72,7 +72,7 @@ import {
|
||||||
result,
|
result,
|
||||||
} from '@/composables/game-state'
|
} from '@/composables/game-state'
|
||||||
import { operatorIcons } from '@/composables/operators'
|
import { operatorIcons } from '@/composables/operators'
|
||||||
import { GameState } from '@/globals'
|
import { GameStateType } from '@/globals'
|
||||||
import { Operation } from '@/types'
|
import { Operation } from '@/types'
|
||||||
import IconEquals from '~icons/ph/equals-bold'
|
import IconEquals from '~icons/ph/equals-bold'
|
||||||
import IconSad from '~icons/ph/smiley-sad'
|
import IconSad from '~icons/ph/smiley-sad'
|
||||||
|
@ -85,7 +85,7 @@ function canOperationBeDeleted(op: Operation): boolean {
|
||||||
}
|
}
|
||||||
|
|
||||||
function undoOperation(index: number): void {
|
function undoOperation(index: number): void {
|
||||||
if (gameState.value !== GameState.Playing) return
|
if (gameState.value !== GameStateType.Playing) return
|
||||||
const len = operations.length
|
const len = operations.length
|
||||||
for (let i = len - 1; i >= index; --i) {
|
for (let i = len - 1; i >= index; --i) {
|
||||||
let popped: Operation
|
let popped: Operation
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="grid grid-cols-6 grid-rows-2 gap-2 justify-center px-2 mx-auto max-w-sm font-mono">
|
class="grid grid-rows-2 gap-2 justify-center px-2 mx-auto max-w-sm font-mono"
|
||||||
|
:class="{
|
||||||
|
'grid-cols-6': startingNumberOfPlaquettes === 6,
|
||||||
|
'grid-cols-5': startingNumberOfPlaquettes === 5,
|
||||||
|
}">
|
||||||
<TransitionGroup name="slide_left">
|
<TransitionGroup name="slide_left">
|
||||||
<PlaquetteBox
|
<PlaquetteBox
|
||||||
v-for="(item, i) in plaquettes"
|
v-for="(item, i) in plaquettes"
|
||||||
|
@ -25,6 +29,10 @@
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
import PlaquetteBox from '@/components/common/PlaquetteBox.vue'
|
import PlaquetteBox from '@/components/common/PlaquetteBox.vue'
|
||||||
|
import {
|
||||||
|
maxNumberOfOperations,
|
||||||
|
startingNumberOfPlaquettes,
|
||||||
|
} from '@/composables/game-state'
|
||||||
import { Plaquette } from '@/types'
|
import { Plaquette } from '@/types'
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|
|
@ -3,25 +3,45 @@ 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 { BXL_TZ, GAME_STARTING_DATE, GameState } from '@/globals'
|
import {
|
||||||
|
BXL_TZ,
|
||||||
|
GAME_STARTING_DATE,
|
||||||
|
GameDifficultyType,
|
||||||
|
GameStateType,
|
||||||
|
} from '@/globals'
|
||||||
import { Operation, Plaquette } from '@/types'
|
import { Operation, Plaquette } from '@/types'
|
||||||
import { getCurrentDate } from '@/utils'
|
import { getCurrentDate } from '@/utils'
|
||||||
|
|
||||||
export const gameState = ref(GameState.Undefined)
|
export const gameState = ref(GameStateType.Undefined)
|
||||||
|
|
||||||
export const operations = reactive<Operation[]>([getEmptyOperation()])
|
export const operations = reactive<Operation[]>([getEmptyOperation()])
|
||||||
export const plaquettes = ref<Plaquette[]>([])
|
export const plaquettes = ref<Plaquette[]>([])
|
||||||
|
|
||||||
export const result = ref(0)
|
export const result = ref(0)
|
||||||
|
|
||||||
|
// #region Difficulty-related constants
|
||||||
|
|
||||||
|
export const gameDifficulty = ref(GameDifficultyType.Normal)
|
||||||
|
|
||||||
|
export const maxNumberOfOperations = computed(() =>
|
||||||
|
gameDifficulty.value === GameDifficultyType.Hard ? 5 : 4,
|
||||||
|
)
|
||||||
|
|
||||||
|
export const startingNumberOfPlaquettes = computed(() =>
|
||||||
|
gameDifficulty.value === GameDifficultyType.Hard ? 6 : 5,
|
||||||
|
)
|
||||||
|
|
||||||
|
// #endregion Difficulty-related constants
|
||||||
|
|
||||||
export const currentOperation = computed(
|
export const currentOperation = computed(
|
||||||
() => operations[operations.length - 1],
|
() => operations[operations.length - 1],
|
||||||
)
|
)
|
||||||
|
|
||||||
export const gameIsRunning = computed(() => gameState.value > GameState.Loading)
|
export const gameIsRunning = computed(
|
||||||
|
() => gameState.value > GameStateType.Loading,
|
||||||
|
)
|
||||||
export const isEndGame = computed(
|
export const isEndGame = computed(
|
||||||
() =>
|
() =>
|
||||||
(operations.length === 5 && isOperationReady(currentOperation.value)) ||
|
(operations.length === maxNumberOfOperations.value &&
|
||||||
|
isOperationReady(currentOperation.value)) ||
|
||||||
isResultPerfect.value,
|
isResultPerfect.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -13,9 +13,14 @@ export const pools = {
|
||||||
3: [5, 5, 5, 5, 5, 2, 2, 2, 2, 2],
|
3: [5, 5, 5, 5, 5, 2, 2, 2, 2, 2],
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export enum GameState {
|
export enum GameStateType {
|
||||||
Undefined = 0,
|
Undefined = 0,
|
||||||
Waiting = 1,
|
Waiting = 1,
|
||||||
Loading = 2,
|
Loading = 2,
|
||||||
Playing = 3,
|
Playing = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum GameDifficultyType {
|
||||||
|
Normal = 1,
|
||||||
|
Hard = 2,
|
||||||
|
}
|
||||||
|
|
|
@ -15,5 +15,7 @@
|
||||||
"gameDescription": "Combine the numbers to reach the result.",
|
"gameDescription": "Combine the numbers to reach the result.",
|
||||||
"randomDescription": "A challenge at random, just for fun.",
|
"randomDescription": "A challenge at random, just for fun.",
|
||||||
"copiedInClipboard": "Copied in clipboard",
|
"copiedInClipboard": "Copied in clipboard",
|
||||||
"soon": "Soon"
|
"soon": "Soon",
|
||||||
|
"dailyHard": "Hard Mode",
|
||||||
|
"dailyNormal": "Normal Mode"
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,5 +15,7 @@
|
||||||
"share": "Partager",
|
"share": "Partager",
|
||||||
"finishDailyToPlayRandom": "Terminez le défi quotidien pour débloquer.",
|
"finishDailyToPlayRandom": "Terminez le défi quotidien pour débloquer.",
|
||||||
"copiedInClipboard": "Copié dans le presse-papier",
|
"copiedInClipboard": "Copié dans le presse-papier",
|
||||||
"soon": "Bientôt"
|
"soon": "Bientôt",
|
||||||
|
"dailyNormal": "Mode Normal",
|
||||||
|
"dailyHard": "Mode Difficile"
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,14 +12,14 @@ const router = createRouter({
|
||||||
meta: { transition: '' },
|
meta: { transition: '' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/daily',
|
path: '/hard',
|
||||||
name: 'daily',
|
name: 'hard',
|
||||||
component: () => import('../views/GameView.vue'),
|
component: () => import('../views/GameView.vue'),
|
||||||
meta: { transition: 'route' },
|
meta: { transition: 'route' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/random',
|
path: '/normal',
|
||||||
name: 'random',
|
name: 'normal',
|
||||||
component: () => import('../views/GameView.vue'),
|
component: () => import('../views/GameView.vue'),
|
||||||
meta: { transition: 'route' },
|
meta: { transition: 'route' },
|
||||||
},
|
},
|
||||||
|
|
20
src/types.ts
20
src/types.ts
|
@ -18,3 +18,23 @@ export type GameStats = {
|
||||||
nbGames: number
|
nbGames: number
|
||||||
games: { [key: string]: { score: number; won: boolean } }
|
games: { [key: string]: { score: number; won: boolean } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
type SerializableOperation = {
|
||||||
|
left: number
|
||||||
|
right: number
|
||||||
|
operator: OperatorType
|
||||||
|
}
|
||||||
|
|
||||||
|
type SrGame = {
|
||||||
|
type: 'daily' | 'random'
|
||||||
|
over: boolean
|
||||||
|
won: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GameState = {
|
||||||
|
games: SrGame[]
|
||||||
|
}
|
||||||
|
|
|
@ -77,7 +77,7 @@ export function shuffle<T>(array: T[]): T[] {
|
||||||
|
|
||||||
function initDailyPRNG(): () => number {
|
function initDailyPRNG(): () => number {
|
||||||
// Prefix the seed when in dev to avoid spoiling myself while working on it
|
// Prefix the seed when in dev to avoid spoiling myself while working on it
|
||||||
const prefix = import.meta.env.DEV ? 'dev-' : ''
|
const prefix = ''// import.meta.env.DEV ? 'dev-' : ''
|
||||||
const hashed = hashStr(prefix + getCurrentSessionKey())
|
const hashed = hashStr(prefix + getCurrentSessionKey())
|
||||||
return mulberry32(hashed)
|
return mulberry32(hashed)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,13 @@
|
||||||
<Transition name="zero_height">
|
<Transition name="zero_height">
|
||||||
<div
|
<div
|
||||||
class="text-center"
|
class="text-center"
|
||||||
v-if="gameState <= GameState.Waiting">
|
v-if="gameState <= GameStateType.Waiting">
|
||||||
<div v-if="isDaily">
|
{{ t('dailyDescription') }}<br><br>
|
||||||
{{ t('dailyDescription') }}<br>
|
<div v-if="isHardMode">
|
||||||
|
{{ t('hardModeDescription') }}
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
{{ t('normalModeDescription') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
@ -54,13 +58,6 @@
|
||||||
v-else
|
v-else
|
||||||
v-html="t('endGame.failureLabel')" />
|
v-html="t('endGame.failureLabel')" />
|
||||||
<button
|
<button
|
||||||
v-if="!isDaily"
|
|
||||||
class="btn-border"
|
|
||||||
@click="reboot">
|
|
||||||
{{ t('playAgain') }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-else
|
|
||||||
@click="toClipboard"
|
@click="toClipboard"
|
||||||
class="inline-flex items-center btn-border">
|
class="inline-flex items-center btn-border">
|
||||||
<IconShare class="mr-2" />
|
<IconShare class="mr-2" />
|
||||||
|
@ -74,7 +71,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getEmptyOperation,
|
getEmptyOperation,
|
||||||
|
@ -90,6 +87,7 @@ import PlaquettesList from '@/components/PlaquettesList.vue'
|
||||||
import {
|
import {
|
||||||
clearOperationsList,
|
clearOperationsList,
|
||||||
currentOperation,
|
currentOperation,
|
||||||
|
gameDifficulty,
|
||||||
gameIsRunning,
|
gameIsRunning,
|
||||||
gameState,
|
gameState,
|
||||||
isEndGame,
|
isEndGame,
|
||||||
|
@ -100,7 +98,7 @@ import {
|
||||||
} from '@/composables/game-state'
|
} from '@/composables/game-state'
|
||||||
import { toClipboard } from '@/composables/sharing'
|
import { toClipboard } from '@/composables/sharing'
|
||||||
import { hasPlayed } from '@/composables/statistics'
|
import { hasPlayed } from '@/composables/statistics'
|
||||||
import { GameState, pools } from '@/globals'
|
import { GameDifficultyType, GameStateType, pools } from '@/globals'
|
||||||
import { OperatorType, Plaquette } from '@/types'
|
import { OperatorType, Plaquette } from '@/types'
|
||||||
import {
|
import {
|
||||||
getCurrentSessionKey,
|
getCurrentSessionKey,
|
||||||
|
@ -118,10 +116,12 @@ const { t } = useI18n() // call `useI18n`, and spread `t` from `useI18n` return
|
||||||
* Computed & refs
|
* Computed & refs
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const pool = ref<1 | 2 | 3>(1)
|
const poolType = ref<1 | 2 | 3>(1)
|
||||||
const cmpPlaquettes = ref<InstanceType<typeof PlaquettesList> | null>(null)
|
const cmpPlaquettes = ref<InstanceType<typeof PlaquettesList> | null>(null)
|
||||||
|
|
||||||
const isDaily = computed(() => useRoute()?.name === 'daily')
|
const isHardMode = computed(
|
||||||
|
() => gameDifficulty.value === GameDifficultyType.Hard,
|
||||||
|
)
|
||||||
const shownPlaquettes = computed(() =>
|
const shownPlaquettes = computed(() =>
|
||||||
gameIsRunning.value ? plaquettes.value : [],
|
gameIsRunning.value ? plaquettes.value : [],
|
||||||
)
|
)
|
||||||
|
@ -148,8 +148,8 @@ watch(
|
||||||
)
|
)
|
||||||
|
|
||||||
function startGame(): void {
|
function startGame(): void {
|
||||||
gameState.value = GameState.Loading
|
gameState.value = GameStateType.Loading
|
||||||
setTimeout(() => (gameState.value = GameState.Playing), 500)
|
setTimeout(() => (gameState.value = GameStateType.Playing), 500)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (cmpPlaquettes.value) {
|
if (cmpPlaquettes.value) {
|
||||||
// Remove the animation delay for the plaquettes,
|
// Remove the animation delay for the plaquettes,
|
||||||
|
@ -185,15 +185,15 @@ function selectOperator(o: OperatorType): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
function reboot(): void {
|
function reboot(): void {
|
||||||
gameState.value = GameState.Playing
|
gameState.value = GameStateType.Playing
|
||||||
|
|
||||||
// The daily number is >= 500 to have a minimum challenge
|
// The daily number is >= 500 to have a minimum challenge
|
||||||
const minValue = isDaily.value ? 500 : 101
|
const minValue = isHardMode.value ? 500 : 101
|
||||||
|
const numPlaquettes = isHardMode.value ? 6 : 5
|
||||||
do {
|
do {
|
||||||
// Find a problem
|
// Find a problem
|
||||||
// result.value = randRange(101, 1000)
|
// result.value = randRange(101, 1000)
|
||||||
pool.value = randItem([1, 1, 1, 1, 1, 2, 3])
|
poolType.value = randItem([1, 1, 1, 1, 1, 2, 3])
|
||||||
result.value = randRange(minValue, 1000)
|
result.value = randRange(minValue, 1000)
|
||||||
// result.value = 29
|
// result.value = 29
|
||||||
// Reset Operations list
|
// Reset Operations list
|
||||||
|
@ -201,8 +201,8 @@ function reboot(): void {
|
||||||
|
|
||||||
// Generate result and plaquettes
|
// Generate result and plaquettes
|
||||||
plaquettes.value = []
|
plaquettes.value = []
|
||||||
const poolCopy = [...pools[pool.value]]
|
const poolCopy = [...pools[poolType.value]]
|
||||||
for (let i = 0; i < 6; ++i) {
|
for (let i = 0; i < numPlaquettes; ++i) {
|
||||||
const rndItem = Math.floor(random() * poolCopy.length)
|
const rndItem = Math.floor(random() * poolCopy.length)
|
||||||
const el = poolCopy.splice(rndItem, 1)[0]
|
const el = poolCopy.splice(rndItem, 1)[0]
|
||||||
plaquettes.value.push({ value: el, free: true })
|
plaquettes.value.push({ value: el, free: true })
|
||||||
|
@ -213,7 +213,7 @@ function reboot(): void {
|
||||||
// }))
|
// }))
|
||||||
// Solve it
|
// Solve it
|
||||||
} while (
|
} while (
|
||||||
(isDaily.value ? !isHardEnough(result.value) : false) ||
|
(isHardMode.value ? !isHardEnough(result.value) : false) ||
|
||||||
!isSolvable(
|
!isSolvable(
|
||||||
result.value,
|
result.value,
|
||||||
plaquettes.value.map(p => p.value),
|
plaquettes.value.map(p => p.value),
|
||||||
|
@ -227,33 +227,24 @@ function reboot(): void {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// if (!isDaily.value && !hasPlayed(getCurrentSessionKey())) {
|
gameDifficulty.value =
|
||||||
// const router = useRouter()
|
useRoute()?.name === 'hard'
|
||||||
// router.replace({ name: 'home' })
|
? GameDifficultyType.Hard
|
||||||
// return
|
: GameDifficultyType.Normal
|
||||||
// }
|
|
||||||
|
|
||||||
if (isDaily.value) {
|
|
||||||
console.log('daily rng')
|
|
||||||
setDailyPRNG()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.log('normal rng')
|
|
||||||
setMathPRNG()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
setDailyPRNG()
|
||||||
result.value = 0
|
result.value = 0
|
||||||
|
|
||||||
// Wait until after the page transion to generate the number
|
// Wait until after the page transion to generate the number
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
reboot()
|
reboot()
|
||||||
gameState.value = GameState.Waiting
|
gameState.value = GameStateType.Waiting
|
||||||
}, 800)
|
}, 800)
|
||||||
// But make sure the operations list is empty asap
|
// But make sure the operations list is empty asap
|
||||||
clearOperationsList()
|
clearOperationsList()
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
gameState.value = GameState.Waiting
|
gameState.value = GameStateType.Waiting
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -2,47 +2,35 @@
|
||||||
<div class="flex flex-col grow p-1 w-full text-center">
|
<div class="flex flex-col grow p-1 w-full text-center">
|
||||||
<div class="flex flex-col grow gap-16 justify-center md:mt-16">
|
<div class="flex flex-col grow gap-16 justify-center md:mt-16">
|
||||||
<div>
|
<div>
|
||||||
{{ t('gameDescription') }}<br>
|
{{ t('gameDescription') }}<br><br>
|
||||||
|
<span v-html="t('dailyDescription')" />
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
</div>
|
</div>
|
||||||
<!-- Daily -->
|
|
||||||
|
<!-- Normal -->
|
||||||
<div class="flex flex-col gap-4 items-center">
|
<div class="flex flex-col gap-4 items-center">
|
||||||
<RouterLink
|
<RouterLink
|
||||||
to="/daily"
|
to="/normal"
|
||||||
class="text-2xl btn-border">
|
class="text-2xl btn-border">
|
||||||
{{ t('dailyGame') }}
|
{{ t('dailyNormal') }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<span v-html="t('dailyDescription')" />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Random -->
|
<!-- Hard -->
|
||||||
<div class="flex flex-col gap-4 items-center">
|
<div class="flex flex-col gap-4 items-center">
|
||||||
<RouterLink
|
<RouterLink
|
||||||
:to="canPlayRandom ? '/random' : ''"
|
to="/hard"
|
||||||
class="text-2xl btn-border"
|
class="text-2xl btn-border">
|
||||||
:class="{ 'btn-disabled': !canPlayRandom }">
|
{{ t('dailyHard') }}
|
||||||
{{ t('randomGame') }}
|
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
{{ t('soon') }} ™
|
|
||||||
<!-- {{ t('randomDescription') }} -->
|
|
||||||
<!-- <span
|
|
||||||
class="italic"
|
|
||||||
v-if="!canPlayRandom">{{
|
|
||||||
t('finishDailyToPlayRandom')
|
|
||||||
}}
|
|
||||||
</span> -->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
import { hasPlayed } from '@/composables/statistics'
|
|
||||||
import { getCurrentSessionKey } from '@/utils'
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const canPlayRandom = computed(() => hasPlayed(getCurrentSessionKey()))
|
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user