UNPKG

@logic-pad/core

Version:
132 lines (131 loc) 5.26 kB
import { handlesFinalValidation } from './events/onFinalValidation.js'; import { handlesSymbolValidation } from './events/onSymbolValidation.js'; import { Color, State } from './primitives.js'; export function aggregateState(rules, grid, symbols) { if (rules.some(s => s.state === State.Error)) return State.Error; for (const [_, symbolList] of symbols) { if (symbolList.some(s => s === State.Error)) return State.Error; } if (rules.some((s, idx) => s.state === State.Incomplete && grid.rules[idx].necessaryForCompletion)) return State.Incomplete; for (const [key, symbolList] of symbols) { if (symbolList.some((s, idx) => s === State.Incomplete && grid.symbols.get(key)[idx].necessaryForCompletion)) return State.Incomplete; } if (rules.length === 0 && symbols.size === 0) return State.Incomplete; return State.Satisfied; } export function applyFinalOverrides(grid, solution, state) { grid.symbols.forEach(list => { list.forEach(sym => { if (handlesFinalValidation(sym)) { state = sym.onFinalValidation(grid, solution, state); } }); }); grid.rules.forEach(rule => { if (handlesFinalValidation(rule)) { state = rule.onFinalValidation(grid, solution, state); } }); return state; } export default function validateGrid(grid, solution) { let requireSolution = false; // validate all rules with self-contained logic const ruleStates = grid.rules.map(rule => { if (rule.validateWithSolution) requireSolution = true; return rule.validateGrid(grid); }); // validate all symbols with symbol overrides const symbolOverrideStates = ruleStates.map(() => []); const applySymbolOverrides = (grid, rules, symbol, validator) => { const [rule, ...rest] = rules; if (rule) { const newValidator = (grid) => applySymbolOverrides(grid, rest, symbol, () => validator(grid)); if (!handlesSymbolValidation(rule)) return newValidator(grid); const result = rule.onSymbolValidation(grid, symbol, newValidator); if (result === undefined) { return newValidator(grid); } const index = grid.rules.indexOf(rule); symbolOverrideStates[index].push(result); return result; } return validator(grid); }; const symbolStates = new Map(); grid.symbols.forEach((symbolList, id) => symbolStates.set(id, symbolList.map(s => { if (s.validateWithSolution) requireSolution = true; return applySymbolOverrides(grid, grid.rules, s, g => s.validateSymbol(g, solution)); }))); // apply the result of symbol overrides to the rules that provided them symbolOverrideStates.forEach((states, i) => { if (ruleStates[i].state !== State.Incomplete) return; if (states.some(s => s === State.Error)) ruleStates[i] = { state: State.Error, positions: [] }; else if (states.length > 0 && states.every(s => State.isSatisfied(s))) ruleStates[i] = { state: State.Satisfied }; }); let final = aggregateState(ruleStates, grid, symbolStates); // in addition to satisfying all rules and symbols, a solution must also fill the grid completely if (!requireSolution && State.isSatisfied(final)) { final = grid.forEach(tile => tile.exists && tile.color === Color.Gray ? true : undefined) ? State.Incomplete : State.Satisfied; } // return early if there is no need to validate against a solution if (State.isSatisfied(final) || !requireSolution || !solution || solution.width !== grid.width || solution.height !== grid.height) { return applyFinalOverrides(grid, solution, { final, rules: ruleStates, symbols: symbolStates, }); } // validate against the solution for (let y = 0; y < grid.height; y++) { for (let x = 0; x < grid.width; x++) { if (grid.getTile(x, y).exists && grid.getTile(x, y).color !== solution.getTile(x, y).color) { return applyFinalOverrides(grid, solution, { final: State.Incomplete, rules: ruleStates, symbols: symbolStates, }); } } } // mark all rules and symbols that are satisfied by the solution grid.rules.forEach((rule, i) => { if (rule.validateWithSolution) { ruleStates[i] = { ...ruleStates[i], state: State.Satisfied }; } }); grid.symbols.forEach((_, id) => { const symbolList = symbolStates.get(id); symbolList.forEach((_, i) => { const symbol = grid.symbols.get(id)[i]; if (symbol.validateWithSolution) { symbolList[i] = State.Satisfied; } }); }); const finalState = { final: State.Satisfied, rules: ruleStates, symbols: symbolStates, }; return applyFinalOverrides(grid, solution, finalState); }