game Of Life

This commit is contained in:
JurajKubrican
2025-03-04 16:31:19 +01:00
parent 7ed413648c
commit 00c209032f
16 changed files with 540 additions and 346 deletions

View File

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

View File

@@ -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(";"); };
};
const handleInstruction = (instruction) => {
if (instruction.startsWith("b:")) {
const items = instruction.split(";");
items.forEach((i) => { items.forEach((i) => {
const parts = event.data.split(":"); const box = deserializeBox(i);
document.getElementById("box-" + parts[1]).checked = parts[2] === "+"; const el = document.getElementById("box-" + box.id);
console.log("box-" + parts[1]); el.checked = box.value;
// console.log(i,box,el)
}); });
return; return;
} }
};
if (event.data.startsWith("i:")) { socket.addEventListener("message", (event) => {
const str = event.data.split("i:")[1]; const instructions = event.data.split("\n");
const items = str.split(";"); instructions.forEach(handleInstruction);
if (items.length === 0) {
return;
}
document.querySelectorAll("input").forEach((input) => {
input.checked = false;
}); });
items.forEach((i) => { document.querySelectorAll(".boxes input").forEach((input) => {
if (!i) {
return;
}
console.log(i, document.getElementById("box-" + i));
document.getElementById("box-" + i).checked = true;
});
return;
}
});
setInterval(() => {
socket.send("ping");
}, 10000);
document.querySelectorAll(".boxes input").forEach((input) => {
input.addEventListener("change", (event) => { input.addEventListener("change", (event) => {
if (socket.readyState !== socket.OPEN) { const target = event.target;
socket = new WebSocket(socketUrl); const id = target?.id.split("-")[1];
} const value = target.checked ? "+" : "-";
const id = event.target.id.split("-")[1]; socket.send("b:" + id + ":" + value);
const value = event.target.checked ? "+" : "-";
socket.send("u:" + id + ":" + value);
}); });
}); });
var autoPlayTimer = undefined;
var timer = null const handleAutoPlay = (el) => {
document.querySelector("#auto-boxes").addEventListener("change",(e)=>{ if (el.checked) {
if (e.target.checked){ autoPlayTimer = setInterval(() => {
timer = setInterval(()=>{ socket.send("random");
var id = Math.round(Math.random() * 1000) }, 50);
const value = document.querySelector('#box-'+id).checked ? "-" : "+";
if (socket.readyState !== socket.OPEN) {
socket = new WebSocket(socketUrl);
} }
socket.send("u:" + id + ":" + value); else {
},500) clearInterval(autoPlayTimer);
}else{
clearInterval(timer)
} }
}) };
const autoPlayEl = document.querySelector("#auto-boxes");
handleAutoPlay(autoPlayEl);
autoPlayEl?.addEventListener("change", (e) => handleAutoPlay(e.target));
var golTimer = undefined;
const handleGol = (el) => {
if (el.checked) {
golTimer = setInterval(() => {
socket.send("gol");
}, 500);
}
else {
clearInterval(golTimer);
}
};
const golEl = document.querySelector("#game-of-life");
handleGol(golEl);
golEl.addEventListener("change", (e) => handleGol(e.target));
})();

80
js/boxes.ts Normal file
View 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));
})();

View File

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

View File

@@ -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) => {
const { from, to } = data;
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); };
});
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
View 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
View 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;
};

View File

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

View File

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

View File

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

View File

@@ -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 {

View File

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

View File

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