From a6d02f43671311df46e1f3cc532dabc639826041 Mon Sep 17 00:00:00 2001 From: Grzegorz Kucmierz Date: Tue, 10 Feb 2026 23:13:14 +0100 Subject: [PATCH] refactor: optimize solver to use pure logic without guessing --- src/components/CustomGameModal.vue | 9 ++------- src/components/WinModal.vue | 10 +++++----- src/composables/useSolver.js | 3 +++ src/utils/puzzleUtils.js | 22 ++++++++++++++++++++++ src/workers/solverWorker.js | 30 +++++------------------------- 5 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/components/CustomGameModal.vue b/src/components/CustomGameModal.vue index 108e18c..2ea5262 100644 --- a/src/components/CustomGameModal.vue +++ b/src/components/CustomGameModal.vue @@ -2,6 +2,7 @@ import { ref, computed } from 'vue'; import { usePuzzleStore } from '@/stores/puzzle'; import { useI18n } from '@/composables/useI18n'; +import { calculateDifficulty } from '@/utils/puzzleUtils'; const emit = defineEmits(['close']); const store = usePuzzleStore(); @@ -21,13 +22,7 @@ const handleSnap = () => { }; const difficultyLevel = computed(() => { - const rate = fillRate.value; - const dist = Math.abs(rate - 50); - - if (dist <= 5) return 'extreme'; - if (dist <= 15) return 'hardest'; - if (dist <= 25) return 'harder'; - return 'easy'; + return calculateDifficulty(fillRate.value / 100); }); const difficultyColor = computed(() => { diff --git a/src/components/WinModal.vue b/src/components/WinModal.vue index f2ddc49..eef16cf 100644 --- a/src/components/WinModal.vue +++ b/src/components/WinModal.vue @@ -7,6 +7,7 @@ import { useTimer } from '@/composables/useTimer'; import xIcon from '@/assets/brands/x.svg'; import facebookIcon from '@/assets/brands/facebook.svg'; import whatsappIcon from '@/assets/brands/whatsapp.svg'; +import { calculateDifficulty } from '@/utils/puzzleUtils'; const store = usePuzzleStore(); const { t } = useI18n(); @@ -113,12 +114,11 @@ const buildShareCanvas = () => { // Difficulty & Density Info const densityPercent = Math.round(store.currentDensity * 100); - const dist = Math.abs(densityPercent - 50); - let difficultyKey = 'easy'; + const difficultyKey = calculateDifficulty(store.currentDensity); let diffColor = '#33ff33'; - if (dist <= 5) { difficultyKey = 'extreme'; diffColor = '#ff3333'; } - else if (dist <= 15) { difficultyKey = 'hardest'; diffColor = '#ff9933'; } - else if (dist <= 25) { difficultyKey = 'harder'; diffColor = '#ffff33'; } + if (difficultyKey === 'extreme') diffColor = '#ff3333'; + else if (difficultyKey === 'hardest') diffColor = '#ff9933'; + else if (difficultyKey === 'harder') diffColor = '#ffff33'; const difficultyText = t(`difficulty.${difficultyKey}`); ctx.font = '600 14px "Segoe UI", sans-serif'; diff --git a/src/composables/useSolver.js b/src/composables/useSolver.js index 4ee5d94..bc016f9 100644 --- a/src/composables/useSolver.js +++ b/src/composables/useSolver.js @@ -78,6 +78,9 @@ export function useSolver() { } else if (type === 'done') { isProcessing.value = false; pause(); + } else if (type === 'stuck') { + isProcessing.value = false; + pause(); } else { isProcessing.value = false; } diff --git a/src/utils/puzzleUtils.js b/src/utils/puzzleUtils.js index fc58dbc..ad978a1 100644 --- a/src/utils/puzzleUtils.js +++ b/src/utils/puzzleUtils.js @@ -51,3 +51,25 @@ export function generateRandomGrid(size, density = 0.5) { } return grid; } + +export function calculateDifficulty(density) { + // Shannon Entropy: H(x) = -x*log2(x) - (1-x)*log2(1-x) + // Normalized to 0-1 range (since max entropy at 0.5 is 1) + + // Avoid log(0) + if (density <= 0 || density >= 1) return 'easy'; + + const entropy = -density * Math.log2(density) - (1 - density) * Math.log2(1 - density); + + // Thresholds based on entropy + // 0.5 density -> entropy 1.0 (Extreme) + // 0.4/0.6 density -> entropy ~0.97 (Extreme) + // 0.3/0.7 density -> entropy ~0.88 (Hardest) + // 0.2/0.8 density -> entropy ~0.72 (Harder) + // <0.2/>0.8 density -> entropy <0.72 (Easy) + + if (entropy >= 0.96) return 'extreme'; // approx 38% - 62% + if (entropy >= 0.85) return 'hardest'; // approx 28% - 38% & 62% - 72% + if (entropy >= 0.65) return 'harder'; // approx 17% - 28% & 72% - 83% + return 'easy'; +} diff --git a/src/workers/solverWorker.js b/src/workers/solverWorker.js index 8bbcce9..c2080b9 100644 --- a/src/workers/solverWorker.js +++ b/src/workers/solverWorker.js @@ -5,7 +5,7 @@ const messages = { 'worker.solved': 'Rozwiązane!', 'worker.logicRow': 'Logika: Wiersz {row}, Kolumna {col} -> {state}', 'worker.logicCol': 'Logika: Kolumna {col}, Wiersz {row} -> {state}', - 'worker.guess': 'Zgadywanie: Wiersz {row}, Kolumna {col}', + 'worker.stuck': 'Brak logicznego ruchu. Spróbuj zgadnąć lub cofnąć.', 'worker.done': 'Koniec!', 'worker.state.filled': 'Pełne', 'worker.state.empty': 'Puste' @@ -14,7 +14,7 @@ const messages = { 'worker.solved': 'Solved!', 'worker.logicRow': 'Logic: Row {row}, Column {col} -> {state}', 'worker.logicCol': 'Logic: Column {col}, Row {row} -> {state}', - 'worker.guess': 'Guessing: Row {row}, Column {col}', + 'worker.stuck': 'No logical move found. Try guessing or undoing.', 'worker.done': 'Done!', 'worker.state.filled': 'Filled', 'worker.state.empty': 'Empty' @@ -236,29 +236,9 @@ const handleStep = (playerGrid, solution, locale) => { } } - for (let r = 0; r < size; r++) { - for (let c = 0; c < size; c++) { - const current = playerGrid[r][c]; - const target = solution[r][c]; - let isCorrect = false; - if (target === 1 && current === 1) isCorrect = true; - if (target === 0 && current === 2) isCorrect = true; - if (target === 0 && current === 0) isCorrect = false; - if (target === 1 && current === 0) isCorrect = false; - if (!isCorrect) { - const newState = target === 1 ? 1 : 2; - return { - type: 'move', - r, - c, - state: newState, - statusText: t(locale, 'worker.guess', { row: r + 1, col: c + 1 }) - }; - } - } - } - - return { type: 'done', statusText: t(locale, 'worker.done') }; + // Check for guess logic - we want to avoid this unless strictly necessary + // If no logic move found, return 'stuck' instead of cheating + return { type: 'stuck', statusText: t(locale, 'worker.stuck') }; }; self.onmessage = (event) => {