refactor: optimize solver to use pure logic without guessing

This commit is contained in:
2026-02-10 23:13:14 +01:00
parent 454c755268
commit a6d02f4367
5 changed files with 37 additions and 37 deletions

View File

@@ -2,6 +2,7 @@
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { usePuzzleStore } from '@/stores/puzzle'; import { usePuzzleStore } from '@/stores/puzzle';
import { useI18n } from '@/composables/useI18n'; import { useI18n } from '@/composables/useI18n';
import { calculateDifficulty } from '@/utils/puzzleUtils';
const emit = defineEmits(['close']); const emit = defineEmits(['close']);
const store = usePuzzleStore(); const store = usePuzzleStore();
@@ -21,13 +22,7 @@ const handleSnap = () => {
}; };
const difficultyLevel = computed(() => { const difficultyLevel = computed(() => {
const rate = fillRate.value; return calculateDifficulty(fillRate.value / 100);
const dist = Math.abs(rate - 50);
if (dist <= 5) return 'extreme';
if (dist <= 15) return 'hardest';
if (dist <= 25) return 'harder';
return 'easy';
}); });
const difficultyColor = computed(() => { const difficultyColor = computed(() => {

View File

@@ -7,6 +7,7 @@ import { useTimer } from '@/composables/useTimer';
import xIcon from '@/assets/brands/x.svg'; import xIcon from '@/assets/brands/x.svg';
import facebookIcon from '@/assets/brands/facebook.svg'; import facebookIcon from '@/assets/brands/facebook.svg';
import whatsappIcon from '@/assets/brands/whatsapp.svg'; import whatsappIcon from '@/assets/brands/whatsapp.svg';
import { calculateDifficulty } from '@/utils/puzzleUtils';
const store = usePuzzleStore(); const store = usePuzzleStore();
const { t } = useI18n(); const { t } = useI18n();
@@ -113,12 +114,11 @@ const buildShareCanvas = () => {
// Difficulty & Density Info // Difficulty & Density Info
const densityPercent = Math.round(store.currentDensity * 100); const densityPercent = Math.round(store.currentDensity * 100);
const dist = Math.abs(densityPercent - 50); const difficultyKey = calculateDifficulty(store.currentDensity);
let difficultyKey = 'easy';
let diffColor = '#33ff33'; let diffColor = '#33ff33';
if (dist <= 5) { difficultyKey = 'extreme'; diffColor = '#ff3333'; } if (difficultyKey === 'extreme') diffColor = '#ff3333';
else if (dist <= 15) { difficultyKey = 'hardest'; diffColor = '#ff9933'; } else if (difficultyKey === 'hardest') diffColor = '#ff9933';
else if (dist <= 25) { difficultyKey = 'harder'; diffColor = '#ffff33'; } else if (difficultyKey === 'harder') diffColor = '#ffff33';
const difficultyText = t(`difficulty.${difficultyKey}`); const difficultyText = t(`difficulty.${difficultyKey}`);
ctx.font = '600 14px "Segoe UI", sans-serif'; ctx.font = '600 14px "Segoe UI", sans-serif';

View File

@@ -78,6 +78,9 @@ export function useSolver() {
} else if (type === 'done') { } else if (type === 'done') {
isProcessing.value = false; isProcessing.value = false;
pause(); pause();
} else if (type === 'stuck') {
isProcessing.value = false;
pause();
} else { } else {
isProcessing.value = false; isProcessing.value = false;
} }

View File

@@ -51,3 +51,25 @@ export function generateRandomGrid(size, density = 0.5) {
} }
return grid; 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';
}

View File

@@ -5,7 +5,7 @@ const messages = {
'worker.solved': 'Rozwiązane!', 'worker.solved': 'Rozwiązane!',
'worker.logicRow': 'Logika: Wiersz {row}, Kolumna {col} -> {state}', 'worker.logicRow': 'Logika: Wiersz {row}, Kolumna {col} -> {state}',
'worker.logicCol': 'Logika: Kolumna {col}, Wiersz {row} -> {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.done': 'Koniec!',
'worker.state.filled': 'Pełne', 'worker.state.filled': 'Pełne',
'worker.state.empty': 'Puste' 'worker.state.empty': 'Puste'
@@ -14,7 +14,7 @@ const messages = {
'worker.solved': 'Solved!', 'worker.solved': 'Solved!',
'worker.logicRow': 'Logic: Row {row}, Column {col} -> {state}', 'worker.logicRow': 'Logic: Row {row}, Column {col} -> {state}',
'worker.logicCol': 'Logic: Column {col}, Row {row} -> {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.done': 'Done!',
'worker.state.filled': 'Filled', 'worker.state.filled': 'Filled',
'worker.state.empty': 'Empty' 'worker.state.empty': 'Empty'
@@ -236,29 +236,9 @@ const handleStep = (playerGrid, solution, locale) => {
} }
} }
for (let r = 0; r < size; r++) { // Check for guess logic - we want to avoid this unless strictly necessary
for (let c = 0; c < size; c++) { // If no logic move found, return 'stuck' instead of cheating
const current = playerGrid[r][c]; return { type: 'stuck', statusText: t(locale, 'worker.stuck') };
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') };
}; };
self.onmessage = (event) => { self.onmessage = (event) => {