diff --git a/css/grid.css b/css/grid.css
index b88e6b0..00437d3 100644
--- a/css/grid.css
+++ b/css/grid.css
@@ -1,5 +1,10 @@
#grid {
border-collapse: collapse;
+ border: 1px solid #ddd;
+}
+
+#grid.active {
+ border: none;
background: #ddd;
}
@@ -7,11 +12,16 @@
width: 2em;
height: 2em;
display: inline-block;
- cursor: pointer;
padding: 1px;
+ text-align: center;
+ line-height: 2em;
}
-#grid td:hover {
+#grid.active td {
+ cursor: pointer;
+}
+
+#grid.active td:hover {
padding: 0;
border: 1px dashed #aaa;
}
@@ -19,3 +29,15 @@
#grid td[class*="color"] {
padding: 0;
}
+
+#grid td.off {
+ filter: invert(1);
+}
+
+#save {
+ display: none;
+}
+
+#save.active {
+ display: initial;
+}
diff --git a/guix.scm b/guix.scm
index 4d7d263..e76fb45 100644
--- a/guix.scm
+++ b/guix.scm
@@ -10,7 +10,7 @@
(UnitJS (load "/home/Bureau/unitJS/guix.scm"))
(WTK (load "/home/Bureau/WTK/guix.scm")))
(package
- (name "etoiles")
+ (name "constellations")
(version "devel")
(source
(local-file %source-dir
@@ -29,7 +29,7 @@
(modify-phases %standard-phases
(delete 'configure)
(delete 'check))))
- (home-page "https://git.marvid.fr/Tissevert/Étoiles")
+ (home-page "https://git.marvid.fr/Tissevert/Constellations")
(synopsis "A game webapp")
(description
"someday")
diff --git a/index.html b/index.html
index 8a10a1f..75907bb 100644
--- a/index.html
+++ b/index.html
@@ -2,7 +2,7 @@
- Étoiles
+ Constellations
diff --git a/js/CellSet.js b/js/CellSet.js
new file mode 100644
index 0000000..7b74eed
--- /dev/null
+++ b/js/CellSet.js
@@ -0,0 +1,41 @@
+function CellSet(definition) {
+ definition = definition || {};
+ this.cells = {};
+ if(definition.shape == 'rectangle') {
+ var xMax = definition.x + definition.width;
+ var yMax = definition.y + definition.height;
+ for(var i = definition.x; i < xMax; i++) {
+ for(var j = definition.y; j < yMax; j++) {
+ this.add(i, j);
+ }
+ }
+ } else if(definition.shape == 'points') {
+ for(var i = 0; i < definition.points.length; i++) {
+ this.cells[id(definition.points[i].i, definition.points[i].j)];
+ }
+ }
+}
+
+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;
+}
+
+return {
+ make: function(definition) {return new CellSet(definition);},
+};
+
+function id(i, j) {
+ return i + ':' + j;
+}
diff --git a/js/GUI.js b/js/GUI.js
new file mode 100644
index 0000000..2c558c9
--- /dev/null
+++ b/js/GUI.js
@@ -0,0 +1,16 @@
+return {
+ activate: activate,
+ mouse: {
+ left: 0,
+ right: 2
+ }
+};
+
+function activate(on, targets) {
+ if(!Array.isArray(targets)) {
+ targets = [targets]
+ }
+ targets.forEach(function(element) {
+ element.classList[on ? 'add' : 'remove']('active');
+ });
+}
diff --git a/js/Grid.js b/js/Grid.js
index 6b57d7c..ce1d844 100644
--- a/js/Grid.js
+++ b/js/Grid.js
@@ -1,122 +1,66 @@
-import * as File from WTK.File;
-import * as Async from UnitJS.Async;
-import compose from UnitJS.Fun;
+import CellSet;
import * as Dom from UnitJS.Dom;
-import Toolbox;
-
var grid = {
- element: null,
- data: null
+ element: document.getElementById('grid'),
+ data: null,
+ missing: null,
+ size: null
};
-var down = false;
+
+var iter = generate; // alias for more intuitive use (discarding the result)
return {
- init: init
+ cell: cell,
+ clear: clear,
+ init: init,
+ get: get,
+ iter: iter,
};
-function init(size, elementId) {
- grid.element = document.getElementById(elementId || 'grid');
+function init(size, eventHandlers) {
+ grid.size = size;
for(var row = 0; row < size; row++) {
grid.element.appendChild(
- makeRow({tag: 'td', attributes: bodyAttributes, row: row, size: size})
+ makeRow({tag: 'td', attributes: eventHandlers, row: row})
);
}
- grid.data = emptyGrid(size);
- grid.element.addEventListener('mouseleave', function() {
- down = false;
- });
- document.getElementById('load').addEventListener('click', load);
- document.getElementById('save').addEventListener('click', save);
-}
-
-function emptyGrid(size) {
- var result = {};
- for(var row = 0; row < size; row++) {
- result[row] = {};
- }
- return result;
+ clear();
}
function makeRow(config) {
var cells = [];
- for(var column = 0; column < config.size; column++) {
+ 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 bodyAttributes(row, column) {
- return {
- onMousedown: function() {
- down = true;
- if(Toolbox.tool() == 'draw') {
- colorize(row, column);
- }
- },
- onMouseup: function() {
- down = false;
- if(Toolbox.tool() == 'paint') {
- paint(row, column);
- }
- },
- onMouseenter: function() {
- if(down && Toolbox.tool() == 'draw') {
- colorize(row, column);
- }
- }
- };
-}
-
-function colorize(row, column, color) {
- grid.data[row][column] = color || Toolbox.color();
- grid.element.children[row].children[column].className = grid.data[row][column];
-}
-
-function paint(i0, j0) {
- var originColor = grid.data[i0][j0];
- var size = grid.element.children.length;
- var done = emptyGrid(size);
- var queue = [{i: i0, j: j0}];
- while(queue.length > 0) {
- var p0 = queue[0];
- colorize(p0.i, p0.j);
- done[p0.i][p0.j] = true;
- for(var d = -1; d < 2; d += 2) {
- [{i: p0.i + d, j: p0.j}, {i: p0.i, j: p0.j + d}].forEach(function(p1) {
- if(p1.i >= 0 && p1.i < size && p1.j >= 0 && p1.j < size
- && !done[p1.i][p1.j]
- && grid.data[p1.i][p1.j] == originColor) {
- queue.push(p1);
- }
- });
- }
- queue.shift();
- }
-}
-
-function load() {
- Async.run(
- Async.bind(
- File.pick({accept: 'text/json,.json'}),
- function(input) {
- return File.load(input.files[0]);
- },
- Async.map(compose(setGridData, JSON.parse))
- )
+function clear() {
+ grid.data = generate(function() {return; });
+ grid.missing = CellSet.make(
+ {shape: 'rectangle', x: 0, y: 0, width: 8, height: 8}
);
+ iter(function(row, column) {
+ cell(row, column).className = '';
+ });
}
-function setGridData(data) {
- if(data != undefined) {
- for(var row = 0; row < grid.element.children.length; row++) {
- for(var column = 0; column < grid.element.children[row].children.length; column++) {
- colorize(row, column, data[row][column]);
- }
+function generate(f) {
+ var result = {}
+ for(var row = 0; row < grid.size; row++) {
+ result[row] = {};
+ for(var column = 0; column < grid.size; column++) {
+ result[row][column] = f(row, column);
}
}
+ return result;
}
-function save() {
- File.save('data:text/json,' + JSON.stringify(grid.data), "grid.json");
+function get() {
+ return grid;
+}
+
+function cell(row, column) {
+ return grid.element.children[row].children[column];
}
diff --git a/js/Grid/Color.js b/js/Grid/Color.js
new file mode 100644
index 0000000..cefdba9
--- /dev/null
+++ b/js/Grid/Color.js
@@ -0,0 +1,42 @@
+import CellSet;
+import Grid;
+import Toolbox;
+
+return {
+ ize: colorize,
+ paint: paint
+};
+
+function colorize(row, column, color) {
+ var grid = Grid.get();
+ grid.data[row][column] = color || Toolbox.color();
+ Grid.cell(row, column).className = grid.data[row][column];
+ grid.missing.remove(row, column);
+}
+
+function paint(i0, j0) {
+ var originColor = Grid.get().data[i0][j0];
+ var done = CellSet.make();
+ var queue = [{i: i0, j: j0}];
+ while(queue.length > 0) {
+ var p0 = queue[0];
+ colorize(p0.i, p0.j);
+ done.add(p0.i, p0.j);
+ extend(p0, queue, done, originColor);
+ queue.shift();
+ }
+}
+
+function extend(p0, queue, done, originColor) {
+ var size = Grid.get().size;
+ for(var d = -1; d < 2; d += 2) {
+ [{i: p0.i + d, j: p0.j}, {i: p0.i, j: p0.j + d}].forEach(function(p1) {
+ if(p1.i >= 0 && p1.i < size && p1.j >= 0 && p1.j < size
+ && !done.contains(p1.i, p1.j)
+ && Grid.get().data[p1.i][p1.j] == originColor) {
+ queue.push(p1);
+ }
+ });
+ }
+}
+
diff --git a/js/Grid/IO.js b/js/Grid/IO.js
new file mode 100644
index 0000000..262d534
--- /dev/null
+++ b/js/Grid/IO.js
@@ -0,0 +1,49 @@
+import * as File from WTK.File;
+import * as Async from UnitJS.Async;
+import Grid;
+import Grid.Color;
+import Mode;
+
+return {
+ init: init
+}
+
+function init() {
+ document.getElementById('load').addEventListener('click', load);
+ document.getElementById('save').addEventListener('click', save);
+}
+
+function load() {
+ Grid.clear();
+ Mode.setEnabled(false, ['play', 'solve']);
+ Async.run(
+ Async.bind(
+ File.pick({accept: 'text/json,.json'}),
+ function(input) {
+ return File.load(input.files[0]);
+ },
+ function(data) {
+ return Async.wrap(setGridData(JSON.parse(data)));
+ }
+ )
+ );
+}
+
+function setGridData(data) {
+ if(data != undefined) {
+ Grid.iter(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() {
+ File.save('data:text/json,' + JSON.stringify(Grid.get().data), "grid.json");
+}
diff --git a/js/Main.js b/js/Main.js
index 5c13ca3..61c70cd 100644
--- a/js/Main.js
+++ b/js/Main.js
@@ -1,9 +1,18 @@
import Grid;
+import Grid.IO;
import Toolbox;
import Mode;
+import * as Play from Mode.Play;
+import * as Solve from Mode.Solve;
+import * as Edit from Mode.Edit;
var size = 8;
-Grid.init(size);
Toolbox.init(size);
-Mode.init();
+Mode.init({
+ play: Play,
+ solve: Solve,
+ edit: Edit
+});
+Grid.init(size, Mode.dispatch);
+Grid.IO.init();
diff --git a/js/Mode.js b/js/Mode.js
index 66e509f..a06aa74 100644
--- a/js/Mode.js
+++ b/js/Mode.js
@@ -1,14 +1,57 @@
-import Toolbox;
-
var mode;
+var modes;
+var mouseEvents = ['onClick', 'onMousedown', 'onMouseup', 'onMouseenter'];
+var currentMode = 'play';
return {
- init: init
+ init: init,
+ dispatch: dispatch,
+ set: set,
+ setEnabled: setEnabled
}
-function init(elementId) {
+function init(chosenModes, elementId) {
mode = document.getElementById(elementId || 'mode');
- mode.addEventListener('change', function() {
- Toolbox.activate(mode.value == 'edit');
- });
+ mode.addEventListener('change', onChange);
+ modes = chosenModes;
+}
+
+function setEnabled(enabled, targets) {
+ if(!Array.isArray(targets)) {
+ targets = [targets];
+ }
+ for(var i = 0; i < mode.options.length; i++) {
+ var option = mode.options[i];
+ if(targets.indexOf(option.value) >= 0) {
+ option.disabled = !enabled;
+ }
+ }
+}
+
+function runEvent(eventName) {
+ var handler = modes[currentMode].events[eventName];
+ if(typeof handler == 'function') {
+ handler.apply(null, Array.prototype.slice.call(arguments, 1));
+ }
+}
+
+function onChange() {
+ runEvent('onLeave');
+ currentMode = mode.value;
+ runEvent('onEnter');
+}
+
+function set(newMode) {
+ if(modes[newMode]) {
+ mode.value = newMode;
+ onChange();
+ }
+}
+
+function dispatch(row, column) {
+ var handler = {};
+ mouseEvents.forEach(function(eventName) {
+ handler[eventName] = function(e) {runEvent(eventName, e, row, column);};
+ });
+ return handler;
}
diff --git a/js/Mode/Edit.js b/js/Mode/Edit.js
new file mode 100644
index 0000000..6a4fb0e
--- /dev/null
+++ b/js/Mode/Edit.js
@@ -0,0 +1,68 @@
+import GUI;
+import Grid;
+import Grid.Color;
+import Mode;
+import Toolbox;
+
+var down = false;
+Grid.get().element.addEventListener('mouseleave', function() {
+ down = false;
+});
+var save = document.getElementById('save');
+
+return {
+ events: {
+ onEnter: onEnter,
+ onLeave: onLeave,
+ onMousedown: onMousedown,
+ onMouseup: onMouseup,
+ onMouseenter: onMouseenter
+ }
+};
+
+function onEnter() {
+ GUI.activate(true, [Grid.get().element, Toolbox.get(), save]);
+ if(!Grid.get().missing.isEmpty()) {
+ Mode.setEnabled(false, ['play', 'solve']);
+ }
+}
+
+function onLeave() {
+ GUI.activate(false, [Grid.get().element, Toolbox.get(), save]);
+}
+
+function onMousedown(e, row, column) {
+ if(e.button == GUI.mouse.left) {
+ down = true;
+ if(Toolbox.tool() == 'draw') {
+ colorCell(row, column);
+ }
+ }
+}
+
+function onMouseup(e, row, column) {
+ if(e.button == GUI.mouse.left) {
+ down = false;
+ if(Toolbox.tool() == 'paint') {
+ Grid.Color.paint(row, column);
+ checkCompleteness();
+ }
+ }
+}
+
+function onMouseenter(e, row, column) {
+ if(down && Toolbox.tool() == 'draw') {
+ colorCell(row, column);
+ }
+}
+
+function colorCell(row, column) {
+ Grid.Color.ize(row, column);
+ checkCompleteness();
+}
+
+function checkCompleteness() {
+ if(Grid.get().missing.isEmpty()) {
+ Mode.setEnabled(true, ['play', 'solve']);
+ }
+}
diff --git a/js/Mode/Play.js b/js/Mode/Play.js
new file mode 100644
index 0000000..863ecb0
--- /dev/null
+++ b/js/Mode/Play.js
@@ -0,0 +1,27 @@
+import Grid;
+import GUI;
+
+return {
+ events: {
+ onClick: onClick
+ }
+};
+
+function onClick(e, row, column) {
+ if(Grid.get().missing.isEmpty()) {
+ rotateState(Grid.cell(row, column));
+ }
+}
+
+function rotateState(cell) {
+ if(cell.classList.contains('off')) {
+ if(cell.textContent == '*') {
+ cell.classList.remove('off');
+ cell.textContent = '';
+ } else {
+ cell.textContent = '*';
+ }
+ } else {
+ cell.classList.add('off');
+ }
+}
diff --git a/js/Mode/Solve.js b/js/Mode/Solve.js
new file mode 100644
index 0000000..f80ae81
--- /dev/null
+++ b/js/Mode/Solve.js
@@ -0,0 +1,8 @@
+return {
+ events: {
+ onEnter: onEnter,
+ }
+};
+
+function onEnter() {
+}
diff --git a/js/Toolbox.js b/js/Toolbox.js
index 5815c8e..086cda7 100644
--- a/js/Toolbox.js
+++ b/js/Toolbox.js
@@ -5,16 +5,12 @@ var tool;
var colors;
return {
- activate: activate,
init: init,
- tool: tool,
- color: color
+ get: get,
+ color: color,
+ tool: tool
};
-function activate(on) {
- toolbox.classList[on ? 'add' : 'remove']('active');
-}
-
function init(size, elementId) {
toolbox = document.getElementById(elementId || 'toolbox');
colors = toolbox.querySelector('#colors');
@@ -27,6 +23,10 @@ function init(size, elementId) {
tool = toolbox.querySelector('#tool');
}
+function get() {
+ return toolbox;
+}
+
function tool() {
return tool.value;
}