auto global game of life

This commit is contained in:
JurajKubrican
2025-08-04 16:28:42 +02:00
parent a393b03d98
commit b13c8c4f14
7 changed files with 147 additions and 68 deletions

View File

@@ -4,7 +4,45 @@
}
.boxes input{
margin: 0;
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
width: 20px;
height: 20px;
background: transparent;
border: none;
position: relative;
cursor: pointer;
}
.boxes input[type="checkbox"]:checked {
background: transparent;
}
.boxes input[type="checkbox"]:checked::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 1px;
background: #4a7c59;
transform-origin: 0 0;
transform: rotate(45deg) scaleX(1.414);
}
.boxes input[type="checkbox"]:checked::after {
content: '';
position: absolute;
top: 0;
right: 0;
width: 100%;
height: 1px;
background: #4a7c59;
transform-origin: 100% 0;
transform: rotate(-45deg) scaleX(1.414);
}
.boxes{
display: grid;
grid-template-columns: repeat(32, 1fr); /* 32 equal columns */

View File

@@ -33,22 +33,22 @@
socket.send("b:" + id + ":" + value);
});
});
const autoPlayEl = document.querySelector("#randomize");
autoPlayEl?.addEventListener("click", (e) => socket.send("r:1000"));
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));
// const autoPlayEl = document.querySelector("#randomize");
// autoPlayEl?.addEventListener("click", (e) => socket.send("r:1000"));
// 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));
const container = document.querySelector('.boxes');
const resizeObserver = new ResizeObserver((entries) => {
const entry = entries.at(0);

View File

@@ -7,8 +7,9 @@ import (
)
var (
random = rand.New(rand.NewSource(time.Now().UnixNano()))
store = initStore()
random = rand.New(rand.NewSource(time.Now().UnixNano()))
store = initStore()
repeatStore = make([]map[int]bool, 0)
)
func initStore() []Box {
@@ -33,6 +34,5 @@ func (box Box) persist() {
func getBox(id int) Box {
box := store[id]
return box
}

18
src/boxes/box-ticker.go Normal file
View File

@@ -0,0 +1,18 @@
package boxes
import (
"time"
)
var (
ticker = time.Tick(time.Millisecond * 200)
)
func RegisterTicker() {
for range ticker {
if len(wsConnections) > 0 {
GameOfLifeTick()
// fmt.Print("g")
}
}
}

View File

@@ -61,7 +61,7 @@ func broadcastMessage(message string) {
}
}
func randomizeBoxes(count int) []Box {
func RandomizeBoxes(count int) []Box {
boxes := make([]Box, count)
for i := 0; i < count; i++ {
index := random.Int() % 1000
@@ -74,47 +74,76 @@ func randomizeBoxes(count int) []Box {
}
func EqualRuns(a, b map[int]bool) bool {
for key, v := range a {
if v != b[key] {
return false
}
}
return true
}
func getMap(a []Box) map[int]bool {
res := make(map[int]bool, 0)
for _, val := range a {
if val.Value {
res[val.Id] = val.Value
}
}
return res
}
func CheckRepeats() {
currentRun := getMap(store)
if len(repeatStore) < 2 {
repeatStore = append(repeatStore, currentRun)
return
}
for _, run := range repeatStore {
if EqualRuns(run, currentRun) {
RandomizeBoxes(30)
}
}
repeatStore = append(repeatStore, currentRun)
if len(repeatStore) > 100 {
repeatStore = repeatStore[1:]
}
}
func GameOfLifeTick() {
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++
}
}
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()
}
CheckRepeats()
broadcastMessage(nextMessage)
}
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++
}
}
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 strings.Contains(msg, "r:") {
count, err := strconv.Atoi(strings.Split(msg, ":")[1])
if err != nil {
log.Errorf("Failed to parse randomize count: %v", err)
}
boxes := randomizeBoxes(count)
message := ""
for _, box := range boxes {
message += box.serialize()
}
broadcastMessage(message)
} else if strings.Contains(msg, "b:") {
if strings.Contains(msg, "b:") {
box, err := deserializeBox(msg)
if err != nil {
return err

View File

@@ -66,6 +66,8 @@ func main() {
e.GET("/draw", draw.Page)
e.GET("/draw/ws", draw.InitWs)
go boxes.RegisterTicker()
e.Logger.Fatal(e.Start(":54321"))
}

View File

@@ -1,14 +1,6 @@
{{block "boxes" .}}
<link rel="stylesheet" href="/css/boxes.css?v={{.BuildNumber}}" />
<div class="boxes-container">
<label>
<Button id="randomize">Randomize</Button>
</label>
<label>
Auto play Game of Life
<input type="checkbox" id="game-of-life" />
</label>
<div class="boxes">
{{range .Boxes}}<input
type="checkbox"