diff --git a/scripts/simulate_difficulty.js b/scripts/simulate_difficulty.js index f34676f..147b91c 100644 --- a/scripts/simulate_difficulty.js +++ b/scripts/simulate_difficulty.js @@ -1,75 +1,38 @@ - -import fs from 'fs'; -import path from 'path'; import { generateRandomGrid, calculateHints } from '../src/utils/puzzleUtils.js'; import { solvePuzzle } from '../src/utils/solver.js'; -const OUTPUT_FILE = 'difficulty_simulation_results.json'; -const CSV_FILE = 'difficulty_simulation_results.csv'; - -// Configuration -const SIZES = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 60, 70, 80]; // Steps of 5 up to 50, then 10 +const SIZES = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 60, 70, 80]; const DENSITIES = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]; -const SAMPLES_PER_POINT = 20; // Adjust based on time/accuracy needs +const SAMPLES_SMALL = 100; // For size <= 25 +const SAMPLES_LARGE = 30; // For size > 25 -console.log('Starting Monte Carlo Simulation for Nonogram Difficulty...'); -console.log(`Config: Sizes=${SIZES.length}, Densities=${DENSITIES.length}, Samples=${SAMPLES_PER_POINT}`); +const results = {}; -const results = []; -const csvRows = ['size,density,avg_solved_percent,min_solved_percent,max_solved_percent,avg_time_ms']; +console.log('Starting Monte Carlo Simulation...'); const startTime = Date.now(); for (const size of SIZES) { + const samples = size <= 25 ? SAMPLES_SMALL : SAMPLES_LARGE; + const rowData = []; + for (const density of DENSITIES) { let totalSolved = 0; - let minSolved = 100; - let maxSolved = 0; - let totalTime = 0; - - process.stdout.write(`Simulating Size: ${size}x${size}, Density: ${density} ... `); - - for (let i = 0; i < SAMPLES_PER_POINT; i++) { - const t0 = performance.now(); - - // 1. Generate + + for (let i = 0; i < samples; i++) { const grid = generateRandomGrid(size, density); const { rowHints, colHints } = calculateHints(grid); - - // 2. Solve const { percentSolved } = solvePuzzle(rowHints, colHints); - - const t1 = performance.now(); - totalSolved += percentSolved; - minSolved = Math.min(minSolved, percentSolved); - maxSolved = Math.max(maxSolved, percentSolved); - totalTime += (t1 - t0); } - - const avgSolved = totalSolved / SAMPLES_PER_POINT; - const avgTime = totalTime / SAMPLES_PER_POINT; - - results.push({ - size, - density, - avgSolved, - minSolved, - maxSolved, - avgTime - }); - - csvRows.push(`${size},${density},${avgSolved.toFixed(2)},${minSolved.toFixed(2)},${maxSolved.toFixed(2)},${avgTime.toFixed(2)}`); - console.log(`Avg Solved: ${avgSolved.toFixed(1)}%`); + const avg = Math.round(totalSolved / samples); + rowData.push(avg); } + results[size] = rowData; + console.log(` Size ${size}: [${rowData.join(', ')}]`); } -const totalDuration = (Date.now() - startTime) / 1000; -console.log(`Simulation complete in ${totalDuration.toFixed(1)}s`); - -// Save results -fs.writeFileSync(OUTPUT_FILE, JSON.stringify(results, null, 2)); -fs.writeFileSync(CSV_FILE, csvRows.join('\n')); - -console.log(`Results saved to ${OUTPUT_FILE} and ${CSV_FILE}`); +const duration = (Date.now() - startTime) / 1000; +console.log(`\nSimulation Complete in ${duration.toFixed(2)}s. Result JSON:`); +console.log(JSON.stringify(results, null, 4)); diff --git a/src/components/SimulationView.vue b/src/components/SimulationView.vue index 7565f1a..a5cffef 100644 --- a/src/components/SimulationView.vue +++ b/src/components/SimulationView.vue @@ -9,9 +9,9 @@ import { X, Play, Square, RotateCcw } from 'lucide-vue-next'; const emit = defineEmits(['close']); const { t } = useI18n(); -const SIZES = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50]; +const SIZES = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 60, 70, 80]; const DENSITIES = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]; -const SAMPLES_PER_POINT = 10; // Reduced for web performance demo +const SAMPLES_PER_POINT = 50; // Increased for better accuracy const isRunning = ref(false); const progress = ref(0); diff --git a/src/utils/puzzleUtils.js b/src/utils/puzzleUtils.js index 5b95ce0..4d5a93f 100644 --- a/src/utils/puzzleUtils.js +++ b/src/utils/puzzleUtils.js @@ -63,21 +63,20 @@ export function generateRandomGrid(size, density = 0.5) { export function calculateDifficulty(density, size = 10) { // Data derived from Monte Carlo Simulation (Logical Solver) // Format: { size: [solved_pct_at_0.1, ..., solved_pct_at_0.9] } - // Densities: 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9 const SIM_DATA = { - 5: [89, 74, 74, 81, 97, 98, 99, 100, 100], - 10: [57, 20, 16, 54, 92, 100, 100, 100, 100], - 15: [37, 10, 2, 12, 68, 100, 100, 100, 100], - 20: [23, 3, 1, 2, 37, 100, 100, 100, 100], - 25: [16, 0, 0, 1, 19, 99, 100, 100, 100], - 30: [8, 0, 0, 0, 5, 99, 100, 100, 100], - 35: [6, 0, 0, 0, 4, 91, 100, 100, 100], - 40: [3, 0, 0, 0, 2, 91, 100, 100, 100], - 45: [2, 0, 0, 0, 1, 82, 100, 100, 100], - 50: [2, 0, 0, 0, 1, 73, 100, 100, 100], - 60: [0, 0, 0, 0, 0, 35, 100, 100, 100], - 71: [0, 0, 0, 0, 0, 16, 100, 100, 100], - 80: [0, 0, 0, 0, 0, 1, 100, 100, 100] + 5: [86, 73, 74, 80, 88, 98, 99, 99, 100], + 10: [57, 22, 19, 44, 86, 99, 100, 100, 100], + 15: [37, 7, 2, 12, 70, 99, 100, 100, 100], + 20: [23, 3, 0, 3, 40, 99, 100, 100, 100], + 25: [13, 1, 0, 1, 19, 99, 100, 100, 100], + 30: [8, 1, 0, 0, 4, 100, 100, 100, 100], + 35: [5, 0, 0, 0, 3, 99, 100, 100, 100], + 40: [3, 0, 0, 0, 1, 96, 100, 100, 100], + 45: [2, 0, 0, 0, 1, 83, 100, 100, 100], + 50: [1, 0, 0, 0, 0, 62, 100, 100, 100], + 60: [0, 0, 0, 0, 0, 18, 100, 100, 100], + 70: [0, 0, 0, 0, 0, 14, 100, 100, 100], + 80: [0, 0, 0, 0, 0, 4, 100, 100, 100] }; // Helper to get interpolated value from array @@ -122,17 +121,32 @@ export function calculateDifficulty(density, size = 10) { const solvedPct = getSimulatedSolvedPct(size, density); - // Difficulty Score: Inverse of Solved Percent - // 100% Solved -> 0 Difficulty - // 0% Solved -> 100 Difficulty - const value = Math.round(100 - solvedPct); - - // Thresholds - let level = 'easy'; - if (value >= 90) level = 'extreme'; // < 10% Solved - else if (value >= 60) level = 'hardest'; // < 40% Solved - else if (value >= 30) level = 'harder'; // < 70% Solved - else level = 'easy'; // > 70% Solved + let value; + let level; - return { level, value }; + // "Hardest" threshold is 99% solvability. + if (solvedPct < 99) { + // Extreme: Requires guessing + level = 'extreme'; + // Map 0-99% solved to value 85-100 + value = 85 + ((99 - solvedPct) / 99) * 15; + } else { + // Solvable (>= 99%) + // Density factor: 0.5 is hardest (1), 0.1/0.9 is easiest (0.2) + const densityFactor = 1 - 2 * Math.abs(density - 0.5); + + // Complexity based on Size and Density + // Max size 80. + // Formula: size * (0.4 + 0.6 * densityFactor) + // Max: 80 * 1 = 80. + const complexity = size * (0.4 + 0.6 * densityFactor); + + value = Math.min(85, complexity); + + if (value < 25) level = 'easy'; + else if (value < 55) level = 'harder'; + else level = 'hardest'; + } + + return { level, value: Math.round(value) }; }