@logic-pad/core
Version:
126 lines (125 loc) • 4.92 kB
JavaScript
import { ConfigType } from '../config.js';
import { array } from '../dataHelper.js';
import GridData from '../grid.js';
import { Color, State } from '../primitives.js';
import { getShapeVariants, sanitizePatternGrid, tilesToShape, } from '../shapes.js';
import RegionShapeRule from './regionShapeRule.js';
export default class ContainsShapeRule extends RegionShapeRule {
title = 'Areas Contain Pattern';
static EXAMPLE_GRID_LIGHT = Object.freeze(GridData.create(['nnnnn', 'nnnnn', 'wwwwn', 'nnnnn', 'nnnnn']));
static EXAMPLE_GRID_DARK = Object.freeze(GridData.create(['nnnnn', 'nnnnn', 'bbbbn', 'nnnnn', 'nnnnn']));
static CONFIGS = Object.freeze([
{
type: ConfigType.Color,
default: Color.Light,
allowGray: false,
field: 'color',
description: 'Color',
configurable: true,
},
{
type: ConfigType.Shape,
default: ContainsShapeRule.EXAMPLE_GRID_LIGHT,
resizable: true,
field: 'pattern',
description: 'Pattern',
explanation: 'The pattern to be contained. Must only include tiles of the selected color.',
configurable: true,
},
]);
static SEARCH_VARIANTS = [
new ContainsShapeRule(Color.Light, ContainsShapeRule.EXAMPLE_GRID_LIGHT).searchVariant(),
new ContainsShapeRule(Color.Dark, ContainsShapeRule.EXAMPLE_GRID_DARK).searchVariant(),
];
pattern;
cache;
/**
* **All <color> areas must contain this pattern**
*
* @param color - The color of the regions to compare.
* @param pattern - GridData representing the required pattern. Only non-gray tiles are considered.
*/
constructor(color, pattern) {
super(color);
this.pattern = sanitizePatternGrid(pattern, t => t.color === color ? t : t.withColor(Color.Gray));
this.cache = getShapeVariants(tilesToShape(this.pattern.tiles));
}
get id() {
return `contains_shape`;
}
get explanation() {
return `All ${this.color} areas must contain this pattern`;
}
get configs() {
return ContainsShapeRule.CONFIGS;
}
createExampleGrid() {
let minX = Number.POSITIVE_INFINITY;
let minY = Number.POSITIVE_INFINITY;
let maxX = Number.NEGATIVE_INFINITY;
let maxY = Number.NEGATIVE_INFINITY;
this.pattern.forEach((tile, x, y) => {
if (tile.color !== Color.Gray && tile.exists) {
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
}
});
const width = maxX - minX + 1;
const height = maxY - minY + 1;
if (!Number.isFinite(width) || !Number.isFinite(height)) {
return GridData.create(0, 0);
}
const tiles = array(width, height, (x, y) => {
const tile = this.pattern.getTile(x + minX, y + minY);
if (!tile.exists || tile.color !== Color.Gray)
return tile;
return tile.withExists(false);
});
return GridData.create(width, height, tiles);
}
get searchVariants() {
return ContainsShapeRule.SEARCH_VARIANTS;
}
validateGrid(grid) {
const { regions, complete } = this.getShapeRegions(grid);
const errorRegion = regions.find(r => {
for (const pattern of this.cache) {
if (r.shape.elements.length < pattern.elements.length)
continue;
for (let y = 0; y <= r.shape.height - pattern.height; y++) {
for (let x = 0; x <= r.shape.width - pattern.width; x++) {
let match = true;
for (const element of pattern.elements) {
const tile = r.shape.elements.find(e => e.x === x + element.x && e.y === y + element.y);
if (!tile || tile.color !== element.color) {
match = false;
break;
}
}
if (match)
return false;
}
}
}
return true;
});
if (errorRegion) {
return {
state: State.Error,
positions: errorRegion.positions,
};
}
else {
return { state: complete ? State.Satisfied : State.Incomplete };
}
}
copyWith({ color, pattern, }) {
return new ContainsShapeRule(color ?? this.color, pattern ?? this.pattern);
}
withPattern(pattern) {
return this.copyWith({ pattern });
}
}
export const instance = new ContainsShapeRule(Color.Dark, GridData.create([]));