Add geometry tooling
This commit is contained in:
parent
11f3902ca2
commit
671bfdcb5f
18 changed files with 394 additions and 167 deletions
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.grid[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.grid, 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, grid, queue, originColor) {
|
|
||||||
return function(p1) {
|
|
||||||
if(p1.i >= 0 && p1.i < grid.length && p1.j >= 0 && p1.j < grid.length
|
|
||||||
&& !cellSet.contains(p1.i, p1.j)
|
|
||||||
&& grid[p1.i][p1.j] == originColor) {
|
|
||||||
queue.push(p1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
99
js/Geometry/CellSet.js
Normal file
99
js/Geometry/CellSet.js
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
import {diagonal, isSmaller, key, plus, ofKey, zero} from Geometry.Vector;
|
||||||
|
import {at, generate} from Grid.Util;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CellSet.prototype.fromRectangle = function(origin, maxOffset) {
|
||||||
|
generate(
|
||||||
|
maxOffset.row,
|
||||||
|
maxOffset.column,
|
||||||
|
function(offset) {
|
||||||
|
this.add(plus(origin, offset));
|
||||||
|
}.bind(this)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
CellSet.prototype.fromIsochrome = function(grid, origin) {
|
||||||
|
var originColor = at(grid, origin);
|
||||||
|
var queue = [origin];
|
||||||
|
while(queue.length > 0) {
|
||||||
|
var cell = queue.shift();
|
||||||
|
this.add(cell);
|
||||||
|
for(var d = -1; d < 2; d += 2) {
|
||||||
|
[plus(cell, vertical(d)), plus(cell, horizontal(d))].forEach(
|
||||||
|
gateKeeper(this, grid, queue, originColor)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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) {
|
||||||
|
if(isSmaller(zero(), cell) && isSmaller(cell, diagonal(grid.length-1))
|
||||||
|
&& !cellSet.contains(cell)
|
||||||
|
&& at(grid, cell) == originColor) {
|
||||||
|
queue.push(cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
52
js/Geometry/Vector.js
Normal file
52
js/Geometry/Vector.js
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
return {
|
||||||
|
make: make,
|
||||||
|
vertical: vertical,
|
||||||
|
horizontal: horizontal,
|
||||||
|
diagonal: diagonal,
|
||||||
|
zero: zero,
|
||||||
|
plus: plus,
|
||||||
|
opposite: opposite,
|
||||||
|
isSmaller: isSmaller,
|
||||||
|
key: key,
|
||||||
|
ofKey: ofKey
|
||||||
|
};
|
||||||
|
|
||||||
|
function make(row, column) {
|
||||||
|
return {row: row, column: 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 {row: v0.row + v1.row, column: v0.column + v1.column};
|
||||||
|
}
|
||||||
|
|
||||||
|
function opposite(v) {
|
||||||
|
return {row: -v.row, column: -v.column};
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSmaller(v0, v1) {
|
||||||
|
return v0.row <= v1.row && v0.column <= v1.column;
|
||||||
|
}
|
||||||
|
|
||||||
|
function key(v) {
|
||||||
|
return v.row + ':' + v.column;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ofKey(k) {
|
||||||
|
return make.apply(null, k.split(':'));
|
||||||
|
}
|
36
js/Grid.js
36
js/Grid.js
|
@ -1,53 +1,43 @@
|
||||||
import CellSet;
|
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'),
|
||||||
|
cells: null,
|
||||||
colors: null,
|
colors: null,
|
||||||
missing: null,
|
missing: null,
|
||||||
size: 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(size, eventHandlers) {
|
||||||
grid.size = size;
|
grid.size = size;
|
||||||
|
grid.cells = generate(size, 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.colors = square(grid.size);
|
grid.colors = square(grid.size);
|
||||||
grid.missing = CellSet.make(
|
grid.missing = CellSet.make(
|
||||||
{type: 'rectangle', row: 0, column: 0, width: 8, height: 8}
|
{type: 'rectangle', origin: zero(), offset: diagonal(grid.size)}
|
||||||
);
|
);
|
||||||
iter(grid.colors, function(row, column) {
|
iter(grid.colors, function(cell) {
|
||||||
cell(row, column).className = '';
|
at(grid.cells, cell).className = '';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function get() {
|
function get() {
|
||||||
return grid;
|
return grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
function cell(row, column) {
|
|
||||||
return grid.element.children[row].children[column];
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,34 +1,34 @@
|
||||||
import CellSet;
|
import * as CellSet from Geometry.CellSet;
|
||||||
import Grid;
|
import Grid;
|
||||||
import iter from Grid.Util;
|
import {at, iter, set} from Grid.Util;
|
||||||
import Toolbox;
|
import Toolbox;
|
||||||
import Mode;
|
import Mode;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ize: colorize,
|
ize: colorize,
|
||||||
paint: paint,
|
paint: paint,
|
||||||
set: set
|
setColors: setColors
|
||||||
};
|
};
|
||||||
|
|
||||||
function colorize(row, column, color) {
|
function colorize(cell, color) {
|
||||||
var grid = Grid.get();
|
var grid = Grid.get();
|
||||||
grid.colors[row][column] = color || Toolbox.color();
|
set(grid.colors, cell, color || Toolbox.color());
|
||||||
Grid.cell(row, column).className = 'color' + grid.colors[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(
|
var cellSet = CellSet.make(
|
||||||
{type: 'isochrome', row: row, column: column, grid: Grid.get().colors}
|
{type: 'isochrome', grid: Grid.get().colors, origin: origin}
|
||||||
);
|
);
|
||||||
cellSet.iter(colorize);
|
cellSet.iter(colorize);
|
||||||
}
|
}
|
||||||
|
|
||||||
function set(grid) {
|
function setColors(grid) {
|
||||||
if(grid != undefined) {
|
if(grid != undefined) {
|
||||||
iter(grid, function(row, column) {
|
iter(grid, function(cell) {
|
||||||
if(grid[row][column] != undefined) {
|
if(at(grid, cell) != undefined) {
|
||||||
colorize(row, column, grid[row][column]);
|
colorize(cell, at(grid, cell));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if(Grid.get().missing.isEmpty()) {
|
if(Grid.get().missing.isEmpty()) {
|
||||||
|
|
|
@ -23,7 +23,7 @@ function load() {
|
||||||
return File.load(input.files[0]);
|
return File.load(input.files[0]);
|
||||||
},
|
},
|
||||||
function(data) {
|
function(data) {
|
||||||
Grid.Color.set(JSON.parse(data));
|
Grid.Color.setColors(JSON.parse(data));
|
||||||
return Async.wrap();
|
return Async.wrap();
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
|
import * as Vector from Geometry.Vector;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
at: at,
|
||||||
|
column: column,
|
||||||
generate: generate,
|
generate: generate,
|
||||||
iter: iter,
|
iter: iter,
|
||||||
|
row: row,
|
||||||
|
set: set,
|
||||||
square: square
|
square: square
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -9,7 +15,7 @@ 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;
|
||||||
|
@ -19,6 +25,22 @@ function iter(grid, f) {
|
||||||
generate(grid.length > 0 ? grid[0].length : null, grid.length, f);
|
generate(grid.length > 0 ? grid[0].length : null, grid.length, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
function square(size) {
|
function square(size, value) {
|
||||||
return generate(size, size, function() {return;});
|
return generate(size, size, function() {return value;});
|
||||||
|
}
|
||||||
|
|
||||||
|
function at(grid, vector) {
|
||||||
|
return grid[vector.row][vector.column];
|
||||||
|
}
|
||||||
|
|
||||||
|
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];});
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,6 @@ Mode.init({
|
||||||
Grid.init(size, Mode.dispatch);
|
Grid.init(size, 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.naiveDecode(size, urlSearchParameters.get('game')));
|
Grid.Color.set(Share.decode(size, 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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import Toolbox;
|
||||||
import Share;
|
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');
|
||||||
|
@ -22,7 +22,7 @@ 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.isEmpty()) {
|
||||||
Mode.setEnabled(false, ['play', 'solve']);
|
Mode.setEnabled(false, ['play', 'solve']);
|
||||||
} else {
|
} else {
|
||||||
|
@ -31,36 +31,36 @@ function onEnter() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onLeave() {
|
function onLeave() {
|
||||||
GUI.activate(false, [Grid.get().element, Toolbox.get(), save, Share.get()]);
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.isEmpty()) {
|
||||||
rotateState(Grid.cell(row, column));
|
rotateState(at(Grid.get().cells, cell));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
15
js/Share.js
15
js/Share.js
|
@ -1,6 +1,6 @@
|
||||||
import * as Decoder from Share.Decoder;
|
import {at, generate, iter, square} from Grid.Util;
|
||||||
import * as Encoder from Share.Encoder;
|
import * as Decode from Share.Decoder.Protocol;
|
||||||
import {generate, iter, square} from Grid.Util;
|
import * as Encode from Share.Encoder.Protocol;
|
||||||
import GUI;
|
import GUI;
|
||||||
import Grid;
|
import Grid;
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ var share = document.getElementById('share')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
get: get,
|
get: get,
|
||||||
naiveDecode: naiveDecode,
|
decode: Decode.grid,
|
||||||
link: link
|
link: link
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,8 +18,8 @@ function get() {
|
||||||
|
|
||||||
function naiveEncode(grid) {
|
function naiveEncode(grid) {
|
||||||
var encoder = Encoder.make();
|
var encoder = Encoder.make();
|
||||||
iter(grid, function(row, column) {
|
iter(grid, function(cell) {
|
||||||
encoder.int(3)(grid[row][column]);
|
encoder.int(3)(at(grid, cell));
|
||||||
});
|
});
|
||||||
return encoder.output();
|
return encoder.output();
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ function naiveDecode(size, input) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function link(grid) {
|
function link(grid) {
|
||||||
share.href = '?game=' + naiveEncode(Grid.get().colors);
|
//share.href = '?game=' + naiveEncode(Grid.get().colors);
|
||||||
|
share.href = '?game=' + Encode.grid(Grid.get().colors);
|
||||||
GUI.activate(true, share);
|
GUI.activate(true, share);
|
||||||
}
|
}
|
||||||
|
|
79
js/Share/Decoder/Protocol.js
Normal file
79
js/Share/Decoder/Protocol.js
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
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(size, input) {
|
||||||
|
if(input != undefined) {
|
||||||
|
return decoderLoop(
|
||||||
|
Decoder.make(input),
|
||||||
|
square(size),
|
||||||
|
CellSet.make({type: 'rectangle', origin: zero(), offset: 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)];
|
||||||
|
}
|
|
@ -42,13 +42,12 @@ Encoder.prototype.variableLength6 = function(n) {
|
||||||
};
|
};
|
||||||
|
|
||||||
Encoder.prototype.int = function(size) {
|
Encoder.prototype.int = function(size) {
|
||||||
var encoder = this;
|
|
||||||
return function(n) {
|
return function(n) {
|
||||||
for(var i = 0; i < size; i++) {
|
for(var i = 0; i < size; i++) {
|
||||||
encoder.push(n > 3);
|
encoder.push(n > 3);
|
||||||
n = (2*n) & 7;
|
n = (2*n) & 7;
|
||||||
}
|
}
|
||||||
}
|
}.bind(this);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
81
js/Share/Encoder/Protocol.js
Normal file
81
js/Share/Encoder/Protocol.js
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
import * as Encoder from Share.Encoder.Class;
|
||||||
|
import {at, iter, square} 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 != undefined) {
|
||||||
|
encodeBlock(encoder, block);
|
||||||
|
} else {
|
||||||
|
encodeSingleCell(
|
||||||
|
encoder,
|
||||||
|
getColorGradient(gradients, grid, cell)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
return {
|
||||||
|
direction: longestDirection.coordinate == 'row' ? 'vertical' : 'horizontal',
|
||||||
|
size: size,
|
||||||
|
color: color
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
encoder.push(1);
|
||||||
|
encoder.push(block.direction == 'vertical');
|
||||||
|
encoder.variableLength6(block.size - Protocol.MIN_BLOCK_SIZE);
|
||||||
|
encoder.int(3)(block.color);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getColorGradient(gradients, grid, 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
|
||||||
|
};
|
|
@ -1,4 +1,4 @@
|
||||||
import CellSet;
|
import * as CellSet from Geometry.CellSet;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
step: step
|
step: step
|
||||||
|
|
Loading…
Reference in a new issue