Sort of reached an acceptable state where two first solver rules work

This commit is contained in:
Tissevert 2022-09-01 21:51:48 +02:00
parent 0a61d9a389
commit 213eec19fe
18 changed files with 213 additions and 279 deletions

View file

@ -1,99 +1,58 @@
import {diagonal, isSmaller, key, plus, ofKey, zero} from Geometry.Vector;
import {at, generate} from Grid.Util;
import size from Config;
import {isSmaller, plus, zero} from Geometry.Vector;
import * as Vector from Geometry.Vector;
import {at, iter} from Grid.Util;
import * as Matrix from Grid.Util;
import Set;
function CellSet(definition) {
definition = definition || {};
this.cells = {};
if(definition.type == 'rectangle') {
this.fromRectangle(definition.origin, definition.offset);
} else if(definition.type == 'isochrome') {
this.fromIsochrome(definition.grid, definition.origin);
} else if(definition.type == 'union') {
this.fromUnion(definition.sets);
}
}
var CellSet = Set.make(Vector);
CellSet.prototype.fromRectangle = function(origin, maxOffset) {
generate(
maxOffset.row,
maxOffset.column,
function(offset) {
this.add(plus(origin, offset));
}.bind(this)
);
return {
CellSet: CellSet,
union: CellSet.union,
rectangle: rectangle,
isochrome: isochrome,
row: row,
column: column
};
CellSet.prototype.fromIsochrome = function(grid, origin) {
var originColor = at(grid, origin);
function rectangle(origin, delta) {
var cellSet = new CellSet();
iter(Matrix.make(delta), function(_, cell) {cellSet.add(plus(origin, cell));});
return cellSet;
}
function isochrome(coloring, origin) {
var cellSet = new CellSet();
var color = at(coloring, origin);
var queue = [origin];
while(queue.length > 0) {
var cell = queue.shift();
this.add(cell);
cellSet.add(cell);
for(var d = -1; d < 2; d += 2) {
[plus(cell, vertical(d)), plus(cell, horizontal(d))].forEach(
gateKeeper(this, grid, queue, originColor)
[plus(cell, Vector.vertical(d)), plus(cell, Vector.horizontal(d))].forEach(
gateKeeper(cellSet, coloring, queue, color)
);
}
}
};
CellSet.prototype.fromUnion = function(sets) {
for(var i = 0; i < sets.length; i++) {
for(var k in sets[i].cells) {
this.cells[k] = true;
}
}
return cellSet;
}
CellSet.prototype.add = function(v) {
this.cells[key(v)] = true;
};
CellSet.prototype.remove = function(v) {
delete this.cells[key(v)];
};
CellSet.prototype.contains = function(v) {
return !!this.cells[key(v)];
};
CellSet.prototype.isEmpty = function() {
return Object.keys(this.cells).length < 1;
};
CellSet.prototype.map = function(f) {
var result = [];
for(var k in this.cells) {
result.push(f(ofKey(k)));
}
return result;
}
CellSet.prototype.iter = function(f) {
this.map(f);
}
CellSet.prototype.difference = function(cellSet) {
var newCellSet = new CellSet();
this.iter(function(v) {
if(!cellSet.contains(v)) {
newCellSet.add(v);
}
});
return newCellSet;
}
return {
make: function(definition) {return new CellSet(definition);},
union: function(sets) {return new CellSet({type: 'union', sets: sets});}
};
function gateKeeper(cellSet, grid, queue, originColor) {
function gateKeeper(cellSet, coloring, queue, color) {
return function(cell) {
if(isSmaller(zero(), cell) && isSmaller(cell, diagonal(grid.length-1))
if(isSmaller(zero(), cell)
&& isSmaller(cell, Vector.diagonal(size-1))
&& !cellSet.contains(cell)
&& at(grid, cell) == originColor) {
&& at(coloring, cell) == color) {
queue.push(cell);
}
}
}
function row(n) {
return rectangle(Vector.vertical(n), Vector.make(1, size));
}
function column(n) {
return rectangle(Vector.horizontal(n), Vector.make(size, 1));
}

View file

@ -1,5 +1,7 @@
return {
make: make,
getRow: getRow,
getColumn: getColumn,
vertical: vertical,
horizontal: horizontal,
diagonal: diagonal,
@ -7,12 +9,20 @@ return {
plus: plus,
opposite: opposite,
isSmaller: isSmaller,
key: key,
ofKey: ofKey
toKey: toKey,
ofKey: ofKey,
};
function make(row, column) {
return {row: row, column: column};
return {row: parseInt(row), column: parseInt(column)};
}
function getRow(v) {
return v.row;
}
function getColumn(v) {
return v.column;
}
function zero() {
@ -32,21 +42,23 @@ function diagonal(length) {
}
function plus(v0, v1) {
return {row: v0.row + v1.row, column: v0.column + v1.column};
return make(v0.row + v1.row, v0.column + v1.column);
}
function opposite(v) {
return {row: -v.row, column: -v.column};
return make(-v.row, -v.column);
}
function isSmaller(v0, v1) {
return v0.row <= v1.row && v0.column <= v1.column;
}
function key(v) {
function toKey(v) {
return v.row + ':' + v.column;
//return 8*v.row + v.column;
}
function ofKey(k) {
return make.apply(null, k.split(':'));
//return make.call(null, Math.floor(k / 8), k % 8);
}

View file

@ -1,3 +1,4 @@
import size from Config;
import * as CellSet from Geometry.CellSet;
import * as Dom from UnitJS.Dom;
import {at, generate, iter, square} from Grid.Util;
@ -8,7 +9,6 @@ var grid = {
cells: null,
colors: null,
missing: null,
size: null
};
return {
@ -17,9 +17,8 @@ return {
init: init,
};
function init(size, eventHandlers) {
grid.size = size;
grid.cells = generate(size, size, function(cell) {
function init(eventHandlers) {
grid.cells = square(size, function(cell) {
return Dom.make('td', eventHandlers(cell));
});
for(var row = 0; row < size; row++) {
@ -29,13 +28,9 @@ function init(size, eventHandlers) {
}
function clear() {
grid.colors = square(grid.size);
grid.missing = CellSet.make(
{type: 'rectangle', origin: zero(), offset: diagonal(grid.size)}
);
iter(grid.colors, function(cell) {
at(grid.cells, cell).className = '';
});
grid.colors = square(size);
grid.missing = CellSet.rectangle(zero(), diagonal(size));
iter(grid.cells, function(td) {td.className = '';});
}
function get() {

View file

@ -12,29 +12,27 @@ return {
function colorize(cell, color) {
var grid = Grid.get();
set(grid.colors, cell, color || Toolbox.color());
set(grid.colors, cell, color ?? Toolbox.color());
at(Grid.get().cells, cell).className = 'color' + at(grid.colors, cell);
grid.missing.remove(cell);
}
function paint(origin) {
var cellSet = CellSet.make(
{type: 'isochrome', grid: Grid.get().colors, origin: origin}
);
cellSet.iter(colorize);
CellSet.isochrome(Grid.get().colors, origin)
.iter(function(cell) {colorize(cell);});
}
function setColors(grid) {
if(grid != undefined) {
iter(grid, function(cell) {
if(at(grid, cell) != undefined) {
colorize(cell, at(grid, cell));
function setColors(coloring) {
if(coloring != undefined) {
iter(coloring, function(color, cell) {
if(color != undefined) {
colorize(cell, color);
}
});
if(Grid.get().missing.isEmpty()) {
Mode.setEnabled(true, ['play', 'solve']);
} else {
if(Grid.get().missing.size() > 0) {
Mode.set('edit');
} else {
Mode.setEnabled(true, ['play', 'solve']);
}
}
}

View file

@ -1,7 +1,9 @@
import * as Vector from Geometry.Vector;
return {
make: make,
at: at,
asAFunction: asAFunction,
column: column,
generate: generate,
iter: iter,
@ -21,18 +23,39 @@ function generate(width, height, f) {
return result;
}
function make(width, height) {
if(width instanceof Object && width.row != undefined) {
height = width.row;
width = width.column;
}
return Array.from({length: height}).map(
function() {return Array.from({length: width});}
);
}
function iter(grid, f) {
generate(grid.length > 0 ? grid[0].length : null, grid.length, f);
map(grid, f);
}
function map(grid, f) {
return grid.map(function(rowArray, row) {return rowArray.map(
function(value, column) {return f(value, Vector.make(row, column), grid);}
)});
}
function square(size, value) {
return generate(size, size, function() {return value;});
var generator = value instanceof Function ? value : function() {return value;};
return map(make(size, size), function(_, cell) {return generator(cell);});
}
function at(grid, vector) {
return grid[vector.row][vector.column];
}
function asAFunction(grid) {
return function (vector) {return at(grid, vector);};
}
function set(grid, vector, value) {
return grid[vector.row][vector.column] = value;
}

View file

@ -8,17 +8,15 @@ import * as Play from Mode.Play;
import * as Solve from Mode.Solve;
import * as Edit from Mode.Edit;
var size = 8;
Toolbox.init(size);
Toolbox.init();
Mode.init({
play: Play,
solve: Solve,
edit: Edit
});
Grid.init(size, Mode.dispatch);
Grid.init(Mode.dispatch);
if(window.location.search.length > 0) {
var urlSearchParameters = new URLSearchParams(window.location.search);
Grid.Color.set(Share.decode(size, urlSearchParameters.get('game')));
Grid.Color.set(Share.decode(urlSearchParameters.get('game')));
}
Grid.IO.init();

View file

@ -23,7 +23,7 @@ return {
function onEnter() {
GUI.activate(true, [Grid.get().root, Toolbox.get(), save]);
if(!Grid.get().missing.isEmpty()) {
if(Grid.get().missing.size() > 0) {
Mode.setEnabled(false, ['play', 'solve']);
} else {
Share.link(Grid.get().colors);
@ -65,7 +65,7 @@ function colorCell(cell) {
}
function checkCompleteness() {
if(Grid.get().missing.isEmpty()) {
if(Grid.get().missing.size() < 1) {
Mode.setEnabled(true, ['play', 'solve']);
Share.link(Grid.get().colors);
}

View file

@ -9,7 +9,7 @@ return {
};
function onClick(e, cell) {
if(Grid.get().missing.isEmpty()) {
if(Grid.get().missing.size() < 1) {
rotateState(at(Grid.get().cells, cell));
}
}

View file

@ -8,5 +8,5 @@ return {
};
function onEnter() {
console.log(Solver.step(Grid.get().colors));
console.log(Solver.solve(Grid.get().colors));
}

View file

@ -1,13 +1,15 @@
return {
make: make
make: make,
Int: make({toKey: id, ofKey: parseInt})
};
function make(type) {
if(type == undefined) {
var id = function(x) {return x;};
type = {toKey: id, ofKey: id};
function id(x) {
return x;
}
function make(type) {
type = type ?? {toKey: id, ofKey: id};
function Set(s) {
this.elements = {};
if(Array.isArray(s)) {
@ -65,5 +67,9 @@ function make(type) {
return newSet;
}
Set.uniq = function(t) {
return (new Set(t)).toList();
}
return Set;
}

View file

@ -1,6 +1,8 @@
import size from Config;
import {at, generate, iter, square} from Grid.Util;
import * as Decode from Share.Decoder.Protocol;
import * as Encode from Share.Encoder.Protocol;
import * as Encoder from Share.Encoder.Class;
import GUI;
import Grid;
@ -16,23 +18,24 @@ function get() {
return share;
}
function naiveEncode(grid) {
function naiveEncode(coloring) {
var encoder = Encoder.make();
iter(grid, function(cell) {
encoder.int(3)(at(grid, cell));
iter(coloring, function(color) {
encoder.int(3)(color);
});
return encoder.output();
}
function naiveDecode(size, input) {
function naiveDecode(input) {
if(input != undefined) {
var decoder = Decoder.make(input);
return generate(size, size, function() {return decoder.int(3);});
}
}
function link(grid) {
function link() {
//share.href = '?game=' + naiveEncode(Grid.get().colors);
console.log(naiveEncode(Grid.get().colors));
share.href = '?game=' + Encode.grid(Grid.get().colors);
GUI.activate(true, share);
}

View file

@ -1,3 +1,4 @@
import size from Config;
import * as CellSet from Geometry.CellSet;
import * as Decoder from Share.Decoder.Class;
import {diagonal, plus, zero} from Geometry.Vector;
@ -8,12 +9,12 @@ return {
grid: decodeGrid
};
function decodeGrid(size, input) {
function decodeGrid(input) {
if(input != undefined) {
return decoderLoop(
Decoder.make(input),
square(size),
CellSet.make({type: 'rectangle', origin: zero(), offset: diagonal(size)})
CellSet.rectangle(zero(), diagonal(size))
);
}
}

View file

@ -44,7 +44,7 @@ Encoder.prototype.variableLength6 = function(n) {
Encoder.prototype.int = function(size) {
return function(n) {
for(var i = 0; i < size; i++) {
encoder.push(n > 3);
this.push(n > 3);
n = (2*n) & 7;
}
}.bind(this);

View file

@ -1,5 +1,8 @@
import {diagonal, isSmaller, plus} from Geometry.Vector;
import * as Vector from Geometry.Vector;
import * as Encoder from Share.Encoder.Class;
import {at, iter, square} from Grid.Util;
import * as Grid from Grid.Util;
import * as Protocol from Share.Protocol;
return {
@ -10,65 +13,55 @@ function encodeGrid(grid) {
var encoder = Encoder.make();
var done = square(grid.length, false);
var gradients = square(grid.length);
iter(grid, function(cell) {
iter(grid, function(_, cell) {
if(!at(done, cell)) {
let block = getLongestBlock(grid, cell);
if(block != undefined) {
encodeBlock(encoder, block);
if(block.size >= Protocol.MIN_BLOCK_SIZE) {
encodeBlock(encoder, done, block);
} else {
encodeSingleCell(
encoder,
getColorGradient(gradients, grid, cell)
getColorGradient(gradients, grid, done, cell)
);
}
}
});
return encoder.output();
}
function getLongestBlock(grid, origin) {
var color = at(grid, origin);
var longestDirection = findLongestDirection(grid, origin, color);
var size = extend(grid, origin, color, longestDirection);
if(size >= Protocol.MIN_BLOCK_SIZE) {
var hSize = getStreak(Grid.row(grid, origin.row), origin.column, color);
var vSize = getStreak(Grid.column(grid, origin.column), origin.row, color);
return {
direction: longestDirection.coordinate == 'row' ? 'vertical' : 'horizontal',
size: size,
color: color
isVertical: vSize > hSize,
size: Math.max(hSize, vSize),
color: color,
origin: origin
};
}
function getStreak(t, firstIndex, color) {
var fromFirst = t.slice(firstIndex);
var index = fromFirst.findIndex(
function(x) {return x != color;}
);
return index >= 0 ? index : fromFirst.length;
}
function findLongestDirection(grid, p, originColor) {
var delta = 1;
while(true) {
var newRow = p.row + delta;
var newColumn = p.column + delta;
if(newRow >= grid.length || grid[newRow][p.column] != originColor) {
return {coordinate: 'column', delta: delta};
} else if(newColumn >= grid[p.row].length || grid[p.row][newColumn] != originColor) {
return {coordinate: 'row', delta: delta+1};
}
delta++;
}
}
function extend(grid, p, originColor, direction) {
var origin = p[direction.coordinate];
p[direction.coordinate] += direction.delta;
while(isSmaller(p, diagonal(grid.length)) && at(grid, p) == originColor) {
p[direction.coordinate]++;
}
return p[direction.coordinate] - origin;
}
function encodeBlock(encoder, block) {
function encodeBlock(encoder, done, block) {
encoder.push(1);
encoder.push(block.direction == 'vertical');
encoder.push(block.isVertical);
encoder.variableLength6(block.size - Protocol.MIN_BLOCK_SIZE);
encoder.int(3)(block.color);
var cell = Vector.make(block.origin.row, block.origin.column);
for(var i = 0; i < block.size; i++) {
Grid.set(done, cell, true);
cell[block.isVertical ? 'row' : 'column']++;
}
}
function getColorGradient(gradients, grid, cell) {
function getColorGradient(gradients, grid, done, cell) {
if(at(gradients, cell) == undefined) {
}

View file

@ -1,114 +1,54 @@
import * as CellSet from Geometry.CellSet;
import set from Grid.Util;
import * as Inclusion from Solver.Inclusion;
import * as SingleCell from Solver.SingleCell;
import * as State from Solver.State;
import * as Strategy from Solver.Strategy;
return {
step: step
rate: rate,
solve: solve,
findNextStep: findNextStep
};
function step(grid) {
var zones = getZones(grid);
var lines = getLines(grid.length);
var rowClusters = checkRowsInclusions(grid);
if(rowClusters.length > 0) {
rowClusters.forEach(function(rowCluster) {
rowCluster.toClear = difference(
rowCluster.colors.map(function(color) {return zones[color];}),
rowCluster.rows.map(function(row) {return lines.rows[row];})
function solve(coloring) {
var solvingState = State.start(coloring);
var stuck = false;
while(!stuck && solvingState.missing.size() > 0) {
Strategy.execute(
findNextStep(solvingState),
applyStep(solvingState),
function() {console.log('Solver is stuck'); stuck = true;}
);
}
return solvingState.constellation;
}
function rate(coloring) {
}
function findNextStep(solvingState) {
return Strategy.tryEach([
Inclusion.find(solvingState),
SingleCell.find(solvingState)
]);
}
function applyStep(solvingState) {
return function(step) {
console.log(step);
['empty', 'star'].forEach(function(attribute) {
if(step[attribute] != undefined) {
step[attribute].iter(function(cell) {
set(solvingState.constellation, cell, attribute == 'star');
forget(solvingState, cell);
});
}
return rowClusters;
}
function getZones(grid) {
var zones = {};
for(var row = 0; row < grid.length; row++) {
for(var column = 0; column < grid[row].length; column++) {
var color = grid[row][column];
if(zones[color] == undefined) {
zones[color] = CellSet.make(
{type: 'isochrome', row: row, column: column, grid: grid}
);
}
}
}
return zones;
}
function line(type, size, i) {
if(type == 'row') {
return CellSet.make(
{type: 'rectangle', row: i, column: 0, width: size, height: 1}
);
} else if(type == 'column') {
return CellSet.make(
{type: 'rectangle', row: 0, column: i, width: 1, height: size}
);
}
}
function getLines(size) {
var empty = Array.from({length: size});
return {
rows: empty.map(function(x, i) {return line('row', size, i);}),
columns: empty.map(function(x, i) {return line('column', size, i);}),
});
};
}
function getColorsByRow(grid) {
var colorsByRow = [];
for(var row = 0; row < grid.length; row++) {
colorsByRow.push(
quotient(grid[row], function(c0, c1) {return c0 == c1;}).map(
function(colorClass) {return colorClass.specimen;}
)
);
}
return colorsByRow;
}
function checkRowsInclusions(grid) {
var colorsByRow = getColorsByRow(grid);
var colorSets = quotient(colorsByRow, sameColorsSet);
return colorSets.reduce(function(commands, colorSet) {
if(colorSet.occurrences.length == colorSet.specimen.length) {
commands.push({
reason: 'rowsInColors',
rows: colorSet.occurrences,
colors: colorSet.specimen
});
}
return commands;
}, []);
}
function quotient(elements, equivalence) {
var classes = [];
elements.forEach(function(element, i) {
for(var c = 0; c < classes.length; c++) {
if(equivalence(element, classes[c].specimen)) {
classes[c].occurrences.push(i);
return;
}
}
classes.push({specimen: element, occurrences: [i]});
});
return classes;
}
function sameColorsSet(s0, s1) {
if(s0.length != s1.length) {
return false;
}
var o0 = {};
s0.forEach(function(x) {o0[x] = true;});
for(var i = 0; i < s1.length; i++) {
if(!o0[s1[i]]) {
return false;
}
}
return true;
}
function difference(setsFrom, setsSubstracted) {
return CellSet.union(setsFrom).difference(CellSet.union(setsSubstracted));
function forget(solvingState, cell) {
State.getCellSets(solvingState).concat(solvingState.missing)
.forEach(function(cellSet) {cellSet.remove(cell);});
}

View file

@ -10,7 +10,7 @@ return {
function find(solvingState) {
var cellSets = getCellSets(solvingState);
return Strategy.map(
stepOfCell,
stepOfCell(solvingState.missing),
Strategy.tryEach(cellSets.map(getSingleCellIn))
);
}
@ -23,12 +23,14 @@ function getSingleCellIn(cellSet) {
};
}
function stepOfCell(cell) {
function stepOfCell(missing) {
return function(cell) {
return {
reason: 'singleCell',
empty: toEmpty(cell),
empty: toEmpty(cell).intersection(missing),
star: new CellSet.CellSet(cell)
};
};
}
function toEmpty(cell) {

View file

@ -1,3 +1,6 @@
import size from Config;
import {asAFunction, iter, square} from Grid.Util;
import {diagonal, zero} from Geometry.Vector;
import * as CellSet from Geometry.CellSet;
return {

View file

@ -1,3 +1,4 @@
import size from Config;
import * as Dom from UnitJS.Dom;
var toolbox;
@ -11,7 +12,7 @@ return {
tool: tool
};
function init(size, elementId) {
function init(elementId) {
toolbox = document.getElementById(elementId || 'toolbox');
colors = toolbox.querySelector('#colors');
for(var i = 0; i < size; i++) {