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])
|
||||
}
|
||||
return found
|
||||
|
|
|
@ -72,7 +72,7 @@ import {
|
|||
result,
|
||||
} from '@/composables/game-state'
|
||||
import { operatorIcons } from '@/composables/operators'
|
||||
import { GameState } from '@/globals'
|
||||
import { GameStateType } from '@/globals'
|
||||
import { Operation } from '@/types'
|
||||
import IconEquals from '~icons/ph/equals-bold'
|
||||
import IconSad from '~icons/ph/smiley-sad'
|
||||
|
@ -85,7 +85,7 @@ function canOperationBeDeleted(op: Operation): boolean {
|
|||
}
|
||||
|
||||
function undoOperation(index: number): void {
|
||||
if (gameState.value !== GameState.Playing) return
|
||||
if (gameState.value !== GameStateType.Playing) return
|
||||
const len = operations.length
|
||||
for (let i = len - 1; i >= index; --i) {
|
||||
let popped: Operation
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
<template>
|
||||
<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">
|
||||
<PlaquetteBox
|
||||
v-for="(item, i) in plaquettes"
|
||||
|
@ -25,6 +29,10 @@
|
|||
import { ref } from 'vue'
|
||||
|
||||
import PlaquetteBox from '@/components/common/PlaquetteBox.vue'
|
||||
import {
|
||||
maxNumberOfOperations,
|
||||
startingNumberOfPlaquettes,
|
||||
} from '@/composables/game-state'
|
||||
import { Plaquette } from '@/types'
|
||||
|
||||
defineProps<{
|
||||
|
|
|
@ -3,25 +3,45 @@ import { utcToZonedTime } from 'date-fns-tz'
|
|||
import { computed, reactive, ref } from 'vue'
|
||||
|
||||
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 { getCurrentDate } from '@/utils'
|
||||
|
||||
export const gameState = ref(GameState.Undefined)
|
||||
|
||||
export const gameState = ref(GameStateType.Undefined)
|
||||
export const operations = reactive<Operation[]>([getEmptyOperation()])
|
||||
export const plaquettes = ref<Plaquette[]>([])
|
||||
|
||||
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(
|
||||
() => operations[operations.length - 1],
|
||||
)
|
||||
|
||||
export const gameIsRunning = computed(() => gameState.value > GameState.Loading)
|
||||
export const gameIsRunning = computed(
|
||||
() => gameState.value > GameStateType.Loading,
|
||||
)
|
||||
export const isEndGame = computed(
|
||||
() =>
|
||||
(operations.length === 5 && isOperationReady(currentOperation.value)) ||
|
||||
(operations.length === maxNumberOfOperations.value &&
|
||||
isOperationReady(currentOperation.value)) ||
|
||||
isResultPerfect.value,
|
||||
)
|
||||
|
||||
|
|
|
@ -13,9 +13,14 @@ export const pools = {
|
|||
3: [5, 5, 5, 5, 5, 2, 2, 2, 2, 2],
|
||||
} as const
|
||||
|
||||
export enum GameState {
|
||||
export enum GameStateType {
|
||||
Undefined = 0,
|
||||
Waiting = 1,
|
||||
Loading = 2,
|
||||
Playing = 3,
|
||||
}
|
||||
|
||||
export enum GameDifficultyType {
|
||||
Normal = 1,
|
||||
Hard = 2,
|
||||
}
|
||||
|
|
|
@ -15,5 +15,7 @@
|
|||
"gameDescription": "Combine the numbers to reach the result.",
|
||||
"randomDescription": "A challenge at random, just for fun.",
|
||||
"copiedInClipboard": "Copied in clipboard",
|
||||
"soon": "Soon"
|
||||
"soon": "Soon",
|
||||
"dailyHard": "Hard Mode",
|
||||
"dailyNormal": "Normal Mode"
|
||||
}
|
||||
|
|
|
@ -15,5 +15,7 @@
|
|||
"share": "Partager",
|
||||
"finishDailyToPlayRandom": "Terminez le défi quotidien pour débloquer.",
|
||||
"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: '' },
|
||||
},
|
||||
{
|
||||
path: '/daily',
|
||||
name: 'daily',
|
||||
path: '/hard',
|
||||
name: 'hard',
|
||||
component: () => import('../views/GameView.vue'),
|
||||
meta: { transition: 'route' },
|
||||
},
|
||||
{
|
||||
path: '/random',
|
||||
name: 'random',
|
||||
path: '/normal',
|
||||
name: 'normal',
|
||||
component: () => import('../views/GameView.vue'),
|
||||
meta: { transition: 'route' },
|
||||
},
|
||||
|
|
20
src/types.ts
20
src/types.ts
|
@ -18,3 +18,23 @@ export type GameStats = {
|
|||
nbGames: number
|
||||
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 {
|
||||
// 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())
|
||||
return mulberry32(hashed)
|
||||
}
|
||||
|
|
|
@ -10,9 +10,13 @@
|
|||
<Transition name="zero_height">
|
||||
<div
|
||||
class="text-center"
|
||||
v-if="gameState <= GameState.Waiting">
|
||||
<div v-if="isDaily">
|
||||
{{ t('dailyDescription') }}<br>
|
||||
v-if="gameState <= GameStateType.Waiting">
|
||||
{{ t('dailyDescription') }}<br><br>
|
||||
<div v-if="isHardMode">
|
||||
{{ t('hardModeDescription') }}
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ t('normalModeDescription') }}
|
||||
</div>
|
||||
|
||||
<button
|
||||
|
@ -54,13 +58,6 @@
|
|||
v-else
|
||||
v-html="t('endGame.failureLabel')" />
|
||||
<button
|
||||
v-if="!isDaily"
|
||||
class="btn-border"
|
||||
@click="reboot">
|
||||
{{ t('playAgain') }}
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
@click="toClipboard"
|
||||
class="inline-flex items-center btn-border">
|
||||
<IconShare class="mr-2" />
|
||||
|
@ -74,7 +71,7 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import {
|
||||
getEmptyOperation,
|
||||
|
@ -90,6 +87,7 @@ import PlaquettesList from '@/components/PlaquettesList.vue'
|
|||
import {
|
||||
clearOperationsList,
|
||||
currentOperation,
|
||||
gameDifficulty,
|
||||
gameIsRunning,
|
||||
gameState,
|
||||
isEndGame,
|
||||
|
@ -100,7 +98,7 @@ import {
|
|||
} from '@/composables/game-state'
|
||||
import { toClipboard } from '@/composables/sharing'
|
||||
import { hasPlayed } from '@/composables/statistics'
|
||||
import { GameState, pools } from '@/globals'
|
||||
import { GameDifficultyType, GameStateType, pools } from '@/globals'
|
||||
import { OperatorType, Plaquette } from '@/types'
|
||||
import {
|
||||
getCurrentSessionKey,
|
||||
|
@ -118,10 +116,12 @@ const { t } = useI18n() // call `useI18n`, and spread `t` from `useI18n` return
|
|||
* 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 isDaily = computed(() => useRoute()?.name === 'daily')
|
||||
const isHardMode = computed(
|
||||
() => gameDifficulty.value === GameDifficultyType.Hard,
|
||||
)
|
||||
const shownPlaquettes = computed(() =>
|
||||
gameIsRunning.value ? plaquettes.value : [],
|
||||
)
|
||||
|
@ -148,8 +148,8 @@ watch(
|
|||
)
|
||||
|
||||
function startGame(): void {
|
||||
gameState.value = GameState.Loading
|
||||
setTimeout(() => (gameState.value = GameState.Playing), 500)
|
||||
gameState.value = GameStateType.Loading
|
||||
setTimeout(() => (gameState.value = GameStateType.Playing), 500)
|
||||
setTimeout(() => {
|
||||
if (cmpPlaquettes.value) {
|
||||
// Remove the animation delay for the plaquettes,
|
||||
|
@ -185,15 +185,15 @@ function selectOperator(o: OperatorType): void {
|
|||
}
|
||||
|
||||
function reboot(): void {
|
||||
gameState.value = GameState.Playing
|
||||
gameState.value = GameStateType.Playing
|
||||
|
||||
// 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 {
|
||||
// Find a problem
|
||||
// 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 = 29
|
||||
// Reset Operations list
|
||||
|
@ -201,8 +201,8 @@ function reboot(): void {
|
|||
|
||||
// Generate result and plaquettes
|
||||
plaquettes.value = []
|
||||
const poolCopy = [...pools[pool.value]]
|
||||
for (let i = 0; i < 6; ++i) {
|
||||
const poolCopy = [...pools[poolType.value]]
|
||||
for (let i = 0; i < numPlaquettes; ++i) {
|
||||
const rndItem = Math.floor(random() * poolCopy.length)
|
||||
const el = poolCopy.splice(rndItem, 1)[0]
|
||||
plaquettes.value.push({ value: el, free: true })
|
||||
|
@ -213,7 +213,7 @@ function reboot(): void {
|
|||
// }))
|
||||
// Solve it
|
||||
} while (
|
||||
(isDaily.value ? !isHardEnough(result.value) : false) ||
|
||||
(isHardMode.value ? !isHardEnough(result.value) : false) ||
|
||||
!isSolvable(
|
||||
result.value,
|
||||
plaquettes.value.map(p => p.value),
|
||||
|
@ -227,33 +227,24 @@ function reboot(): void {
|
|||
*/
|
||||
|
||||
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()
|
||||
}
|
||||
gameDifficulty.value =
|
||||
useRoute()?.name === 'hard'
|
||||
? GameDifficultyType.Hard
|
||||
: GameDifficultyType.Normal
|
||||
|
||||
setDailyPRNG()
|
||||
result.value = 0
|
||||
|
||||
// Wait until after the page transion to generate the number
|
||||
setTimeout(() => {
|
||||
reboot()
|
||||
gameState.value = GameState.Waiting
|
||||
gameState.value = GameStateType.Waiting
|
||||
}, 800)
|
||||
// But make sure the operations list is empty asap
|
||||
clearOperationsList()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
gameState.value = GameState.Waiting
|
||||
gameState.value = GameStateType.Waiting
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -2,47 +2,35 @@
|
|||
<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>
|
||||
{{ t('gameDescription') }}<br>
|
||||
{{ t('gameDescription') }}<br><br>
|
||||
<span v-html="t('dailyDescription')" />
|
||||
|
||||
<br>
|
||||
</div>
|
||||
<!-- Daily -->
|
||||
|
||||
<!-- Normal -->
|
||||
<div class="flex flex-col gap-4 items-center">
|
||||
<RouterLink
|
||||
to="/daily"
|
||||
to="/normal"
|
||||
class="text-2xl btn-border">
|
||||
{{ t('dailyGame') }}
|
||||
{{ t('dailyNormal') }}
|
||||
</RouterLink>
|
||||
<span v-html="t('dailyDescription')" />
|
||||
</div>
|
||||
|
||||
<!-- Random -->
|
||||
<!-- Hard -->
|
||||
<div class="flex flex-col gap-4 items-center">
|
||||
<RouterLink
|
||||
:to="canPlayRandom ? '/random' : ''"
|
||||
class="text-2xl btn-border"
|
||||
:class="{ 'btn-disabled': !canPlayRandom }">
|
||||
{{ t('randomGame') }}
|
||||
to="/hard"
|
||||
class="text-2xl btn-border">
|
||||
{{ t('dailyHard') }}
|
||||
</RouterLink>
|
||||
{{ t('soon') }} ™
|
||||
<!-- {{ t('randomDescription') }} -->
|
||||
<!-- <span
|
||||
class="italic"
|
||||
v-if="!canPlayRandom">{{
|
||||
t('finishDailyToPlayRandom')
|
||||
}}
|
||||
</span> -->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { hasPlayed } from '@/composables/statistics'
|
||||
import { getCurrentSessionKey } from '@/utils'
|
||||
|
||||
const { t } = useI18n()
|
||||
const canPlayRandom = computed(() => hasPlayed(getCurrentSessionKey()))
|
||||
</script>
|
||||
|
|
Loading…
Reference in New Issue
Block a user