@logic-pad/core
Version:
129 lines (128 loc) • 4.63 kB
JavaScript
import { Color, State } from '../../primitives.js';
import { Serializer } from '../../serializer/allSerializers.js';
import { instance as undercluedInstance } from '../../rules/undercluedRule.js';
import { array } from '../../dataHelper.js';
import validateGrid from '../../validate.js';
function gridToRawTiles(grid) {
return array(grid.width, grid.height, (x, y) => grid.getTile(x, y).color);
}
function rawTilesToGrid(rawTiles, grid) {
return grid.copyWith({
tiles: array(grid.width, grid.height, (x, y) => grid.getTile(x, y).withColor(rawTiles[y][x])),
}, false, false);
}
function getNextTile(grid, rawTiles) {
for (let y = 0; y < grid.height; y++) {
for (let x = 0; x < grid.width; x++) {
const tile = grid.getTile(x, y);
if (!tile.exists || tile.fixed)
continue;
if (rawTiles[y][x] === Color.Gray)
return [{ x, y }, Color.Dark];
}
}
return undefined;
}
function backtrack(grid, rawTiles, submitSolution) {
// Find the best empty cell to guess
const target = getNextTile(grid, rawTiles);
// Found a solution
if (!target)
return !submitSolution(rawTiles);
const [pos, color] = target;
const positions = grid.connections.getConnectedTiles(pos);
for (let i = 0; i <= 1; i++) {
const tile = i === 0 ? color : color === Color.Dark ? Color.Light : Color.Dark;
positions.forEach(({ x, y }) => (rawTiles[y][x] = tile));
const newGrid = rawTilesToGrid(rawTiles, grid);
const isValid = validateGrid(newGrid, null);
if (isValid.final !== State.Error &&
backtrack(newGrid, rawTiles, submitSolution))
return true;
}
positions.forEach(({ x, y }) => (rawTiles[y][x] = Color.Gray));
return false;
}
function solveNormal(input, submitSolution) {
const isValid = validateGrid(input, null);
if (isValid.final === State.Error) {
return;
}
// Call backtrack
backtrack(input, gridToRawTiles(input), rawTiles => submitSolution(rawTiles ? rawTilesToGrid(rawTiles, input) : null));
}
function solveUnderclued(input) {
let grid = input;
const possibles = array(grid.width, grid.height, () => ({
dark: false,
light: false,
}));
function search(x, y, tile, color) {
const newGrid = grid.copyWith({
tiles: grid.setTile(x, y, tile.withColor(color)),
}, false, false);
// Solve
let solution;
solveNormal(newGrid, sol => {
solution = sol;
return false;
});
if (!solution)
return false;
// Update the new possible states
solution.forEach((solTile, solX, solY) => {
if (solTile.color === Color.Dark) {
possibles[solY][solX].dark = true;
}
else {
possibles[solY][solX].light = true;
}
});
return true;
}
for (let y = 0; y < grid.height; y++) {
for (let x = 0; x < grid.width; x++) {
const tile = grid.getTile(x, y);
if (!tile.exists || tile.color !== Color.Gray)
continue;
// We can skip this solve if it is proved to be solvable
const darkPossible = possibles[y][x].dark || search(x, y, tile, Color.Dark);
const lightPossible = possibles[y][x].light || search(x, y, tile, Color.Light);
// No solution
if (!darkPossible && !lightPossible)
return null;
if (darkPossible && !lightPossible)
grid = grid.copyWith({
tiles: grid.setTile(x, y, tile.withColor(Color.Dark)),
}, false, false);
if (!darkPossible && lightPossible)
grid = grid.copyWith({
tiles: grid.setTile(x, y, tile.withColor(Color.Light)),
}, false, false);
}
}
return grid;
}
function solve(grid, submitSolution) {
if (grid.findRule(rule => rule.id === undercluedInstance.id)) {
submitSolution(solveUnderclued(grid));
}
else {
solveNormal(grid, submitSolution);
}
}
onmessage = e => {
const grid = Serializer.parseGrid(e.data);
let count = 0;
solve(grid, solution => {
// if (solution) {
// if (solution.resetTiles().colorEquals(solution)) {
// solution = null;
// }
// }
postMessage(solution ? Serializer.stringifyGrid(solution) : null);
count += 1;
return count < 2;
});
postMessage(null);
};