Compare commits
No commits in common. "ba0000af2a4113519ad3c7715c0dcb00e7b1955c" and "80064a98e5cb5710944849b5ce253738d28871d0" have entirely different histories.
ba0000af2a
...
80064a98e5
|
@ -12,7 +12,6 @@
|
|||
<link rel="apple-touch-icon" href="/icons/apple-touch-icon.png" sizes="180x180">
|
||||
<link rel="mask-icon" href="/icons/favicon.svg" color="#1C1917">
|
||||
<meta name="theme-color" content="#1C1917">
|
||||
<script async src="https://stats.scambier.xyz/script.js" data-website-id="25b0bcf4-c4c1-4da4-a1bd-3251f7bc3878"></script>
|
||||
</head>
|
||||
|
||||
<body class="bg-stone-300 text-stone-900 dark:bg-stone-900 dark:text-stone-200 h-full relative">
|
||||
|
|
|
@ -20,7 +20,14 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
import AppHeader from '@/components/AppHeader.vue'
|
||||
import SideMenu from '@/components/SideMenu.vue'
|
||||
import ToastMessage from '@/components/ToastMessage.vue'
|
||||
import { plausible } from '@/analytics'
|
||||
|
||||
onMounted(() => {
|
||||
plausible.enableAutoPageviews()
|
||||
})
|
||||
</script>
|
||||
|
|
10
src/algo.ts
10
src/algo.ts
|
@ -5,7 +5,7 @@ import { shuffle } from './utils'
|
|||
type HistoryType = { a: number; b: number; op: OperatorType }[]
|
||||
|
||||
export function isHardEnough(num: number): boolean {
|
||||
return num % 100 > 0 && num % 50 > 0
|
||||
return num % 100 > 0
|
||||
}
|
||||
|
||||
export function isOperationResultValid(op: Operation): boolean {
|
||||
|
@ -63,7 +63,7 @@ export function isSolvable(result: number, plaquettes: number[]): boolean {
|
|||
for (const item of history) {
|
||||
if (item.a < item.b) [item.a, item.b] = [item.b, item.a]
|
||||
console.log(
|
||||
`${item.a} ${item.op === '*' ? 'x' : item.op} ${item.b} = ${operate(item.op, item.a, item.b)}`,
|
||||
`${item.a} ${item.op} ${item.b} = ${operate(item.op, item.a, item.b)}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -117,7 +117,7 @@ export function isSolvable(result: number, plaquettes: number[]): boolean {
|
|||
histories.push(history)
|
||||
if (!found) {
|
||||
found = true
|
||||
console.log(`1st solution found in ${Date.now() - start}ms`)
|
||||
console.log(`1e solution trouvée en ${Date.now() - start}ms`)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -138,12 +138,12 @@ export function isSolvable(result: number, plaquettes: number[]): boolean {
|
|||
loopOperations(plaquettes, [])
|
||||
if (found) {
|
||||
console.log(
|
||||
`Success : ${Date.now() - start}ms and ${numberOfIterations} iterations`,
|
||||
`Réussite : ${Date.now() - start}ms et ${numberOfIterations} iterations`,
|
||||
)
|
||||
}
|
||||
else {
|
||||
console.log(
|
||||
`Failure : ${Date.now() - start}ms and ${numberOfIterations} iterations`,
|
||||
`Echec : ${Date.now() - start}ms et ${numberOfIterations} iterations`,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
7
src/analytics.ts
Normal file
7
src/analytics.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import Plausible from 'plausible-tracker'
|
||||
|
||||
// Setup tracking
|
||||
export const plausible = Plausible({
|
||||
domain: 'n0mbers.scambier.xyz',
|
||||
apiHost: 'https://stats.scambier.xyz',
|
||||
})
|
|
@ -8,7 +8,7 @@
|
|||
</RouterLink>
|
||||
<h1
|
||||
class="z-20 py-2 font-mono text-3xl text-center bg-stone-300 dark:bg-stone-900">
|
||||
N<span class="text-cyan-500">0</span>mbers<span class="text-xs">alpha</span>
|
||||
N<span class="text-cyan-500">0</span>mbers<span class="text-xs">beta</span>
|
||||
</h1>
|
||||
<button @click="isSideMenuVisible = true">
|
||||
<IconMenu class="text-xl btn" />
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<IconClose class="absolute top-0 right-1 h-12 text-xl btn" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex flex-col flex-1 content-center items-center text-sm">
|
||||
<div class="flex flex-col flex-1 content-center items-center">
|
||||
<div class="h-12" />
|
||||
<InputSwitch
|
||||
class="mb-4"
|
||||
|
@ -24,6 +24,15 @@
|
|||
v-model="isLocaleFrench" />
|
||||
</div>
|
||||
|
||||
<div class="mb-8 text-xs text-stone-500">
|
||||
Features to come:
|
||||
<ul class="ml-4 list-disc">
|
||||
<li>Keyboard input</li>
|
||||
<li>Random number</li>
|
||||
<li>Less bugs</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-stone-500 dark:text-stone-600">
|
||||
Build {{ buildDate }}
|
||||
</div>
|
||||
|
|
|
@ -17,7 +17,7 @@ export const operations = reactive<Operation[]>([getEmptyOperation()])
|
|||
export const plaquettes = ref<Plaquette[]>([])
|
||||
export const result = ref(0)
|
||||
|
||||
// #region Difficulty-related values
|
||||
// #region Difficulty-related constants
|
||||
|
||||
export const gameDifficulty = ref(GameDifficultyType.Normal)
|
||||
|
||||
|
@ -29,10 +29,6 @@ export const startingNumberOfPlaquettes = computed(() =>
|
|||
gameDifficulty.value === GameDifficultyType.Hard ? 6 : 5,
|
||||
)
|
||||
|
||||
export const isHardMode = computed(
|
||||
() => gameDifficulty.value === GameDifficultyType.Hard,
|
||||
)
|
||||
|
||||
// #endregion Difficulty-related constants
|
||||
|
||||
export const currentOperation = computed(
|
||||
|
|
|
@ -1,56 +1,34 @@
|
|||
import { i18n } from '@/i18n'
|
||||
import { percentageDiff } from '@/utils'
|
||||
|
||||
import {
|
||||
isHardMode,
|
||||
numberOfGamesSinceStart,
|
||||
operations,
|
||||
result,
|
||||
} from './game-state'
|
||||
import { numberOfGamesSinceStart, operations, result } from './game-state'
|
||||
import { showToast } from './toast-manager'
|
||||
|
||||
function getSharingText(): string {
|
||||
const endResult = operations[operations.length - 1].result?.value ?? 0
|
||||
const success = result.value === endResult
|
||||
|
||||
// const allSymbols = ['𝙰', '𝙱', '𝙲', '𝙳', '𝙴', '𝙵', '𝚅', '𝚆', '𝚇', '𝚈', '𝚉']
|
||||
const abcSymbols = ['𝚊', '𝚋', '𝚌', '𝚍', '𝚎', '𝚏']
|
||||
const xyzSymbols = ['𝚟', '𝚠', '𝚡', '𝚢', '𝚣'].slice(-(operations.length - 1))
|
||||
// const xyzSymbols = ["𝚊'", "𝚋'", "𝚌'", "𝚍'", "𝚎'", "𝚏'"]
|
||||
// const xyzSymbols = ['𝚅', '𝚆', '𝚇', '𝚈', '𝚉']
|
||||
const squares = ['🟦', '🟩', '🟨', '🟧', '🟥']
|
||||
|
||||
const lines: string[] = []
|
||||
// × ÷ + -
|
||||
const emojis: string[] = []
|
||||
for (const op of operations) {
|
||||
op.left!.symbol = op.left!.symbol ?? abcSymbols.shift()
|
||||
op.right!.symbol = op.right!.symbol ?? abcSymbols.shift()
|
||||
op.result!.symbol = op.result!.symbol ?? xyzSymbols.shift()
|
||||
|
||||
let s = ''
|
||||
switch (op.operator) {
|
||||
case '*':
|
||||
s = '×'
|
||||
break
|
||||
case '/':
|
||||
s = '÷'
|
||||
case '+':
|
||||
emojis.push('+')
|
||||
break
|
||||
case '-':
|
||||
s = '−'
|
||||
emojis.push('-')
|
||||
break
|
||||
case '+':
|
||||
s = '+'
|
||||
case '*':
|
||||
emojis.push('×')
|
||||
break
|
||||
case '/':
|
||||
emojis.push('÷')
|
||||
break
|
||||
}
|
||||
lines.push(
|
||||
`${squares.shift()} ${op.left?.symbol} ${s} ${op.right?.symbol} = ${
|
||||
op.result?.value === endResult ? endResult : op.result?.symbol
|
||||
}`,
|
||||
)
|
||||
}
|
||||
|
||||
return `N0mbers #${numberOfGamesSinceStart()} - ${
|
||||
isHardMode.value ? 'Advanced' : 'Normal'
|
||||
}
|
||||
${lines.join('\n')} ${success ? '✔' : '❌'}
|
||||
const endResult = operations[operations.length - 1].result?.value ?? 0
|
||||
return `N0mbers #${numberOfGamesSinceStart()}
|
||||
===========
|
||||
${emojis.join(' ')} = ${endResult}
|
||||
Score: ${100 - percentageDiff(result.value, endResult) * 100}%
|
||||
===========
|
||||
https://n0mbers.scambier.xyz`
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { merge } from 'lodash-es'
|
||||
import { reactive, watch } from 'vue'
|
||||
|
||||
// import { plausible } from '@/analytics'
|
||||
import { LSK_STATS } from '@/globals'
|
||||
import * as storage from '@/storage'
|
||||
import { GameStats } from '@/types'
|
||||
|
@ -61,7 +62,8 @@ function setScore(seed: string, won: boolean, score: number): void {
|
|||
// Don't overwrite an existing score
|
||||
if (!gameStats.games[seed]) {
|
||||
gameStats.games[seed] = { score, won }
|
||||
|
||||
// plausible.trackEvent(won ? 'win_game' : 'lose_game')
|
||||
// plausible.trackEvent('end_game')
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export const GAME_STARTING_DATE = '2022-03-22'
|
||||
export const GAME_STARTING_DATE = '2022-02-18'
|
||||
export const BXL_TZ = 'Europe/Brussels'
|
||||
|
||||
export const LSK_DARKMODE = 'n0_dark'
|
||||
|
@ -8,9 +8,9 @@ export const LSK_STATS = 'n0_stats'
|
|||
export const operators = ['+', '-', '*', '/'] as const
|
||||
|
||||
export const pools = {
|
||||
1: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 25, 50, 75, 100],
|
||||
2: [2, 2, 3, 3, 5, 5, 7, 7, 11, 13, 17, 19, 23],
|
||||
3: [1, 1, 2, 3, 5, 8, 13, 21],
|
||||
1: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 25],
|
||||
2: [2, 2, 3, 3, 5, 5, 7, 11, 13, 17, 19, 23],
|
||||
3: [5, 5, 5, 5, 5, 2, 2, 2, 2, 2],
|
||||
} as const
|
||||
|
||||
export enum GameStateType {
|
||||
|
|
|
@ -12,14 +12,10 @@
|
|||
"dailyDescription": "The daily challenge changes every day at midnight (CET), and is common to all players.",
|
||||
"share": "Share",
|
||||
"finishDailyToPlayRandom": "Finish the daily challenge to unlock.",
|
||||
"gameDescription": "Combine the required numbers to reach the result.<br><strong>Game under development, some features are missing or changing.</strong>",
|
||||
"gameDescription": "Combine the numbers to reach the result.",
|
||||
"randomDescription": "A challenge at random, just for fun.",
|
||||
"copiedInClipboard": "Copied in clipboard",
|
||||
"soon": "Soon",
|
||||
"dailyHard": "Advanced Mode",
|
||||
"dailyNormal": "Normal Mode",
|
||||
"practiceMode": "Practice",
|
||||
"practiceModeDescription": "Use the Practice mode to improve your skills.",
|
||||
"hardModeDescription": "The Advanced mode is generally more challenging, with 6 starting numbers.",
|
||||
"normalModeDescription": "The Normal mode has 5 starting numbers, for a generally easier challenge."
|
||||
"dailyHard": "Hard Mode",
|
||||
"dailyNormal": "Normal Mode"
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"impossible": "☠",
|
||||
"dailyGame": "Défi quotidien",
|
||||
"randomGame": "Nombre au hasard",
|
||||
"gameDescription": "Combinez les nombres imposés afin d'atteindre le résultat.<br><strong>Jeu en cours de développement, certaines fonctionnalités sont manquantes ou changeantes.</strong>",
|
||||
"gameDescription": "Combinez les nombres imposés afin d'atteindre le résultat.",
|
||||
"dailyDescription": "Le défi quotidien change chaque jour à minuit (CET), et est commun à tous les joueurs.",
|
||||
"randomDescription": "Une partie au hasard, pour le plaisir.",
|
||||
"share": "Partager",
|
||||
|
@ -17,9 +17,5 @@
|
|||
"copiedInClipboard": "Copié dans le presse-papier",
|
||||
"soon": "Bientôt",
|
||||
"dailyNormal": "Mode Normal",
|
||||
"dailyHard": "Mode Avancé",
|
||||
"practiceMode": "Entraînement",
|
||||
"practiceModeDescription": "Profitez du mode Entraînement pour améliorer vos compétences.",
|
||||
"hardModeDescription": "Le mode Avancé propose un challenge généralement plus relevé, avec 6 nombres de base.",
|
||||
"normalModeDescription": "Le mode Normal propose un challenge généralement plus facile, avec 5 nombres de base."
|
||||
"dailyHard": "Mode Difficile"
|
||||
}
|
||||
|
|
|
@ -12,8 +12,8 @@ const router = createRouter({
|
|||
meta: { transition: '' },
|
||||
},
|
||||
{
|
||||
path: '/advanced',
|
||||
name: 'advanced',
|
||||
path: '/hard',
|
||||
name: 'hard',
|
||||
component: () => import('../views/GameView.vue'),
|
||||
meta: { transition: 'route' },
|
||||
},
|
||||
|
@ -23,12 +23,6 @@ const router = createRouter({
|
|||
component: () => import('../views/GameView.vue'),
|
||||
meta: { transition: 'route' },
|
||||
},
|
||||
{
|
||||
path: '/practice',
|
||||
name: 'practice',
|
||||
component: () => import('../views/GameView.vue'),
|
||||
meta: { transition: 'route' },
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
|
|
|
@ -3,12 +3,6 @@ export type OperatorType = '+' | '-' | '*' | '/'
|
|||
export type Plaquette = {
|
||||
value: number
|
||||
free: boolean
|
||||
|
||||
/** If the plaquette is one of the pre-selected */
|
||||
original: boolean
|
||||
|
||||
/** For sharing */
|
||||
symbol?: string
|
||||
}
|
||||
|
||||
export type Operation = {
|
||||
|
|
|
@ -12,16 +12,11 @@
|
|||
class="text-center"
|
||||
v-if="gameState <= GameStateType.Waiting">
|
||||
{{ t('dailyDescription') }}<br><br>
|
||||
<div v-if="isPracticeMode">
|
||||
{{ t('practiceModeDescription') }}
|
||||
<div v-if="isHardMode">
|
||||
{{ t('hardModeDescription') }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-if="isHardMode">
|
||||
{{ t('hardModeDescription') }}
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ t('normalModeDescription') }}
|
||||
</div>
|
||||
{{ t('normalModeDescription') }}
|
||||
</div>
|
||||
|
||||
<button
|
||||
|
@ -40,7 +35,7 @@
|
|||
|
||||
<Transition name="slide_up">
|
||||
<div v-if="gameIsRunning">
|
||||
<!-- Operators -->
|
||||
<!-- OPERATORS -->
|
||||
<OperatorsList @click="selectOperator" />
|
||||
|
||||
<!-- Divider -->
|
||||
|
@ -63,19 +58,11 @@
|
|||
v-else
|
||||
v-html="t('endGame.failureLabel')" />
|
||||
<button
|
||||
v-if="!isPracticeMode"
|
||||
@click="sharing.toClipboard"
|
||||
@click="toClipboard"
|
||||
class="inline-flex items-center btn-border">
|
||||
<IconShare class="mr-2" />
|
||||
{{ t('share') }}
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
@click="reboot"
|
||||
class="inline-flex items-center btn-border">
|
||||
<IconReload class="mr-2" />
|
||||
{{ t('playAgain') }}
|
||||
</button>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
|
@ -104,17 +91,23 @@ import {
|
|||
gameIsRunning,
|
||||
gameState,
|
||||
isEndGame,
|
||||
isHardMode,
|
||||
isResultPerfect,
|
||||
operations,
|
||||
plaquettes,
|
||||
result,
|
||||
} from '@/composables/game-state'
|
||||
import * as sharing from '@/composables/sharing'
|
||||
import { toClipboard } from '@/composables/sharing'
|
||||
import { hasPlayed } from '@/composables/statistics'
|
||||
import { GameDifficultyType, GameStateType, pools } from '@/globals'
|
||||
import { OperatorType, Plaquette } from '@/types'
|
||||
import { randItem, random, randRange, setDailyPRNG, setMathPRNG } from '@/utils'
|
||||
import IconReload from '~icons/ph/arrow-clockwise'
|
||||
import {
|
||||
getCurrentSessionKey,
|
||||
randItem,
|
||||
random,
|
||||
randRange,
|
||||
setDailyPRNG,
|
||||
setMathPRNG,
|
||||
} from '@/utils'
|
||||
import IconShare from '~icons/ph/share-network'
|
||||
|
||||
const { t } = useI18n() // call `useI18n`, and spread `t` from `useI18n` returning
|
||||
|
@ -126,7 +119,9 @@ const { t } = useI18n() // call `useI18n`, and spread `t` from `useI18n` return
|
|||
const poolType = ref<1 | 2 | 3>(1)
|
||||
const cmpPlaquettes = ref<InstanceType<typeof PlaquettesList> | null>(null)
|
||||
|
||||
const isPracticeMode = ref(false)
|
||||
const isHardMode = computed(
|
||||
() => gameDifficulty.value === GameDifficultyType.Hard,
|
||||
)
|
||||
const shownPlaquettes = computed(() =>
|
||||
gameIsRunning.value ? plaquettes.value : [],
|
||||
)
|
||||
|
@ -141,7 +136,6 @@ watch(
|
|||
if (isOperationReady(op) && isOperationResultValid(op) && !op.result) {
|
||||
op.result = {
|
||||
free: true,
|
||||
original: false,
|
||||
value: operate(op.operator!, op.left!.value, op.right!.value),
|
||||
}
|
||||
if (operations.length < 5 && !isEndGame.value) {
|
||||
|
@ -193,12 +187,15 @@ function selectOperator(o: OperatorType): void {
|
|||
function reboot(): void {
|
||||
gameState.value = GameStateType.Playing
|
||||
|
||||
const minValue = 101
|
||||
const numPlaquettes = isHardMode.value || isPracticeMode.value ? 6 : 5
|
||||
// The daily number is >= 500 to have a minimum challenge
|
||||
const minValue = isHardMode.value ? 500 : 101
|
||||
const numPlaquettes = isHardMode.value ? 6 : 5
|
||||
do {
|
||||
// Find a problem
|
||||
// result.value = randRange(101, 1000)
|
||||
poolType.value = randItem([1, 1, 1, 1, 1, 2, 3])
|
||||
result.value = randRange(minValue, 1000)
|
||||
// result.value = 29
|
||||
// Reset Operations list
|
||||
clearOperationsList()
|
||||
|
||||
|
@ -208,9 +205,12 @@ function reboot(): void {
|
|||
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, original: true })
|
||||
plaquettes.value.push({ value: el, free: true })
|
||||
}
|
||||
|
||||
// plaquettes.value = [4, 8, 10, 25, 50, 100].map(value => ({
|
||||
// free: true,
|
||||
// value,
|
||||
// }))
|
||||
// Solve it
|
||||
} while (
|
||||
(isHardMode.value ? !isHardEnough(result.value) : false) ||
|
||||
|
@ -228,18 +228,11 @@ function reboot(): void {
|
|||
|
||||
onMounted(() => {
|
||||
gameDifficulty.value =
|
||||
useRoute()?.name !== 'normal'
|
||||
useRoute()?.name === 'hard'
|
||||
? GameDifficultyType.Hard
|
||||
: GameDifficultyType.Normal
|
||||
|
||||
isPracticeMode.value = useRoute().name === 'practice'
|
||||
|
||||
if (isPracticeMode.value) {
|
||||
setMathPRNG()
|
||||
}
|
||||
else {
|
||||
setDailyPRNG()
|
||||
}
|
||||
setDailyPRNG()
|
||||
result.value = 0
|
||||
|
||||
// Wait until after the page transion to generate the number
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
<template>
|
||||
<div class="flex flex-col grow p-1 w-full text-center">
|
||||
<div class="flex flex-col grow gap-8 justify-center my-4 md:my-16">
|
||||
<div v-html="t('gameDescription')" />
|
||||
<div class="flex flex-col grow gap-16 justify-center md:mt-16">
|
||||
<div>
|
||||
{{ t('gameDescription') }}<br><br>
|
||||
<span v-html="t('dailyDescription')" />
|
||||
|
||||
<br>
|
||||
</div>
|
||||
|
||||
<!-- Normal -->
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="flex flex-col gap-4 items-center">
|
||||
<RouterLink
|
||||
to="/normal"
|
||||
class="text-2xl btn-border">
|
||||
|
@ -13,22 +18,14 @@
|
|||
</div>
|
||||
|
||||
<!-- Hard -->
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="flex flex-col gap-4 items-center">
|
||||
<RouterLink
|
||||
to="/advanced"
|
||||
to="/hard"
|
||||
class="text-2xl btn-border">
|
||||
{{ t('dailyHard') }}
|
||||
</RouterLink>
|
||||
</div>
|
||||
|
||||
<!-- Practice -->
|
||||
<div class="flex flex-col items-center mt-4">
|
||||
<RouterLink
|
||||
to="/practice"
|
||||
class="text-xl btn-border">
|
||||
{{ t('practiceMode') }}
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
Loading…
Reference in New Issue
Block a user