export function calculateLineHints(line) { const hints = []; let currentRun = 0; for (const cell of line) { if (cell === 1) { currentRun++; } else { if (currentRun > 0) { hints.push(currentRun); currentRun = 0; } } } if (currentRun > 0) { hints.push(currentRun); } return hints.length > 0 ? hints : [0]; } export function validateLine(line, targetHints) { const currentHints = calculateLineHints(line); if (currentHints.length !== targetHints.length) return false; return currentHints.every((h, i) => h === targetHints[i]); } export function calculateHints(grid) { if (!grid || grid.length === 0) return { rowHints: [], colHints: [] }; const size = grid.length; const rowHints = []; const colHints = []; // Row Hints for (let r = 0; r < size; r++) { rowHints.push(calculateLineHints(grid[r])); } // Col Hints for (let c = 0; c < size; c++) { const col = []; for (let r = 0; r < size; r++) { col.push(grid[r][c]); } colHints.push(calculateLineHints(col)); } return { rowHints, colHints }; } export function generateRandomGrid(size, density = 0.5) { const grid = []; for (let i = 0; i < size; i++) { const row = []; for (let j = 0; j < size; j++) { row.push(Math.random() < density ? 1 : 0); } grid.push(row); } return grid; } 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] } const SIM_DATA = { 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 const getSimulatedSolvedPct = (s, d) => { // Find closest sizes const sizes = Object.keys(SIM_DATA).map(Number).sort((a, b) => a - b); let sLower = sizes[0]; let sUpper = sizes[sizes.length - 1]; for (let i = 0; i < sizes.length - 1; i++) { if (s >= sizes[i] && s <= sizes[i+1]) { sLower = sizes[i]; sUpper = sizes[i+1]; break; } } // Clamp density to 0.1 - 0.9 const dClamped = Math.max(0.1, Math.min(0.9, d)); // Index in array: 0.1 -> 0, 0.9 -> 8 const dIndex = (dClamped - 0.1) * 10; const dLowerIdx = Math.floor(dIndex); const dUpperIdx = Math.ceil(dIndex); const dFraction = dIndex - dLowerIdx; // Bilinear Interpolation // 1. Interpolate Density for Lower Size const rowLower = SIM_DATA[sLower]; const valLower = rowLower[dLowerIdx] * (1 - dFraction) + (rowLower[dUpperIdx] || rowLower[dLowerIdx]) * dFraction; // 2. Interpolate Density for Upper Size const rowUpper = SIM_DATA[sUpper]; const valUpper = rowUpper[dLowerIdx] * (1 - dFraction) + (rowUpper[dUpperIdx] || rowUpper[dLowerIdx]) * dFraction; // 3. Interpolate Size if (sLower === sUpper) return valLower; const sFraction = (s - sLower) / (sUpper - sLower); return valLower * (1 - sFraction) + valUpper * sFraction; }; const solvedPct = getSimulatedSolvedPct(size, density); let value; let level; // "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) }; }