Implement mode switching, rename project to 'Constellations'

This commit is contained in:
Tissevert 2022-07-28 17:39:51 +02:00
parent ec5279127f
commit 6e62789c89
14 changed files with 383 additions and 114 deletions

View File

@ -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;
}

View File

@ -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")

View File

@ -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
View 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
View 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');
});
}

View File

@ -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
View 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
View 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");
}

View File

@ -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();

View File

@ -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
View 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
View 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
View File

@ -0,0 +1,8 @@
return {
events: {
onEnter: onEnter,
}
};
function onEnter() {
}

View File

@ -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;
}