game Of Life
This commit is contained in:
@@ -1,9 +1,10 @@
|
|||||||
.boxes-container {
|
.boxes-container {
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
margin: 100px auto;
|
margin: 100px auto;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
.boxes-container input{
|
.boxes-container input{
|
||||||
height: 20px;
|
height: 3.125%;
|
||||||
width: 20px;
|
width: 3.125%;
|
||||||
margin: 2;
|
margin: 0;
|
||||||
}
|
}
|
||||||
128
js/boxes.js
128
js/boxes.js
@@ -1,68 +1,64 @@
|
|||||||
const socketUrl =
|
"use strict";
|
||||||
(window.location.protocol.startsWith("https") ? "wss://" : "ws://") +
|
(() => {
|
||||||
window.location.host +
|
const socket = getSocket("/boxes/ws");
|
||||||
"/ws";
|
const deserializeBox = (msg) => {
|
||||||
|
msg = msg.replaceAll("b:", "");
|
||||||
let socket = new WebSocket(socketUrl);
|
const parts = msg.split(":");
|
||||||
|
return {
|
||||||
socket.addEventListener("message", function (event) {
|
id: Number(parts[0]),
|
||||||
if (event.data.startsWith("u:")) {
|
value: parts[1] === "+",
|
||||||
const items = event.data.split(";");
|
};
|
||||||
items.forEach((i) => {
|
};
|
||||||
const parts = event.data.split(":");
|
const handleInstruction = (instruction) => {
|
||||||
document.getElementById("box-" + parts[1]).checked = parts[2] === "+";
|
if (instruction.startsWith("b:")) {
|
||||||
console.log("box-" + parts[1]);
|
const items = instruction.split(";");
|
||||||
|
items.forEach((i) => {
|
||||||
|
const box = deserializeBox(i);
|
||||||
|
const el = document.getElementById("box-" + box.id);
|
||||||
|
el.checked = box.value;
|
||||||
|
// console.log(i,box,el)
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
socket.addEventListener("message", (event) => {
|
||||||
|
const instructions = event.data.split("\n");
|
||||||
|
instructions.forEach(handleInstruction);
|
||||||
});
|
});
|
||||||
return;
|
document.querySelectorAll(".boxes input").forEach((input) => {
|
||||||
}
|
input.addEventListener("change", (event) => {
|
||||||
|
const target = event.target;
|
||||||
if (event.data.startsWith("i:")) {
|
const id = target?.id.split("-")[1];
|
||||||
const str = event.data.split("i:")[1];
|
const value = target.checked ? "+" : "-";
|
||||||
const items = str.split(";");
|
socket.send("b:" + id + ":" + value);
|
||||||
if (items.length === 0) {
|
});
|
||||||
return;
|
|
||||||
}
|
|
||||||
document.querySelectorAll("input").forEach((input) => {
|
|
||||||
input.checked = false;
|
|
||||||
});
|
});
|
||||||
items.forEach((i) => {
|
var autoPlayTimer = undefined;
|
||||||
if (!i) {
|
const handleAutoPlay = (el) => {
|
||||||
return;
|
if (el.checked) {
|
||||||
}
|
autoPlayTimer = setInterval(() => {
|
||||||
console.log(i, document.getElementById("box-" + i));
|
socket.send("random");
|
||||||
document.getElementById("box-" + i).checked = true;
|
}, 50);
|
||||||
});
|
}
|
||||||
return;
|
else {
|
||||||
}
|
clearInterval(autoPlayTimer);
|
||||||
});
|
}
|
||||||
|
};
|
||||||
setInterval(() => {
|
const autoPlayEl = document.querySelector("#auto-boxes");
|
||||||
socket.send("ping");
|
handleAutoPlay(autoPlayEl);
|
||||||
}, 10000);
|
autoPlayEl?.addEventListener("change", (e) => handleAutoPlay(e.target));
|
||||||
|
var golTimer = undefined;
|
||||||
document.querySelectorAll(".boxes input").forEach((input) => {
|
const handleGol = (el) => {
|
||||||
input.addEventListener("change", (event) => {
|
if (el.checked) {
|
||||||
if (socket.readyState !== socket.OPEN) {
|
golTimer = setInterval(() => {
|
||||||
socket = new WebSocket(socketUrl);
|
socket.send("gol");
|
||||||
}
|
}, 500);
|
||||||
const id = event.target.id.split("-")[1];
|
}
|
||||||
const value = event.target.checked ? "+" : "-";
|
else {
|
||||||
socket.send("u:" + id + ":" + value);
|
clearInterval(golTimer);
|
||||||
});
|
}
|
||||||
});
|
};
|
||||||
|
const golEl = document.querySelector("#game-of-life");
|
||||||
var timer = null
|
handleGol(golEl);
|
||||||
document.querySelector("#auto-boxes").addEventListener("change",(e)=>{
|
golEl.addEventListener("change", (e) => handleGol(e.target));
|
||||||
if (e.target.checked){
|
})();
|
||||||
timer = setInterval(()=>{
|
|
||||||
var id = Math.round(Math.random() * 1000)
|
|
||||||
const value = document.querySelector('#box-'+id).checked ? "-" : "+";
|
|
||||||
if (socket.readyState !== socket.OPEN) {
|
|
||||||
socket = new WebSocket(socketUrl);
|
|
||||||
}
|
|
||||||
socket.send("u:" + id + ":" + value);
|
|
||||||
},500)
|
|
||||||
}else{
|
|
||||||
clearInterval(timer)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|||||||
80
js/boxes.ts
Normal file
80
js/boxes.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
type CheckboxEl = HTMLInputElement & { checked: boolean; id: string };
|
||||||
|
type Box = {
|
||||||
|
id: number;
|
||||||
|
value: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
const socket = getSocket("/boxes/ws");
|
||||||
|
|
||||||
|
const deserializeBox = (msg: string): Box => {
|
||||||
|
msg = msg.replaceAll("b:", "");
|
||||||
|
const parts = msg.split(":");
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: Number(parts[0]),
|
||||||
|
value: parts[1] === "+",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInstruction = (instruction: string) => {
|
||||||
|
if (instruction.startsWith("b:")) {
|
||||||
|
const items = instruction.split(";");
|
||||||
|
items.forEach((i) => {
|
||||||
|
const box = deserializeBox(i);
|
||||||
|
|
||||||
|
const el = document.getElementById("box-" + box.id) as CheckboxEl;
|
||||||
|
el.checked = box.value;
|
||||||
|
// console.log(i,box,el)
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.addEventListener("message", (event: MessageEvent<string>) => {
|
||||||
|
const instructions = event.data.split("\n");
|
||||||
|
instructions.forEach(handleInstruction);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll(".boxes input").forEach((input) => {
|
||||||
|
input.addEventListener("change", (event) => {
|
||||||
|
const target = event.target as CheckboxEl;
|
||||||
|
|
||||||
|
const id = target?.id.split("-")[1];
|
||||||
|
const value = target.checked ? "+" : "-";
|
||||||
|
socket.send("b:" + id + ":" + value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var autoPlayTimer: number | undefined = undefined;
|
||||||
|
const handleAutoPlay = (el: CheckboxEl) => {
|
||||||
|
if (el.checked) {
|
||||||
|
autoPlayTimer = setInterval(() => {
|
||||||
|
socket.send("random");
|
||||||
|
}, 50);
|
||||||
|
} else {
|
||||||
|
clearInterval(autoPlayTimer);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const autoPlayEl = document.querySelector("#auto-boxes") as CheckboxEl;
|
||||||
|
handleAutoPlay(autoPlayEl);
|
||||||
|
autoPlayEl?.addEventListener("change", (e) =>
|
||||||
|
handleAutoPlay(e.target as CheckboxEl)
|
||||||
|
);
|
||||||
|
|
||||||
|
var golTimer: number | undefined = undefined;
|
||||||
|
const handleGol = (el: CheckboxEl) => {
|
||||||
|
if (el.checked) {
|
||||||
|
golTimer = setInterval(() => {
|
||||||
|
socket.send("gol");
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
clearInterval(golTimer);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const golEl = document.querySelector("#game-of-life") as CheckboxEl;
|
||||||
|
handleGol(golEl);
|
||||||
|
golEl.addEventListener("change", (e) => handleGol(e.target as CheckboxEl));
|
||||||
|
})();
|
||||||
62
js/draw.js
62
js/draw.js
@@ -4,33 +4,20 @@
|
|||||||
if (!drawElement) {
|
if (!drawElement) {
|
||||||
throw new Error("canvas not found");
|
throw new Error("canvas not found");
|
||||||
}
|
}
|
||||||
const drawEmitter = mitt();
|
const emitter = mitt();
|
||||||
let drawing = false;
|
let drawing = false;
|
||||||
const socketUrl = "/draw/ws";
|
let previous = null;
|
||||||
let socket = new WebSocket(socketUrl);
|
const socket = getSocket("/draw/ws");
|
||||||
socket.onerror = console.error;
|
|
||||||
socket.onclose = console.warn;
|
|
||||||
const serializeDraw = ({ from, to }) => `${from.x},${from.y},${to.x},${to.y}`;
|
const serializeDraw = ({ from, to }) => `${from.x},${from.y},${to.x},${to.y}`;
|
||||||
const deserializeDraw = (message) => {
|
const deserializeDraw = (message) => {
|
||||||
const parts = message.split(",");
|
const parts = message.split(",");
|
||||||
return {
|
return {
|
||||||
from: {
|
from: { x: Number(parts[0]), y: Number(parts[1]) },
|
||||||
x: Number(parts[0]),
|
to: { x: Number(parts[2]), y: Number(parts[3]) },
|
||||||
y: Number(parts[1]),
|
|
||||||
},
|
|
||||||
to: {
|
|
||||||
x: Number(parts[2]),
|
|
||||||
y: Number(parts[3]),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
drawEmitter.on("draw", (e) => sendMessage(e));
|
emitter.on("mouseDraw", (e) => sendMessage(e));
|
||||||
const sendMessage = (e) => {
|
const sendMessage = (e) => socket.send(serializeDraw(e));
|
||||||
if (socket.readyState !== socket.OPEN) {
|
|
||||||
socket = new WebSocket(socketUrl);
|
|
||||||
}
|
|
||||||
socket.send(serializeDraw(e));
|
|
||||||
};
|
|
||||||
const mouseDown = () => {
|
const mouseDown = () => {
|
||||||
drawing = true;
|
drawing = true;
|
||||||
previous = null;
|
previous = null;
|
||||||
@@ -38,12 +25,11 @@
|
|||||||
const mouseUp = () => {
|
const mouseUp = () => {
|
||||||
drawing = false;
|
drawing = false;
|
||||||
};
|
};
|
||||||
let previous = null;
|
|
||||||
const mouseMove = (e) => {
|
const mouseMove = (e) => {
|
||||||
if (!drawing)
|
if (!drawing)
|
||||||
return;
|
return;
|
||||||
if (previous) {
|
if (previous) {
|
||||||
drawEmitter.emit("draw", {
|
emitter.emit("mouseDraw", {
|
||||||
from: previous,
|
from: previous,
|
||||||
to: { x: e.offsetX, y: e.offsetY },
|
to: { x: e.offsetX, y: e.offsetY },
|
||||||
});
|
});
|
||||||
@@ -68,30 +54,26 @@
|
|||||||
if (!recCtx || !receiveCanvas) {
|
if (!recCtx || !receiveCanvas) {
|
||||||
throw new Error("canvas not found");
|
throw new Error("canvas not found");
|
||||||
}
|
}
|
||||||
//@ts-expect-error
|
const render = (data) => {
|
||||||
window.ctx = recCtx;
|
const { from, to } = data;
|
||||||
setTimeout(() => {
|
if (recCtx.strokeStyle != "white") {
|
||||||
recCtx.lineWidth = 2;
|
recCtx.lineWidth = 2;
|
||||||
recCtx.lineCap = "round";
|
recCtx.lineCap = "round";
|
||||||
recCtx.strokeStyle = "white";
|
recCtx.strokeStyle = "white";
|
||||||
}, 10);
|
}
|
||||||
socket.addEventListener("message", (event) => {
|
|
||||||
if (event.data === "ping" || event.data.startsWith('pong'))
|
|
||||||
return;
|
|
||||||
const { from, to } = deserializeDraw(event.data);
|
|
||||||
recCtx.beginPath();
|
recCtx.beginPath();
|
||||||
recCtx.moveTo(from.x, from.y);
|
recCtx.moveTo(from.x, from.y);
|
||||||
recCtx.lineTo(to.x, to.y);
|
recCtx.lineTo(to.x, to.y);
|
||||||
recCtx.closePath();
|
recCtx.closePath();
|
||||||
recCtx.stroke();
|
recCtx.stroke();
|
||||||
console.log(from, to);
|
};
|
||||||
|
emitter.on("mouseDraw", render);
|
||||||
|
emitter.on("netDraw", render);
|
||||||
|
socket.addEventListener("message", (event) => {
|
||||||
|
if (event.data === "ping" || event.data.startsWith("pong"))
|
||||||
|
return;
|
||||||
|
emitter.emit("netDraw", deserializeDraw(event.data));
|
||||||
});
|
});
|
||||||
setInterval(() => {
|
|
||||||
if (socket.readyState !== socket.OPEN) {
|
|
||||||
socket = new WebSocket(socketUrl);
|
|
||||||
}
|
|
||||||
socket.send("ping");
|
|
||||||
}, 10000);
|
|
||||||
const resizeObserver = new ResizeObserver((entries) => {
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
const entry = entries.at(0);
|
const entry = entries.at(0);
|
||||||
receiveCanvas.height = entry?.contentRect.height ?? 500;
|
receiveCanvas.height = entry?.contentRect.height ?? 500;
|
||||||
|
|||||||
74
js/draw.ts
74
js/draw.ts
@@ -15,58 +15,44 @@
|
|||||||
to: DrawPoint;
|
to: DrawPoint;
|
||||||
};
|
};
|
||||||
type Events = {
|
type Events = {
|
||||||
draw: DrawEvent;
|
mouseDraw: DrawEvent;
|
||||||
|
netDraw: DrawEvent;
|
||||||
};
|
};
|
||||||
|
|
||||||
const drawEmitter = mitt<Events>();
|
const emitter = mitt<Events>();
|
||||||
|
|
||||||
let drawing = false;
|
let drawing = false;
|
||||||
const socketUrl = "/draw/ws";
|
let previous: DrawPoint | null = null;
|
||||||
|
|
||||||
let socket = new WebSocket(socketUrl);
|
const socket = getSocket("/draw/ws");
|
||||||
|
|
||||||
socket.onerror = console.error;
|
const serializeDraw = ({ from, to }: DrawEvent) =>
|
||||||
socket.onclose = console.warn;
|
|
||||||
|
|
||||||
const serializeDraw = ({from, to }: DrawEvent) =>
|
|
||||||
`${from.x},${from.y},${to.x},${to.y}`;
|
`${from.x},${from.y},${to.x},${to.y}`;
|
||||||
|
|
||||||
const deserializeDraw = (message: string): DrawEvent => {
|
const deserializeDraw = (message: string): DrawEvent => {
|
||||||
const parts = message.split(",");
|
const parts = message.split(",");
|
||||||
return {
|
return {
|
||||||
from: {
|
from: { x: Number(parts[0]), y: Number(parts[1]) },
|
||||||
x: Number(parts[0]),
|
to: { x: Number(parts[2]), y: Number(parts[3]) },
|
||||||
y: Number(parts[1]),
|
|
||||||
},
|
|
||||||
to: {
|
|
||||||
x: Number(parts[2]),
|
|
||||||
y: Number(parts[3]),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
drawEmitter.on("draw", (e) => sendMessage(e));
|
emitter.on("mouseDraw", (e) => sendMessage(e));
|
||||||
const sendMessage = (e: DrawEvent) => {
|
const sendMessage = (e: DrawEvent) => socket.send(serializeDraw(e));
|
||||||
if (socket.readyState !== socket.OPEN) {
|
|
||||||
socket = new WebSocket(socketUrl);
|
|
||||||
}
|
|
||||||
socket.send(serializeDraw(e));
|
|
||||||
};
|
|
||||||
|
|
||||||
const mouseDown = () => {
|
const mouseDown = () => {
|
||||||
drawing = true;
|
drawing = true;
|
||||||
previous=null
|
previous = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mouseUp = () => {
|
const mouseUp = () => {
|
||||||
drawing = false;
|
drawing = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
let previous: DrawPoint | null = null;
|
|
||||||
const mouseMove = (e: Pick<MouseEvent, "offsetX" | "offsetY">) => {
|
const mouseMove = (e: Pick<MouseEvent, "offsetX" | "offsetY">) => {
|
||||||
if (!drawing) return;
|
if (!drawing) return;
|
||||||
if (previous) {
|
if (previous) {
|
||||||
drawEmitter.emit("draw", {
|
emitter.emit("mouseDraw", {
|
||||||
from: previous,
|
from: previous,
|
||||||
to: { x: e.offsetX, y: e.offsetY },
|
to: { x: e.offsetX, y: e.offsetY },
|
||||||
});
|
});
|
||||||
@@ -98,36 +84,30 @@
|
|||||||
if (!recCtx || !receiveCanvas) {
|
if (!recCtx || !receiveCanvas) {
|
||||||
throw new Error("canvas not found");
|
throw new Error("canvas not found");
|
||||||
}
|
}
|
||||||
//@ts-expect-error
|
|
||||||
window.ctx = recCtx;
|
|
||||||
|
|
||||||
setTimeout(()=>{
|
const render = (data: DrawEvent) => {
|
||||||
recCtx.lineWidth = 2;
|
const { from, to } = data;
|
||||||
recCtx.lineCap = "round";
|
|
||||||
recCtx.strokeStyle = "white";
|
|
||||||
},10)
|
|
||||||
|
|
||||||
|
if (recCtx.strokeStyle != "white") {
|
||||||
|
recCtx.lineWidth = 2;
|
||||||
|
recCtx.lineCap = "round";
|
||||||
socket.addEventListener("message", (event) => {
|
recCtx.strokeStyle = "white";
|
||||||
if(event.data==="ping" || event.data.startsWith('pong')) return
|
}
|
||||||
const { from, to } = deserializeDraw(event.data);
|
|
||||||
|
|
||||||
recCtx.beginPath();
|
recCtx.beginPath();
|
||||||
recCtx.moveTo(from.x, from.y);
|
recCtx.moveTo(from.x, from.y);
|
||||||
recCtx.lineTo(to.x, to.y);
|
recCtx.lineTo(to.x, to.y);
|
||||||
recCtx.closePath();
|
recCtx.closePath();
|
||||||
recCtx.stroke();
|
recCtx.stroke();
|
||||||
console.log(from, to);
|
};
|
||||||
});
|
|
||||||
|
|
||||||
setInterval(() => {
|
emitter.on("mouseDraw", render);
|
||||||
if (socket.readyState !== socket.OPEN) {
|
emitter.on("netDraw", render);
|
||||||
socket = new WebSocket(socketUrl);
|
|
||||||
}
|
socket.addEventListener("message", (event) => {
|
||||||
socket.send("ping");
|
if (event.data === "ping" || event.data.startsWith("pong")) return;
|
||||||
}, 10000);
|
emitter.emit("netDraw", deserializeDraw(event.data));
|
||||||
|
});
|
||||||
|
|
||||||
const resizeObserver = new ResizeObserver((entries) => {
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
const entry = entries.at(0);
|
const entry = entries.at(0);
|
||||||
|
|||||||
7
js/ws.js
Normal file
7
js/ws.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
"use strict";
|
||||||
|
const getSocket = (url) => {
|
||||||
|
let ws = new WebSocket(url);
|
||||||
|
ws.onclose = () => setTimeout(() => (ws = getSocket(url)), 100);
|
||||||
|
ws.onerror = () => ws.close();
|
||||||
|
return ws;
|
||||||
|
};
|
||||||
8
js/ws.ts
Normal file
8
js/ws.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
const getSocket = (url: string): WebSocket => {
|
||||||
|
let ws = new WebSocket(url);
|
||||||
|
|
||||||
|
ws.onclose = () => setTimeout(() => (ws = getSocket(url)), 100);
|
||||||
|
ws.onerror = () => ws.close();
|
||||||
|
|
||||||
|
return ws;
|
||||||
|
};
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
_ "modernc.org/sqlite" // Import the SQLite driver
|
|
||||||
)
|
|
||||||
|
|
||||||
func initDb() *sql.DB {
|
|
||||||
|
|
||||||
db, err := sql.Open("sqlite", "./data/data.db?_pragma=journal_mode(WAL)&_pragma=synchronous(NORMAL)&_pragma=foreign_keys(ON)")
|
|
||||||
if err != nil {
|
|
||||||
e.Logger.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a table
|
|
||||||
sqlStmt := `
|
|
||||||
CREATE TABLE IF NOT EXISTS boxes (id INTEGER PRIMARY KEY, value BOOLEAN);
|
|
||||||
`
|
|
||||||
|
|
||||||
_, err = db.Exec(sqlStmt)
|
|
||||||
if err != nil {
|
|
||||||
e.Logger.Fatalf("%q: %s\n", err, sqlStmt)
|
|
||||||
}
|
|
||||||
|
|
||||||
return db
|
|
||||||
}
|
|
||||||
|
|
||||||
func getBoxes() map[int]bool {
|
|
||||||
|
|
||||||
rows, err := db.Query("SELECT id, value FROM boxes")
|
|
||||||
if err != nil {
|
|
||||||
e.Logger.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
boxes := make(map[int]bool, 1000)
|
|
||||||
for i := 0; i < 1000; i++ {
|
|
||||||
boxes[i] = false
|
|
||||||
}
|
|
||||||
for rows.Next() {
|
|
||||||
var id int
|
|
||||||
var value bool
|
|
||||||
err = rows.Scan(&id, &value)
|
|
||||||
if err != nil {
|
|
||||||
e.Logger.Fatal(err)
|
|
||||||
}
|
|
||||||
boxes[id] = value
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
return boxes
|
|
||||||
}
|
|
||||||
|
|
||||||
func setBox(id int, value bool) {
|
|
||||||
e.Logger.Info("START INSERT")
|
|
||||||
|
|
||||||
stmt, err := db.Prepare("INSERT OR REPLACE INTO boxes(id, value) VALUES(?, ?)")
|
|
||||||
if err != nil {
|
|
||||||
e.Logger.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Logger.Info("PREPARED")
|
|
||||||
|
|
||||||
res, err := stmt.Exec(id, value)
|
|
||||||
if err != nil {
|
|
||||||
e.Logger.Fatal(err)
|
|
||||||
}
|
|
||||||
stmt.Close()
|
|
||||||
|
|
||||||
e.Logger.Info("INSERTED")
|
|
||||||
|
|
||||||
e.Logger.Info(res)
|
|
||||||
}
|
|
||||||
102
src/boxes.go
102
src/boxes.go
@@ -1,102 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
"github.com/labstack/gommon/log"
|
|
||||||
"golang.org/x/net/websocket"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
wsConnections = make(map[*websocket.Conn]bool)
|
|
||||||
wsMutex sync.Mutex
|
|
||||||
db = initDb()
|
|
||||||
)
|
|
||||||
|
|
||||||
func handleWsError(err error, conn *websocket.Conn) {
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to handle websocket: %v", err)
|
|
||||||
conn.Close()
|
|
||||||
wsMutex.Lock()
|
|
||||||
defer wsMutex.Unlock()
|
|
||||||
delete(wsConnections, conn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func broadcastMessage(message string) {
|
|
||||||
wsMutex.Lock()
|
|
||||||
defer wsMutex.Unlock()
|
|
||||||
|
|
||||||
for conn := range wsConnections {
|
|
||||||
err := websocket.Message.Send(conn, message)
|
|
||||||
handleWsError(err, conn)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatMessage(index int, checked bool) string {
|
|
||||||
message := "u:" + strconv.Itoa(index)
|
|
||||||
if checked {
|
|
||||||
message += ":+"
|
|
||||||
} else {
|
|
||||||
message += ":-"
|
|
||||||
}
|
|
||||||
return message
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleMessage(msg string, ws *websocket.Conn) error {
|
|
||||||
if strings.Contains(msg, "u:") {
|
|
||||||
parts := strings.Split(msg, ":")
|
|
||||||
index, err := strconv.Atoi(parts[1])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
checked := parts[2] == "+"
|
|
||||||
|
|
||||||
message := formatMessage(index, checked)
|
|
||||||
broadcastMessage(message)
|
|
||||||
|
|
||||||
setBox(index, checked)
|
|
||||||
}
|
|
||||||
if strings.Contains(msg, "ping") {
|
|
||||||
len := len(wsConnections)
|
|
||||||
websocket.Message.Send(ws, "pong-"+strconv.Itoa(len))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func initWs(c echo.Context) error {
|
|
||||||
websocket.Handler(func(ws *websocket.Conn) {
|
|
||||||
wsMutex.Lock()
|
|
||||||
wsConnections[ws] = true
|
|
||||||
wsMutex.Unlock()
|
|
||||||
|
|
||||||
boxes := getBoxes()
|
|
||||||
|
|
||||||
initialState := "i:"
|
|
||||||
for index, checked := range boxes {
|
|
||||||
if checked {
|
|
||||||
initialState += strconv.Itoa(index) + ";"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err := websocket.Message.Send(ws, initialState)
|
|
||||||
handleWsError(err, ws)
|
|
||||||
|
|
||||||
for {
|
|
||||||
var msg string
|
|
||||||
err := websocket.Message.Receive(ws, &msg)
|
|
||||||
handleWsError(err, ws)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
handleMessage(msg, ws)
|
|
||||||
log.Infof("Received message: %s", msg)
|
|
||||||
}
|
|
||||||
}).ServeHTTP(c.Response(), c.Request())
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
87
src/boxes/box-db.go
Normal file
87
src/boxes/box-db.go
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
package boxes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/labstack/gommon/log"
|
||||||
|
|
||||||
|
_ "modernc.org/sqlite" // Import the SQLite driver
|
||||||
|
)
|
||||||
|
|
||||||
|
func initDb() *sql.DB {
|
||||||
|
|
||||||
|
db, err := sql.Open("sqlite", "./data/data.db?_pragma=journal_mode(WAL)&_pragma=synchronous(NORMAL)&_pragma=foreign_keys(ON)")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a table
|
||||||
|
sqlStmt := `CREATE TABLE IF NOT EXISTS boxes (id INTEGER PRIMARY KEY, value BOOLEAN);`
|
||||||
|
|
||||||
|
_, err = db.Exec(sqlStmt)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("%q: %s\n", err, sqlStmt)
|
||||||
|
}
|
||||||
|
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBoxes() []Box {
|
||||||
|
rows, err := db.Query("SELECT id, value FROM boxes")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
boxes := make([]Box, 1024)
|
||||||
|
for i := 0; i < 1024; i++ {
|
||||||
|
boxes[i] = Box{i, false}
|
||||||
|
}
|
||||||
|
for rows.Next() {
|
||||||
|
var id int
|
||||||
|
var value bool
|
||||||
|
err = rows.Scan(&id, &value)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
boxes[id].Value = value
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
return boxes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (box Box) persist() {
|
||||||
|
|
||||||
|
stmt, err := db.Prepare("INSERT OR REPLACE INTO boxes(id, value) VALUES(?, ?)")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := stmt.Exec(box.Id, box.Value)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
stmt.Close()
|
||||||
|
|
||||||
|
log.Info(res.LastInsertId())
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBox(id int) (Box, error) {
|
||||||
|
var value bool
|
||||||
|
|
||||||
|
stmt, err := db.Prepare("SELECT value FROM boxes WHERE id = ?")
|
||||||
|
if err != nil {
|
||||||
|
return Box{}, err
|
||||||
|
}
|
||||||
|
defer stmt.Close()
|
||||||
|
|
||||||
|
err = stmt.QueryRow(id).Scan(&value)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return Box{}, nil
|
||||||
|
}
|
||||||
|
return Box{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return Box{id, value}, nil
|
||||||
|
}
|
||||||
152
src/boxes/boxes.go
Normal file
152
src/boxes/boxes.go
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
package boxes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/labstack/gommon/log"
|
||||||
|
"golang.org/x/net/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
wsConnections = make(map[*websocket.Conn]bool)
|
||||||
|
wsMutex sync.Mutex
|
||||||
|
db = initDb()
|
||||||
|
random = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
)
|
||||||
|
|
||||||
|
type Box struct {
|
||||||
|
Id int
|
||||||
|
Value bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func deserializeBox(msg string) (Box, error) {
|
||||||
|
parts := strings.Split(msg, ":")
|
||||||
|
index, err := strconv.Atoi(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return Box{}, err
|
||||||
|
}
|
||||||
|
checked := parts[2] == "+"
|
||||||
|
return Box{index, checked}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (box Box) serialize() string {
|
||||||
|
message := "b:" + strconv.Itoa(box.Id)
|
||||||
|
if box.Value {
|
||||||
|
message += ":+"
|
||||||
|
} else {
|
||||||
|
message += ":-"
|
||||||
|
}
|
||||||
|
return message + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleWsError(err error, conn *websocket.Conn) {
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to handle websocket: %v", err)
|
||||||
|
conn.Close()
|
||||||
|
wsMutex.Lock()
|
||||||
|
defer wsMutex.Unlock()
|
||||||
|
delete(wsConnections, conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func broadcastMessage(message string) {
|
||||||
|
wsMutex.Lock()
|
||||||
|
defer wsMutex.Unlock()
|
||||||
|
|
||||||
|
for conn := range wsConnections {
|
||||||
|
err := websocket.Message.Send(conn, message)
|
||||||
|
handleWsError(err, conn)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleMessage(msg string, ws *websocket.Conn) error {
|
||||||
|
if msg == "gol" {
|
||||||
|
matrix := make(map[string]Box)
|
||||||
|
boxes := GetBoxes()
|
||||||
|
index := 0
|
||||||
|
for i := 0; i < 32; i++ {
|
||||||
|
for j := 0; j < 32; j++ {
|
||||||
|
matrix[fmt.Sprintf("%d-%d", i, j)] = boxes[index]
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Error(matrix)
|
||||||
|
nextGen := make([]Box, 0)
|
||||||
|
for id, item := range matrix {
|
||||||
|
nextItem := shouldBeAlive(matrix, id)
|
||||||
|
if nextItem.Value != item.Value {
|
||||||
|
nextGen = append(nextGen, shouldBeAlive(matrix, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
nextMessage := ""
|
||||||
|
for _, item := range nextGen {
|
||||||
|
item.persist()
|
||||||
|
nextMessage += item.serialize()
|
||||||
|
}
|
||||||
|
broadcastMessage(nextMessage)
|
||||||
|
|
||||||
|
} else if msg == "random" {
|
||||||
|
index := random.Int() % 1000
|
||||||
|
box, err := getBox(index)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
box.Value = !box.Value
|
||||||
|
box.persist()
|
||||||
|
|
||||||
|
message := box.serialize()
|
||||||
|
broadcastMessage(message)
|
||||||
|
|
||||||
|
} else if strings.Contains(msg, "b:") {
|
||||||
|
box, err := deserializeBox(msg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcastMessage(msg)
|
||||||
|
box.persist()
|
||||||
|
} else if strings.Contains(msg, "ping") {
|
||||||
|
len := len(wsConnections)
|
||||||
|
websocket.Message.Send(ws, "pong-"+strconv.Itoa(len))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitWs(c echo.Context) error {
|
||||||
|
websocket.Handler(func(ws *websocket.Conn) {
|
||||||
|
wsMutex.Lock()
|
||||||
|
wsConnections[ws] = true
|
||||||
|
wsMutex.Unlock()
|
||||||
|
|
||||||
|
boxes := GetBoxes()
|
||||||
|
msg := ""
|
||||||
|
for _, box := range boxes {
|
||||||
|
msg += box.serialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
err := websocket.Message.Send(ws, msg)
|
||||||
|
handleWsError(err, ws)
|
||||||
|
|
||||||
|
for {
|
||||||
|
var msg string
|
||||||
|
err := websocket.Message.Receive(ws, &msg)
|
||||||
|
handleWsError(err, ws)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
handleMessage(msg, ws)
|
||||||
|
log.Infof("Received message: %s", msg)
|
||||||
|
}
|
||||||
|
}).ServeHTTP(c.Response(), c.Request())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
70
src/boxes/matrix.go
Normal file
70
src/boxes/matrix.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package boxes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func shouldBeAlive(matrix map[string]Box, key string) Box {
|
||||||
|
parts := strings.Split(key, "-")
|
||||||
|
i, _ := strconv.Atoi(parts[0])
|
||||||
|
j, _ := strconv.Atoi(parts[1])
|
||||||
|
|
||||||
|
self := matrix[key]
|
||||||
|
nextGen := Box{self.Id, self.Value}
|
||||||
|
|
||||||
|
count := getCount(matrix, i, j)
|
||||||
|
if self.Value && count < 2 {
|
||||||
|
nextGen.Value = false
|
||||||
|
} else if self.Value && count > 3 {
|
||||||
|
nextGen.Value = false
|
||||||
|
} else if !self.Value && count == 3 {
|
||||||
|
nextGen.Value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextGen
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCount(matrix map[string]Box, i, j int) int {
|
||||||
|
count := 0
|
||||||
|
|
||||||
|
if isAlive(matrix, i-1, j-1) {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
if isAlive(matrix, i-1, j) {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
if isAlive(matrix, i-1, j+1) {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
if isAlive(matrix, i, j-1) {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
if isAlive(matrix, i, j+1) {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
if isAlive(matrix, i+1, j-1) {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
if isAlive(matrix, i+1, j) {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
if isAlive(matrix, i+1, j+1) {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAlive(matrix map[string]Box, i, j int) bool {
|
||||||
|
item, ok := matrix[fmt.Sprintf("%d-%d", i, j)]
|
||||||
|
return ok && item.Value
|
||||||
|
}
|
||||||
@@ -30,9 +30,9 @@ func broadcastMessage(message string, ws *websocket.Conn) {
|
|||||||
defer wsMutex.Unlock()
|
defer wsMutex.Unlock()
|
||||||
|
|
||||||
for conn := range wsConnections {
|
for conn := range wsConnections {
|
||||||
// if ws == conn {
|
if ws == conn {
|
||||||
// continue
|
continue
|
||||||
// }
|
}
|
||||||
err := websocket.Message.Send(conn, message)
|
err := websocket.Message.Send(conn, message)
|
||||||
handleWsError(err, conn)
|
handleWsError(err, conn)
|
||||||
|
|
||||||
|
|||||||
13
src/main.go
13
src/main.go
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/labstack/echo/v4/middleware"
|
"github.com/labstack/echo/v4/middleware"
|
||||||
"github.com/labstack/gommon/log"
|
"github.com/labstack/gommon/log"
|
||||||
|
"knet.sk/src/boxes"
|
||||||
"knet.sk/src/draw"
|
"knet.sk/src/draw"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -25,10 +26,10 @@ func NewTemplates() *Templates {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Page struct {
|
type Page struct {
|
||||||
Boxes map[int]bool
|
Boxes []boxes.Box
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPage(boxes map[int]bool) Page {
|
func newPage(boxes []boxes.Box) Page {
|
||||||
return Page{
|
return Page{
|
||||||
Boxes: boxes,
|
Boxes: boxes,
|
||||||
}
|
}
|
||||||
@@ -50,20 +51,16 @@ func main() {
|
|||||||
e.Static("/js", "js")
|
e.Static("/js", "js")
|
||||||
|
|
||||||
e.GET("/", func(c echo.Context) error {
|
e.GET("/", func(c echo.Context) error {
|
||||||
return c.Render(200, "index", newPage(getBoxes()))
|
return c.Render(200, "index", newPage(boxes.GetBoxes()))
|
||||||
})
|
})
|
||||||
|
e.GET("/boxes/ws", boxes.InitWs)
|
||||||
|
|
||||||
e.GET("/ws", initWs)
|
|
||||||
|
|
||||||
//favicon
|
|
||||||
e.File("/favicon.ico", "images/favicon.ico")
|
e.File("/favicon.ico", "images/favicon.ico")
|
||||||
|
|
||||||
e.GET("/draw", draw.Page)
|
e.GET("/draw", draw.Page)
|
||||||
e.GET("/draw/ws", draw.InitWs)
|
e.GET("/draw/ws", draw.InitWs)
|
||||||
|
|
||||||
e.Logger.Fatal(e.Start(":54321"))
|
e.Logger.Fatal(e.Start(":54321"))
|
||||||
|
|
||||||
defer db.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func healthCheck(c echo.Context) error {
|
func healthCheck(c echo.Context) error {
|
||||||
|
|||||||
@@ -3,19 +3,27 @@
|
|||||||
<div class="boxes-container">
|
<div class="boxes-container">
|
||||||
<label>
|
<label>
|
||||||
Auto play boxes
|
Auto play boxes
|
||||||
<input type="checkbox" id="auto-boxes"/>
|
<input type="checkbox" id="auto-boxes" />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Auto play Game of Life
|
||||||
|
<input type="checkbox" id="game-of-life" />
|
||||||
</label>
|
</label>
|
||||||
<div class="boxes">
|
<div class="boxes">
|
||||||
{{range $index, $value := .}}<label
|
{{range .}}<label
|
||||||
><input
|
><input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id="box-{{$index}}"
|
id="box-{{.Id}}"
|
||||||
name="checked"
|
name="checked"
|
||||||
{{if
|
{{if
|
||||||
$value}}checked{{end}} /></label
|
.Value
|
||||||
|
}}
|
||||||
|
checked="true"
|
||||||
|
{{end}} /></label
|
||||||
>{{end}}
|
>{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script src="js/ws.js"></script>
|
||||||
<script src="js/boxes.js"></script>
|
<script src="js/boxes.js"></script>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
"
|
"
|
||||||
></div>
|
></div>
|
||||||
|
<script src="js/ws.js"></script>
|
||||||
<script src="/js/mitt/mitt.js"></script>
|
<script src="/js/mitt/mitt.js"></script>
|
||||||
<script src="/js/draw.js"></script>
|
<script src="/js/draw.js"></script>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
Reference in New Issue
Block a user