@logic-pad/core
Version:
182 lines (181 loc) • 7.06 kB
JavaScript
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 <count> symbols per <color> 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);