Normal and Hard modes

This commit is contained in:
Simon Cambier 2022-03-01 23:16:40 +01:00
parent 1909b75663
commit 80064a98e5
12 changed files with 117 additions and 81 deletions

View File

@ -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

View File

@ -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

View File

@ -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<{

View File

@ -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,
)

View File

@ -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,
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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' },
},

View File

@ -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[]
}

View File

@ -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)
}

View File

@ -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>

View File

@ -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>