UNPKG

@logic-pad/core

Version:
182 lines (181 loc) 7.06 kB
import Rule from './rule.js'; import GridData from '../grid.js'; import { ConfigType } from '../config.js'; import { Color, Comparison, State, } from '../primitives.js'; import { array } from '../dataHelper.js'; import LetterSymbol from '../symbols/letterSymbol.js'; import GridConnections from '../gridConnections.js'; export default class SymbolsPerRegionRule extends Rule { color; count; comparison; title = 'Symbols Per Area'; static SYMBOL_POSITIONS = [ { x: 1, y: 1 }, { x: 2, y: 2 }, { x: 2, y: 1 }, { x: 3, y: 1 }, { x: 1, y: 2 }, ]; static CONFIGS = Object.freeze([ { type: ConfigType.Number, default: 1, field: 'count', description: 'Count', configurable: true, }, { type: ConfigType.Color, default: Color.Light, field: 'color', description: 'Color', configurable: true, allowGray: true, }, { type: ConfigType.Comparison, default: Comparison.Equal, field: 'comparison', description: 'Comparison', configurable: true, }, ]); static EXAMPLE_GRIDS = { [Color.Dark]: GridData.create(['wwwww', 'wbbbw', 'wbbww', 'wwwww']), [Color.Light]: GridData.create(['bbbbb', 'bwwwb', 'bwwbb', 'bbbbb']), [Color.Gray]: GridData.create(['bwbwb', 'wnnnw', 'bnnwb', 'wbwbw']), }; static SEARCH_VARIANTS = [ new SymbolsPerRegionRule(Color.Light, 1).searchVariant(), new SymbolsPerRegionRule(Color.Dark, 1).searchVariant(), new SymbolsPerRegionRule(Color.Light, 1, Comparison.AtLeast).searchVariant(), new SymbolsPerRegionRule(Color.Dark, 1, Comparison.AtLeast).searchVariant(), new SymbolsPerRegionRule(Color.Light, 1, Comparison.AtMost).searchVariant(), new SymbolsPerRegionRule(Color.Dark, 1, Comparison.AtMost).searchVariant(), ]; /** * **Exactly &lt;count&gt; symbols per &lt;color&gt; area** * * @param color - Color of the region affected by the rule * @param count - Number of symbols to have in each region * @param comparison - Comparison to use when checking the number of symbols */ constructor(color, count, comparison = Comparison.Equal) { super(); this.color = color; this.count = count; this.comparison = comparison; this.color = color; this.count = count; this.comparison = comparison; } get id() { return `symbols_per_region`; } get explanation() { switch (this.comparison) { case Comparison.AtLeast: return `At least ${this.count} symbol${this.count === 1 ? '' : 's'} per ${this.color} area`; case Comparison.AtMost: return `At most ${this.count} symbol${this.count === 1 ? '' : 's'} per ${this.color} area`; default: return `Exactly ${this.count} symbol${this.count === 1 ? '' : 's'} per ${this.color} area`; } } get configs() { return SymbolsPerRegionRule.CONFIGS; } createExampleGrid() { if (this.count > SymbolsPerRegionRule.SYMBOL_POSITIONS.length || this.comparison !== Comparison.Equal) { let description = ''; switch (this.comparison) { case Comparison.AtLeast: description = `≥${this.count}X`; break; case Comparison.AtMost: description = `≤${this.count}X`; break; default: description = `${this.count}X`; break; } const symbol = new LetterSymbol(1.5, 1.5, description); return SymbolsPerRegionRule.EXAMPLE_GRIDS[this.color] .addSymbol(symbol) .withConnections(GridConnections.create(['.....', '.aa..', '.aa..', '.....'])); } const symbols = []; for (let i = 0; i < this.count; i++) { const { x, y } = SymbolsPerRegionRule.SYMBOL_POSITIONS[i]; symbols.push(new LetterSymbol(x, y, 'X')); } return SymbolsPerRegionRule.EXAMPLE_GRIDS[this.color].withSymbols(symbols); } get searchVariants() { return SymbolsPerRegionRule.SEARCH_VARIANTS; } validateGrid(grid) { const visited = array(grid.width, grid.height, (i, j) => !(grid.getTile(i, j).exists && grid.getTile(i, j).color === this.color)); let complete = true; while (true) { const seed = grid.find((_tile, x, y) => !visited[y][x]); if (!seed) break; const completed = []; const gray = []; let nbSymbolsIn = 0; grid.iterateArea({ x: seed.x, y: seed.y }, tile => tile.color === this.color, (_, x, y) => { completed.push({ x, y }); visited[y][x] = true; nbSymbolsIn += SymbolsPerRegionRule.countAllSymbolsOfPosition(grid, x, y); }); if (this.comparison !== Comparison.AtLeast && nbSymbolsIn > this.count) { return { state: State.Error, positions: completed }; } let nbSymbolsOut = 0; if (this.color === Color.Gray) { gray.push(...completed); nbSymbolsOut = nbSymbolsIn; } else { grid.iterateArea({ x: seed.x, y: seed.y }, tile => tile.color === Color.Gray || tile.color === this.color, (_, x, y) => { gray.push({ x, y }); nbSymbolsOut += SymbolsPerRegionRule.countAllSymbolsOfPosition(grid, x, y); }); } if (this.comparison !== Comparison.AtMost && nbSymbolsOut < this.count) { return { state: State.Error, positions: gray }; } if (gray.length !== completed.length) { complete = false; } } return complete ? { state: State.Satisfied } : { state: State.Incomplete }; } copyWith({ count, color, comparison, }) { return new SymbolsPerRegionRule(color ?? this.color, count ?? this.count, comparison ?? this.comparison); } withColor(color) { return this.copyWith({ color }); } withCount(count) { return this.copyWith({ count }); } withComparison(comparison) { return this.copyWith({ comparison }); } static countAllSymbolsOfPosition(grid, x, y) { let count = 0; for (const symbolKind of grid.symbols.values()) { if (symbolKind.some(symbol => Math.floor(symbol.x) === x && Math.floor(symbol.y) === y && symbol.necessaryForCompletion)) { count++; } } return count; } } export const instance = new SymbolsPerRegionRule(Color.Dark, 1);