Sort of reached an acceptable state where two first solver rules work
This commit is contained in:
parent
0a61d9a389
commit
213eec19fe
18 changed files with 213 additions and 279 deletions
|
@ -1,99 +1,58 @@
|
||||||
import {diagonal, isSmaller, key, plus, ofKey, zero} from Geometry.Vector;
|
import size from Config;
|
||||||
import {at, generate} from Grid.Util;
|
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) {
|
var CellSet = Set.make(Vector);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CellSet.prototype.fromRectangle = function(origin, maxOffset) {
|
return {
|
||||||
generate(
|
CellSet: CellSet,
|
||||||
maxOffset.row,
|
union: CellSet.union,
|
||||||
maxOffset.column,
|
rectangle: rectangle,
|
||||||
function(offset) {
|
isochrome: isochrome,
|
||||||
this.add(plus(origin, offset));
|
row: row,
|
||||||
}.bind(this)
|
column: column
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
CellSet.prototype.fromIsochrome = function(grid, origin) {
|
function rectangle(origin, delta) {
|
||||||
var originColor = at(grid, origin);
|
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];
|
var queue = [origin];
|
||||||
while(queue.length > 0) {
|
while(queue.length > 0) {
|
||||||
var cell = queue.shift();
|
var cell = queue.shift();
|
||||||
this.add(cell);
|
cellSet.add(cell);
|
||||||
for(var d = -1; d < 2; d += 2) {
|
for(var d = -1; d < 2; d += 2) {
|
||||||
[plus(cell, vertical(d)), plus(cell, horizontal(d))].forEach(
|
[plus(cell, Vector.vertical(d)), plus(cell, Vector.horizontal(d))].forEach(
|
||||||
gateKeeper(this, grid, queue, originColor)
|
gateKeeper(cellSet, coloring, queue, color)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
return cellSet;
|
||||||
|
|
||||||
CellSet.prototype.fromUnion = function(sets) {
|
|
||||||
for(var i = 0; i < sets.length; i++) {
|
|
||||||
for(var k in sets[i].cells) {
|
|
||||||
this.cells[k] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CellSet.prototype.add = function(v) {
|
function gateKeeper(cellSet, coloring, queue, color) {
|
||||||
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) {
|
|
||||||
return function(cell) {
|
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)
|
&& !cellSet.contains(cell)
|
||||||
&& at(grid, cell) == originColor) {
|
&& at(coloring, cell) == color) {
|
||||||
queue.push(cell);
|
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));
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
return {
|
return {
|
||||||
make: make,
|
make: make,
|
||||||
|
getRow: getRow,
|
||||||
|
getColumn: getColumn,
|
||||||
vertical: vertical,
|
vertical: vertical,
|
||||||
horizontal: horizontal,
|
horizontal: horizontal,
|
||||||
diagonal: diagonal,
|
diagonal: diagonal,
|
||||||
|
@ -7,12 +9,20 @@ return {
|
||||||
plus: plus,
|
plus: plus,
|
||||||
opposite: opposite,
|
opposite: opposite,
|
||||||
isSmaller: isSmaller,
|
isSmaller: isSmaller,
|
||||||
key: key,
|
toKey: toKey,
|
||||||
ofKey: ofKey
|
ofKey: ofKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
function make(row, column) {
|
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() {
|
function zero() {
|
||||||
|
@ -32,21 +42,23 @@ function diagonal(length) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function plus(v0, v1) {
|
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) {
|
function opposite(v) {
|
||||||
return {row: -v.row, column: -v.column};
|
return make(-v.row, -v.column);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSmaller(v0, v1) {
|
function isSmaller(v0, v1) {
|
||||||
return v0.row <= v1.row && v0.column <= v1.column;
|
return v0.row <= v1.row && v0.column <= v1.column;
|
||||||
}
|
}
|
||||||
|
|
||||||
function key(v) {
|
function toKey(v) {
|
||||||
return v.row + ':' + v.column;
|
return v.row + ':' + v.column;
|
||||||
|
//return 8*v.row + v.column;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ofKey(k) {
|
function ofKey(k) {
|
||||||
return make.apply(null, k.split(':'));
|
return make.apply(null, k.split(':'));
|
||||||
|
//return make.call(null, Math.floor(k / 8), k % 8);
|
||||||
}
|
}
|
||||||
|
|
17
js/Grid.js
17
js/Grid.js
|
@ -1,3 +1,4 @@
|
||||||
|
import size from Config;
|
||||||
import * as CellSet from Geometry.CellSet;
|
import * as CellSet from Geometry.CellSet;
|
||||||
import * as Dom from UnitJS.Dom;
|
import * as Dom from UnitJS.Dom;
|
||||||
import {at, generate, iter, square} from Grid.Util;
|
import {at, generate, iter, square} from Grid.Util;
|
||||||
|
@ -8,7 +9,6 @@ var grid = {
|
||||||
cells: null,
|
cells: null,
|
||||||
colors: null,
|
colors: null,
|
||||||
missing: null,
|
missing: null,
|
||||||
size: null
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -17,9 +17,8 @@ return {
|
||||||
init: init,
|
init: init,
|
||||||
};
|
};
|
||||||
|
|
||||||
function init(size, eventHandlers) {
|
function init(eventHandlers) {
|
||||||
grid.size = size;
|
grid.cells = square(size, function(cell) {
|
||||||
grid.cells = generate(size, size, function(cell) {
|
|
||||||
return Dom.make('td', eventHandlers(cell));
|
return Dom.make('td', eventHandlers(cell));
|
||||||
});
|
});
|
||||||
for(var row = 0; row < size; row++) {
|
for(var row = 0; row < size; row++) {
|
||||||
|
@ -29,13 +28,9 @@ function init(size, eventHandlers) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function clear() {
|
function clear() {
|
||||||
grid.colors = square(grid.size);
|
grid.colors = square(size);
|
||||||
grid.missing = CellSet.make(
|
grid.missing = CellSet.rectangle(zero(), diagonal(size));
|
||||||
{type: 'rectangle', origin: zero(), offset: diagonal(grid.size)}
|
iter(grid.cells, function(td) {td.className = '';});
|
||||||
);
|
|
||||||
iter(grid.colors, function(cell) {
|
|
||||||
at(grid.cells, cell).className = '';
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function get() {
|
function get() {
|
||||||
|
|
|
@ -12,29 +12,27 @@ return {
|
||||||
|
|
||||||
function colorize(cell, color) {
|
function colorize(cell, color) {
|
||||||
var grid = Grid.get();
|
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);
|
at(Grid.get().cells, cell).className = 'color' + at(grid.colors, cell);
|
||||||
grid.missing.remove(cell);
|
grid.missing.remove(cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
function paint(origin) {
|
function paint(origin) {
|
||||||
var cellSet = CellSet.make(
|
CellSet.isochrome(Grid.get().colors, origin)
|
||||||
{type: 'isochrome', grid: Grid.get().colors, origin: origin}
|
.iter(function(cell) {colorize(cell);});
|
||||||
);
|
|
||||||
cellSet.iter(colorize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setColors(grid) {
|
function setColors(coloring) {
|
||||||
if(grid != undefined) {
|
if(coloring != undefined) {
|
||||||
iter(grid, function(cell) {
|
iter(coloring, function(color, cell) {
|
||||||
if(at(grid, cell) != undefined) {
|
if(color != undefined) {
|
||||||
colorize(cell, at(grid, cell));
|
colorize(cell, color);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if(Grid.get().missing.isEmpty()) {
|
if(Grid.get().missing.size() > 0) {
|
||||||
Mode.setEnabled(true, ['play', 'solve']);
|
|
||||||
} else {
|
|
||||||
Mode.set('edit');
|
Mode.set('edit');
|
||||||
|
} else {
|
||||||
|
Mode.setEnabled(true, ['play', 'solve']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import * as Vector from Geometry.Vector;
|
import * as Vector from Geometry.Vector;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
make: make,
|
||||||
at: at,
|
at: at,
|
||||||
|
asAFunction: asAFunction,
|
||||||
column: column,
|
column: column,
|
||||||
generate: generate,
|
generate: generate,
|
||||||
iter: iter,
|
iter: iter,
|
||||||
|
@ -21,18 +23,39 @@ function generate(width, height, f) {
|
||||||
return result;
|
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) {
|
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) {
|
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) {
|
function at(grid, vector) {
|
||||||
return grid[vector.row][vector.column];
|
return grid[vector.row][vector.column];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function asAFunction(grid) {
|
||||||
|
return function (vector) {return at(grid, vector);};
|
||||||
|
}
|
||||||
|
|
||||||
function set(grid, vector, value) {
|
function set(grid, vector, value) {
|
||||||
return grid[vector.row][vector.column] = value;
|
return grid[vector.row][vector.column] = value;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,17 +8,15 @@ import * as Play from Mode.Play;
|
||||||
import * as Solve from Mode.Solve;
|
import * as Solve from Mode.Solve;
|
||||||
import * as Edit from Mode.Edit;
|
import * as Edit from Mode.Edit;
|
||||||
|
|
||||||
var size = 8;
|
Toolbox.init();
|
||||||
|
|
||||||
Toolbox.init(size);
|
|
||||||
Mode.init({
|
Mode.init({
|
||||||
play: Play,
|
play: Play,
|
||||||
solve: Solve,
|
solve: Solve,
|
||||||
edit: Edit
|
edit: Edit
|
||||||
});
|
});
|
||||||
Grid.init(size, Mode.dispatch);
|
Grid.init(Mode.dispatch);
|
||||||
if(window.location.search.length > 0) {
|
if(window.location.search.length > 0) {
|
||||||
var urlSearchParameters = new URLSearchParams(window.location.search);
|
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();
|
Grid.IO.init();
|
||||||
|
|
|
@ -23,7 +23,7 @@ return {
|
||||||
|
|
||||||
function onEnter() {
|
function onEnter() {
|
||||||
GUI.activate(true, [Grid.get().root, Toolbox.get(), save]);
|
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']);
|
Mode.setEnabled(false, ['play', 'solve']);
|
||||||
} else {
|
} else {
|
||||||
Share.link(Grid.get().colors);
|
Share.link(Grid.get().colors);
|
||||||
|
@ -65,7 +65,7 @@ function colorCell(cell) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkCompleteness() {
|
function checkCompleteness() {
|
||||||
if(Grid.get().missing.isEmpty()) {
|
if(Grid.get().missing.size() < 1) {
|
||||||
Mode.setEnabled(true, ['play', 'solve']);
|
Mode.setEnabled(true, ['play', 'solve']);
|
||||||
Share.link(Grid.get().colors);
|
Share.link(Grid.get().colors);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ return {
|
||||||
};
|
};
|
||||||
|
|
||||||
function onClick(e, cell) {
|
function onClick(e, cell) {
|
||||||
if(Grid.get().missing.isEmpty()) {
|
if(Grid.get().missing.size() < 1) {
|
||||||
rotateState(at(Grid.get().cells, cell));
|
rotateState(at(Grid.get().cells, cell));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,5 +8,5 @@ return {
|
||||||
};
|
};
|
||||||
|
|
||||||
function onEnter() {
|
function onEnter() {
|
||||||
console.log(Solver.step(Grid.get().colors));
|
console.log(Solver.solve(Grid.get().colors));
|
||||||
}
|
}
|
||||||
|
|
16
js/Set.js
16
js/Set.js
|
@ -1,12 +1,14 @@
|
||||||
return {
|
return {
|
||||||
make: make
|
make: make,
|
||||||
|
Int: make({toKey: id, ofKey: parseInt})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function id(x) {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
function make(type) {
|
function make(type) {
|
||||||
if(type == undefined) {
|
type = type ?? {toKey: id, ofKey: id};
|
||||||
var id = function(x) {return x;};
|
|
||||||
type = {toKey: id, ofKey: id};
|
|
||||||
}
|
|
||||||
|
|
||||||
function Set(s) {
|
function Set(s) {
|
||||||
this.elements = {};
|
this.elements = {};
|
||||||
|
@ -65,5 +67,9 @@ function make(type) {
|
||||||
return newSet;
|
return newSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Set.uniq = function(t) {
|
||||||
|
return (new Set(t)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
return Set;
|
return Set;
|
||||||
}
|
}
|
||||||
|
|
13
js/Share.js
13
js/Share.js
|
@ -1,6 +1,8 @@
|
||||||
|
import size from Config;
|
||||||
import {at, generate, iter, square} from Grid.Util;
|
import {at, generate, iter, square} from Grid.Util;
|
||||||
import * as Decode from Share.Decoder.Protocol;
|
import * as Decode from Share.Decoder.Protocol;
|
||||||
import * as Encode from Share.Encoder.Protocol;
|
import * as Encode from Share.Encoder.Protocol;
|
||||||
|
import * as Encoder from Share.Encoder.Class;
|
||||||
import GUI;
|
import GUI;
|
||||||
import Grid;
|
import Grid;
|
||||||
|
|
||||||
|
@ -16,23 +18,24 @@ function get() {
|
||||||
return share;
|
return share;
|
||||||
}
|
}
|
||||||
|
|
||||||
function naiveEncode(grid) {
|
function naiveEncode(coloring) {
|
||||||
var encoder = Encoder.make();
|
var encoder = Encoder.make();
|
||||||
iter(grid, function(cell) {
|
iter(coloring, function(color) {
|
||||||
encoder.int(3)(at(grid, cell));
|
encoder.int(3)(color);
|
||||||
});
|
});
|
||||||
return encoder.output();
|
return encoder.output();
|
||||||
}
|
}
|
||||||
|
|
||||||
function naiveDecode(size, input) {
|
function naiveDecode(input) {
|
||||||
if(input != undefined) {
|
if(input != undefined) {
|
||||||
var decoder = Decoder.make(input);
|
var decoder = Decoder.make(input);
|
||||||
return generate(size, size, function() {return decoder.int(3);});
|
return generate(size, size, function() {return decoder.int(3);});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function link(grid) {
|
function link() {
|
||||||
//share.href = '?game=' + naiveEncode(Grid.get().colors);
|
//share.href = '?game=' + naiveEncode(Grid.get().colors);
|
||||||
|
console.log(naiveEncode(Grid.get().colors));
|
||||||
share.href = '?game=' + Encode.grid(Grid.get().colors);
|
share.href = '?game=' + Encode.grid(Grid.get().colors);
|
||||||
GUI.activate(true, share);
|
GUI.activate(true, share);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import size from Config;
|
||||||
import * as CellSet from Geometry.CellSet;
|
import * as CellSet from Geometry.CellSet;
|
||||||
import * as Decoder from Share.Decoder.Class;
|
import * as Decoder from Share.Decoder.Class;
|
||||||
import {diagonal, plus, zero} from Geometry.Vector;
|
import {diagonal, plus, zero} from Geometry.Vector;
|
||||||
|
@ -8,12 +9,12 @@ return {
|
||||||
grid: decodeGrid
|
grid: decodeGrid
|
||||||
};
|
};
|
||||||
|
|
||||||
function decodeGrid(size, input) {
|
function decodeGrid(input) {
|
||||||
if(input != undefined) {
|
if(input != undefined) {
|
||||||
return decoderLoop(
|
return decoderLoop(
|
||||||
Decoder.make(input),
|
Decoder.make(input),
|
||||||
square(size),
|
square(size),
|
||||||
CellSet.make({type: 'rectangle', origin: zero(), offset: diagonal(size)})
|
CellSet.rectangle(zero(), diagonal(size))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ Encoder.prototype.variableLength6 = function(n) {
|
||||||
Encoder.prototype.int = function(size) {
|
Encoder.prototype.int = function(size) {
|
||||||
return function(n) {
|
return function(n) {
|
||||||
for(var i = 0; i < size; i++) {
|
for(var i = 0; i < size; i++) {
|
||||||
encoder.push(n > 3);
|
this.push(n > 3);
|
||||||
n = (2*n) & 7;
|
n = (2*n) & 7;
|
||||||
}
|
}
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
|
|
|
@ -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 * as Encoder from Share.Encoder.Class;
|
||||||
import {at, iter, square} from Grid.Util;
|
import {at, iter, square} from Grid.Util;
|
||||||
|
import * as Grid from Grid.Util;
|
||||||
import * as Protocol from Share.Protocol;
|
import * as Protocol from Share.Protocol;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -10,65 +13,55 @@ function encodeGrid(grid) {
|
||||||
var encoder = Encoder.make();
|
var encoder = Encoder.make();
|
||||||
var done = square(grid.length, false);
|
var done = square(grid.length, false);
|
||||||
var gradients = square(grid.length);
|
var gradients = square(grid.length);
|
||||||
iter(grid, function(cell) {
|
iter(grid, function(_, cell) {
|
||||||
if(!at(done, cell)) {
|
if(!at(done, cell)) {
|
||||||
let block = getLongestBlock(grid, cell);
|
let block = getLongestBlock(grid, cell);
|
||||||
if(block != undefined) {
|
if(block.size >= Protocol.MIN_BLOCK_SIZE) {
|
||||||
encodeBlock(encoder, block);
|
encodeBlock(encoder, done, block);
|
||||||
} else {
|
} else {
|
||||||
encodeSingleCell(
|
encodeSingleCell(
|
||||||
encoder,
|
encoder,
|
||||||
getColorGradient(gradients, grid, cell)
|
getColorGradient(gradients, grid, done, cell)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return encoder.output();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLongestBlock(grid, origin) {
|
function getLongestBlock(grid, origin) {
|
||||||
var color = at(grid, origin);
|
var color = at(grid, origin);
|
||||||
var longestDirection = findLongestDirection(grid, origin, color);
|
var hSize = getStreak(Grid.row(grid, origin.row), origin.column, color);
|
||||||
var size = extend(grid, origin, color, longestDirection);
|
var vSize = getStreak(Grid.column(grid, origin.column), origin.row, color);
|
||||||
if(size >= Protocol.MIN_BLOCK_SIZE) {
|
|
||||||
return {
|
return {
|
||||||
direction: longestDirection.coordinate == 'row' ? 'vertical' : 'horizontal',
|
isVertical: vSize > hSize,
|
||||||
size: size,
|
size: Math.max(hSize, vSize),
|
||||||
color: color
|
color: color,
|
||||||
|
origin: origin
|
||||||
};
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function findLongestDirection(grid, p, originColor) {
|
function getStreak(t, firstIndex, color) {
|
||||||
var delta = 1;
|
var fromFirst = t.slice(firstIndex);
|
||||||
while(true) {
|
var index = fromFirst.findIndex(
|
||||||
var newRow = p.row + delta;
|
function(x) {return x != color;}
|
||||||
var newColumn = p.column + delta;
|
);
|
||||||
if(newRow >= grid.length || grid[newRow][p.column] != originColor) {
|
return index >= 0 ? index : fromFirst.length;
|
||||||
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) {
|
function encodeBlock(encoder, done, block) {
|
||||||
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) {
|
|
||||||
encoder.push(1);
|
encoder.push(1);
|
||||||
encoder.push(block.direction == 'vertical');
|
encoder.push(block.isVertical);
|
||||||
encoder.variableLength6(block.size - Protocol.MIN_BLOCK_SIZE);
|
encoder.variableLength6(block.size - Protocol.MIN_BLOCK_SIZE);
|
||||||
encoder.int(3)(block.color);
|
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) {
|
if(at(gradients, cell) == undefined) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
144
js/Solver.js
144
js/Solver.js
|
@ -1,114 +1,54 @@
|
||||||
import * as CellSet from Geometry.CellSet;
|
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 {
|
return {
|
||||||
step: step
|
rate: rate,
|
||||||
|
solve: solve,
|
||||||
|
findNextStep: findNextStep
|
||||||
};
|
};
|
||||||
|
|
||||||
function step(grid) {
|
function solve(coloring) {
|
||||||
var zones = getZones(grid);
|
var solvingState = State.start(coloring);
|
||||||
var lines = getLines(grid.length);
|
var stuck = false;
|
||||||
var rowClusters = checkRowsInclusions(grid);
|
while(!stuck && solvingState.missing.size() > 0) {
|
||||||
if(rowClusters.length > 0) {
|
Strategy.execute(
|
||||||
rowClusters.forEach(function(rowCluster) {
|
findNextStep(solvingState),
|
||||||
rowCluster.toClear = difference(
|
applyStep(solvingState),
|
||||||
rowCluster.colors.map(function(color) {return zones[color];}),
|
function() {console.log('Solver is stuck'); stuck = true;}
|
||||||
rowCluster.rows.map(function(row) {return lines.rows[row];})
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
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) {
|
function forget(solvingState, cell) {
|
||||||
var colorsByRow = [];
|
State.getCellSets(solvingState).concat(solvingState.missing)
|
||||||
for(var row = 0; row < grid.length; row++) {
|
.forEach(function(cellSet) {cellSet.remove(cell);});
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ return {
|
||||||
function find(solvingState) {
|
function find(solvingState) {
|
||||||
var cellSets = getCellSets(solvingState);
|
var cellSets = getCellSets(solvingState);
|
||||||
return Strategy.map(
|
return Strategy.map(
|
||||||
stepOfCell,
|
stepOfCell(solvingState.missing),
|
||||||
Strategy.tryEach(cellSets.map(getSingleCellIn))
|
Strategy.tryEach(cellSets.map(getSingleCellIn))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -23,12 +23,14 @@ function getSingleCellIn(cellSet) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function stepOfCell(cell) {
|
function stepOfCell(missing) {
|
||||||
|
return function(cell) {
|
||||||
return {
|
return {
|
||||||
reason: 'singleCell',
|
reason: 'singleCell',
|
||||||
empty: toEmpty(cell),
|
empty: toEmpty(cell).intersection(missing),
|
||||||
star: new CellSet.CellSet(cell)
|
star: new CellSet.CellSet(cell)
|
||||||
};
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function toEmpty(cell) {
|
function toEmpty(cell) {
|
||||||
|
|
|
@ -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;
|
import * as CellSet from Geometry.CellSet;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import size from Config;
|
||||||
import * as Dom from UnitJS.Dom;
|
import * as Dom from UnitJS.Dom;
|
||||||
|
|
||||||
var toolbox;
|
var toolbox;
|
||||||
|
@ -11,7 +12,7 @@ return {
|
||||||
tool: tool
|
tool: tool
|
||||||
};
|
};
|
||||||
|
|
||||||
function init(size, elementId) {
|
function init(elementId) {
|
||||||
toolbox = document.getElementById(elementId || 'toolbox');
|
toolbox = document.getElementById(elementId || 'toolbox');
|
||||||
colors = toolbox.querySelector('#colors');
|
colors = toolbox.querySelector('#colors');
|
||||||
for(var i = 0; i < size; i++) {
|
for(var i = 0; i < size; i++) {
|
||||||
|
|
Loading…
Reference in a new issue