Implement mode switching, rename project to 'Constellations'
This commit is contained in:
parent
ec5279127f
commit
6e62789c89
14 changed files with 383 additions and 114 deletions
26
css/grid.css
26
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;
|
||||
}
|
||||
|
|
4
guix.scm
4
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")
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Étoiles</title>
|
||||
<title>Constellations</title>
|
||||
<script src="main.js"></script>
|
||||
<link rel="stylesheet" href="style.css" type="text/css"/>
|
||||
</head>
|
||||
|
|
41
js/CellSet.js
Normal file
41
js/CellSet.js
Normal file
|
@ -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;
|
||||
}
|
16
js/GUI.js
Normal file
16
js/GUI.js
Normal file
|
@ -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');
|
||||
});
|
||||
}
|
130
js/Grid.js
130
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];
|
||||
}
|
||||
|
|
42
js/Grid/Color.js
Normal file
42
js/Grid/Color.js
Normal file
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
49
js/Grid/IO.js
Normal file
49
js/Grid/IO.js
Normal file
|
@ -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");
|
||||
}
|
13
js/Main.js
13
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();
|
||||
|
|
57
js/Mode.js
57
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;
|
||||
}
|
||||
|
|
68
js/Mode/Edit.js
Normal file
68
js/Mode/Edit.js
Normal file
|
@ -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']);
|
||||
}
|
||||
}
|
27
js/Mode/Play.js
Normal file
27
js/Mode/Play.js
Normal file
|
@ -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');
|
||||
}
|
||||
}
|
8
js/Mode/Solve.js
Normal file
8
js/Mode/Solve.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
return {
|
||||
events: {
|
||||
onEnter: onEnter,
|
||||
}
|
||||
};
|
||||
|
||||
function onEnter() {
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue