UNPKG

@logic-pad/core

Version:
170 lines (169 loc) 5.61 kB
import GridData from '../grid.js'; import { Color, State, Mode, } from '../primitives.js'; import Rule from './rule.js'; import CustomIconSymbol from '../symbols/customIconSymbol.js'; import validateGrid from '../validate.js'; class PerfectionRule extends Rule { /** * **Quest for Perfection: cell colors are final** * * @param editor - whether to enable editor mode. This field is automatically set by the editor. */ constructor(editor = false) { super(); Object.defineProperty(this, "editor", { enumerable: true, configurable: true, writable: true, value: editor }); this.editor = editor; } get id() { return `perfection`; } get explanation() { return `*Quest for Perfection*: cell colors are final`; } get configs() { return null; } createExampleGrid() { return PerfectionRule.EXAMPLE_GRID; } get searchVariants() { return PerfectionRule.SEARCH_VARIANTS; } get necessaryForCompletion() { return false; } get isSingleton() { return true; } modeVariant(mode) { // only allow this rule in perfection mode if (this.editor === (mode === Mode.Create)) { return this; } else if (mode === Mode.Create) { return this.copyWith({ editor: true }); } else { return this.copyWith({ editor: false }); } } validateGrid(grid) { if (grid.getTileCount(true, undefined, Color.Gray) > 0) { return { state: State.Incomplete }; } else { return { state: State.Satisfied }; } } /** * If the grid passes validation but is different from the solution, indicate the error in the final state. */ onFinalValidation(grid, solution, state) { if (state.final === State.Error) return state; if (solution === null) return state; const positions = []; grid.tiles.forEach((row, y) => row.forEach((t, x) => { if (t.exists && t.color !== Color.Gray && t.color !== solution.getTile(x, y).color) { positions.push({ x, y }); } })); if (positions.length > 0) { const ruleId = grid.rules.indexOf(this); return { final: State.Error, rules: state.rules.map((r, idx) => { if (idx === ruleId) { return { state: State.Error, positions }; } else { return r; } }), symbols: state.symbols, }; } return state; } fixTiles(grid, exclusions) { if (grid.getTileCount(true, false, Color.Light) > 0 || grid.getTileCount(true, false, Color.Dark) > 0) { return grid.withTiles(tiles => tiles.map((row, y) => row.map((t, x) => t.exists && t.color !== Color.Gray && !exclusions?.some(e => e.x === x && e.y === y) ? t.withFixed(true) : t))); } return grid; } isValid(grid, solution) { return validateGrid(grid, solution).final !== State.Error; } findSingleError(grid, solution) { if (solution === null) return []; const positions = []; // If a solution is available, we can compare against the solution and allow the user to modify the one single error. grid.tiles.forEach((row, y) => row.forEach((t, x) => { if (t.exists && t.color !== Color.Gray && t.color !== solution.getTile(x, y).color) { positions.push({ x, y }); } })); if (positions.length > 1) { const connected = grid.connections.getConnectedTiles(positions[0]); if (!positions.every(p => connected.some(c => c.x === p.x && c.y === p.y))) { return []; } } return positions; } /** * Force all tiles to be fixed. * * If the grid is already wrong, prevent the player from changing it further. */ onSetGrid(oldGrid, newGrid, solution) { if (this.editor) return newGrid; const oldGridIsValid = this.isValid(oldGrid, solution); const newGridIsValid = this.isValid(newGrid, solution); if (!oldGridIsValid && !newGridIsValid) { const oldPositions = this.findSingleError(oldGrid, solution); return this.fixTiles(oldGrid, oldPositions); } else if (!newGridIsValid) { const positions = this.findSingleError(newGrid, solution); return this.fixTiles(newGrid, positions); } return this.fixTiles(newGrid); } copyWith({ editor }) { return new PerfectionRule(editor ?? this.editor); } } Object.defineProperty(PerfectionRule, "EXAMPLE_GRID", { enumerable: true, configurable: true, writable: true, value: Object.freeze(GridData.create(['w']).addSymbol(new CustomIconSymbol('', GridData.create(['w']), 0, 0, 'MdStars'))) }); Object.defineProperty(PerfectionRule, "SEARCH_VARIANTS", { enumerable: true, configurable: true, writable: true, value: [ new PerfectionRule().searchVariant(), ] }); export default PerfectionRule; export const instance = new PerfectionRule();