UNPKG

@logic-pad/core

Version:
157 lines (156 loc) 6.53 kB
import { Color, State } from '../../primitives.js'; import { instance as lyingSymbolInstance } from '../../rules/lyingSymbolRule.js'; import { instance as offByXInstance } from '../../rules/offByXRule.js'; import { instance as wrapAroundInstance } from '../../rules/wrapAroundRule.js'; import { instance as areaNumberInstance } from '../../symbols/areaNumberSymbol.js'; import { instance as letterInstance } from '../../symbols/letterSymbol.js'; import { allSolvers } from '../allSolvers.js'; import Solver from '../solver.js'; import UndercluedRule from '../../rules/undercluedRule.js'; import validateGrid from '../../validate.js'; import UnsupportedSymbol from '../../symbols/unsupportedSymbol.js'; export default class AutoSolver extends Solver { id = 'auto'; author = 'various contributors'; description = 'Automatically select the fastest solver based on supported instructions and environment.'; supportsCancellation = true; static nonAdditiveInstructions = new Set([ offByXInstance.id, lyingSymbolInstance.id, wrapAroundInstance.id, ]); isGridSupported(grid) { for (const solver of allSolvers.values()) { if (solver.id === this.id) continue; if (solver.isGridSupported(grid)) { return true; } } return false; } isInstructionSupported(grid, instruction) { for (const solver of allSolvers.values()) { if (solver.id === this.id) continue; if (solver.isInstructionSupported(grid, instruction)) { return true; } } return false; } async isEnvironmentSupported() { for (const solver of allSolvers.values()) { if (solver.id === this.id) continue; if (await solver.environmentCheck.value) { return true; } } return false; } fillSolution(grid, solution) { return grid.withTiles(tiles => { return tiles.map((row, y) => row.map((tile, x) => { if (!tile.exists || tile.fixed) return tile; const solutionTile = solution.tiles[y][x]; return tile.withColor(solutionTile.color); })); }); } fixGrid(grid) { return grid.withTiles(tiles => { return tiles.map(row => row.map(tile => { if (tile.fixed) return tile; return tile.withFixed(tile.color !== Color.Gray); })); }); } async *solveWithProgress(solver, grid, progress, abortSignal) { for await (const updatedGrid of solver.solve(progress, abortSignal)) { if (abortSignal?.aborted) return; if (!updatedGrid) { yield updatedGrid; return; } yield this.fillSolution(grid, updatedGrid); } } async solveOne(generator) { for await (const grid of generator) { return grid; } return null; } async *solve(grid, abortSignal) { if (!!grid.findRule(r => AutoSolver.nonAdditiveInstructions.has(r.id)) || !!grid.findSymbol(s => AutoSolver.nonAdditiveInstructions.has(s.id))) { for (const solver of allSolvers.values()) { if (solver.id === this.id) continue; if (solver.isGridSupported(grid)) { yield* solver.solve(grid, abortSignal); return; } } throw new Error('No solver supports the given grid'); } else { let progressGrid = grid; for (const solver of allSolvers.values()) { if (solver.id === this.id) continue; if (solver.isGridSupported(progressGrid)) { yield* this.solveWithProgress(solver, grid, progressGrid, abortSignal); return; } else if (solver.isGridSupported(grid)) { yield* solver.solve(grid, abortSignal); return; } else { const undercluedGrid = progressGrid .withRules(rules => rules.filter(r => solver.isInstructionSupported(progressGrid, r))) .withSymbols(symbols => { for (const [id, symbolList] of symbols.entries()) { symbols.set(id, symbolList.map(symbol => { // special handling: do not delete area number and letter symbols as they can be solved // underclued even if the solver doesn't fully support them if (symbol.id === areaNumberInstance.id || symbol.id === letterInstance.id) return symbol; if (solver.isInstructionSupported(progressGrid, symbol)) return symbol; return new UnsupportedSymbol(symbol.x, symbol.y); })); } return symbols; }) .addRule(new UndercluedRule()); if (!solver.isGridSupported(undercluedGrid)) continue; const undercluedSolution = await this.solveOne(this.solveWithProgress(solver, progressGrid, undercluedGrid, abortSignal)); if (undercluedSolution === null) continue; if (undercluedSolution.getTileCount(true, false, Color.Gray) === 0) { const result = this.fillSolution(grid, undercluedSolution); if (validateGrid(result, null).final !== State.Error) { yield result; yield null; return; } else { yield null; return; } } progressGrid = this.fixGrid(undercluedSolution); } } yield this.fillSolution(grid, progressGrid); } } }