Compare commits
14 commits
Author | SHA1 | Date | |
---|---|---|---|
213eec19fe | |||
0a61d9a389 | |||
71c405e5cd | |||
ac7cd6333b | |||
671bfdcb5f | |||
11f3902ca2 | |||
b503a9e389 | |||
d2e683dfb9 | |||
17fd855ab2 | |||
ffebb1efb8 | |||
5c0ef70220 | |||
259d1fc175 | |||
e06b31087a | |||
78ce48ca5c |
29 changed files with 878 additions and 296 deletions
|
@ -34,10 +34,10 @@
|
||||||
filter: invert(1);
|
filter: invert(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#save {
|
#export > * {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#save.active {
|
#save.active, #share.active {
|
||||||
display: initial;
|
display: initial;
|
||||||
}
|
}
|
||||||
|
|
2
guix.scm
2
guix.scm
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
(let
|
(let
|
||||||
((%source-dir (dirname (current-filename)))
|
((%source-dir (dirname (current-filename)))
|
||||||
(SJW (load "/home/Bureau/sjw/SJW.scm"))
|
(SJW (load "/home/Bureau/sjw/guix.scm"))
|
||||||
(UnitJS (load "/home/Bureau/unitJS/guix.scm"))
|
(UnitJS (load "/home/Bureau/unitJS/guix.scm"))
|
||||||
(WTK (load "/home/Bureau/WTK/guix.scm")))
|
(WTK (load "/home/Bureau/WTK/guix.scm")))
|
||||||
(package
|
(package
|
||||||
|
|
|
@ -28,7 +28,10 @@
|
||||||
</ul>
|
</ul>
|
||||||
<button id="load">Load</button>
|
<button id="load">Load</button>
|
||||||
<table id="grid"></table>
|
<table id="grid"></table>
|
||||||
|
<div id="export">
|
||||||
<button id="save">Save</button>
|
<button id="save">Save</button>
|
||||||
|
<a id="share">Share this grid</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
101
js/CellSet.js
101
js/CellSet.js
|
@ -1,101 +0,0 @@
|
||||||
function CellSet(definition) {
|
|
||||||
definition = definition || {};
|
|
||||||
this.cells = {};
|
|
||||||
if(definition.type == 'rectangle') {
|
|
||||||
this.fromRectangle(definition);
|
|
||||||
} else if(definition.type == 'isochrome') {
|
|
||||||
this.fromIsochrome(definition);
|
|
||||||
} else if(definition.type == 'union') {
|
|
||||||
this.fromUnion(definition.sets);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CellSet.prototype.fromRectangle = function(definition) {
|
|
||||||
var iMax = definition.row + definition.height;
|
|
||||||
var jMax = definition.column + definition.width;
|
|
||||||
for(var i = definition.row; i < iMax; i++) {
|
|
||||||
for(var j = definition.column; j < jMax; j++) {
|
|
||||||
this.add(i, j);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
CellSet.prototype.fromIsochrome = function(definition) {
|
|
||||||
var originColor = definition.data[definition.row][definition.column];
|
|
||||||
var queue = [{i: definition.row, j: definition.column}];
|
|
||||||
while(queue.length > 0) {
|
|
||||||
var p = queue[0];
|
|
||||||
this.add(p.i, p.j);
|
|
||||||
for(var d = -1; d < 2; d += 2) {
|
|
||||||
[{i: p.i + d, j: p.j}, {i: p.i, j: p.j + d}].forEach(
|
|
||||||
gateKeeper(this, definition.data, queue, originColor)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
queue.shift();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
CellSet.prototype.fromUnion = function(sets) {
|
|
||||||
for(var i = 0; i < sets.length; i++) {
|
|
||||||
for(var key in sets[i].cells) {
|
|
||||||
this.cells[key] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CellSet.prototype.add = function(i, j) {
|
|
||||||
this.cells[id(i, j)] = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
CellSet.prototype.remove = function(i, j) {
|
|
||||||
delete this.cells[id(i, j)];
|
|
||||||
};
|
|
||||||
|
|
||||||
CellSet.prototype.contains = function(i, j) {
|
|
||||||
return !!this.cells[id(i, j)];
|
|
||||||
};
|
|
||||||
|
|
||||||
CellSet.prototype.isEmpty = function() {
|
|
||||||
return Object.keys(this.cells).length < 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
CellSet.prototype.map = function(f) {
|
|
||||||
var result = [];
|
|
||||||
for(var key in this.cells) {
|
|
||||||
result.push(f.apply(null, key.split(':')));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
CellSet.prototype.iter = function(f) {
|
|
||||||
this.map(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
CellSet.prototype.difference = function(cellSet) {
|
|
||||||
var newCellSet = new CellSet();
|
|
||||||
this.iter(function(i, j) {
|
|
||||||
if(!cellSet.contains(i, j)) {
|
|
||||||
newCellSet.add(i, j);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return newCellSet;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
make: function(definition) {return new CellSet(definition);},
|
|
||||||
union: function(sets) {return new CellSet({type: 'union', sets: sets});}
|
|
||||||
};
|
|
||||||
|
|
||||||
function id(i, j) {
|
|
||||||
return i + ':' + j;
|
|
||||||
}
|
|
||||||
|
|
||||||
function gateKeeper(cellSet, data, queue, originColor) {
|
|
||||||
return function(p1) {
|
|
||||||
if(p1.i >= 0 && p1.i < data.length && p1.j >= 0 && p1.j < data.length
|
|
||||||
&& !cellSet.contains(p1.i, p1.j)
|
|
||||||
&& data[p1.i][p1.j] == originColor) {
|
|
||||||
queue.push(p1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
3
js/Config.js
Normal file
3
js/Config.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
return {
|
||||||
|
size: 8
|
||||||
|
}
|
58
js/Geometry/CellSet.js
Normal file
58
js/Geometry/CellSet.js
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
var CellSet = Set.make(Vector);
|
||||||
|
|
||||||
|
return {
|
||||||
|
CellSet: CellSet,
|
||||||
|
union: CellSet.union,
|
||||||
|
rectangle: rectangle,
|
||||||
|
isochrome: isochrome,
|
||||||
|
row: row,
|
||||||
|
column: column
|
||||||
|
};
|
||||||
|
|
||||||
|
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();
|
||||||
|
cellSet.add(cell);
|
||||||
|
for(var d = -1; d < 2; d += 2) {
|
||||||
|
[plus(cell, Vector.vertical(d)), plus(cell, Vector.horizontal(d))].forEach(
|
||||||
|
gateKeeper(cellSet, coloring, queue, color)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cellSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
function gateKeeper(cellSet, coloring, queue, color) {
|
||||||
|
return function(cell) {
|
||||||
|
if(isSmaller(zero(), cell)
|
||||||
|
&& isSmaller(cell, Vector.diagonal(size-1))
|
||||||
|
&& !cellSet.contains(cell)
|
||||||
|
&& 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));
|
||||||
|
}
|
64
js/Geometry/Vector.js
Normal file
64
js/Geometry/Vector.js
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
return {
|
||||||
|
make: make,
|
||||||
|
getRow: getRow,
|
||||||
|
getColumn: getColumn,
|
||||||
|
vertical: vertical,
|
||||||
|
horizontal: horizontal,
|
||||||
|
diagonal: diagonal,
|
||||||
|
zero: zero,
|
||||||
|
plus: plus,
|
||||||
|
opposite: opposite,
|
||||||
|
isSmaller: isSmaller,
|
||||||
|
toKey: toKey,
|
||||||
|
ofKey: ofKey,
|
||||||
|
};
|
||||||
|
|
||||||
|
function make(row, column) {
|
||||||
|
return {row: parseInt(row), column: parseInt(column)};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRow(v) {
|
||||||
|
return v.row;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getColumn(v) {
|
||||||
|
return v.column;
|
||||||
|
}
|
||||||
|
|
||||||
|
function zero() {
|
||||||
|
return make(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function vertical(length) {
|
||||||
|
return make(length, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function horizontal(length) {
|
||||||
|
return make(0, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
function diagonal(length) {
|
||||||
|
return make(length, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
function plus(v0, v1) {
|
||||||
|
return make(v0.row + v1.row, v0.column + v1.column);
|
||||||
|
}
|
||||||
|
|
||||||
|
function opposite(v) {
|
||||||
|
return make(-v.row, -v.column);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSmaller(v0, v1) {
|
||||||
|
return v0.row <= v1.row && v0.column <= v1.column;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
47
js/Grid.js
47
js/Grid.js
|
@ -1,53 +1,38 @@
|
||||||
import CellSet;
|
import size from Config;
|
||||||
|
import * as CellSet from Geometry.CellSet;
|
||||||
import * as Dom from UnitJS.Dom;
|
import * as Dom from UnitJS.Dom;
|
||||||
import {iter, square} from Grid.Util;
|
import {at, generate, iter, square} from Grid.Util;
|
||||||
|
import {diagonal, zero} from Geometry.Vector;
|
||||||
|
|
||||||
var grid = {
|
var grid = {
|
||||||
element: document.getElementById('grid'),
|
root: document.getElementById('grid'),
|
||||||
data: null,
|
cells: null,
|
||||||
|
colors: null,
|
||||||
missing: null,
|
missing: null,
|
||||||
size: null
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cell: cell,
|
|
||||||
clear: clear,
|
clear: clear,
|
||||||
init: init,
|
|
||||||
get: get,
|
get: get,
|
||||||
|
init: init,
|
||||||
};
|
};
|
||||||
|
|
||||||
function init(size, eventHandlers) {
|
function init(eventHandlers) {
|
||||||
grid.size = size;
|
grid.cells = square(size, function(cell) {
|
||||||
|
return Dom.make('td', eventHandlers(cell));
|
||||||
|
});
|
||||||
for(var row = 0; row < size; row++) {
|
for(var row = 0; row < size; row++) {
|
||||||
grid.element.appendChild(
|
grid.root.appendChild(Dom.make('tr', {}, grid.cells[row]));
|
||||||
makeRow({tag: 'td', attributes: eventHandlers, row: row})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
clear();
|
clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeRow(config) {
|
|
||||||
var cells = [];
|
|
||||||
for(var column = 0; column < grid.size; column++) {
|
|
||||||
cells.push(Dom.make(config.tag, config.attributes(config.row, column)));
|
|
||||||
}
|
|
||||||
return Dom.make('tr', {}, cells);
|
|
||||||
}
|
|
||||||
|
|
||||||
function clear() {
|
function clear() {
|
||||||
grid.data = square(grid.size);
|
grid.colors = square(size);
|
||||||
grid.missing = CellSet.make(
|
grid.missing = CellSet.rectangle(zero(), diagonal(size));
|
||||||
{type: 'rectangle', row: 0, column: 0, width: 8, height: 8}
|
iter(grid.cells, function(td) {td.className = '';});
|
||||||
);
|
|
||||||
iter(grid.data, function(row, column) {
|
|
||||||
cell(row, column).className = '';
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function get() {
|
function get() {
|
||||||
return grid;
|
return grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
function cell(row, column) {
|
|
||||||
return grid.element.children[row].children[column];
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,22 +1,38 @@
|
||||||
import CellSet;
|
import * as CellSet from Geometry.CellSet;
|
||||||
import Grid;
|
import Grid;
|
||||||
|
import {at, iter, set} from Grid.Util;
|
||||||
import Toolbox;
|
import Toolbox;
|
||||||
|
import Mode;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ize: colorize,
|
ize: colorize,
|
||||||
paint: paint
|
paint: paint,
|
||||||
|
setColors: setColors
|
||||||
};
|
};
|
||||||
|
|
||||||
function colorize(row, column, color) {
|
function colorize(cell, color) {
|
||||||
var grid = Grid.get();
|
var grid = Grid.get();
|
||||||
grid.data[row][column] = color || Toolbox.color();
|
set(grid.colors, cell, color ?? Toolbox.color());
|
||||||
Grid.cell(row, column).className = 'color' + grid.data[row][column];
|
at(Grid.get().cells, cell).className = 'color' + at(grid.colors, cell);
|
||||||
grid.missing.remove(row, column);
|
grid.missing.remove(cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
function paint(row, column) {
|
function paint(origin) {
|
||||||
var cellSet = CellSet.make(
|
CellSet.isochrome(Grid.get().colors, origin)
|
||||||
{type: 'isochrome', row: row, column: column, data: Grid.get().data}
|
.iter(function(cell) {colorize(cell);});
|
||||||
);
|
}
|
||||||
cellSet.iter(colorize);
|
|
||||||
|
function setColors(coloring) {
|
||||||
|
if(coloring != undefined) {
|
||||||
|
iter(coloring, function(color, cell) {
|
||||||
|
if(color != undefined) {
|
||||||
|
colorize(cell, color);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if(Grid.get().missing.size() > 0) {
|
||||||
|
Mode.set('edit');
|
||||||
|
} else {
|
||||||
|
Mode.setEnabled(true, ['play', 'solve']);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ import * as File from WTK.File;
|
||||||
import * as Async from UnitJS.Async;
|
import * as Async from UnitJS.Async;
|
||||||
import Grid;
|
import Grid;
|
||||||
import Grid.Color;
|
import Grid.Color;
|
||||||
import iter from Grid.Util;
|
|
||||||
import Mode;
|
import Mode;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -24,27 +23,13 @@ function load() {
|
||||||
return File.load(input.files[0]);
|
return File.load(input.files[0]);
|
||||||
},
|
},
|
||||||
function(data) {
|
function(data) {
|
||||||
return Async.wrap(setGridData(JSON.parse(data)));
|
Grid.Color.setColors(JSON.parse(data));
|
||||||
|
return Async.wrap();
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setGridData(data) {
|
|
||||||
if(data != undefined) {
|
|
||||||
iter(data, function(row, column) {
|
|
||||||
if(data[row][column] != undefined) {
|
|
||||||
Grid.Color.ize(row, column, data[row][column]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if(Grid.get().missing.isEmpty()) {
|
|
||||||
Mode.setEnabled(true, ['play', 'solve']);
|
|
||||||
} else {
|
|
||||||
Mode.set('edit');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
File.save('data:text/json,' + JSON.stringify(Grid.get().data), "grid.json");
|
File.save('data:text/json,' + JSON.stringify(Grid.get().colors), "grid.json");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
|
import * as Vector from Geometry.Vector;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
make: make,
|
||||||
|
at: at,
|
||||||
|
asAFunction: asAFunction,
|
||||||
|
column: column,
|
||||||
generate: generate,
|
generate: generate,
|
||||||
iter: iter,
|
iter: iter,
|
||||||
|
row: row,
|
||||||
|
set: set,
|
||||||
square: square
|
square: square
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -9,16 +17,53 @@ function generate(width, height, f) {
|
||||||
for(var row = 0; row < height; row++) {
|
for(var row = 0; row < height; row++) {
|
||||||
result[row] = [];;
|
result[row] = [];;
|
||||||
for(var column = 0; column < width; column++) {
|
for(var column = 0; column < width; column++) {
|
||||||
result[row].push(f(row, column));
|
result[row].push(f(Vector.make(row, column)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function iter(grid, f) {
|
function make(width, height) {
|
||||||
generate(grid.length > 0 ? grid[0].length : null, grid.length, f);
|
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 square(size) {
|
function iter(grid, f) {
|
||||||
return generate(size, size, function() {return;});
|
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) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
function row(grid, n) {
|
||||||
|
return grid[n];
|
||||||
|
}
|
||||||
|
|
||||||
|
function column(grid, n) {
|
||||||
|
return grid.map(function(row) {return row[n];});
|
||||||
}
|
}
|
||||||
|
|
12
js/Main.js
12
js/Main.js
|
@ -1,18 +1,22 @@
|
||||||
import Grid;
|
import Grid;
|
||||||
import Grid.IO;
|
import Grid.IO;
|
||||||
|
import Grid.Color;
|
||||||
|
import Share;
|
||||||
import Toolbox;
|
import Toolbox;
|
||||||
import Mode;
|
import Mode;
|
||||||
import * as Play from Mode.Play;
|
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) {
|
||||||
|
var urlSearchParameters = new URLSearchParams(window.location.search);
|
||||||
|
Grid.Color.set(Share.decode(urlSearchParameters.get('game')));
|
||||||
|
}
|
||||||
Grid.IO.init();
|
Grid.IO.init();
|
||||||
|
|
|
@ -48,10 +48,10 @@ function set(newMode) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function dispatch(row, column) {
|
function dispatch(cell) {
|
||||||
var handler = {};
|
var handler = {};
|
||||||
mouseEvents.forEach(function(eventName) {
|
mouseEvents.forEach(function(eventName) {
|
||||||
handler[eventName] = function(e) {runEvent(eventName, e, row, column);};
|
handler[eventName] = function(e) {runEvent(eventName, e, cell);};
|
||||||
});
|
});
|
||||||
return handler;
|
return handler;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,10 @@ import Grid;
|
||||||
import Grid.Color;
|
import Grid.Color;
|
||||||
import Mode;
|
import Mode;
|
||||||
import Toolbox;
|
import Toolbox;
|
||||||
|
import Share;
|
||||||
|
|
||||||
var down = false;
|
var down = false;
|
||||||
Grid.get().element.addEventListener('mouseleave', function() {
|
Grid.get().root.addEventListener('mouseleave', function() {
|
||||||
down = false;
|
down = false;
|
||||||
});
|
});
|
||||||
var save = document.getElementById('save');
|
var save = document.getElementById('save');
|
||||||
|
@ -21,48 +22,51 @@ return {
|
||||||
};
|
};
|
||||||
|
|
||||||
function onEnter() {
|
function onEnter() {
|
||||||
GUI.activate(true, [Grid.get().element, 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 {
|
||||||
|
Share.link(Grid.get().colors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onLeave() {
|
function onLeave() {
|
||||||
GUI.activate(false, [Grid.get().element, Toolbox.get(), save]);
|
GUI.activate(false, [Grid.get().root, Toolbox.get(), save, Share.get()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMousedown(e, row, column) {
|
function onMousedown(e, cell) {
|
||||||
if(e.button == GUI.mouse.left) {
|
if(e.button == GUI.mouse.left) {
|
||||||
down = true;
|
down = true;
|
||||||
if(Toolbox.tool() == 'draw') {
|
if(Toolbox.tool() == 'draw') {
|
||||||
colorCell(row, column);
|
colorCell(cell);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMouseup(e, row, column) {
|
function onMouseup(e, cell) {
|
||||||
if(e.button == GUI.mouse.left) {
|
if(e.button == GUI.mouse.left) {
|
||||||
down = false;
|
down = false;
|
||||||
if(Toolbox.tool() == 'paint') {
|
if(Toolbox.tool() == 'paint') {
|
||||||
Grid.Color.paint(row, column);
|
Grid.Color.paint(cell);
|
||||||
checkCompleteness();
|
checkCompleteness();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMouseenter(e, row, column) {
|
function onMouseenter(e, cell) {
|
||||||
if(down && Toolbox.tool() == 'draw') {
|
if(down && Toolbox.tool() == 'draw') {
|
||||||
colorCell(row, column);
|
colorCell(cell);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function colorCell(row, column) {
|
function colorCell(cell) {
|
||||||
Grid.Color.ize(row, column);
|
Grid.Color.ize(cell);
|
||||||
checkCompleteness();
|
checkCompleteness();
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import Grid;
|
import Grid;
|
||||||
import GUI;
|
import GUI;
|
||||||
|
import at from Grid.Util;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
events: {
|
events: {
|
||||||
|
@ -7,9 +8,9 @@ return {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function onClick(e, row, column) {
|
function onClick(e, cell) {
|
||||||
if(Grid.get().missing.isEmpty()) {
|
if(Grid.get().missing.size() < 1) {
|
||||||
rotateState(Grid.cell(row, column));
|
rotateState(at(Grid.get().cells, cell));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,5 +8,5 @@ return {
|
||||||
};
|
};
|
||||||
|
|
||||||
function onEnter() {
|
function onEnter() {
|
||||||
console.log(Solver.step(Grid.get().data));
|
console.log(Solver.solve(Grid.get().colors));
|
||||||
}
|
}
|
||||||
|
|
75
js/Set.js
Normal file
75
js/Set.js
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
return {
|
||||||
|
make: make,
|
||||||
|
Int: make({toKey: id, ofKey: parseInt})
|
||||||
|
};
|
||||||
|
|
||||||
|
function id(x) {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
function make(type) {
|
||||||
|
type = type ?? {toKey: id, ofKey: id};
|
||||||
|
|
||||||
|
function Set(s) {
|
||||||
|
this.elements = {};
|
||||||
|
if(Array.isArray(s)) {
|
||||||
|
s.forEach(this.add.bind(this));
|
||||||
|
} else if(s != undefined) {
|
||||||
|
this.add(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Set.prototype.add = function(e) { this.elements[type.toKey(e)] = true; };
|
||||||
|
Set.prototype.remove = function(e) { delete this.elements[type.toKey(e)]; };
|
||||||
|
Set.prototype.contains = function(e) { return !!this.elements[type.toKey(e)]; };
|
||||||
|
Set.prototype.size = function() { return Object.keys(this.elements).length; };
|
||||||
|
|
||||||
|
Set.prototype.toList = function() {
|
||||||
|
var result = [];
|
||||||
|
for(var k in this.elements) {
|
||||||
|
result.push(type.ofKey(k));
|
||||||
|
}
|
||||||
|
return Array.from(result).sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
Set.prototype.iter = function(f) { this.toList().forEach(f); };
|
||||||
|
Set.prototype.map = function(f) { return this.toList().map(f); };
|
||||||
|
|
||||||
|
Set.prototype.equals = function(set) {
|
||||||
|
var sorted = [this.toList(), set.toList()];
|
||||||
|
return sorted[0].length == sorted[1].length &&
|
||||||
|
sorted[0].every(function(x) {return x == sorted[1].shift();});
|
||||||
|
}
|
||||||
|
|
||||||
|
Set.prototype.subset = function(predicate) {
|
||||||
|
var newSet = new Set();
|
||||||
|
this.iter(function(e) {
|
||||||
|
if(predicate(e)) {
|
||||||
|
newSet.add(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return newSet;
|
||||||
|
};
|
||||||
|
|
||||||
|
Set.prototype.difference = function(set) {
|
||||||
|
return this.subset(function(e) {return !set.contains(e);});
|
||||||
|
}
|
||||||
|
|
||||||
|
Set.prototype.intersection = function(set) {
|
||||||
|
return this.subset(function(e) {return set.contains(e);});
|
||||||
|
}
|
||||||
|
|
||||||
|
Set.union = function(sets) {
|
||||||
|
var newSet = new Set();
|
||||||
|
for(var i = 0; i < sets.length; i++) {
|
||||||
|
sets[i].iter(newSet.add.bind(newSet));
|
||||||
|
}
|
||||||
|
return newSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set.uniq = function(t) {
|
||||||
|
return (new Set(t)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Set;
|
||||||
|
}
|
41
js/Share.js
Normal file
41
js/Share.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
var share = document.getElementById('share')
|
||||||
|
|
||||||
|
return {
|
||||||
|
get: get,
|
||||||
|
decode: Decode.grid,
|
||||||
|
link: link
|
||||||
|
}
|
||||||
|
|
||||||
|
function get() {
|
||||||
|
return share;
|
||||||
|
}
|
||||||
|
|
||||||
|
function naiveEncode(coloring) {
|
||||||
|
var encoder = Encoder.make();
|
||||||
|
iter(coloring, function(color) {
|
||||||
|
encoder.int(3)(color);
|
||||||
|
});
|
||||||
|
return encoder.output();
|
||||||
|
}
|
||||||
|
|
||||||
|
function naiveDecode(input) {
|
||||||
|
if(input != undefined) {
|
||||||
|
var decoder = Decoder.make(input);
|
||||||
|
return generate(size, size, function() {return decoder.int(3);});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
49
js/Share/Decoder/Class.js
Normal file
49
js/Share/Decoder/Class.js
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
function Decoder(input) {
|
||||||
|
this.input = atob(input);
|
||||||
|
this.cache = 0;
|
||||||
|
this.size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Decoder.prototype.pop = function() {
|
||||||
|
if(this.size < 1) {
|
||||||
|
if(this.input.length < 1) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
this.cache = this.input.charCodeAt(0);
|
||||||
|
this.size = 8;
|
||||||
|
this.input = this.input.slice(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.size--;
|
||||||
|
var wasFirstBitOne = this.cache > 0x7f;
|
||||||
|
this.cache = (2*this.cache) & 0xff;
|
||||||
|
return wasFirstBitOne ? 1 : 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
Decoder.prototype.variableLength3 = function() {
|
||||||
|
if(this.pop() == 1) {
|
||||||
|
return this.pop() + 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Decoder.prototype.variableLength6 = function() {
|
||||||
|
if(this.pop() == 1) {
|
||||||
|
return 2 + this.int(2);
|
||||||
|
} else {
|
||||||
|
return this.pop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Decoder.prototype.int = function(size) {
|
||||||
|
var result = 0;
|
||||||
|
for(var i = 0; i < size; i++) {
|
||||||
|
result = 2*result + this.pop();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
make: function(input) {return new Decoder(input);}
|
||||||
|
};
|
80
js/Share/Decoder/Protocol.js
Normal file
80
js/Share/Decoder/Protocol.js
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import size from Config;
|
||||||
|
import * as CellSet from Geometry.CellSet;
|
||||||
|
import * as Decoder from Share.Decoder.Class;
|
||||||
|
import {diagonal, plus, zero} from Geometry.Vector;
|
||||||
|
import * as Vector from Geometry.Vector;
|
||||||
|
import * as Protocol from Share.Protocol;
|
||||||
|
|
||||||
|
return {
|
||||||
|
grid: decodeGrid
|
||||||
|
};
|
||||||
|
|
||||||
|
function decodeGrid(input) {
|
||||||
|
if(input != undefined) {
|
||||||
|
return decoderLoop(
|
||||||
|
Decoder.make(input),
|
||||||
|
square(size),
|
||||||
|
CellSet.rectangle(zero(), diagonal(size))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function decoderLoop(decoder, grid, missing) {
|
||||||
|
var queue = [];
|
||||||
|
var nextBit = decoder.pop();
|
||||||
|
var cell = zero();
|
||||||
|
var danglingCells = [];
|
||||||
|
while(nextBit != null) {
|
||||||
|
if(nextBit == 1) {
|
||||||
|
fillBlock(grid, decodeBlock(decoder), missing);
|
||||||
|
} else {
|
||||||
|
handleCell(grid, cell, decodeDirection(decoder), danglingCells);
|
||||||
|
}
|
||||||
|
moveToNext(cell, grid, missing);
|
||||||
|
nextBit = decoder.pop();
|
||||||
|
}
|
||||||
|
resolve(grid, danglingCells);
|
||||||
|
return grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveToNext(cell, grid, missing) {
|
||||||
|
cell.column++;
|
||||||
|
while(!missing.contains(cell)) {
|
||||||
|
if(cell.column >= grid[cell.row].length) {
|
||||||
|
cell.row++;
|
||||||
|
cell.column = 0;
|
||||||
|
} else {
|
||||||
|
cell.column++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillBlock(grid, block, missing, cell) {
|
||||||
|
var offset = Vector[block.direction](1);
|
||||||
|
var newCell = Vector.make(cell.row, cell.column);
|
||||||
|
for(var delta = 0; delta < block.size; delta++) {
|
||||||
|
set(grid, newCell, block.color);
|
||||||
|
missing.remove(newCell);
|
||||||
|
newCell = plus(newCell, offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeBlock(decoder) {
|
||||||
|
return {
|
||||||
|
direction: decoder.pop() == 1 ? 'vertical' : 'horizontal',
|
||||||
|
size: decoder.variableLength6() + Protocol.MIN_BLOCK_SIZE,
|
||||||
|
color: decoder.int(3)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCell(grid, cell, direction, danglingCells) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolve(grid, danglingCells) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeDirection(decoder) {
|
||||||
|
return Protocol.directions[decoder.int(2)];
|
||||||
|
}
|
55
js/Share/Encoder/Class.js
Normal file
55
js/Share/Encoder/Class.js
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
function Encoder() {
|
||||||
|
this.buffer = '';
|
||||||
|
this.stack = 0;
|
||||||
|
this.size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Encoder.prototype.push = function(one) {
|
||||||
|
if(this.size > 7) {
|
||||||
|
this.flush();
|
||||||
|
}
|
||||||
|
this.stack = 2*this.stack + (one ? 1 : 0);
|
||||||
|
this.size++;
|
||||||
|
};
|
||||||
|
|
||||||
|
Encoder.prototype.flush = function() {
|
||||||
|
this.buffer = this.buffer + String.fromCharCode(this.stack);
|
||||||
|
this.stack = 0;
|
||||||
|
this.size = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
Encoder.prototype.output = function() {
|
||||||
|
while(this.size < 8) {
|
||||||
|
this.push(0);
|
||||||
|
}
|
||||||
|
this.flush();
|
||||||
|
return btoa(this.buffer);
|
||||||
|
};
|
||||||
|
|
||||||
|
Encoder.prototype.variableLength3 = function(n) {
|
||||||
|
if(n > 0) {
|
||||||
|
this.push(1);
|
||||||
|
}
|
||||||
|
this.push(n > 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
Encoder.prototype.variableLength6 = function(n) {
|
||||||
|
if(n > 1) {
|
||||||
|
this.push(1);
|
||||||
|
}
|
||||||
|
this.push(n > 3);
|
||||||
|
this.push(n % 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
Encoder.prototype.int = function(size) {
|
||||||
|
return function(n) {
|
||||||
|
for(var i = 0; i < size; i++) {
|
||||||
|
this.push(n > 3);
|
||||||
|
n = (2*n) & 7;
|
||||||
|
}
|
||||||
|
}.bind(this);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
make: function() {return new Encoder();}
|
||||||
|
};
|
74
js/Share/Encoder/Protocol.js
Normal file
74
js/Share/Encoder/Protocol.js
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
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 {
|
||||||
|
grid: encodeGrid
|
||||||
|
};
|
||||||
|
|
||||||
|
function encodeGrid(grid) {
|
||||||
|
var encoder = Encoder.make();
|
||||||
|
var done = square(grid.length, false);
|
||||||
|
var gradients = square(grid.length);
|
||||||
|
iter(grid, function(_, cell) {
|
||||||
|
if(!at(done, cell)) {
|
||||||
|
let block = getLongestBlock(grid, cell);
|
||||||
|
if(block.size >= Protocol.MIN_BLOCK_SIZE) {
|
||||||
|
encodeBlock(encoder, done, block);
|
||||||
|
} else {
|
||||||
|
encodeSingleCell(
|
||||||
|
encoder,
|
||||||
|
getColorGradient(gradients, grid, done, cell)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return encoder.output();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLongestBlock(grid, origin) {
|
||||||
|
var color = at(grid, origin);
|
||||||
|
var hSize = getStreak(Grid.row(grid, origin.row), origin.column, color);
|
||||||
|
var vSize = getStreak(Grid.column(grid, origin.column), origin.row, color);
|
||||||
|
return {
|
||||||
|
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 encodeBlock(encoder, done, block) {
|
||||||
|
encoder.push(1);
|
||||||
|
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, done, cell) {
|
||||||
|
if(at(gradients, cell) == undefined) {
|
||||||
|
|
||||||
|
}
|
||||||
|
return at(gradients, cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeSingleCell(encoder, direction) {
|
||||||
|
encoder.push(0);
|
||||||
|
encoder.int(2)(Protocol.directions.indexOf(direction));
|
||||||
|
}
|
4
js/Share/Protocol.js
Normal file
4
js/Share/Protocol.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
return {
|
||||||
|
directions: ['up', 'right', 'down', 'left'],
|
||||||
|
MIN_BLOCK_SIZE: 3
|
||||||
|
};
|
146
js/Solver.js
146
js/Solver.js
|
@ -1,114 +1,54 @@
|
||||||
import 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(matrix) {
|
function solve(coloring) {
|
||||||
var zones = getZones(matrix);
|
var solvingState = State.start(coloring);
|
||||||
var grid = getGrid(matrix.length);
|
var stuck = false;
|
||||||
var rowClusters = checkRowsInclusions(matrix);
|
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 grid.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(matrix) {
|
|
||||||
var zones = {};
|
|
||||||
for(var row = 0; row < matrix.length; row++) {
|
|
||||||
for(var column = 0; column < matrix[row].length; column++) {
|
|
||||||
var color = matrix[row][column];
|
|
||||||
if(zones[color] == undefined) {
|
|
||||||
zones[color] = CellSet.make(
|
|
||||||
{type: 'isochrome', row: row, column: column, data: matrix}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 getGrid(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(matrix) {
|
function forget(solvingState, cell) {
|
||||||
var colorsByRow = [];
|
State.getCellSets(solvingState).concat(solvingState.missing)
|
||||||
for(var row = 0; row < matrix.length; row++) {
|
.forEach(function(cellSet) {cellSet.remove(cell);});
|
||||||
colorsByRow.push(
|
|
||||||
quotient(matrix[row], function(c0, c1) {return c0 == c1;}).map(
|
|
||||||
function(colorClass) {return colorClass.specimen;}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return colorsByRow;
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkRowsInclusions(matrix) {
|
|
||||||
var colorsByRow = getColorsByRow(matrix);
|
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
83
js/Solver/Inclusion.js
Normal file
83
js/Solver/Inclusion.js
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
import * as CellSet from Geometry.CellSet;
|
||||||
|
import * as Strategy from Solver.Strategy;
|
||||||
|
import {getColumn, getRow} from Geometry.Vector;
|
||||||
|
import Set;
|
||||||
|
|
||||||
|
return {
|
||||||
|
find: find
|
||||||
|
};
|
||||||
|
|
||||||
|
function find(solvingState) {
|
||||||
|
var inclusion = findInclusion(cellDimensionsByname(solvingState));
|
||||||
|
return Strategy.tryEach([
|
||||||
|
inclusion('rows', 'colors'),
|
||||||
|
inclusion('columns', 'colors'),
|
||||||
|
inclusion('colors', 'rows'),
|
||||||
|
inclusion('colors', 'columns')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cellDimensionsByname(solvingState) {
|
||||||
|
return {
|
||||||
|
rows: {sets: solvingState.rows, property: getRow},
|
||||||
|
columns: {sets: solvingState.columns, property: getColumn},
|
||||||
|
colors: {sets: solvingState.colorZones, property: solvingState.getColor}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAll(property) {
|
||||||
|
return function(cellSet) {return new Set.Int(cellSet.map(property));};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSet(namedSet) {
|
||||||
|
return function(i) {return namedSet.sets[i];};
|
||||||
|
}
|
||||||
|
|
||||||
|
function findInclusion(cellDimensions) {
|
||||||
|
return function(subName, superName) {
|
||||||
|
var subDimension = cellDimensions[subName];
|
||||||
|
var superDimension = cellDimensions[superName];
|
||||||
|
var diff = difference(getSet(subDimension), getSet(superDimension));
|
||||||
|
return Strategy.map(
|
||||||
|
stepOfInclusion(subName, superName),
|
||||||
|
Strategy.tryEach(
|
||||||
|
group(subDimension.sets.map(getAll(superDimension.property))).map(diff)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function difference(getSubset, getSuperset) {
|
||||||
|
return function(group) {
|
||||||
|
return function() {
|
||||||
|
if(group.indices.length == group.value.size()) {
|
||||||
|
var empty = CellSet.union(group.value.map(getSuperset))
|
||||||
|
.difference(CellSet.union(group.indices.map(getSubset)));
|
||||||
|
return empty.size() > 0 ? {empty: empty, group: group} : null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function stepOfInclusion(subName, superName) {
|
||||||
|
return function(diffedGroup) {
|
||||||
|
var step = {reason: 'inclusion', empty: diffedGroup.empty};
|
||||||
|
step[subName] = diffedGroup.group.indices;
|
||||||
|
step['in_' + superName] = diffedGroup.group.value.toList();
|
||||||
|
return step;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function group(sets) {
|
||||||
|
var groups = [];
|
||||||
|
sets.forEach(function(set, i) {
|
||||||
|
for(var g = 0; g < groups.length; g++) {
|
||||||
|
if(groups[g].value.equals(set)) {
|
||||||
|
groups[g].indices.push(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
groups.push({value: set, indices: [i]});
|
||||||
|
});
|
||||||
|
return groups;
|
||||||
|
}
|
44
js/Solver/SingleCell.js
Normal file
44
js/Solver/SingleCell.js
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import * as CellSet from Geometry.CellSet;
|
||||||
|
import getCellSets from Solver.State;
|
||||||
|
import * as Strategy from Solver.Strategy;
|
||||||
|
import {diagonal, getColumn, getRow, plus} from Geometry.Vector;
|
||||||
|
|
||||||
|
return {
|
||||||
|
find: find
|
||||||
|
};
|
||||||
|
|
||||||
|
function find(solvingState) {
|
||||||
|
var cellSets = getCellSets(solvingState);
|
||||||
|
return Strategy.map(
|
||||||
|
stepOfCell(solvingState.missing),
|
||||||
|
Strategy.tryEach(cellSets.map(getSingleCellIn))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSingleCellIn(cellSet) {
|
||||||
|
return function() {
|
||||||
|
if(cellSet.size() == 1) {
|
||||||
|
return cellSet.toList()[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function stepOfCell(missing) {
|
||||||
|
return function(cell) {
|
||||||
|
return {
|
||||||
|
reason: 'singleCell',
|
||||||
|
empty: toEmpty(cell).intersection(missing),
|
||||||
|
star: new CellSet.CellSet(cell)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function toEmpty(cell) {
|
||||||
|
var union = CellSet.union([
|
||||||
|
CellSet.rectangle(plus(cell, diagonal(-1)), diagonal(3)),
|
||||||
|
CellSet.row(getRow(cell)),
|
||||||
|
CellSet.column(getColumn(cell))
|
||||||
|
]);
|
||||||
|
union.remove(cell);
|
||||||
|
return union;
|
||||||
|
}
|
38
js/Solver/State.js
Normal file
38
js/Solver/State.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import size from Config;
|
||||||
|
import {asAFunction, iter, square} from Grid.Util;
|
||||||
|
import {diagonal, zero} from Geometry.Vector;
|
||||||
|
import * as CellSet from Geometry.CellSet;
|
||||||
|
|
||||||
|
return {
|
||||||
|
getCellSets: getCellSets,
|
||||||
|
start: start
|
||||||
|
};
|
||||||
|
|
||||||
|
function start(coloring) {
|
||||||
|
var empty = Array.from({length: size});
|
||||||
|
return {
|
||||||
|
constellation: square(size),
|
||||||
|
getColor: asAFunction(coloring),
|
||||||
|
missing: CellSet.rectangle(zero(), diagonal(size)),
|
||||||
|
colorZones: getZones(coloring),
|
||||||
|
rows: empty.map(function(_, i) {return CellSet.row(i);}),
|
||||||
|
columns: empty.map(function(_, i) {return CellSet.column(i);})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getZones(grid) {
|
||||||
|
var zones = Array.from({length: size});
|
||||||
|
iter(grid, function(color, cell) {
|
||||||
|
if(zones[color] == undefined) {
|
||||||
|
zones[color] = new CellSet.CellSet();
|
||||||
|
}
|
||||||
|
zones[color].add(cell);
|
||||||
|
});
|
||||||
|
return zones;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCellSets(solvingState) {
|
||||||
|
return solvingState.colorZones
|
||||||
|
.concat(solvingState.rows)
|
||||||
|
.concat(solvingState.columns);
|
||||||
|
}
|
31
js/Solver/Strategy.js
Normal file
31
js/Solver/Strategy.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
return {
|
||||||
|
execute: execute,
|
||||||
|
tryEach: tryEach,
|
||||||
|
map: map
|
||||||
|
}
|
||||||
|
|
||||||
|
function tryEach(strategies) {
|
||||||
|
return function() {
|
||||||
|
for(var i = 0; i < strategies.length; i++) {
|
||||||
|
var result = strategies[i]();
|
||||||
|
if(result != undefined) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function execute(strategy, onSuccess, onError) {
|
||||||
|
var result = strategy();
|
||||||
|
if(result != undefined) {
|
||||||
|
return onSuccess(result);
|
||||||
|
} else {
|
||||||
|
return onError(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function map(f, strategy) {
|
||||||
|
return function() {
|
||||||
|
return execute(strategy, f, function() {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