diff --git a/Makefile b/Makefile
deleted file mode 100644
index 02f6ec1..0000000
--- a/Makefile
+++ /dev/null
@@ -1,17 +0,0 @@
-.PHONY all:
-all: build
-
-.PHONY: build
-build:
- make -C src
- mv src/golsky .
-
-.PHONY: clean
-clean:
- make -C src clean
- rm -f dump* rect*
-
-.PHONY: profile
-profile: build
- ./golsky -W 1500 -H 1500 -d --profile-file cpu.profile
- go tool pprof --http localhost:8888 golsky cpu.profile
diff --git a/README.md b/README.md
index 6d2274c..b73ef9b 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,13 @@
+[](https://github.com/tlinden/golsky/blob/master/LICENSE)
+[](https://goreportcard.com/report/github.com/tlinden/golsky)
+
# golsky - Conway's game of life written in GO
+> [!IMPORTANT]
+> This software is now being maintained on [Codeberg](https://codeberg.org/scip/golsky/).
+

-[](https://github.com/tlinden/golsky/blob/master/LICENSE)
-[](https://goreportcard.com/report/github.com/tlinden/golsky)
I wanted to play around a little bit with [**Conways Game of Life**](https://conwaylife.com/)
in golang and here's the result. It's a simple game using
diff --git a/TODO.md b/TODO.md
deleted file mode 100644
index aca5140..0000000
--- a/TODO.md
+++ /dev/null
@@ -1,51 +0,0 @@
-- add all other options like size etc
-- add gif export
-- add toolbar (not working yet, see branch trackui)
-- only draw visible part of the world
-- print current mode to the bottom like pause, insert and mark
-- add https://www.ibiblio.org/lifepatterns/october1970.html
-- history: dont count age but do calc to get index to age tile based on cell age
-- maybe pre calc neighbors as 8 slice of pointers to neighboring cells to faster do the count
- see various-tests/perf-2dim-pointers/: it's NOT faster :(
-- use an array of 8 pointers to neighbors. on edge just add either fake dead neighbors or the wrap around neighbors.
-- try arche ecs variant with either a component of the cells neighbors or using relations.
-- https://mattnakama.com/blog/go-branchless-coding/
-- add performance measurements, see:
- DrawTriangles: https://github.com/TLINDEN/testgol
- WritePixels: https://github.com/TLINDEN/testgol/tree/wrpixels
-https://www.tasnimzotder.com/blog/optimizing-game-of-life-algorithm
-- pre-draw the grid separately to a cache grid image, then during
- rendering, first draw the dead background, then the life cells, and
- lastly the grid - if enabled. If disabled, there's be no gap between
- the cells anymore.
-- Speed
- https://conwaylife.com/forums/viewtopic.php?f=7&t=3237
- Look at try-pointers-and-cells branch, we're using pre-calculated
- neighbor list of pointers to cells, but it's only a liiiiitle bit
- better :(
-
-
-- Patterns:
-
-A Catagolue textcensus of, say, period-2 oscillators from
-non-symmetrical soups can be found at
-
-https://catagolue.hatsya.com/textcensus/b3s23/C1/xp2
-
-The URL is made by just adding the prefix "text" to the word "census",
-in any URL linked to from a Catagolue census page such as this one:
-
-https://catagolue.hatsya.com/census/b3s23/C1
-
-Format:
-https://conwaylife.com/wiki/Apgcode
-
-
-Collections:
-
-https://conwaylife.com/wiki/Pattern_of_the_Year
-https://www.ibiblio.org/lifepatterns/
-https://entropymine.com/jason/life/
-https://github.com/Matthias-Merzenich/jslife-moving
-https://conwaylife.com/ref/mniemiec/lifepage.htm
-https://conwaylife.com/wiki/Spaceship ff.
diff --git a/go.mod b/go.mod
deleted file mode 100644
index 3e54e3a..0000000
--- a/go.mod
+++ /dev/null
@@ -1,25 +0,0 @@
-module github.com/tlinden/golsky
-
-go 1.22
-
-require (
- github.com/hajimehoshi/ebiten/v2 v2.7.4
- github.com/spf13/pflag v1.0.5
- golang.org/x/image v0.16.0
-)
-
-require (
- github.com/alecthomas/repr v0.4.0 // indirect
- github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 // indirect
- github.com/ebitengine/hideconsole v1.0.0 // indirect
- github.com/ebitengine/purego v0.7.0 // indirect
- github.com/ebitenui/ebitenui v0.5.8-0.20240608175527-424f62327b21 // indirect
- github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
- github.com/jezek/xgb v1.1.1 // indirect
- github.com/mlange-42/arche v0.13.0 // indirect
- github.com/tinne26/etxt v0.0.8 // indirect
- golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
- golang.org/x/sync v0.7.0 // indirect
- golang.org/x/sys v0.20.0 // indirect
- golang.org/x/text v0.15.0 // indirect
-)
diff --git a/go.sum b/go.sum
deleted file mode 100644
index 46f6477..0000000
--- a/go.sum
+++ /dev/null
@@ -1,36 +0,0 @@
-github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
-github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
-github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 h1:48bCqKTuD7Z0UovDfvpCn7wZ0GUZ+yosIteNDthn3FU=
-github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895/go.mod h1:XZdLv05c5hOZm3fM2NlJ92FyEZjnslcMcNRrhxs8+8M=
-github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE=
-github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A=
-github.com/ebitengine/purego v0.7.0 h1:HPZpl61edMGCEW6XK2nsR6+7AnJ3unUxpTZBkkIXnMc=
-github.com/ebitengine/purego v0.7.0/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
-github.com/ebitenui/ebitenui v0.5.6 h1:qyJRU5j+lQo1lamxB48IBwMxMfz1xNb5iWUayCtA0Wk=
-github.com/ebitenui/ebitenui v0.5.6/go.mod h1:I0rVbTOUi7gWKTPet2gzbvhOdkHp5pJXMM6c6b3dRoE=
-github.com/ebitenui/ebitenui v0.5.8-0.20240608175527-424f62327b21 h1:dElhYGyf+FYY+makAndUQNOSDwFSFYyFWziPwQrPObY=
-github.com/ebitenui/ebitenui v0.5.8-0.20240608175527-424f62327b21/go.mod h1:I0rVbTOUi7gWKTPet2gzbvhOdkHp5pJXMM6c6b3dRoE=
-github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
-github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
-github.com/hajimehoshi/ebiten/v2 v2.7.4 h1:X+heODRQ3Ie9F9QFjm24gEZqQd5FSfR9XuT2XfHwgf8=
-github.com/hajimehoshi/ebiten/v2 v2.7.4/go.mod h1:H2pHVgq29rfm5yeQ7jzWOM3VHsjo7/AyucODNLOhsVY=
-github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4=
-github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
-github.com/mlange-42/arche v0.13.0 h1:ef0fu9qC2KIr8wIlVs+CgeQ5CSUJ8A1Hut6nXYdf+xk=
-github.com/mlange-42/arche v0.13.0/go.mod h1:bFktKnvGDj2kP01xar79z0hKwGHdnoaEZR8HWmJkIyU=
-github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
-github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/tinne26/etxt v0.0.8 h1:rjb58jkMkapRGLmhBMWnT76E/nMTXC5P1Q956BRZkoc=
-github.com/tinne26/etxt v0.0.8/go.mod h1:QM/hlNkstsKC39elTFNKAR34xsMb9QoVosf+g9wlYxM=
-github.com/tinne26/etxt v0.0.9-alpha.6.0.20240409152929-91bfc562becc h1:+USGSXbkrRAy6bz3Qm4GUczhqeXe7XlRfkRexCSFxkw=
-github.com/tinne26/etxt v0.0.9-alpha.6.0.20240409152929-91bfc562becc/go.mod h1:Icbd4bDjrXag1oYIhB51CrkMYqRb7YMv0AsrOSfNKfU=
-golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
-golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
-golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw=
-golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs=
-golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
-golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
-golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
-golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
-golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
diff --git a/mkrel.sh b/mkrel.sh
deleted file mode 100755
index f88a0a7..0000000
--- a/mkrel.sh
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/bin/bash
-
-# Copyright © 2024 Thomas von Dein
-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-
-# get list with: go tool dist list
-DIST="linux/amd64
-windows/amd64 "
-
-tool="$1"
-version="$2"
-
-if test -z "$version"; then
- echo "Usage: $0 "
- exit 1
-fi
-
-rm -rf releases
-mkdir -p releases
-
-
-for D in $DIST; do
- os=${D/\/*/}
- arch=${D/*\//}
- binfile="releases/${tool}-${os}-${arch}-${version}"
-
- if test "$os" = "windows"; then
- binfile="${binfile}.exe"
- fi
-
- tardir="${tool}-${os}-${arch}-${version}"
- tarfile="releases/${tool}-${os}-${arch}-${version}.tar.gz"
- set -x
- GOOS=${os} GOARCH=${arch} go build -o ${binfile}
- set +x
- continue
-
- mkdir -p ${tardir}
- cp ${binfile} README.md LICENSE ${tardir}/
- echo 'tool = gfn
-PREFIX = /usr/local
-UID = root
-GID = 0
-
-install:
- install -d -o $(UID) -g $(GID) $(PREFIX)/bin
- install -d -o $(UID) -g $(GID) $(PREFIX)/man/man1
- install -o $(UID) -g $(GID) -m 555 $(tool) $(PREFIX)/sbin/
- install -o $(UID) -g $(GID) -m 444 $(tool).1 $(PREFIX)/man/man1/' > ${tardir}/Makefile
- tar cpzf ${tarfile} ${tardir}
- sha256sum ${binfile} | cut -d' ' -f1 > ${binfile}.sha256
- sha256sum ${tarfile} | cut -d' ' -f1 > ${tarfile}.sha256
- rm -rf ${tardir}
- set +x
-done
-
diff --git a/rle/pattern_parser.go b/rle/pattern_parser.go
deleted file mode 100644
index fb80090..0000000
--- a/rle/pattern_parser.go
+++ /dev/null
@@ -1,159 +0,0 @@
-package rle
-
-import (
- "strconv"
-)
-
-type TokenType string
-
-type Token struct {
- Type TokenType
- Literal string
-}
-
-const (
- RUN_COUNT = "RUN_COUNT"
- DEAD_CELL = "DEAD_CELL"
- ALIVE_CELL = "ALIVE_CELL"
- EOL = "EOL"
- EOP = "EOP"
-)
-
-type Lexer struct {
- input string
- position int
- readPosition int
- char byte
-}
-
-func NewLexer(input string) *Lexer {
- l := &Lexer{input: input}
- l.readChar()
- return l
-}
-
-func (l *Lexer) NextToken() Token {
- var tok Token
-
- l.skipWhitespace()
-
- switch l.char {
- case '$':
- tok = newToken(EOL, l.char)
- case '!':
- tok = newToken(EOP, l.char)
- case 'b':
- tok = newToken(DEAD_CELL, l.char)
- case 'o':
- tok = newToken(ALIVE_CELL, l.char)
- default:
- if isDigit(l.char) {
- tok.Type = RUN_COUNT
- tok.Literal = l.readNumber()
- return tok
- }
- }
-
- l.readChar()
- return tok
-}
-
-func newToken(tokenType TokenType, char byte) Token {
- return Token{Type: tokenType, Literal: string(char)}
-}
-
-type PatternParser struct {
- lexer *Lexer
- currentToken Token
- peekToken Token
-}
-
-func NewParser(lexer *Lexer) *PatternParser {
- p := &PatternParser{
- lexer: lexer,
- }
- p.nextToken()
- p.nextToken()
-
- return p
-}
-
-func (pp *PatternParser) ParsePattern(width, height int) [][]int {
- result := make([][]int, height)
-
- row := make([]int, width)
- var rowIndex int
- var colIndex int
- for {
- switch pp.currentToken.Type {
- case RUN_COUNT:
- count, _ := strconv.Atoi(pp.currentToken.Literal)
- for i := 0; i < count; i++ {
- switch pp.peekToken.Type {
- case ALIVE_CELL:
- row[rowIndex+i] = 1
- case DEAD_CELL:
- row[rowIndex+i] = 0
- case EOL:
- result[colIndex] = row
- row = make([]int, width)
- rowIndex = -1
- colIndex++
- }
- }
-
- if pp.peekToken.Type != EOL {
- rowIndex += count - 1
- }
- pp.nextToken()
- case ALIVE_CELL:
- row[rowIndex] = 1
- case DEAD_CELL:
- row[rowIndex] = 0
- case EOL:
- result[colIndex] = row
- row = make([]int, width)
- rowIndex = -1
- colIndex++
- case EOP:
- result[colIndex] = row
- return result
- }
- rowIndex++
- pp.nextToken()
- }
-}
-
-func (pp *PatternParser) nextToken() {
- pp.currentToken = pp.peekToken
- pp.peekToken = pp.lexer.NextToken()
-}
-
-func isDigit(char byte) bool {
- return '0' <= char && char <= '9'
-}
-
-func (l *Lexer) readChar() {
- if l.readPosition >= len(l.input) {
- l.char = 0
- } else {
- l.char = l.input[l.readPosition]
- }
- l.position = l.readPosition
- l.readPosition++
-}
-
-func (l *Lexer) readNumber() string {
- position := l.position
- for isDigit(l.char) {
- l.readChar()
- }
-
- return l.input[position:l.position]
-}
-
-func (l *Lexer) skipWhitespace() {
- for l.char == ' ' || l.char == '\t' || l.char == '\n' || l.char == '\r' {
- l.readChar()
- }
-}
diff --git a/rle/pattern_parser_test.go b/rle/pattern_parser_test.go
deleted file mode 100644
index 893e7f2..0000000
--- a/rle/pattern_parser_test.go
+++ /dev/null
@@ -1,163 +0,0 @@
-package rle
-
-import (
- "reflect"
- "testing"
-)
-
-func TestNextToken(t *testing.T) {
- input := "bo$2bo$3o!"
-
- tests := []struct {
- expectedType TokenType
- expectedLiteral string
- }{
- {DEAD_CELL, "b"},
- {ALIVE_CELL, "o"},
- {EOL, "$"},
- {RUN_COUNT, "2"},
- {DEAD_CELL, "b"},
- {ALIVE_CELL, "o"},
- {EOL, "$"},
- {RUN_COUNT, "3"},
- {ALIVE_CELL, "o"},
- {EOP, "!"},
- }
-
- l := NewLexer(input)
-
- for _, test := range tests {
- token := l.NextToken()
-
- if token.Type != test.expectedType {
- t.Errorf("Token typ not correct")
- }
-
- if token.Literal != test.expectedLiteral {
- t.Errorf("Literal not correct")
- }
- }
-}
-
-func TestParsePattern(t *testing.T) {
- tests := []struct {
- input string
- expected [][]int
- width int
- height int
- }{
- {
- input: "bo$2bo$3o!",
- expected: [][]int{
- {0, 1, 0},
- {0, 0, 1},
- {1, 1, 1},
- },
- width: 3,
- height: 3,
- },
- {
- input: `24bo$22bobo$12b2o6b2o12b2o$11bo3bo4b2o12b2o$2o8bo5bo3b2o$2o8bo3bob2o4b
- obo$10bo5bo7bo$11bo3bo$12b2o!`,
- expected: [][]int{
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1},
- {1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- },
- width: 36,
- height: 9,
- },
- {
- input: `20b2o$
- 20b2o4$
- 9b2o$
- 8bo2bo10b2o$
- 9b2o11bo$
- 22bo12bo$
- 23bo10bobo$
- 34bobo$
- 35bo7$
- 32bo2bo$
- 33b3o$
- 2o38b2o$
- 2o38b2o$
- 6b3o$
- 6bo2bo7$
- 6bo$
- 5bobo$
- 5bobo10bo$
- 6bo12bo$
- 19bo11b2o$
- 18b2o10bo2bo$
- 31b2o4$
- 20b2o$
- 20b2o!`,
- expected: [][]int{
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0},
- {1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1},
- {1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1},
- {0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- },
- width: 42,
- height: 42,
- },
- }
-
- for _, test := range tests {
- l := NewLexer(test.input)
- pp := NewParser(l)
- result := pp.ParsePattern(test.width, test.height)
-
- if !reflect.DeepEqual(result, test.expected) {
- t.Fatalf(
- "Patterns do not match.\nExpected: %v\nGot: %v",
- test.expected,
- result,
- )
- }
- }
-}
diff --git a/rle/rle.go b/rle/rle.go
deleted file mode 100644
index dbb931a..0000000
--- a/rle/rle.go
+++ /dev/null
@@ -1,195 +0,0 @@
-// original source: https://github.com/nhoffmann/life by N.Hoffmann 2020.
-package rle
-
-import (
- "fmt"
- "os"
- "regexp"
- "strconv"
- "strings"
-)
-
-type RLE struct {
- Rule string // rule
- Width int // x
- Height int // y
- Pattern [][]int // The actual pattern
-
- inputLines []string
- headerLineIndex int
- patternLineIndex int
-}
-
-// wrapper to load a RLE file
-func GetRLE(filename string) (*RLE, error) {
- if filename == "" {
- return nil, nil
- }
-
- content, err := os.ReadFile(filename)
- if err != nil {
- return nil, err
- }
-
- parsedRle, err := Parse(string(content))
- if err != nil {
- return nil, fmt.Errorf("failed to load RLE pattern file: %s", err)
- }
-
- return &parsedRle, nil
-}
-
-func Parse(input string) (RLE, error) {
- rle := RLE{
- inputLines: strings.Split(input, "\n"),
- }
-
- rle.partitionFile()
-
- err := rle.parseComments()
- if err != nil {
- return RLE{}, err
- }
- err = rle.parseHeader()
- if err != nil {
- return RLE{}, err
- }
- err = rle.parsePattern()
- if err != nil {
- return RLE{}, err
- }
-
- return rle, nil
-}
-
-func (rle *RLE) partitionFile() error {
- for index, line := range rle.inputLines {
- cleanLine := removeWhitespace(line)
- if strings.HasPrefix(cleanLine, "x=") {
- rle.headerLineIndex = index
- rle.patternLineIndex = index + 1
- return nil
- }
- }
-
- return fmt.Errorf("invalid input: Header is missing")
-}
-
-func (rle *RLE) parseComments() error {
- return nil
-}
-
-func (rle *RLE) parseHeader() (err error) {
- headerLine := removeWhitespace(rle.inputLines[rle.headerLineIndex])
-
- headerElements := strings.SplitN(headerLine, ",", 3)
-
- rle.Width, err = strconv.Atoi(strings.TrimPrefix(headerElements[0], "x="))
- if err != nil {
- return err
- }
- rle.Height, err = strconv.Atoi(strings.TrimPrefix(headerElements[1], "y="))
- if err != nil {
- return err
- }
-
- rle.Pattern = make([][]int, rle.Width)
-
- // check wehter a rule is present, since it's optional
- if len(headerElements) == 3 {
- rle.Rule = strings.TrimPrefix(headerElements[2], "rule=")
- }
-
- return nil
-}
-
-func (rle *RLE) parsePattern() error {
- patternString := strings.Join(rle.inputLines[rle.patternLineIndex:], "")
-
- l := NewLexer(patternString)
- pp := NewParser(l)
-
- rle.Pattern = pp.ParsePattern(rle.Width, rle.Height)
-
- return nil
-}
-
-func removeWhitespace(input string) string {
- re := regexp.MustCompile(` *\t*\r*\n*`)
- return re.ReplaceAllString(input, "")
-}
-
-// Store a grid to an RLE file
-func StoreGridToRLE(grid [][]uint8, filename, rule string, width, height int) error {
- fd, err := os.Create(filename)
- if err != nil {
- return err
- }
-
- var pattern string
-
- for y := 0; y < height; y++ {
- line := ""
- for x := 0; x < width; x++ {
- char := "b"
- if grid[y][x] == 1 {
- char = "o"
- }
-
- line += char
- }
-
- // if first row is: 001011110, then line is now:
- // bboboooob
-
- encoded := RunLengthEncode(line)
-
- // and now its: 2bob4ob
- pattern += encoded
-
- if y != height-1 {
- pattern += "$"
- }
- }
-
- pattern += "!"
-
- wrapped := ""
- for idx, char := range pattern {
- if idx%70 == 0 && idx != 0 {
- wrapped += "\n"
- }
- wrapped += string(char)
- }
-
- _, err = fmt.Fprintf(fd, "#N %s\nx = %d, y = %d, rule = %s\n%s\n",
- filename, width, height, rule, wrapped)
-
- if err != nil {
- return err
- }
-
- return nil
-}
-
-// by peterSO on
-// https://codereview.stackexchange.com/questions/238893/run-length-encoding-in-golang
-func RunLengthEncode(s string) string {
- e := make([]byte, 0, len(s))
- for i := 0; i < len(s); i++ {
- c := s[i]
- j := i + 1
- for ; j <= len(s); j++ {
- if j < len(s) && s[j] == c {
- continue
- }
- if j-i > 1 {
- e = strconv.AppendInt(e, int64(j-i), 10)
- }
- e = append(e, c)
- break
- }
- i = j - 1
- }
- return string(e)
-}
diff --git a/rle/rle_test.go b/rle/rle_test.go
deleted file mode 100644
index 265d993..0000000
--- a/rle/rle_test.go
+++ /dev/null
@@ -1,84 +0,0 @@
-package rle
-
-import (
- "reflect"
- "testing"
-)
-
-func TestRLE(t *testing.T) {
- t.Run("Parse", func(t *testing.T) {
- tests := []struct {
- input string
- expectedPattern [][]int
- expectedComment string
- expectedWidth int
- expectedHeight int
- expectedRule string
- }{
- {
- input: `#C This is a glider.
- x = 3, y = 3
- bo$2bo$3o!`,
- expectedPattern: [][]int{
- {0, 1, 0},
- {0, 0, 1},
- {1, 1, 1},
- },
- expectedWidth: 3,
- expectedHeight: 3,
- expectedRule: "",
- },
- {
- input: `#N Gosper glider gun
- #C This was the first gun discovered.
- #C As its name suggests, it was discovered by Bill Gosper.
- x = 36, y = 9, rule = B3/S23
- 24bo$22bobo$12b2o6b2o12b2o$11bo3bo4b2o12b2o$2o8bo5bo3b2o$2o8bo3bob2o4b
- obo$10bo5bo7bo$11bo3bo$12b2o!`,
- expectedPattern: [][]int{
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1},
- {1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- },
- expectedWidth: 36,
- expectedHeight: 9,
- expectedRule: "B3/S23",
- },
- }
-
- for _, test := range tests {
- rle, err := Parse(test.input)
-
- if err != nil {
- t.Error(err)
- }
-
- if rle.Width != test.expectedWidth {
- t.Errorf("Width dos not match")
- }
-
- if rle.Height != test.expectedHeight {
- t.Errorf("Height does not match")
- }
-
- if rle.Rule != test.expectedRule {
- t.Errorf("Rule does not match")
- }
-
- if !reflect.DeepEqual(rle.Pattern, test.expectedPattern) {
- t.Errorf(
- "Patterns do not match.\nExpected: %v\nGot: %v",
- test.expectedPattern,
- rle.Pattern,
- )
- }
- }
- })
-
-}
diff --git a/sample-rles/64P2H1V0.rle b/sample-rles/64P2H1V0.rle
deleted file mode 100644
index f2098c1..0000000
--- a/sample-rles/64P2H1V0.rle
+++ /dev/null
@@ -1,3 +0,0 @@
-x = 8, y = 31, rule = B3/S23
-o$4o$2b2o$5bo$2b4o$6bo$2bo2b3o$4b3o$5bo$ob3o$2o2bo$b3o$bo$3bo$bobo$4bo$bobo$
-3bo$bo$b3o$2o2bo$ob3o$5bo$4b3o$2bo2b3o$6bo$2b4o$5bo$2b2o$4o$o!
diff --git a/sample-rles/glider.rle b/sample-rles/glider.rle
deleted file mode 100644
index a15732f..0000000
--- a/sample-rles/glider.rle
+++ /dev/null
@@ -1,2 +0,0 @@
-x = 3, y = 3, rule = B3/S23
-3o$2bo$bo!
diff --git a/sample-rles/lightweight9.rle b/sample-rles/lightweight9.rle
deleted file mode 100644
index 50867bb..0000000
--- a/sample-rles/lightweight9.rle
+++ /dev/null
@@ -1,4 +0,0 @@
-x = 33, y = 10, rule = B3/S23
-5b3o17b3o$4bo3bo3bo7bo3bo3bo$3b2o4bob3o5b3obo4b2o$2bobob2obo3b2o3b2o3bob2obob
-o$b2obo4bobo2b2ob2o2bobo4bob2o$o4bo3bo4bo3bo4bo3bo4bo$14b5o$2o9b2obo3bob2o9b
-2o$11b2obo3bob2o$11b2obobobob2o!
diff --git a/sample-rles/p39piheptominohasslerdimer.rle b/sample-rles/p39piheptominohasslerdimer.rle
deleted file mode 100644
index d4280a2..0000000
--- a/sample-rles/p39piheptominohasslerdimer.rle
+++ /dev/null
@@ -1,8 +0,0 @@
-#N p39piheptominohasslerdimer.rle
-#C https://conwaylife.com/wiki/P39_pi-heptomino_hassler
-#C https://www.conwaylife.com/patterns/p39piheptominohasslerdimer.rle
-x = 51, y = 30, rule = B3/S23
-9b2o$8bobo$8bo$3bob2ob2o$3b2obo$6bo$6b2o27b2o$35b2o4$22b2o3b2o$22bobo
-2b2o$10b3o10bo$2obo6bobo25bobo6b2obo$ob2o6bobo25bobo6bob2o$27bo10b3o$
-22b2o2bobo$22b2o3b2o4$14b2o$14b2o27b2o$44bo$44bob2o$41b2ob2obo$42bo$
-40bobo$40b2o!
\ No newline at end of file
diff --git a/sample-rles/weekender.rle b/sample-rles/weekender.rle
deleted file mode 100644
index 819ee64..0000000
--- a/sample-rles/weekender.rle
+++ /dev/null
@@ -1,13 +0,0 @@
-#N 244p7h3v0.rle
-#C https://conwaylife.com/wiki/232P7H3V0
-#C https://www.conwaylife.com/patterns/244p7h3v0.rle
-x = 51, y = 52, rule = B3/S23
-19b3o9b3o$18bo3bo7bo3bo$17bobo3bo5bo3bobo$17bo3b2o7b2o3bo$17b3o3bo5bo
-3b3o$16bo3b2ob3ob3ob2o3bo$16b2o2bo3b2ob2o3bo2b2o$15b3o3b5ob5o3b3o$23bo
-5bo$20bo11bo$15bo4bo11bo4bo$15bo4b4o5b4o4bo$19bo4bo3bo4bo$18b2ob3o5b3o
-b2o$18b2obo3bobo3bob2o$14b3o7b2ob2o7b3o$13bo3b2o4bobobobo4b2o3bo$12bo
-3bo19bo3bo$12bo9b3o3b3o9bo$16bo6b2o3b2o6bo$11bo12bo3bo12bo$11bo2b2o5bo
-9bo5b2o2bo$12b2o8bo7bo8b2o$10bo12bo5bo12bo$9b3o29b3o$8b2o2bo27bo2b2o$
-11b2o27b2o$11bo29bo2$8bo35bo$9b2o31b2o$7bo2bo31bo2bo$6bo39bo$5b2o39b2o
-$4b4o37b4o$3bo45bo$3b3o41b3o$2bo47bo$4b2o41b2o$6bo39bo$4b2o41b2o$5bo
-41bo$4bo43bo$4bo43bo$2b2o$2obo$o$2o$bo3bo$4bo$o2bo$o!
diff --git a/src/Makefile b/src/Makefile
deleted file mode 100644
index c3beded..0000000
--- a/src/Makefile
+++ /dev/null
@@ -1,97 +0,0 @@
-# Copyright © 2024 Thomas von Dein
-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-
-#
-# no need to modify anything below
-tool = golsky
-VERSION = $(shell grep VERSION main.go | head -1 | cut -d '"' -f2)
-archs = darwin freebsd linux windows
-PREFIX = /usr/local
-UID = root
-GID = 0
-HAVE_POD := $(shell pod2text -h 2>/dev/null)
-#TAGS = -tags=ebitenginedebug
-
-all: buildlocal
-
-
-buildlocal:
- go build $(TAGS) -o $(tool)
-
-install: buildlocal
- install -d -o $(UID) -g $(GID) $(PREFIX)/bin
- install -d -o $(UID) -g $(GID) $(PREFIX)/man/man1
- install -o $(UID) -g $(GID) -m 555 $(tool) $(PREFIX)/sbin/
- install -o $(UID) -g $(GID) -m 444 $(tool).1 $(PREFIX)/man/man1/
-
-clean:
- rm -rf $(tool) coverage.out testdata t/out
-
-test: clean
- mkdir -p t/out
- go test ./... $(ARGS)
-
-testlint: test lint
-
-lint:
- golangci-lint run
-
-lint-full:
- golangci-lint run --enable-all --exclude-use-default --disable exhaustivestruct,exhaustruct,depguard,interfacer,deadcode,golint,structcheck,scopelint,varcheck,ifshort,maligned,nosnakecase,godot,funlen,gofumpt,cyclop,noctx,gochecknoglobals,paralleltest
-
-testfuzzy: clean
- go test -fuzz ./... $(ARGS)
-
-singletest:
- @echo "Call like this: make singletest TEST=TestPrepareColumns ARGS=-v"
- go test -run $(TEST) $(ARGS)
-
-cover-report:
- go test ./... -cover -coverprofile=coverage.out
- go tool cover -html=coverage.out
-
-goupdate:
- go get -t -u=patch ./...
-
-buildall:
- ./mkrel.sh $(tool) $(VERSION)
-
-release: buildall
- gh release create $(VERSION) --generate-notes releases/*
-
-show-versions: buildlocal
- @echo "### golsky version:"
- @./golsky -V
-
- @echo
- @echo "### go module versions:"
- @go list -m all
-
- @echo
- @echo "### go version used for building:"
- @grep -m 1 go go.mod
-
-# lint:
-# golangci-lint run -p bugs -p unused
-
-buildwasm:
- env GOOS=js GOARCH=wasm go build -o $(tool).wasm $(LDFLAGS) .
-
-zipwasm:
- zip -r openquell-$(SHORTVERSION).zip index.html $(tool).wasm wasm_exec.js
-
-wasm: buildwasm zipwasm
- @ls -l $(tool)-$(SHORTVERSION).zip
diff --git a/src/assets/fonts/NotoSans-Regular.ttf b/src/assets/fonts/NotoSans-Regular.ttf
deleted file mode 100644
index 10589e2..0000000
Binary files a/src/assets/fonts/NotoSans-Regular.ttf and /dev/null differ
diff --git a/src/assets/shaders/row.kg b/src/assets/shaders/row.kg
deleted file mode 100644
index f41c7c6..0000000
--- a/src/assets/shaders/row.kg
+++ /dev/null
@@ -1,13 +0,0 @@
-//kage:unit pixels
-
-package main
-
-var Alife int
-
-func Fragment(_ vec4, pos vec2, _ vec4) vec4 {
- if Alife == 1 {
- return vec4(0.0)
- }
-
- return vec4(1.0)
-}
diff --git a/src/assets/sprites/button-9slice1.png b/src/assets/sprites/button-9slice1.png
deleted file mode 100644
index 6a5804d..0000000
Binary files a/src/assets/sprites/button-9slice1.png and /dev/null differ
diff --git a/src/assets/sprites/button-9slice2.png b/src/assets/sprites/button-9slice2.png
deleted file mode 100644
index 6a7a525..0000000
Binary files a/src/assets/sprites/button-9slice2.png and /dev/null differ
diff --git a/src/assets/sprites/button-9slice3.png b/src/assets/sprites/button-9slice3.png
deleted file mode 100644
index f633e3a..0000000
Binary files a/src/assets/sprites/button-9slice3.png and /dev/null differ
diff --git a/src/assets/sprites/checkbox-9slice1.png b/src/assets/sprites/checkbox-9slice1.png
deleted file mode 100644
index 93d9a84..0000000
Binary files a/src/assets/sprites/checkbox-9slice1.png and /dev/null differ
diff --git a/src/assets/sprites/checkbox-9slice2.png b/src/assets/sprites/checkbox-9slice2.png
deleted file mode 100644
index 74adc1f..0000000
Binary files a/src/assets/sprites/checkbox-9slice2.png and /dev/null differ
diff --git a/src/assets/src/button-9slice.ase b/src/assets/src/button-9slice.ase
deleted file mode 100644
index d11bb86..0000000
Binary files a/src/assets/src/button-9slice.ase and /dev/null differ
diff --git a/src/assets/src/checkbox-9slice.ase b/src/assets/src/checkbox-9slice.ase
deleted file mode 100644
index f5640a6..0000000
Binary files a/src/assets/src/checkbox-9slice.ase and /dev/null differ
diff --git a/src/camera.go b/src/camera.go
deleted file mode 100644
index f5a2885..0000000
--- a/src/camera.go
+++ /dev/null
@@ -1,81 +0,0 @@
-// this comes from the camera example but I enhanced it a little bit
-
-package main
-
-import (
- "fmt"
- "math"
-
- "github.com/hajimehoshi/ebiten/v2"
- "golang.org/x/image/math/f64"
-)
-
-type Camera struct {
- ViewPort f64.Vec2
- Position f64.Vec2
- ZoomFactor int
- InitialZoomFactor int
- InitialPosition f64.Vec2
- ZoomOutFactor int
-}
-
-func (c *Camera) String() string {
- return fmt.Sprintf(
- "T: %.1f, S: %d",
- c.Position, c.ZoomFactor,
- )
-}
-
-func (c *Camera) viewportCenter() f64.Vec2 {
- return f64.Vec2{
- c.ViewPort[0] * 0.5,
- c.ViewPort[1] * 0.5,
- }
-}
-
-func (c *Camera) worldMatrix() ebiten.GeoM {
- m := ebiten.GeoM{}
- m.Translate(-c.Position[0], -c.Position[1])
-
- viewportCenter := c.viewportCenter()
-
- // We want to scale and rotate around center of image / screen
- m.Translate(-viewportCenter[0], -viewportCenter[1])
-
- m.Scale(
- math.Pow(1.01, float64(c.ZoomFactor)),
- math.Pow(1.01, float64(c.ZoomFactor)),
- )
-
- m.Translate(viewportCenter[0], viewportCenter[1])
- return m
-}
-
-func (c *Camera) Render(world, screen *ebiten.Image) {
- screen.DrawImage(world, &ebiten.DrawImageOptions{
- GeoM: c.worldMatrix(),
- })
-}
-
-func (c *Camera) ScreenToWorld(posX, posY int) (float64, float64) {
- inverseMatrix := c.worldMatrix()
- if inverseMatrix.IsInvertible() {
- inverseMatrix.Invert()
- return inverseMatrix.Apply(float64(posX), float64(posY))
- } else {
- // When scaling it can happened that matrix is not invertable
- return math.NaN(), math.NaN()
- }
-}
-
-func (c *Camera) Setup() {
- c.Position[0] = c.InitialPosition[0]
- c.Position[1] = c.InitialPosition[1]
- c.ZoomFactor = c.InitialZoomFactor
-}
-
-func (c *Camera) Reset() {
- c.Position[0] = c.InitialPosition[0]
- c.Position[1] = c.InitialPosition[1]
- c.ZoomFactor = c.ZoomOutFactor
-}
diff --git a/src/config.go b/src/config.go
deleted file mode 100644
index 960b11f..0000000
--- a/src/config.go
+++ /dev/null
@@ -1,291 +0,0 @@
-package main
-
-import (
- "errors"
- "fmt"
- "math"
- "os"
- "runtime/pprof"
- "strconv"
- "strings"
-
- "github.com/spf13/pflag"
- "github.com/tlinden/golsky/rle"
-)
-
-// all the settings comming from commandline, but maybe tweaked later from the UI
-type Config struct {
- Width, Height, Cellsize, Density int // measurements
- ScreenWidth, ScreenHeight int
- TPG int // ticks per generation/game speed, 1==max
- Debug, Empty, Paused, Markmode, Drawmode bool // game modi
- ShowEvolution, ShowGrid, RunOneStep bool // flags
- Rule *Rule // which rule to use, default: B3/S23
- RLE *rle.RLE // loaded GOL pattern from RLE file
- Statefile string // load game state from it if non-nil
- StateGrid *Grid // a grid from a statefile
- Wrap bool // wether wraparound mode is in place or not
- ShowVersion bool
- UseShader bool // to use a shader to render alife cells
- Restart, RestartGrid, RestartCache bool
- StartWithMenu bool
- Zoomfactor int
- ZoomOutFactor int
- InitialCamPos []float64
- DelayedStart bool // if true game, we wait. like pause but program induced
- Theme string
- ThemeManager ThemeManager
-
- // for internal profiling
- ProfileFile string
- ProfileDraw bool
- ProfileMaxLoops int64
-}
-
-const (
- VERSION = "v0.0.9"
- Alive = 1
- Dead = 0
-
- DEFAULT_GRID_WIDTH = 600
- DEFAULT_GRID_HEIGHT = 400
- DEFAULT_CELLSIZE = 4
- DEFAULT_ZOOMFACTOR = 400
- DEFAULT_GEOM = "640x384"
- DEFAULT_THEME = "standard"
-)
-
-const KEYBINDINGS string = `
-- SPACE: pause or resume the game
-- N: while game is paused: forward one step
-- PAGE UP: speed up
-- PAGE DOWN: slow down
-- MOUSE WHEEL: zoom in or out
-- LEFT MOUSE BUTTON: use to drag canvas, keep clicked and move mouse
-- I: enter "insert" (draw) mode: use left mouse to toggle a cells alife state.
- Leave with insert mode with "space". While in insert mode, use middle mouse
- button to drag the grid.
-- R: reset to 1:1 zoom
-- ESCAPE: open menu, o: open options menu
-- S: save game state to file (can be loaded with -l)
-- C: enter mark mode. Mark a rectangle with the mouse, when you
- release the mouse buttonx it is being saved to an RLE file
-- D: toggle debug output
-- Q: quit game
-`
-
-func (config *Config) SetupCamera() {
- config.Zoomfactor = DEFAULT_ZOOMFACTOR / config.Cellsize
-
- // calculate the initial cam pos. It is negative if the total grid
- // size is smaller than the screen in a centered position, but
- // it's zero if it's equal or larger than the screen.
- config.InitialCamPos = make([]float64, 2)
-
- config.InitialCamPos[0] = float64(((config.ScreenWidth - (config.Width * config.Cellsize)) / 2) * -1)
- if config.Width*config.Cellsize >= config.ScreenWidth {
- // must be positive if world wider than screen
- config.InitialCamPos[0] = math.Abs(config.InitialCamPos[0])
- }
-
- // same for Y
- config.InitialCamPos[1] = float64(((config.ScreenHeight - (config.Height * config.Cellsize)) / 2) * -1)
- if config.Height*config.Cellsize > config.ScreenHeight {
- config.InitialCamPos[1] = math.Abs(config.InitialCamPos[1])
- }
-
- // Calculate zoom out factor, which shows 100% of the world. We
- // need to reverse math.Pow(1.01, $zoomfactor) to get the correct
- // percentage of the world to show. I.e: with a ScreenHeight of
- // 384px and a world of 800px the factor to show 100% of the world
- // is -75: math.Log(384/800) / math.Log(1.01). The 1.01 constant
- // is being used in camera.go:worldMatrix().
-
- // FIXME: determine if the diff is larger on width, then calc with
- // width instead of height
- config.ZoomOutFactor = int(
- math.Log(float64(config.ScreenHeight)/(float64(config.Height)*float64(config.Cellsize))) /
- math.Log(1.01))
-}
-
-// parse given window geometry and adjust game settings according to it
-func (config *Config) ParseGeom(geom string) error {
- // force a geom
- geometry := strings.Split(geom, "x")
- if len(geometry) != 2 {
- return errors.New("failed to parse -g parameters, expecting WIDTHxHEIGHT")
- }
-
- width, err := strconv.Atoi(geometry[0])
- if err != nil {
- return errors.New("failed to parse width, expecting integer")
- }
-
- height, err := strconv.Atoi(geometry[1])
- if err != nil {
- return errors.New("failed to parse height, expecting integer")
- }
-
- config.ScreenWidth = width
- config.ScreenHeight = height
-
- //config.Cellsize = DEFAULT_CELLSIZE
-
- return nil
-}
-
-// check if we have been given an RLE or LIF file to load, then load
-// it and adjust game settings accordingly
-func (config *Config) ParseRLE(rlefile string) error {
- if rlefile == "" {
- return nil
- }
-
- var rleobj *rle.RLE
-
- if strings.HasSuffix(rlefile, ".lif") {
- lifobj, err := LoadLIF(rlefile)
- if err != nil {
- return err
- }
-
- rleobj = lifobj
- } else {
- rleobject, err := rle.GetRLE(rlefile)
- if err != nil {
- return err
- }
-
- rleobj = rleobject
- }
-
- if rleobj == nil {
- return errors.New("failed to load pattern file (uncatched module error)")
- }
-
- config.RLE = rleobj
-
- // adjust geometry if needed
- if config.RLE.Width > config.Width || config.RLE.Height > config.Height {
- config.Width = config.RLE.Width * 2
- config.Height = config.RLE.Height * 2
- config.Cellsize = config.ScreenWidth / config.Width
- }
-
- fmt.Printf("width: %d, screenwidth: %d, rlewidth: %d, cellsize: %d\n",
- config.Width, config.ScreenWidth, config.RLE.Width, config.Cellsize)
-
- // RLE needs an empty grid
- config.Empty = true
-
- // it may come with its own rule
- if config.RLE.Rule != "" {
- config.Rule = ParseGameRule(config.RLE.Rule)
- }
-
- return nil
-}
-
-func (config *Config) EnableCPUProfiling(filename string) error {
- if filename == "" {
- return nil
- }
-
- fd, err := os.Create(filename)
- if err != nil {
- return err
- }
-
- pprof.StartCPUProfile(fd)
- defer pprof.StopCPUProfile()
-
- return nil
-}
-
-func ParseCommandline() (*Config, error) {
- config := Config{}
-
- var (
- rule, rlefile, geom string
- )
-
- // commandline params, most configure directly config flags
- pflag.IntVarP(&config.Width, "width", "W", DEFAULT_GRID_WIDTH, "grid width in cells")
- pflag.IntVarP(&config.Height, "height", "H", DEFAULT_GRID_HEIGHT, "grid height in cells")
- pflag.IntVarP(&config.Cellsize, "cellsize", "c", 8, "cell size in pixels")
- pflag.StringVarP(&geom, "geom", "G", DEFAULT_GEOM, "window geometry in WxH in pixels, overturns -c")
-
- pflag.IntVarP(&config.Density, "density", "D", 10, "density of random cells")
- pflag.IntVarP(&config.TPG, "ticks-per-generation", "t", 10,
- "game speed: the higher the slower (default: 10)")
-
- pflag.StringVarP(&rule, "rule", "r", "B3/S23", "game rule")
- pflag.StringVarP(&rlefile, "pattern-file", "f", "", "RLE or LIF pattern file")
-
- pflag.BoolVarP(&config.ShowVersion, "version", "v", false, "show version")
- pflag.BoolVarP(&config.ShowGrid, "show-grid", "g", false, "draw grid lines")
- pflag.BoolVarP(&config.ShowEvolution, "show-evolution", "s", false, "show evolution traces")
-
- pflag.BoolVarP(&config.Paused, "paused", "p", false, "do not start simulation (use space to start)")
- pflag.BoolVarP(&config.Debug, "debug", "d", false, "show debug info")
- pflag.BoolVarP(&config.Empty, "empty", "e", false, "start with an empty screen")
-
- // style
- pflag.StringVarP(&config.Theme, "theme", "T", DEFAULT_THEME, "color theme: standard, dark, light (default: standard)")
-
- pflag.BoolVarP(&config.Wrap, "wrap-around", "w", false, "wrap around grid mode")
- pflag.BoolVarP(&config.UseShader, "use-shader", "k", false, "use shader for cell rendering")
-
- pflag.StringVarP(&config.ProfileFile, "profile-file", "", "", "enable profiling")
-
- pflag.Parse()
-
- err := config.ParseGeom(geom)
- if err != nil {
- return nil, err
- }
-
- err = config.ParseRLE(rlefile)
- if err != nil {
- return nil, err
- }
-
- // load rule from commandline when no rule came from RLE file,
- // default is B3/S23, aka conways game of life
- if config.Rule == nil {
- config.Rule = ParseGameRule(rule)
- }
-
- config.SetupCamera()
-
- config.ThemeManager = NewThemeManager(config.Theme, config.Cellsize)
-
- //repr.Println(config)
- return &config, nil
-}
-
-func (config *Config) TogglePaused() {
- config.Paused = !config.Paused
-}
-
-func (config *Config) ToggleDebugging() {
- config.Debug = !config.Debug
-}
-
-func (config *Config) SwitchTheme(theme string) {
- config.ThemeManager.SetCurrentTheme(theme)
- config.RestartCache = true
-}
-
-func (config *Config) ToggleGridlines() {
- config.ShowGrid = !config.ShowGrid
- config.RestartCache = true
-}
-
-func (config *Config) ToggleEvolution() {
- config.ShowEvolution = !config.ShowEvolution
-}
-
-func (config *Config) ToggleWrap() {
- config.Wrap = !config.Wrap
-}
diff --git a/src/game.go b/src/game.go
deleted file mode 100644
index a21240f..0000000
--- a/src/game.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package main
-
-import (
- "github.com/hajimehoshi/ebiten/v2"
-)
-
-type Game struct {
- ScreenWidth, ScreenHeight, ReadlWidth, Cellsize int
- Scenes map[SceneName]Scene
- CurrentScene SceneName
- Config *Config
- Scale float32
-}
-
-func NewGame(config *Config, startscene SceneName) *Game {
- game := &Game{
- Config: config,
- Scenes: map[SceneName]Scene{},
- ScreenWidth: config.ScreenWidth,
- ScreenHeight: config.ScreenHeight,
- }
-
- // setup scene[s]
- game.CurrentScene = startscene
- game.Scenes[Play] = NewPlayScene(game, config)
- game.Scenes[Menu] = NewMenuScene(game, config)
- game.Scenes[Options] = NewOptionsScene(game, config)
- game.Scenes[Keybindings] = NewKeybindingsScene(game, config)
-
- // setup environment
- ebiten.SetWindowSize(game.ScreenWidth, game.ScreenHeight)
- ebiten.SetWindowTitle("golsky - conway's game of life")
- ebiten.SetWindowResizingMode(ebiten.WindowResizingModeEnabled)
- ebiten.SetScreenClearedEveryFrame(true)
-
- return game
-}
-
-func (game *Game) GetCurrentScene() Scene {
- return game.Scenes[game.CurrentScene]
-}
-
-func (game *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
- game.ReadlWidth = outsideWidth
- game.Scale = float32(game.ScreenWidth) / float32(outsideWidth)
- return game.ScreenWidth, game.ScreenHeight
-}
-
-func (game *Game) Update() error {
- scene := game.GetCurrentScene()
-
- if quit := scene.Update(); quit != nil {
- return quit
- }
-
- next := scene.GetNext()
- if next != game.CurrentScene {
- game.Scenes[next].SetPrevious(game.CurrentScene)
- scene.ResetNext()
- game.CurrentScene = next
- }
-
- return nil
-}
-
-func (game *Game) Draw(screen *ebiten.Image) {
- // first draw primary scene[s], although there are only 1
- for current, scene := range game.Scenes {
- if scene.IsPrimary() {
- // primary scenes always draw
- scene.Draw(screen)
-
- if current == game.CurrentScene {
- // avoid to redraw it in the next step
- return
- }
- }
- }
-
- scene := game.GetCurrentScene()
- scene.Draw(screen)
-}
diff --git a/src/generics.go b/src/generics.go
deleted file mode 100644
index c71e1da..0000000
--- a/src/generics.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package main
-
-// find an item in a list, generic variant
-func Contains[E comparable](s []E, v E) bool {
- for _, vs := range s {
- if v == vs {
- return true
- }
- }
-
- return false
-}
-
-func Exists[K comparable, V any](m map[K]V, v K) bool {
- if _, ok := m[v]; ok {
- return true
- }
- return false
-}
diff --git a/src/grid.go b/src/grid.go
deleted file mode 100644
index b1e6098..0000000
--- a/src/grid.go
+++ /dev/null
@@ -1,383 +0,0 @@
-package main
-
-import (
- "bufio"
- "errors"
- "fmt"
- "math/rand"
- "os"
- "strings"
- "time"
-
- "github.com/tlinden/golsky/rle"
-)
-
-// equals grid height, is being used to access grid elements and must be global
-var STRIDE int
-
-type Neighbor struct {
- X, Y int
-}
-
-type Grid struct {
- Data []uint8
- NeighborCount []int
- Neighbors [][]Neighbor
- Empty bool
- Config *Config
- Counter func(x, y int) uint8
-}
-
-// Create new empty grid and allocate Data according to provided dimensions
-func NewGrid(config *Config) *Grid {
- STRIDE = config.Height
- if config.Width > config.Height {
- STRIDE = config.Width
- }
-
- size := STRIDE * STRIDE
-
- grid := &Grid{
- Data: make([]uint8, size),
- NeighborCount: make([]int, size),
- Neighbors: make([][]Neighbor, size),
- Empty: config.Empty,
- Config: config,
- }
-
- // first setup the cells
- for y := 0; y < config.Height; y++ {
- for x := 0; x < config.Width; x++ {
- grid.Data[y+STRIDE*x] = 0
- }
- }
-
- // in a second pass, collect positions to the neighbors of each cell
- for y := 0; y < config.Height; y++ {
- for x := 0; x < config.Width; x++ {
- grid.SetupNeighbors(x, y)
- }
- }
-
- if grid.Config.Wrap {
- grid.Counter = grid.CountNeighborsWrap
- } else {
- grid.Counter = grid.CountNeighbors
- }
-
- return grid
-}
-
-func (grid *Grid) SetupNeighbors(x, y int) {
- idx := 0
-
- var neighbors []Neighbor
-
- for nbgY := -1; nbgY < 2; nbgY++ {
- for nbgX := -1; nbgX < 2; nbgX++ {
- var col, row int
-
- if grid.Config.Wrap {
- // In wrap mode we look at all the 8 neighbors surrounding us.
- // In case we are on an edge we'll look at the neighbor on the
- // other side of the grid, thus wrapping lookahead around
- // using the mod() function.
- col = (x + nbgX + grid.Config.Width) % grid.Config.Width
- row = (y + nbgY + grid.Config.Height) % grid.Config.Height
-
- } else {
- // In traditional grid mode the edges are deadly
- if x+nbgX < 0 || x+nbgX >= grid.Config.Width || y+nbgY < 0 || y+nbgY >= grid.Config.Height {
- continue
- }
-
- col = x + nbgX
- row = y + nbgY
- }
-
- if col == x && row == y {
- continue
- }
-
- neighbors = append(neighbors, Neighbor{X: col, Y: row})
- grid.NeighborCount[y+STRIDE*x]++
- idx++
- }
- }
-
- grid.Neighbors[y+STRIDE*x] = neighbors
-}
-
-func (grid *Grid) CountNeighborsWrap(x, y int) uint8 {
- var sum uint8
-
- for nbgX := -1; nbgX < 2; nbgX++ {
- for nbgY := -1; nbgY < 2; nbgY++ {
- var col, row int
-
- // In wrap mode we look at all the 8 neighbors surrounding us.
- // In case we are on an edge we'll look at the neighbor on the
- // other side of the grid, thus wrapping lookahead around
- // using the mod() function.
- col = (x + nbgX + grid.Config.Width) % grid.Config.Width
- row = (y + nbgY + grid.Config.Height) % grid.Config.Height
-
- sum += grid.Data[row+STRIDE*col]
- }
- }
-
- // don't count ourselfes though
- sum -= grid.Data[y+STRIDE*x]
-
- return sum
-}
-
-func (grid *Grid) CountNeighbors(x, y int) uint8 {
- var sum uint8
-
- width := grid.Config.Width
- height := grid.Config.Height
-
- for nbgX := -1; nbgX < 2; nbgX++ {
- for nbgY := -1; nbgY < 2; nbgY++ {
- var col, row int
-
- xnbgX := x + nbgX
- ynbgY := y + nbgY
-
- // In traditional grid mode the edges are deadly
- if xnbgX < 0 || xnbgX >= width || ynbgY < 0 || ynbgY >= height {
- continue
- }
- col = xnbgX
- row = ynbgY
-
- sum += grid.Data[row+STRIDE*col]
- }
- }
-
- // don't count ourselfes though
- sum -= grid.Data[y+STRIDE*x]
-
- return sum
-}
-
-// count the living neighbors of a cell
-func (grid *Grid) _CountNeighbors(x, y int) uint8 {
- var count uint8
-
- pos := y + STRIDE*x
- neighbors := grid.Neighbors[pos]
- neighborCount := grid.NeighborCount[pos]
-
- for idx := 0; idx < neighborCount; idx++ {
- neighbor := neighbors[idx]
- count += grid.Data[neighbor.Y+STRIDE*neighbor.X]
- }
-
- return count
-}
-
-// Create a new 1:1 instance
-func (grid *Grid) Clone() *Grid {
- newgrid := &Grid{}
-
- newgrid.Config = grid.Config
- newgrid.Data = grid.Data
-
- return newgrid
-}
-
-// copy data
-// func (grid *Grid) Copy(other *Grid) {
-// for y := range grid.Data {
-// for x := range grid.Data[y] {
-// other.Data[y+STRIDE*x] = grid.Data[y+STRIDE*x]
-// }
-// }
-// }
-
-// delete all contents
-// func (grid *Grid) Clear() {
-// for y := range grid.Data {
-// for x := range grid.Data[y] {
-// grid.Data[y+STRIDE*x] = 0
-// }
-// }
-// }
-
-// initialize with random life cells using the given density
-func (grid *Grid) FillRandom() {
- if !grid.Empty {
- for y := 0; y < grid.Config.Height; y++ {
- for x := 0; x < grid.Config.Width; x++ {
- if rand.Intn(grid.Config.Density) == 1 {
- grid.Data[y+STRIDE*x] = 1
- }
- }
- }
- }
-}
-
-func (grid *Grid) Dump() {
- for y := 0; y < grid.Config.Height; y++ {
- for x := 0; x < grid.Config.Width; x++ {
- if grid.Data[y+STRIDE*x] == 1 {
- fmt.Print("XX")
- } else {
- fmt.Print(" ")
- }
- }
- fmt.Println()
- }
-}
-
-// initialize using a given RLE pattern
-func (grid *Grid) LoadRLE(pattern *rle.RLE) {
- if pattern != nil {
- startX := (grid.Config.Width / 2) - (pattern.Width / 2)
- startY := (grid.Config.Height / 2) - (pattern.Height / 2)
- var y, x int
-
- for rowIndex, patternRow := range pattern.Pattern {
- for colIndex := range patternRow {
- if pattern.Pattern[rowIndex][colIndex] > 0 {
- x = colIndex + startX
- y = rowIndex + startY
-
- grid.Data[y+STRIDE*x] = 1
- }
- }
- }
-
- //grid.Dump()
- }
-}
-
-// load a lif file parameters like R and P are not supported yet
-func LoadLIF(filename string) (*rle.RLE, error) {
- fd, err := os.Open(filename)
- if err != nil {
- return nil, err
- }
-
- scanner := bufio.NewScanner(fd)
-
- scanner.Split(bufio.ScanLines)
-
- gothead := false
-
- grid := &rle.RLE{}
-
- for scanner.Scan() {
- line := scanner.Text()
- items := strings.Split(line, "")
-
- if len(items) < 0 {
- continue
- }
-
- if strings.Contains(line, "# r") {
- parts := strings.Split(line, " ")
- if len(parts) == 2 {
- grid.Rule = parts[1]
- }
-
- continue
- }
-
- if items[0] == "#" {
- if gothead {
- break
- }
-
- continue
- }
-
- gothead = true
-
- row := make([]int, len(items))
-
- for idx, item := range items {
- switch item {
- case ".":
- row[idx] = 0
- case "o":
- fallthrough
- case "*":
- row[idx] = 1
- default:
- return nil, errors.New("cells must be . or o")
- }
- }
-
- grid.Pattern = append(grid.Pattern, row)
- }
-
- // sanity check the grid
- explen := 0
- rows := 0
- first := true
- for _, row := range grid.Pattern {
- length := len(row)
-
- if first {
- explen = length
- first = false
- }
-
- if explen != length {
- return nil, fmt.Errorf(
- fmt.Sprintf("all rows must be in the same length, got: %d, expected: %d",
- length, explen))
- }
-
- rows++
- }
-
- grid.Width = explen
- grid.Height = rows
-
- return grid, nil
-}
-
-// save the contents of the whole grid as a simple lif alike
-// file. One line per row, 0 for dead and 1 for life cell.
-// file format: https://conwaylife.com/wiki/Life_1.05
-func (grid *Grid) SaveState(filename, rule string) error {
- file, err := os.Create(filename)
- if err != nil {
- return fmt.Errorf("failed to open state file: %w", err)
- }
- defer file.Close()
-
- fmt.Fprintf(file, "#Life 1.05\n#R %s\n#D golsky state file\n#P -1 -1\n", rule)
-
- for y := 0; y < grid.Config.Height; y++ {
- for x := 0; x < grid.Config.Width; x++ {
- row := "."
- if grid.Data[y+STRIDE*x] == 1 {
- row = "o"
- }
-
- _, err := file.WriteString(row)
- if err != nil {
- return fmt.Errorf("failed to write to state file: %w", err)
- }
- }
- file.WriteString("\n")
- }
-
- return nil
-}
-
-// generate filenames for dumps
-func GetFilename(generations int64) string {
- now := time.Now()
- return fmt.Sprintf("dump-%s-%d.lif", now.Format("20060102150405"), generations)
-}
-
-func GetFilenameRLE(generations int64) string {
- now := time.Now()
- return fmt.Sprintf("rect-%s-%d.rle", now.Format("20060102150405"), generations)
-}
diff --git a/src/keybindings.go b/src/keybindings.go
deleted file mode 100644
index db68f60..0000000
--- a/src/keybindings.go
+++ /dev/null
@@ -1,98 +0,0 @@
-package main
-
-import (
- "image/color"
-
- "github.com/ebitenui/ebitenui"
- "github.com/ebitenui/ebitenui/widget"
- "github.com/hajimehoshi/ebiten/v2"
- "github.com/hajimehoshi/ebiten/v2/inpututil"
-)
-
-type SceneKeybindings struct {
- Game *Game
- Config *Config
- Next SceneName
- Prev SceneName
- Whoami SceneName
- Ui *ebitenui.UI
- FontColor color.RGBA
- First bool
-}
-
-func NewKeybindingsScene(game *Game, config *Config) Scene {
- scene := &SceneKeybindings{
- Whoami: Keybindings,
- Game: game,
- Next: Keybindings,
- Config: config,
- FontColor: color.RGBA{255, 30, 30, 0xff},
- }
-
- scene.Init()
-
- return scene
-}
-
-func (scene *SceneKeybindings) GetNext() SceneName {
- return scene.Next
-}
-
-func (scene *SceneKeybindings) SetPrevious(prev SceneName) {
- scene.Prev = prev
-}
-
-func (scene *SceneKeybindings) ResetNext() {
- scene.Next = scene.Whoami
-}
-
-func (scene *SceneKeybindings) SetNext(next SceneName) {
- scene.Next = next
-}
-
-func (scene *SceneKeybindings) Update() error {
- scene.Ui.Update()
-
- if inpututil.IsKeyJustPressed(ebiten.KeyEscape) || inpututil.IsKeyJustPressed(ebiten.KeyQ) {
- scene.Config.DelayedStart = false
- scene.Leave()
- }
-
- return nil
-
-}
-
-func (scene *SceneKeybindings) IsPrimary() bool {
- return false
-}
-
-func (scene *SceneKeybindings) Draw(screen *ebiten.Image) {
- scene.Ui.Draw(screen)
-}
-
-func (scene *SceneKeybindings) Leave() {
- scene.SetNext(Play)
-}
-
-func (scene *SceneKeybindings) Init() {
- rowContainer := NewRowContainer("Key Bindings")
-
- bindings := widget.NewText(
- widget.TextOpts.WidgetOpts(widget.WidgetOpts.LayoutData(widget.RowLayoutData{
- Stretch: true,
- })),
- widget.TextOpts.Text(KEYBINDINGS, *FontRenderer.FontSmall, color.NRGBA{0xdf, 0xf4, 0xff, 0xff}))
-
- cancel := NewMenuButton("Back",
- func(args *widget.ButtonClickedEventArgs) {
- scene.Leave()
- })
-
- rowContainer.AddChild(bindings)
- rowContainer.AddChild(cancel)
-
- scene.Ui = &ebitenui.UI{
- Container: rowContainer.Container(),
- }
-
-}
diff --git a/src/loader-fonts.go b/src/loader-fonts.go
deleted file mode 100644
index e144762..0000000
--- a/src/loader-fonts.go
+++ /dev/null
@@ -1,113 +0,0 @@
-package main
-
-import (
- "log"
-
- "github.com/golang/freetype/truetype"
- "github.com/tinne26/etxt"
- "golang.org/x/image/font"
-)
-
-var FontRenderer = LoadFonts("assets/fonts")
-
-const (
- GameFont string = "NotoSans-Regular"
- GameFontETXT string = "Noto Sans"
- FontSizeBig int = 48
- FontSizeNormal int = 24
- FontSizeSmall int = 12
-)
-
-type Texter struct {
- Renderer *etxt.Renderer
- FontNormal *font.Face
- FontBig *font.Face
- FontSmall *font.Face
-}
-
-func LoadFonts(dir string) Texter {
- // load the font for use with ebitenui
- fontbytes, err := assetfs.ReadFile(dir + "/" + GameFont + ".ttf")
- if err != nil {
- log.Fatal(err)
- }
-
- gamefont, err := truetype.Parse(fontbytes)
- if err != nil {
- log.Fatal(err)
- }
-
- gameface := truetype.NewFace(gamefont, &truetype.Options{
- Size: float64(FontSizeNormal),
- DPI: 72,
- Hinting: font.HintingFull,
- })
-
- biggameface := truetype.NewFace(gamefont, &truetype.Options{
- Size: float64(FontSizeBig),
- DPI: 72,
- Hinting: font.HintingFull,
- })
-
- smallgameface := truetype.NewFace(gamefont, &truetype.Options{
- Size: float64(FontSizeSmall),
- DPI: 72,
- Hinting: font.HintingFull,
- })
-
- // load the font for use with etxt
- fontlib := etxt.NewFontLibrary()
- _, _, err = fontlib.ParseEmbedDirFonts(dir, assetfs)
- if err != nil {
- log.Fatalf("Error while loading fonts: %s", err.Error())
- }
-
- /*
- err = fontlib.EachFont(
- func(fontName string, font *etxt.Font) error {
- fmt.Printf("font: %s\n", fontName)
- return nil
- })
- if err != nil {
- log.Fatal(err)
- }
- */
-
- if !fontlib.HasFont(GameFontETXT) {
- log.Fatal("missing font: " + GameFontETXT)
- }
-
- err = fontlib.EachFont(checkMissingRunes)
- if err != nil {
- log.Fatal(err)
- }
-
- renderer := etxt.NewStdRenderer()
-
- glyphsCache := etxt.NewDefaultCache(10 * 1024 * 1024) // 10MB
- renderer.SetCacheHandler(glyphsCache.NewHandler())
- renderer.SetFont(fontlib.GetFont(GameFontETXT))
-
- return Texter{
- Renderer: renderer,
- FontNormal: &gameface,
- FontBig: &biggameface,
- FontSmall: &smallgameface,
- }
-}
-
-// helper function used with FontLibrary.EachFont to make sure
-// all loaded fonts contain the characters or alphabet we want
-func checkMissingRunes(name string, font *etxt.Font) error {
- const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
- const symbols = "0123456789 .,;:!?-()[]{}_@"
-
- missing, err := etxt.GetMissingRunes(font, letters+symbols)
- if err != nil {
- return err
- }
- if len(missing) > 0 {
- log.Fatalf("Font '%s' missing runes: %s", name, string(missing))
- }
- return nil
-}
diff --git a/src/loader-shaders.go b/src/loader-shaders.go
deleted file mode 100644
index e3d460f..0000000
--- a/src/loader-shaders.go
+++ /dev/null
@@ -1,49 +0,0 @@
-package main
-
-import (
- "bytes"
- "log"
- "log/slog"
- "path"
- "strings"
-
- "github.com/hajimehoshi/ebiten/v2"
-)
-
-type ShaderRegistry map[string]*ebiten.Shader
-
-var Shaders = LoadShaders("assets/shaders")
-
-func LoadShaders(dir string) ShaderRegistry {
- shaders := ShaderRegistry{}
-
- entries, err := assetfs.ReadDir(dir)
- if err != nil {
- log.Fatalf("failed to read shaders dir %s: %s", dir, err)
- }
-
- for _, file := range entries {
- path := path.Join(dir, file.Name())
- fd, err := assetfs.Open(path)
- if err != nil {
- log.Fatalf("failed to open shader file %s: %s", file.Name(), err)
- }
- defer fd.Close()
-
- name := strings.TrimSuffix(file.Name(), ".kg")
-
- buf := new(bytes.Buffer)
- buf.ReadFrom(fd)
-
- shader, err := ebiten.NewShader([]byte(buf.Bytes()))
- if err != nil {
- log.Fatal(err)
- }
-
- shaders[name] = shader
-
- slog.Debug("loaded shader asset", "path", path)
- }
-
- return shaders
-}
diff --git a/src/loader-sprites.go b/src/loader-sprites.go
deleted file mode 100644
index 4ef3e50..0000000
--- a/src/loader-sprites.go
+++ /dev/null
@@ -1,69 +0,0 @@
-package main
-
-import (
- "embed"
- "image"
- _ "image/png"
- "io/fs"
- "log"
- "path"
- "strings"
-
- "github.com/hajimehoshi/ebiten/v2"
-)
-
-// Maps image name to image data
-type AssetRegistry map[string]*ebiten.Image
-
-// A helper to pass the registry easier around
-type assetData struct {
- Registry AssetRegistry
-}
-
-//go:embed assets/sprites/*.png assets/fonts/*.ttf assets/shaders/*.kg
-var assetfs embed.FS
-
-// Called at build time, creates the global asset and animation registries
-var Assets = LoadImages("assets/sprites")
-
-// load pngs and json files
-func LoadImages(dir string) AssetRegistry {
- Registry := AssetRegistry{}
-
- // we use embed.FS to iterate over all files in ./assets/
- entries, err := assetfs.ReadDir(dir)
- if err != nil {
- log.Fatalf("failed to read assets dir %s: %s", dir, err)
- }
-
- for _, imagefile := range entries {
- path := path.Join(dir, imagefile.Name())
-
- fd, err := assetfs.Open(path)
- if err != nil {
- log.Fatalf("failed to open file %s: %s", imagefile.Name(), err)
- }
- defer fd.Close()
-
- switch {
- case strings.HasSuffix(path, ".png"):
- name, image := ReadImage(imagefile, fd)
- Registry[name] = image
- }
- }
-
- return Registry
-}
-
-func ReadImage(imagefile fs.DirEntry, fd fs.File) (string, *ebiten.Image) {
- name := strings.TrimSuffix(imagefile.Name(), ".png")
-
- img, _, err := image.Decode(fd)
- if err != nil {
- log.Fatalf("failed to decode image %s: %s", imagefile.Name(), err)
- }
-
- image := ebiten.NewImageFromImage(img)
-
- return name, image
-}
diff --git a/src/main.go b/src/main.go
deleted file mode 100644
index b09a890..0000000
--- a/src/main.go
+++ /dev/null
@@ -1,55 +0,0 @@
-package main
-
-import (
- "fmt"
- "log"
- "os"
- "runtime/pprof"
-
- _ "net/http/pprof"
-
- "github.com/hajimehoshi/ebiten/v2"
-)
-
-func main() {
- var directstart bool
-
- if len(os.Args) > 1 {
- directstart = true
- }
-
- config, err := ParseCommandline()
- if err != nil {
- log.Fatal(err)
- }
-
- if config.ShowVersion {
- fmt.Printf("This is golsky version %s\n", VERSION)
- os.Exit(0)
- }
-
- start := Play
- if !directstart {
- start = Menu
- config.DelayedStart = true
- }
- game := NewGame(config, SceneName(start))
-
- if config.ProfileFile != "" {
- // enable cpu profiling. Do NOT use q to stop the game but
- // close the window to get a profile
- fd, err := os.Create(config.ProfileFile)
- if err != nil {
- log.Fatal(err)
- }
- defer fd.Close()
-
- pprof.StartCPUProfile(fd)
- defer pprof.StopCPUProfile()
- }
-
- // main loop
- if err := ebiten.RunGame(game); err != nil {
- log.Fatal(err)
- }
-}
diff --git a/src/menu.go b/src/menu.go
deleted file mode 100644
index 909bdf8..0000000
--- a/src/menu.go
+++ /dev/null
@@ -1,145 +0,0 @@
-package main
-
-import (
- "image/color"
-
- "github.com/ebitenui/ebitenui"
- "github.com/ebitenui/ebitenui/widget"
- "github.com/hajimehoshi/ebiten/v2"
- "github.com/hajimehoshi/ebiten/v2/inpututil"
-)
-
-type SceneMenu struct {
- Game *Game
- Config *Config
- Next SceneName
- Prev SceneName
- Whoami SceneName
- Ui *ebitenui.UI
- FontColor color.RGBA
- First bool
- Exit bool
-}
-
-func NewMenuScene(game *Game, config *Config) Scene {
- scene := &SceneMenu{
- Whoami: Menu,
- Game: game,
- Next: Menu,
- Config: config,
- FontColor: color.RGBA{255, 30, 30, 0xff},
- }
-
- scene.Init()
-
- return scene
-}
-
-func (scene *SceneMenu) GetNext() SceneName {
- return scene.Next
-}
-
-func (scene *SceneMenu) SetPrevious(prev SceneName) {
- scene.Prev = prev
-}
-
-func (scene *SceneMenu) ResetNext() {
- scene.Next = scene.Whoami
-}
-
-func (scene *SceneMenu) SetNext(next SceneName) {
- scene.Next = next
-}
-
-func (scene *SceneMenu) Update() error {
- scene.Ui.Update()
-
- if scene.Exit {
- return ebiten.Termination
- }
-
- if inpututil.IsKeyJustPressed(ebiten.KeyEscape) || inpututil.IsKeyJustPressed(ebiten.KeyQ) {
- scene.Config.DelayedStart = false
- scene.Leave()
- }
-
- return nil
-
-}
-
-func (scene *SceneMenu) IsPrimary() bool {
- return false
-}
-
-func (scene *SceneMenu) Draw(screen *ebiten.Image) {
- scene.Ui.Draw(screen)
-}
-
-func (scene *SceneMenu) Leave() {
- scene.SetNext(Play)
-}
-
-func (scene *SceneMenu) Init() {
- rowContainer := NewRowContainer("Main Menu")
-
- empty := NewMenuButton("Start with empty grid",
- func(args *widget.ButtonClickedEventArgs) {
- scene.Config.Empty = true
- scene.Config.Restart = true
- scene.Leave()
- })
-
- random := NewMenuButton("Start with random patterns",
- func(args *widget.ButtonClickedEventArgs) {
- scene.Config.Empty = false
- scene.Config.Restart = true
- scene.Leave()
- })
-
- copy := NewMenuButton("Save Copy as RLE",
- func(args *widget.ButtonClickedEventArgs) {
- scene.Config.Markmode = true
- scene.Config.Paused = true
- scene.Leave()
- })
-
- options := NewMenuButton("Options",
- func(args *widget.ButtonClickedEventArgs) {
- scene.SetNext(Options)
- })
-
- bindings := NewMenuButton("Show Key Bindings",
- func(args *widget.ButtonClickedEventArgs) {
- scene.SetNext(Keybindings)
- })
-
- separator1 := NewSeparator(3)
- separator2 := NewSeparator(3)
- separator3 := NewSeparator(10)
-
- cancel := NewMenuButton("Back",
- func(args *widget.ButtonClickedEventArgs) {
- scene.Leave()
- })
-
- quit := NewMenuButton("Exit Golsky",
- func(args *widget.ButtonClickedEventArgs) {
- scene.Exit = true
- })
-
- rowContainer.AddChild(empty)
- rowContainer.AddChild(random)
- rowContainer.AddChild(separator1)
- rowContainer.AddChild(options)
- rowContainer.AddChild(copy)
- rowContainer.AddChild(bindings)
- rowContainer.AddChild(separator2)
- rowContainer.AddChild(cancel)
- rowContainer.AddChild(separator3)
- rowContainer.AddChild(quit)
-
- scene.Ui = &ebitenui.UI{
- Container: rowContainer.Container(),
- }
-
-}
diff --git a/src/options.go b/src/options.go
deleted file mode 100644
index 5d44879..0000000
--- a/src/options.go
+++ /dev/null
@@ -1,157 +0,0 @@
-package main
-
-import (
- "image/color"
-
- "github.com/ebitenui/ebitenui"
- "github.com/ebitenui/ebitenui/widget"
- "github.com/hajimehoshi/ebiten/v2"
- "github.com/hajimehoshi/ebiten/v2/inpututil"
-)
-
-type SceneOptions struct {
- Game *Game
- Config *Config
- Next SceneName
- Prev SceneName
- Whoami SceneName
- Ui *ebitenui.UI
- FontColor color.RGBA
-}
-
-func NewOptionsScene(game *Game, config *Config) Scene {
- scene := &SceneOptions{
- Whoami: Options,
- Game: game,
- Next: Options,
- Config: config,
- FontColor: color.RGBA{255, 30, 30, 0xff},
- }
-
- scene.Init()
-
- return scene
-}
-
-func (scene *SceneOptions) GetNext() SceneName {
- return scene.Next
-}
-
-func (scene *SceneOptions) SetPrevious(prev SceneName) {
- scene.Prev = prev
-}
-
-func (scene *SceneOptions) ResetNext() {
- scene.Next = scene.Whoami
-}
-
-func (scene *SceneOptions) SetNext(next SceneName) {
- scene.Next = next
-}
-
-func (scene *SceneOptions) IsPrimary() bool {
- return false
-}
-
-func (scene *SceneOptions) Update() error {
- scene.Ui.Update()
-
- if inpututil.IsKeyJustPressed(ebiten.KeyEscape) || inpututil.IsKeyJustPressed(ebiten.KeyQ) {
- scene.SetNext(Play)
- }
-
- return nil
-
-}
-
-func (scene *SceneOptions) Draw(screen *ebiten.Image) {
- scene.Ui.Draw(screen)
-}
-
-func (scene *SceneOptions) SetInitialValue(w *widget.LabeledCheckbox, value bool) {
- if value {
- w.SetState(
- widget.WidgetChecked,
- )
- }
-}
-
-func (scene *SceneOptions) Init() {
- rowContainer := NewRowContainer("Options")
-
- pause := NewCheckbox("Pause",
- scene.Config.Paused,
- func(args *widget.CheckboxChangedEventArgs) {
- scene.Config.TogglePaused()
- })
-
- debugging := NewCheckbox("Debugging",
- scene.Config.Debug,
- func(args *widget.CheckboxChangedEventArgs) {
- scene.Config.ToggleDebugging()
- })
-
- gridlines := NewCheckbox("Show grid lines",
- scene.Config.ShowGrid,
- func(args *widget.CheckboxChangedEventArgs) {
- scene.Config.ToggleGridlines()
- })
-
- evolution := NewCheckbox("Show evolution traces",
- scene.Config.ShowEvolution,
- func(args *widget.CheckboxChangedEventArgs) {
- scene.Config.ToggleEvolution()
- })
-
- wrap := NewCheckbox("Wrap around edges",
- scene.Config.Wrap,
- func(args *widget.CheckboxChangedEventArgs) {
- scene.Config.ToggleWrap()
- })
-
- themenames := make([]string, len(THEMES))
- i := 0
- for name := range THEMES {
- themenames[i] = name
- i++
- }
-
- themes := NewCombobox(
- themenames,
- scene.Config.Theme,
- func(args *widget.ListComboButtonEntrySelectedEventArgs) {
- scene.Config.SwitchTheme(args.Entry.(ListEntry).Name)
- })
-
- themelabel := NewLabel("Themes")
- combocontainer := NewColumnContainer()
- combocontainer.AddChild(themes)
- combocontainer.AddChild(themelabel)
-
- separator := NewSeparator(3)
- separator2 := NewSeparator(3)
-
- cancel := NewMenuButton("Close",
- func(args *widget.ButtonClickedEventArgs) {
- scene.SetNext(scene.Prev)
- })
-
- rowContainer.AddChild(pause)
- rowContainer.AddChild(debugging)
- rowContainer.AddChild(gridlines)
- rowContainer.AddChild(evolution)
- rowContainer.AddChild(wrap)
-
- rowContainer.AddChild(separator)
-
- rowContainer.AddChild(combocontainer)
-
- rowContainer.AddChild(separator2)
-
- rowContainer.AddChild(cancel)
-
- scene.Ui = &ebitenui.UI{
- Container: rowContainer.Container(),
- }
-
-}
diff --git a/src/play.go b/src/play.go
deleted file mode 100644
index d5d8d8e..0000000
--- a/src/play.go
+++ /dev/null
@@ -1,712 +0,0 @@
-package main
-
-import (
- "fmt"
- "image"
- "log"
- "sync"
- "unsafe"
-
- "github.com/hajimehoshi/ebiten/v2"
- "github.com/hajimehoshi/ebiten/v2/inpututil"
- "github.com/hajimehoshi/ebiten/v2/vector"
- "github.com/tlinden/golsky/rle"
- "golang.org/x/image/math/f64"
-)
-
-type Images struct {
- Black, White, Age1, Age2, Age3, Age4, Old *ebiten.Image
-}
-
-const (
- DEBUG_FORMAT = "FPS: %0.2f, TPG: %d, M: %0.2fMB, Generations: %d\nScale: %.02f, Zoom: %d, Cam: %.02f,%.02f Cursor: %d,%d %s"
-)
-
-type History struct {
- Age [][]int64
-}
-
-func NewHistory(height, width int) History {
- hist := History{}
-
- hist.Age = make([][]int64, height)
- for y := 0; y < height; y++ {
- hist.Age[y] = make([]int64, width)
- }
-
- return hist
-}
-
-type ScenePlay struct {
- Game *Game
- Config *Config
- Next SceneName
- Prev SceneName
- Whoami SceneName
-
- Clear bool
-
- Grids []*Grid // 2 grids: one current, one next
- History History // holds state of past dead cells for evolution traces
- Index int // points to current grid
- Generations int64 // Stats
- TicksElapsed int // tick counter for game speed
- Camera Camera // for zoom+move
- World, Cache *ebiten.Image // actual image we render to
- WheelTurned bool // when user turns wheel multiple times, zoom faster
- Dragging bool // middle mouse is pressed, move canvas
- LastCursorPos []float64 // used to check if the user is dragging
- MarkTaken bool // true when mouse1 pressed
- MarkDone bool // true when mouse1 released, copy cells between Mark+Point
- Mark, Point image.Point // area to marks+save
- RunOneStep bool // mutable flags from config
- TPG int // current game speed (ticks per game)
- Theme Theme
- RuleCheckFunc func(uint8, uint8) uint8
-}
-
-func NewPlayScene(game *Game, config *Config) Scene {
- scene := &ScenePlay{
- Whoami: Play,
- Game: game,
- Next: Play,
- Config: config,
- TPG: config.TPG,
- RunOneStep: config.RunOneStep,
- }
-
- scene.Init()
-
- return scene
-}
-
-func (scene *ScenePlay) IsPrimary() bool {
- return true
-}
-
-func (scene *ScenePlay) GetNext() SceneName {
- return scene.Next
-}
-
-func (scene *ScenePlay) SetPrevious(prev SceneName) {
- scene.Prev = prev
-}
-
-func (scene *ScenePlay) ResetNext() {
- scene.Next = scene.Whoami
-}
-
-func (scene *ScenePlay) SetNext(next SceneName) {
- scene.Next = next
-}
-
-/* The standard Scene of Life is symbolized in rule-string notation
- * as B3/S23 (23/3 here). A cell is born if it has exactly three
- * neighbors, survives if it has two or three living neighbors,
- * and dies otherwise.
- * we abbreviate the calculation: if state is 0 and 3 neighbors
- * are a life, check will be just 3. If the cell is alive, 9 will
- * be added to the life neighbors (to avoid a collision with the
- * result 3), which will be 11|12 in case of 2|3 life neighbors.
- */
-func (scene *ScenePlay) CheckRuleB3S23(state uint8, neighbors uint8) uint8 {
- switch (9 * state) + neighbors {
- case 11:
- fallthrough
- case 12:
- fallthrough
- case 3:
- return Alive
- }
-
- return Dead
-}
-
-/*
- * The generic rule checker is able to calculate cell state for any
- * GOL rul, including B3/S23.
- */
-func (scene *ScenePlay) CheckRuleGeneric(state uint8, neighbors uint8) uint8 {
- var nextstate uint8
-
- if state != 1 && Contains(scene.Config.Rule.Birth, neighbors) {
- nextstate = Alive
- } else if state == 1 && Contains(scene.Config.Rule.Death, neighbors) {
- nextstate = Alive
- } else {
- nextstate = Dead
- }
-
- return nextstate
-}
-
-// Update all cells according to the current rule
-func (scene *ScenePlay) UpdateCells() {
- // count ticks so we know when to actually run
- scene.TicksElapsed++
-
- if scene.TPG > scene.TicksElapsed {
- // need to sleep a little more
- return
- }
-
- // next grid index, we just xor 0|1 to 1|0
- next := scene.Index ^ 1
-
- var wg sync.WaitGroup
- wg.Add(scene.Config.Height)
-
- width := scene.Config.Width
- height := scene.Config.Height
-
- // compute life status of cells
- for y := 0; y < height; y++ {
-
- go func() {
- defer wg.Done()
-
- for x := 0; x < width; x++ {
- state := scene.Grids[scene.Index].Data[y+STRIDE*x] // 0|1 == dead or alive
- neighbors := scene.Grids[scene.Index].Counter(x, y)
-
- // actually apply the current rules
- nextstate := scene.RuleCheckFunc(state, neighbors)
-
- // change state of current cell in next grid
- scene.Grids[next].Data[y+STRIDE*x] = nextstate
-
- if scene.Config.ShowEvolution {
- // set history to current generation so we can infer the
- // age of the cell's state during rendering and use it to
- // deduce the color to use if evolution tracing is enabled
- // 60FPS:
- if state != nextstate {
- scene.History.Age[y][x] = scene.Generations
- }
- }
- }
- }()
- }
-
- wg.Wait()
-
- // switch grid for rendering
- scene.Index ^= 1
-
- // global stats counter
- scene.Generations++
-
- if scene.Config.RunOneStep {
- // setp-wise mode, halt the game
- scene.Config.RunOneStep = false
- }
-
- // reset speed counter
- scene.TicksElapsed = 0
-}
-
-func (scene *ScenePlay) Reset() {
- scene.Config.Paused = true
- scene.InitGrid()
- scene.Config.Paused = false
-}
-
-// check user input
-func (scene *ScenePlay) CheckExit() error {
- if inpututil.IsKeyJustPressed(ebiten.KeyQ) {
- return ebiten.Termination
- }
-
- return nil
-}
-
-func (scene *ScenePlay) CheckInput() {
- // primary functions, always available
- switch {
- case inpututil.IsKeyJustPressed(ebiten.KeyEscape):
- scene.SetNext(Menu)
- case inpututil.IsKeyJustPressed(ebiten.KeyO):
- scene.SetNext(Options)
- case inpututil.IsKeyJustPressed(ebiten.KeyC):
- scene.Config.Markmode = true
- scene.Config.Drawmode = false
- scene.Config.Paused = true
- case inpututil.IsKeyJustPressed(ebiten.KeyI):
- scene.Config.Drawmode = true
- scene.Config.Paused = true
- }
-
- if scene.Config.Markmode {
- // no need to check any more input in mark mode
- return
- }
-
- switch {
- case inpututil.IsKeyJustPressed(ebiten.KeySpace) || inpututil.IsKeyJustPressed(ebiten.KeyEnter):
- scene.Config.TogglePaused()
- case inpututil.IsKeyJustPressed(ebiten.KeyPageDown):
- if scene.TPG < 120 {
- scene.TPG++
- }
- case inpututil.IsKeyJustPressed(ebiten.KeyPageUp):
- if scene.TPG >= 1 {
- scene.TPG--
- }
- case inpututil.IsKeyJustPressed(ebiten.KeyS):
- scene.SaveState()
- case inpututil.IsKeyJustPressed(ebiten.KeyD):
- scene.Config.Debug = !scene.Config.Debug
- }
-
- if scene.Config.Paused {
- if inpututil.IsKeyJustPressed(ebiten.KeyN) {
- scene.Config.RunOneStep = true
- }
- }
-}
-
-func (scene *ScenePlay) CheckDrawingInput() {
- if scene.Config.Drawmode {
- switch {
- case inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft):
- scene.ToggleCellOnCursorPos()
- case inpututil.IsKeyJustPressed(ebiten.KeyEscape):
- scene.Config.Drawmode = false
- }
- }
-}
-
-// Check dragging input. move the canvas with the mouse while pressing
-// the middle mouse button, zoom in and out using the wheel.
-func (scene *ScenePlay) CheckDraggingInput() {
- if scene.Config.Markmode {
- return
- }
-
- dragbutton := ebiten.MouseButtonLeft
-
- if scene.Config.Drawmode {
- dragbutton = ebiten.MouseButtonMiddle
- }
-
- // move canvas
- if scene.Dragging && !ebiten.IsMouseButtonPressed(dragbutton) {
- // release
- scene.Dragging = false
- }
-
- if !scene.Dragging && ebiten.IsMouseButtonPressed(dragbutton) {
- // start dragging
- scene.Dragging = true
- scene.LastCursorPos[0], scene.LastCursorPos[1] = scene.Camera.ScreenToWorld(ebiten.CursorPosition())
- }
-
- if scene.Dragging {
- x, y := scene.Camera.ScreenToWorld(ebiten.CursorPosition())
-
- if x != scene.LastCursorPos[0] || y != scene.LastCursorPos[1] {
- // actually drag by mouse cursor pos diff to last cursor pos
- scene.Camera.Position[0] -= float64(x - scene.LastCursorPos[0])
- scene.Camera.Position[1] -= float64(y - scene.LastCursorPos[1])
- }
-
- scene.LastCursorPos[0], scene.LastCursorPos[1] = scene.Camera.ScreenToWorld(ebiten.CursorPosition())
- }
-
- // also support the arrow keys to move the canvas
- switch {
- case ebiten.IsKeyPressed(ebiten.KeyArrowLeft):
- scene.Camera.Position[0] -= 1
- case ebiten.IsKeyPressed(ebiten.KeyArrowRight):
- scene.Camera.Position[0] += 1
- case ebiten.IsKeyPressed(ebiten.KeyArrowUp):
- scene.Camera.Position[1] -= 1
- case ebiten.IsKeyPressed(ebiten.KeyArrowDown):
- scene.Camera.Position[1] += 1
- }
-
- // Zoom
- _, dy := ebiten.Wheel()
-
- if dy != 0 {
- scene.Camera.ZoomFactor += (int(dy) * 5)
- }
-
- if inpututil.IsKeyJustPressed(ebiten.KeyR) {
- scene.Camera.Reset()
- }
-
-}
-
-func (scene *ScenePlay) GetWorldCursorPos() image.Point {
- worldX, worldY := scene.Camera.ScreenToWorld(ebiten.CursorPosition())
- return image.Point{
- X: int(worldX) / scene.Config.Cellsize,
- Y: int(worldY) / scene.Config.Cellsize,
- }
-}
-
-func (scene *ScenePlay) CheckMarkInput() {
- if !scene.Config.Markmode {
- return
- }
-
- if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
- scene.Config.Markmode = false
- }
-
- if ebiten.IsMouseButtonPressed(ebiten.MouseButton0) {
- if !scene.MarkTaken {
- scene.Mark = scene.GetWorldCursorPos()
- scene.MarkTaken = true
- scene.MarkDone = false
- }
-
- scene.Point = scene.GetWorldCursorPos()
- //fmt.Printf("Mark: %v, Point: %v\n", scene.Mark, scene.Point)
- } else if inpututil.IsMouseButtonJustReleased(ebiten.MouseButton0) {
- scene.Config.Markmode = false
- scene.MarkTaken = false
- scene.MarkDone = true
-
- scene.SaveRectRLE()
- }
-}
-
-func (scene *ScenePlay) SaveState() {
- filename := GetFilename(scene.Generations)
- err := scene.Grids[scene.Index].SaveState(filename, scene.Config.Rule.Definition)
- if err != nil {
- log.Printf("failed to save game state to %s: %s", filename, err)
- }
- log.Printf("saved game state to %s at generation %d\n", filename, scene.Generations)
-}
-
-func (scene *ScenePlay) SaveRectRLE() {
- filename := GetFilenameRLE(scene.Generations)
-
- if scene.Mark.X == scene.Point.X || scene.Mark.Y == scene.Point.Y {
- log.Printf("can't save non-rectangle\n")
- return
- }
-
- var width int
- var height int
- var startx int
- var starty int
-
- if scene.Mark.X < scene.Point.X {
- // mark left point
- startx = scene.Mark.X
- width = scene.Point.X - scene.Mark.X
- } else {
- // mark right point
- startx = scene.Point.X
- width = scene.Mark.X - scene.Point.X
- }
-
- if scene.Mark.Y < scene.Point.Y {
- // mark above point
- starty = scene.Mark.Y
- height = scene.Point.Y - scene.Mark.Y
- } else {
- // mark below point
- starty = scene.Point.Y
- height = scene.Mark.Y - scene.Point.Y
- }
-
- grid := make([][]uint8, height)
-
- for y := 0; y < height; y++ {
- grid[y] = make([]uint8, width)
-
- for x := 0; x < width; x++ {
- grid[y][x] = scene.Grids[scene.Index].Data[(y+starty)+STRIDE*(x+startx)]
- }
- }
-
- err := rle.StoreGridToRLE(grid, filename, scene.Config.Rule.Definition, width, height)
- if err != nil {
- log.Printf("failed to save rect to %s: %s\n", filename, err)
- } else {
- log.Printf("saved selected rect to %s at generation %d\n", filename, scene.Generations)
- }
-
-}
-
-func (scene *ScenePlay) Update() error {
- if scene.Config.Restart {
- scene.Config.Restart = false
- scene.Generations = 0
- scene.InitGrid()
- scene.InitCache()
- return nil
- }
-
- if scene.Config.RestartCache {
- scene.Config.RestartCache = false
- scene.Theme = scene.Config.ThemeManager.GetCurrentTheme()
- scene.InitCache()
- return nil
- }
-
- if quit := scene.CheckExit(); quit != nil {
- return quit
- }
-
- scene.CheckInput()
- scene.CheckDrawingInput()
- scene.CheckDraggingInput()
- scene.CheckMarkInput()
-
- if !scene.Config.Paused || scene.RunOneStep {
- scene.UpdateCells()
- }
-
- return nil
-}
-
-// set a cell to alive or dead
-func (scene *ScenePlay) ToggleCellOnCursorPos() {
- // use cursor pos relative to the world
- worldX, worldY := scene.Camera.ScreenToWorld(ebiten.CursorPosition())
- x := int(worldX) / scene.Config.Cellsize
- y := int(worldY) / scene.Config.Cellsize
-
- if x > -1 && y > -1 && x < scene.Config.Width && y < scene.Config.Height {
- scene.Grids[scene.Index].Data[y+STRIDE*x] ^= 1
- scene.History.Age[y][x] = 1
- }
-}
-
-// draw the new grid state
-func (scene *ScenePlay) Draw(screen *ebiten.Image) {
- // we fill the whole screen with a background color, the cells
- // themselfes will be 1px smaller as their nominal size, producing
- // a nice grey grid with grid lines
- op := &ebiten.DrawImageOptions{}
-
- op.GeoM.Translate(0, 0)
- scene.World.DrawImage(scene.Cache, op)
-
- for y := 0; y < scene.Config.Height; y++ {
- for x := 0; x < scene.Config.Width; x++ {
- op.GeoM.Reset()
- op.GeoM.Translate(
- float64(x*scene.Config.Cellsize),
- float64(y*scene.Config.Cellsize),
- )
-
- if scene.Config.ShowEvolution {
- scene.DrawEvolution(screen, x, y, op)
- } else {
- if scene.Grids[scene.Index].Data[y+STRIDE*x] == 1 {
- scene.World.DrawImage(scene.Theme.Tile(ColLife), op)
- }
- }
- }
- }
-
- scene.DrawMark(scene.World)
-
- scene.Camera.Render(scene.World, screen)
-
- scene.DrawDebug(screen)
-}
-
-func (scene *ScenePlay) DrawEvolution(screen *ebiten.Image, x, y int, op *ebiten.DrawImageOptions) {
- age := scene.Generations - scene.History.Age[y][x]
-
- switch scene.Grids[scene.Index].Data[y+STRIDE*x] {
- case Alive:
- if age > 50 && scene.Config.ShowEvolution {
- scene.World.DrawImage(scene.Theme.Tile(ColOld), op)
- } else {
- scene.World.DrawImage(scene.Theme.Tile(ColLife), op)
- }
- case Dead:
- // only draw dead cells in case evolution trace is enabled
- if scene.History.Age[y][x] > 1 && scene.Config.ShowEvolution {
- switch {
- case age < 10:
- scene.World.DrawImage(scene.Theme.Tile(ColAge1), op)
- case age < 20:
- scene.World.DrawImage(scene.Theme.Tile(ColAge2), op)
- case age < 30:
- scene.World.DrawImage(scene.Theme.Tile(ColAge3), op)
- default:
- scene.World.DrawImage(scene.Theme.Tile(ColAge4), op)
- }
- }
- }
-}
-
-func (scene *ScenePlay) DrawMark(screen *ebiten.Image) {
- if scene.Config.Markmode && scene.MarkTaken {
- x := float32(scene.Mark.X * scene.Config.Cellsize)
- y := float32(scene.Mark.Y * scene.Config.Cellsize)
- w := float32((scene.Point.X - scene.Mark.X) * scene.Config.Cellsize)
- h := float32((scene.Point.Y - scene.Mark.Y) * scene.Config.Cellsize)
-
- vector.StrokeRect(
- scene.World,
- x+1, y+1,
- w, h,
- 1.0, scene.Theme.Color(ColOld), false,
- )
- }
-}
-
-func (scene *ScenePlay) DrawDebug(screen *ebiten.Image) {
- if scene.Config.Debug {
- paused := ""
- if scene.Config.Paused {
- paused = "-- paused --"
- }
-
- if scene.Config.Markmode {
- paused = "-- mark --"
- }
-
- if scene.Config.Drawmode {
- paused = "-- insert --"
- }
-
- x, y := ebiten.CursorPosition()
- debug := fmt.Sprintf(
- DEBUG_FORMAT,
- ebiten.ActualTPS(), scene.TPG, GetMem(), scene.Generations,
- scene.Game.Scale, scene.Camera.ZoomFactor,
- scene.Camera.Position[0], scene.Camera.Position[1],
- x, y,
- paused)
-
- FontRenderer.Renderer.SetSizePx(10 + int(scene.Game.Scale*10))
- FontRenderer.Renderer.SetTarget(screen)
-
- FontRenderer.Renderer.SetColor(scene.Theme.Color(ColLife))
- FontRenderer.Renderer.Draw(debug, 31, 31)
-
- FontRenderer.Renderer.SetColor(scene.Theme.Color(ColOld))
- FontRenderer.Renderer.Draw(debug, 30, 30)
-
- fmt.Println(debug)
- }
-
-}
-
-// load a pre-computed pattern from RLE file
-func (scene *ScenePlay) InitPattern() {
- scene.Grids[0].LoadRLE(scene.Config.RLE)
-
- // rule might have changed
- scene.InitRuleCheckFunc()
-}
-
-// pre-render offscreen cache image
-func (scene *ScenePlay) InitCache() {
- // setup theme
- scene.Theme.SetGrid(scene.Config.ShowGrid)
-
- if !scene.Config.ShowGrid {
- scene.Cache.Fill(scene.Theme.Color(ColDead))
- return
- }
-
- op := &ebiten.DrawImageOptions{}
-
- scene.Cache.Fill(scene.Theme.Color(ColGrid))
-
- for y := 0; y < scene.Config.Height; y++ {
- for x := 0; x < scene.Config.Width; x++ {
- op.GeoM.Reset()
- op.GeoM.Translate(
- float64(x*scene.Config.Cellsize),
- float64(y*scene.Config.Cellsize),
- )
-
- scene.Cache.DrawImage(scene.Theme.Tile(ColDead), op)
- }
- }
-}
-
-// initialize grid[s], either using pre-computed from state or rle file, or random
-func (scene *ScenePlay) InitGrid() {
- grida := NewGrid(scene.Config)
- gridb := NewGrid(scene.Config)
-
- // startup is delayed until user has selected options
- grida.FillRandom()
-
- scene.Grids = []*Grid{
- grida,
- gridb,
- }
-
- scene.History = NewHistory(scene.Config.Height, scene.Config.Width)
-
-}
-
-func (scene *ScenePlay) Init() {
- // setup the scene
- scene.Camera = Camera{
- ViewPort: f64.Vec2{
- float64(scene.Config.ScreenWidth),
- float64(scene.Config.ScreenHeight),
- },
- InitialZoomFactor: scene.Config.Zoomfactor,
- InitialPosition: f64.Vec2{
- scene.Config.InitialCamPos[0],
- scene.Config.InitialCamPos[1],
- },
- ZoomOutFactor: scene.Config.ZoomOutFactor,
- }
-
- scene.World = ebiten.NewImage(
- scene.Config.Width*scene.Config.Cellsize,
- scene.Config.Height*scene.Config.Cellsize,
- )
-
- scene.Cache = ebiten.NewImage(
- scene.Config.Width*scene.Config.Cellsize,
- scene.Config.Height*scene.Config.Cellsize,
- )
-
- scene.Theme = scene.Config.ThemeManager.GetCurrentTheme()
- scene.InitCache()
-
- if scene.Config.DelayedStart && !scene.Config.Empty {
- // do not fill the grid when the main menu comes up first, the
- // user decides interactively what to do
- scene.Config.Empty = true
- scene.InitGrid()
- scene.Config.Empty = false
- } else {
- scene.InitGrid()
- }
-
- scene.InitPattern()
-
- scene.Index = 0
- scene.TicksElapsed = 0
-
- scene.LastCursorPos = make([]float64, 2)
-
- if scene.Config.Zoomfactor < 0 || scene.Config.Zoomfactor > 0 {
- scene.Camera.ZoomFactor = scene.Config.Zoomfactor
- }
-
- scene.Camera.Setup()
-}
-
-func bool2int(b bool) int {
- return int(*(*byte)(unsafe.Pointer(&b)))
-}
-
-func (scene *ScenePlay) InitRuleCheckFunc() {
- if scene.Config.Rule.Definition == "B3/S23" {
- scene.RuleCheckFunc = scene.CheckRuleB3S23
- } else {
- scene.RuleCheckFunc = scene.CheckRuleGeneric
- }
-}
diff --git a/src/rule.go b/src/rule.go
deleted file mode 100644
index 3f2ad05..0000000
--- a/src/rule.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package main
-
-import (
- "log"
- "strconv"
- "strings"
-)
-
-// a GOL rule
-type Rule struct {
- Definition string
- Birth []uint8
- Death []uint8
-}
-
-// parse one part of a GOL rule into rule slice
-func NumbersToList(numbers string) []uint8 {
- list := []uint8{}
-
- items := strings.Split(numbers, "")
- for _, item := range items {
- num, err := strconv.Atoi(item)
- if err != nil {
- log.Fatalf("failed to parse game rule part <%s>: %s", numbers, err)
- }
-
- list = append(list, uint8(num))
- }
-
- return list
-}
-
-// parse GOL rule, used in CheckRule()
-func ParseGameRule(rule string) *Rule {
- parts := strings.Split(rule, "/")
-
- if len(parts) < 2 {
- log.Fatalf("Invalid game rule <%s>", rule)
- }
-
- golrule := &Rule{Definition: rule}
-
- for _, part := range parts {
- if part[0] == 'B' {
- golrule.Birth = NumbersToList(part[1:])
- } else {
- golrule.Death = NumbersToList(part[1:])
- }
- }
-
- return golrule
-}
diff --git a/src/scene.go b/src/scene.go
deleted file mode 100644
index ac1818c..0000000
--- a/src/scene.go
+++ /dev/null
@@ -1,28 +0,0 @@
-package main
-
-import "github.com/hajimehoshi/ebiten/v2"
-
-// Wrapper for different screens to be shown, as Welcome, Options,
-// About, Menu Level and of course the actual game
-// Scenes are responsible for screen clearing! That way a scene is able
-// to render its content onto the running level, e.g. the options scene
-// etc.
-
-type SceneName int
-
-type Scene interface {
- SetNext(SceneName)
- GetNext() SceneName
- SetPrevious(SceneName)
- ResetNext()
- Update() error
- Draw(screen *ebiten.Image)
- IsPrimary() bool // if true, this scene will be always drawn
-}
-
-const (
- Menu = iota // main top level menu
- Play // actual playing happens here
- Options
- Keybindings
-)
diff --git a/src/system.go b/src/system.go
deleted file mode 100644
index d819ad7..0000000
--- a/src/system.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package main
-
-import "runtime"
-
-// returns current memory usage in MB
-func GetMem() float64 {
- var m runtime.MemStats
- runtime.ReadMemStats(&m)
-
- return float64(m.Alloc) / 1024 / 1024
-}
diff --git a/src/theme.go b/src/theme.go
deleted file mode 100644
index 3c2e894..0000000
--- a/src/theme.go
+++ /dev/null
@@ -1,189 +0,0 @@
-package main
-
-import (
- "fmt"
- "image/color"
- "log"
-
- "github.com/hajimehoshi/ebiten/v2"
- "github.com/hajimehoshi/ebiten/v2/vector"
-)
-
-// Color definitions. ColLife could be black or white depending on theme
-const (
- ColLife = iota
- ColDead
- ColOld
- ColAge1
- ColAge2
- ColAge3
- ColAge4
- ColGrid
-)
-
-// A Theme defines how the grid and the cells are colored. We define
-// the colors and the actual tile images here, so that they are
-// readily available from play.go
-type Theme struct {
- Tiles map[int]*ebiten.Image
- GridTiles map[int]*ebiten.Image
- Colors map[int]color.RGBA
- Name string
- ShowGrid bool
-}
-
-type ThemeDef struct {
- life, dead, grid, old, age1, age2, age3, age4 string
-}
-
-var THEMES = map[string]ThemeDef{
- "standard": {
- life: "e15f0b",
- dead: "5a5a5a",
- old: "ff1e1e",
- grid: "808080",
- age3: "6c6059",
- age2: "735f52",
- age1: "7b5e4b",
- age4: "635d59",
- },
- "dark": {
- life: "c8c8c8",
- dead: "000000",
- old: "ff1e1e",
- grid: "808080",
- age1: "522600",
- age2: "422300",
- age3: "2b1b00",
- age4: "191100",
- },
- "light": {
- life: "000000",
- dead: "c8c8c8",
- old: "ff1e1e",
- grid: "808080",
- age1: "ffc361",
- age2: "ffd38c",
- age3: "ffe3b5",
- age4: "fff0e0",
- },
-}
-
-// create a new theme
-func NewTheme(def ThemeDef, cellsize int, name string) Theme {
- theme := Theme{
- Name: name,
- Colors: map[int]color.RGBA{
- ColLife: HexColor2RGBA(def.life),
- ColDead: HexColor2RGBA(def.dead),
- ColGrid: HexColor2RGBA(def.grid),
- ColAge1: HexColor2RGBA(def.age1),
- ColAge2: HexColor2RGBA(def.age2),
- ColAge3: HexColor2RGBA(def.age3),
- ColAge4: HexColor2RGBA(def.age4),
- ColOld: HexColor2RGBA(def.old),
- },
- }
-
- theme.Tiles = make(map[int]*ebiten.Image, 6)
- theme.GridTiles = make(map[int]*ebiten.Image, 6)
-
- for cid, col := range theme.Colors {
- theme.Tiles[cid] = ebiten.NewImage(cellsize, cellsize)
- FillCell(theme.Tiles[cid], cellsize, col, 0)
-
- theme.GridTiles[cid] = ebiten.NewImage(cellsize, cellsize)
- FillCell(theme.GridTiles[cid], cellsize, col, 1)
- }
-
- return theme
-}
-
-// return the tile image for the requested color type. panic if
-// unknown type is being used, which is ok, since the code is the only
-// user anyway
-func (theme *Theme) Tile(col int) *ebiten.Image {
- if theme.ShowGrid {
- return theme.GridTiles[col]
- }
-
- return theme.Tiles[col]
-}
-
-func (theme *Theme) Color(col int) color.RGBA {
- return theme.Colors[col]
-}
-
-func (theme *Theme) SetGrid(showgrid bool) {
- theme.ShowGrid = showgrid
-}
-
-type ThemeManager struct {
- Theme string
- Themes map[string]Theme
-}
-
-// Manager is used to easily switch themes from cli or menu
-func NewThemeManager(initial string, cellsize int) ThemeManager {
- manager := ThemeManager{
- Theme: initial,
- }
-
- manager.Themes = make(map[string]Theme, len(THEMES))
-
- for name, def := range THEMES {
- manager.Themes[name] = NewTheme(def, cellsize, name)
- }
-
- return manager
-}
-
-func (manager *ThemeManager) GetCurrentTheme() Theme {
- return manager.Themes[manager.Theme]
-}
-
-func (manager *ThemeManager) GetCurrentThemeName() string {
- return manager.Theme
-}
-
-func (manager *ThemeManager) SetCurrentTheme(theme string) {
- if Exists(manager.Themes, theme) {
- manager.Theme = theme
- }
-}
-
-// Fill a cell with the given color.
-//
-// We do not draw the cell at 0,0 of it's position but at 1,1. This
-// creates a top and lef transparent. By using a different background
-// for the whole grid we can then decide wether to show grid lines or
-// not.
-//
-// If no gridlines are selected the background will just be filled
-// with the DEAD color. However, IF we are to show the gridlines, we
-// fill it with a lighter color. The transparent edges of all tiles
-// then create the grid.
-//
-// So we don't draw a grid, we just left a grid behind, which saves us
-// from a lot of drawing operations.
-func FillCell(tile *ebiten.Image, cellsize int, col color.RGBA, x int) {
- vector.DrawFilledRect(
- tile,
- float32(x),
- float32(x),
- float32(cellsize),
- float32(cellsize),
- col, false,
- )
-}
-
-func HexColor2RGBA(hex string) color.RGBA {
- var r, g, b uint8
-
- _, err := fmt.Sscanf(hex, "%02x%02x%02x", &r, &g, &b)
- if err != nil {
- log.Fatalf("failed to parse hex color: %s", err)
- }
-
- return color.RGBA{r, g, b, 255}
-}
diff --git a/src/widgets.go b/src/widgets.go
deleted file mode 100644
index e27a4c8..0000000
--- a/src/widgets.go
+++ /dev/null
@@ -1,305 +0,0 @@
-package main
-
-import (
- "image/color"
-
- "github.com/ebitenui/ebitenui/image"
- "github.com/ebitenui/ebitenui/widget"
-)
-
-func NewMenuButton(
- text string,
- action func(args *widget.ButtonClickedEventArgs)) *widget.Button {
-
- buttonImage, _ := LoadButtonImage()
-
- return widget.NewButton(
- widget.ButtonOpts.WidgetOpts(
- widget.WidgetOpts.LayoutData(widget.RowLayoutData{
- Position: widget.RowLayoutPositionCenter,
- Stretch: true,
- MaxWidth: 200,
- MaxHeight: 100,
- }),
- ),
-
- widget.ButtonOpts.Image(buttonImage),
-
- widget.ButtonOpts.Text(text, *FontRenderer.FontSmall, &widget.ButtonTextColor{
- Idle: color.NRGBA{0xdf, 0xf4, 0xff, 0xff},
- }),
-
- widget.ButtonOpts.TextPadding(widget.Insets{
- Left: 5,
- Right: 5,
- Top: 5,
- Bottom: 5,
- }),
-
- widget.ButtonOpts.ClickedHandler(action),
- )
-}
-
-func NewCheckbox(
- text string,
- initialvalue bool,
- action func(args *widget.CheckboxChangedEventArgs)) *widget.LabeledCheckbox {
-
- checkboxImage, _ := LoadCheckboxImage()
- buttonImage, _ := LoadButtonImage()
-
- var state widget.WidgetState
- if initialvalue {
- state = widget.WidgetChecked
- }
-
- return widget.NewLabeledCheckbox(
- widget.LabeledCheckboxOpts.CheckboxOpts(
- widget.CheckboxOpts.ButtonOpts(
- widget.ButtonOpts.Image(buttonImage),
- ),
- widget.CheckboxOpts.Image(checkboxImage),
- widget.CheckboxOpts.StateChangedHandler(action),
- widget.CheckboxOpts.InitialState(state),
- ),
- widget.LabeledCheckboxOpts.LabelOpts(
- widget.LabelOpts.Text(text, *FontRenderer.FontSmall,
- &widget.LabelColor{
- Idle: color.NRGBA{0xdf, 0xf4, 0xff, 0xff},
- }),
- ),
- )
-}
-
-func NewSeparator(padding int) widget.PreferredSizeLocateableWidget {
- c := widget.NewContainer(
- widget.ContainerOpts.Layout(widget.NewRowLayout(
- widget.RowLayoutOpts.Direction(widget.DirectionVertical),
- widget.RowLayoutOpts.Padding(widget.Insets{
- Top: padding,
- Bottom: 0,
- }))),
- widget.ContainerOpts.WidgetOpts(
- widget.WidgetOpts.LayoutData(
- widget.RowLayoutData{Stretch: true})))
- return c
-}
-
-type ListEntry struct {
- id int
- Name string
-}
-
-func NewCombobox(items []string, selected string,
- action func(args *widget.ListComboButtonEntrySelectedEventArgs)) *widget.ListComboButton {
- buttonImage, _ := LoadButtonImage()
-
- entries := make([]any, 0, len(items))
- idxselected := 0
- for i, item := range items {
- entries = append(entries, ListEntry{i, item})
- if items[i] == selected {
- idxselected = i
- }
- }
-
- comboBox := widget.NewListComboButton(
- widget.ListComboButtonOpts.SelectComboButtonOpts(
- widget.SelectComboButtonOpts.ComboButtonOpts(
- //Set the max height of the dropdown list
- widget.ComboButtonOpts.MaxContentHeight(150),
- //Set the parameters for the primary displayed button
- widget.ComboButtonOpts.ButtonOpts(
- widget.ButtonOpts.Image(buttonImage),
- widget.ButtonOpts.TextPadding(widget.NewInsetsSimple(5)),
- widget.ButtonOpts.Text("", *FontRenderer.FontSmall, &widget.ButtonTextColor{
- Idle: color.White,
- Disabled: color.White,
- }),
- widget.ButtonOpts.WidgetOpts(
- //Set how wide the button should be
- widget.WidgetOpts.MinSize(50, 0),
- //Set the combobox's position
- widget.WidgetOpts.LayoutData(widget.AnchorLayoutData{
- HorizontalPosition: widget.AnchorLayoutPositionCenter,
- VerticalPosition: widget.AnchorLayoutPositionCenter,
- })),
- ),
- ),
- ),
- widget.ListComboButtonOpts.ListOpts(
- //Set how wide the dropdown list should be
- widget.ListOpts.ContainerOpts(
- widget.ContainerOpts.WidgetOpts(widget.WidgetOpts.MinSize(50, 0)),
- ),
- //Set the entries in the list
- widget.ListOpts.Entries(entries),
- widget.ListOpts.ScrollContainerOpts(
- //Set the background images/color for the dropdown list
- widget.ScrollContainerOpts.Image(&widget.ScrollContainerImage{
- Idle: image.NewNineSliceColor(color.NRGBA{100, 100, 100, 255}),
- Disabled: image.NewNineSliceColor(color.NRGBA{100, 100, 100, 255}),
- Mask: image.NewNineSliceColor(color.NRGBA{100, 100, 100, 255}),
- }),
- ),
- widget.ListOpts.SliderOpts(
- //Set the background images/color for the background of the slider track
- widget.SliderOpts.Images(&widget.SliderTrackImage{
- Idle: image.NewNineSliceColor(color.NRGBA{100, 100, 100, 255}),
- Hover: image.NewNineSliceColor(color.NRGBA{100, 100, 100, 255}),
- }, buttonImage),
- widget.SliderOpts.MinHandleSize(5),
- //Set how wide the track should be
- widget.SliderOpts.TrackPadding(widget.NewInsetsSimple(2))),
- //Set the font for the list options
- widget.ListOpts.EntryFontFace(*FontRenderer.FontSmall),
- //Set the colors for the list
- widget.ListOpts.EntryColor(&widget.ListEntryColor{
- Selected: color.NRGBA{254, 255, 255, 255},
- Unselected: color.NRGBA{254, 255, 255, 255},
- SelectedBackground: HexColor2RGBA(THEMES["standard"].life),
- SelectedFocusedBackground: HexColor2RGBA(THEMES["standard"].old),
- FocusedBackground: HexColor2RGBA(THEMES["standard"].old),
- DisabledUnselected: HexColor2RGBA(THEMES["standard"].grid),
- DisabledSelected: HexColor2RGBA(THEMES["standard"].grid),
- DisabledSelectedBackground: HexColor2RGBA(THEMES["standard"].grid),
- }),
- //Padding for each entry
- widget.ListOpts.EntryTextPadding(widget.NewInsetsSimple(5)),
- ),
- //Define how the entry is displayed
- widget.ListComboButtonOpts.EntryLabelFunc(
- func(e any) string {
- //Button Label function, visible if not open
- return e.(ListEntry).Name
- },
- func(e any) string {
- //List Label function, visible items if open
- return e.(ListEntry).Name
- }),
- //Callback when a new entry is selected
- widget.ListComboButtonOpts.EntrySelectedHandler(action),
- )
-
- //Select the middle entry -- optional
- comboBox.SetSelectedEntry(entries[idxselected])
-
- return comboBox
-}
-
-func NewLabel(text string) *widget.Text {
- return widget.NewText(
- widget.TextOpts.Text(text, *FontRenderer.FontSmall, color.White),
- widget.TextOpts.Position(widget.TextPositionCenter, widget.TextPositionCenter),
- widget.TextOpts.WidgetOpts(
- widget.WidgetOpts.LayoutData(widget.RowLayoutData{
- Position: widget.RowLayoutPositionCenter,
- }),
- ),
- )
-
-}
-
-/////////////// containers
-
-type RowContainer struct {
- Root *widget.Container
- Row *widget.Container
-}
-
-func (container *RowContainer) AddChild(child widget.PreferredSizeLocateableWidget) {
- container.Row.AddChild(child)
-}
-
-func (container *RowContainer) Container() *widget.Container {
- return container.Root
-}
-
-// set arg to false if no background needed
-func NewRowContainer(title string) *RowContainer {
- buttonImageHover := image.NewNineSlice(Assets["button-9slice3"], [3]int{3, 3, 3}, [3]int{3, 3, 3})
-
- uiContainer := widget.NewContainer(
- widget.ContainerOpts.Layout(widget.NewAnchorLayout()),
- )
-
- titleLabel := widget.NewText(
- widget.TextOpts.WidgetOpts(widget.WidgetOpts.LayoutData(widget.RowLayoutData{
- Stretch: true,
- })),
- widget.TextOpts.Text(title, *FontRenderer.FontNormal, color.NRGBA{0xdf, 0xf4, 0xff, 0xff}))
-
- rowContainer := widget.NewContainer(
- widget.ContainerOpts.WidgetOpts(
- widget.WidgetOpts.LayoutData(widget.AnchorLayoutData{
- HorizontalPosition: widget.AnchorLayoutPositionCenter,
- VerticalPosition: widget.AnchorLayoutPositionCenter,
- }),
- ),
- widget.ContainerOpts.Layout(widget.NewRowLayout(
- widget.RowLayoutOpts.Direction(widget.DirectionVertical),
- widget.RowLayoutOpts.Padding(widget.NewInsetsSimple(8)),
- widget.RowLayoutOpts.Spacing(0),
- )),
- widget.ContainerOpts.BackgroundImage(buttonImageHover),
- )
-
- rowContainer.AddChild(titleLabel)
-
- uiContainer.AddChild(rowContainer)
-
- return &RowContainer{
- Root: uiContainer,
- Row: rowContainer,
- }
-}
-
-func NewColumnContainer() *widget.Container {
- colcontainer := widget.NewContainer(
- widget.ContainerOpts.Layout(
- widget.NewGridLayout(
- widget.GridLayoutOpts.Columns(2),
- widget.GridLayoutOpts.Spacing(5, 0),
- ),
- ),
- )
-
- return colcontainer
-}
-
-func LoadButtonImage() (*widget.ButtonImage, error) {
- idle := image.NewNineSlice(Assets["button-9slice2"], [3]int{3, 3, 3}, [3]int{3, 3, 3})
- hover := image.NewNineSlice(Assets["button-9slice3"], [3]int{3, 3, 3}, [3]int{3, 3, 3})
- pressed := image.NewNineSlice(Assets["button-9slice1"], [3]int{3, 3, 3}, [3]int{3, 3, 3})
-
- return &widget.ButtonImage{
- Idle: idle,
- Hover: hover,
- Pressed: pressed,
- }, nil
-}
-
-func LoadComboLabelImage() *widget.ButtonImageImage {
- return &widget.ButtonImageImage{
- Idle: Assets["checkbox-9slice2"],
- Disabled: Assets["checkbox-9slice2"],
- }
-}
-
-func LoadCheckboxImage() (*widget.CheckboxGraphicImage, error) {
- unchecked := &widget.ButtonImageImage{
- Idle: Assets["checkbox-9slice2"],
- Disabled: Assets["checkbox-9slice2"],
- }
-
- checked := &widget.ButtonImageImage{
- Idle: Assets["checkbox-9slice1"],
- Disabled: Assets["checkbox-9slice1"],
- }
-
- return &widget.CheckboxGraphicImage{
- Checked: checked,
- Unchecked: unchecked,
- Greyed: unchecked,
- }, nil
-}
diff --git a/various-tests/README.md b/various-tests/README.md
deleted file mode 100644
index bf49ecf..0000000
--- a/various-tests/README.md
+++ /dev/null
@@ -1,12 +0,0 @@
-## Various performance tests
-
-Running with 1500x1500 grid 5k times
-
-| Variation | Description | Duration |
-|------------------------------|-----------------------------------------------------------------------------|-------------------|
-| perf-2dim | uses 2d grid of bools, no tuning | 00:03:14 |
-| perf-2dim-pointers | use 2d grid of `Cell{Neighbors,NeighborCount}`s using pointers to neighbors | 00:03:35/00:04:75 |
-| perf-2dim-pointers-array | same as above but array of neighbors instead of slice | 00:02:40 |
-| perf-2dim-pointers-all-array | use arrays for everything, static 1500x1500 | infinite, aborted |
-| perf-1dim | use 1d grid of bools, access using y*x, no further tuning | 00:03:24 |
-| perf-ecs | use arche ecs, unusable | 00:14:51 |
diff --git a/various-tests/drawtriangles/go.mod b/various-tests/drawtriangles/go.mod
deleted file mode 100644
index 7a41db3..0000000
--- a/various-tests/drawtriangles/go.mod
+++ /dev/null
@@ -1,13 +0,0 @@
-module drawminimal
-
-go 1.22
-
-require (
- github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 // indirect
- github.com/ebitengine/hideconsole v1.0.0 // indirect
- github.com/ebitengine/purego v0.7.0 // indirect
- github.com/hajimehoshi/ebiten/v2 v2.7.4 // indirect
- github.com/jezek/xgb v1.1.1 // indirect
- golang.org/x/sync v0.7.0 // indirect
- golang.org/x/sys v0.20.0 // indirect
-)
diff --git a/various-tests/drawtriangles/go.sum b/various-tests/drawtriangles/go.sum
deleted file mode 100644
index d3c38a0..0000000
--- a/various-tests/drawtriangles/go.sum
+++ /dev/null
@@ -1,14 +0,0 @@
-github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 h1:48bCqKTuD7Z0UovDfvpCn7wZ0GUZ+yosIteNDthn3FU=
-github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895/go.mod h1:XZdLv05c5hOZm3fM2NlJ92FyEZjnslcMcNRrhxs8+8M=
-github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE=
-github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A=
-github.com/ebitengine/purego v0.7.0 h1:HPZpl61edMGCEW6XK2nsR6+7AnJ3unUxpTZBkkIXnMc=
-github.com/ebitengine/purego v0.7.0/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
-github.com/hajimehoshi/ebiten/v2 v2.7.4 h1:X+heODRQ3Ie9F9QFjm24gEZqQd5FSfR9XuT2XfHwgf8=
-github.com/hajimehoshi/ebiten/v2 v2.7.4/go.mod h1:H2pHVgq29rfm5yeQ7jzWOM3VHsjo7/AyucODNLOhsVY=
-github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4=
-github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
-golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
-golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
-golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
-golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
diff --git a/various-tests/drawtriangles/main.go b/various-tests/drawtriangles/main.go
deleted file mode 100644
index 259ef74..0000000
--- a/various-tests/drawtriangles/main.go
+++ /dev/null
@@ -1,362 +0,0 @@
-package main
-
-import (
- "fmt"
- "image"
- "image/color"
- "log"
- "math/rand"
- "os"
- "runtime/pprof"
-
- "github.com/hajimehoshi/ebiten/v2"
- "github.com/hajimehoshi/ebiten/v2/inpututil"
- "github.com/hajimehoshi/ebiten/v2/vector"
-)
-
-var (
- blackImage = ebiten.NewImage(3, 3)
- blackSubImage = blackImage.SubImage(image.Rect(1, 1, 2, 2)).(*ebiten.Image)
-)
-
-type Images struct {
- Black, White *ebiten.Image
-}
-
-type Grid struct {
- Data [][]int64
- Width, Height, Density int
-}
-
-// Create new empty grid and allocate Data according to provided dimensions
-func NewGrid(width, height, density int) *Grid {
- grid := &Grid{
- Height: height,
- Width: width,
- Density: density,
- Data: make([][]int64, height),
- }
-
- for y := 0; y < height; y++ {
- grid.Data[y] = make([]int64, width)
- }
-
- return grid
-}
-
-// live console output of the grid
-func (grid *Grid) Dump() {
- /*
- cmd := exec.Command("clear")
- cmd.Stdout = os.Stdout
- cmd.Run()
-
- for y := 0; y < grid.Height; y++ {
- for x := 0; x < grid.Width; x++ {
- if grid.Data[y][x] == 1 {
- fmt.Print("XX")
- } else {
- fmt.Print(" ")
- }
- }
- fmt.Println()
- }
- */
- fmt.Printf("FPS: %0.2f\n", ebiten.ActualTPS())
-}
-
-type Game struct {
- Width, Height, Cellsize, Density int
- ScreenWidth, ScreenHeight int
- Grids []*Grid
- Index int
- Black, White, Grey color.RGBA
- Tiles Images
- Cache *ebiten.Image
- Elapsed int64
- TPG int64 // adjust game speed independently of TPS
- Vertices []ebiten.Vertex
- Indices []uint16
- Pause, Debug bool
-}
-
-// fill a cell
-func FillCell(tile *ebiten.Image, cellsize int, col color.RGBA) {
- vector.DrawFilledRect(
- tile,
- float32(1),
- float32(1),
- float32(cellsize-1),
- float32(cellsize-1),
- col, false,
- )
-}
-
-func (game *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
- return game.ScreenWidth, game.ScreenHeight
-}
-
-func (game *Game) Init() {
- // setup two grids, one for display, one for next state
- grida := NewGrid(game.Width, game.Height, game.Density)
- gridb := NewGrid(game.Width, game.Height, game.Density)
-
- for y := 0; y < game.Height; y++ {
- for x := 0; x < game.Width; x++ {
- if rand.Intn(game.Density) == 1 {
- grida.Data[y][x] = 1
- }
- }
-
- }
-
- game.Grids = []*Grid{
- grida,
- gridb,
- }
-
- // setup colors
- game.Grey = color.RGBA{128, 128, 128, 0xff}
- game.Black = color.RGBA{0, 0, 0, 0xff}
- game.White = color.RGBA{200, 200, 200, 0xff}
-
- game.Tiles.White = ebiten.NewImage(game.Cellsize, game.Cellsize)
- game.Cache = ebiten.NewImage(game.ScreenWidth, game.ScreenHeight)
-
- FillCell(game.Tiles.White, game.Cellsize, game.White)
- game.Cache.Fill(game.Grey)
-
- // draw the offscreen image
- op := &ebiten.DrawImageOptions{}
- for y := 0; y < game.Height; y++ {
- for x := 0; x < game.Width; x++ {
- op.GeoM.Reset()
- op.GeoM.Translate(float64(x*game.Cellsize), float64(y*game.Cellsize))
- game.Cache.DrawImage(game.Tiles.White, op)
- }
- }
-
- blackSubImage.Fill(game.Black)
-
- lenvertices := game.ScreenHeight * game.ScreenWidth
- game.Vertices = make([]ebiten.Vertex, lenvertices)
- game.Indices = make([]uint16, lenvertices+(lenvertices/2))
-}
-
-// count the living neighbors of a cell
-func (game *Game) CountNeighbors(x, y int) int64 {
- var sum int64
-
- for nbgX := -1; nbgX < 2; nbgX++ {
- for nbgY := -1; nbgY < 2; nbgY++ {
- var col, row int
-
- // Wrap mode we look at all the 8 neighbors surrounding
- // us. In case we are on an edge we'll look at the
- // neighbor on the other side of the grid, thus wrapping
- // lookahead around using the mod() function.
- col = (x + nbgX + game.Width) % game.Width
- row = (y + nbgY + game.Height) % game.Height
-
- sum += game.Grids[game.Index].Data[row][col]
- }
- }
-
- // don't count ourselfes though
- sum -= game.Grids[game.Index].Data[y][x]
-
- return sum
-}
-
-// the heart of the game
-func (game *Game) CheckRule(state int64, neighbors int64) int64 {
- var nextstate int64
-
- if state == 0 && neighbors == 3 {
- nextstate = 1
- } else if state == 1 && (neighbors == 2 || neighbors == 3) {
- nextstate = 1
- } else {
- nextstate = 0
- }
-
- return nextstate
-}
-
-// we only update the cells if we are not in pause state or if the
-// game timer (TPG) is elapsed.
-func (game *Game) UpdateCells() {
- if game.Pause {
- return
- }
-
- if game.Elapsed < game.TPG {
- game.Elapsed++
- return
- }
-
- // next grid index. we only have to, so we just xor it
- next := game.Index ^ 1
-
- // reset vertices
- // FIXME: fails!
- game.ClearVertices()
-
- // calculate cell life state, this is the actual game of life
- for y := 0; y < game.Height; y++ {
- for x := 0; x < game.Width; x++ {
- state := game.Grids[game.Index].Data[y][x] // 0|1 == dead or alive
- neighbors := game.CountNeighbors(x, y) // alive neighbor count
-
- // actually apply the current rules
- nextstate := game.CheckRule(state, neighbors)
-
- // change state of current cell in next grid
- game.Grids[next].Data[y][x] = nextstate
- }
- }
-
- // calculate triangles for rendering
- game.UpdateTriangles()
-
- // switch grid for rendering
- game.Index ^= 1
-
- game.Elapsed = 0
-
- if game.Debug {
- game.Grids[next].Dump()
- }
-}
-
-func (game *Game) Update() error {
- game.UpdateCells()
-
- if inpututil.IsKeyJustPressed(ebiten.KeySpace) {
- game.Pause = !game.Pause
- }
-
- return nil
-}
-
-func (game *Game) ClearVertices() {
- // FIXME: fails
- for i := 0; i < len(game.Vertices); i++ {
- game.Vertices[i] = ebiten.Vertex{}
- // game.Vertices[i].DstX = 0
- // game.Vertices[i].DstY = 1
- }
-
- game.Indices = game.Indices[:len(game.Indices)]
-}
-
-// create the triangles needed for rendering. Actual rendering doesn't
-// happen here but in Draw()
-func (game *Game) UpdateTriangles() {
- var base uint16 = 0
- var index uint16 = 0
-
- idx := 0
-
- // iterate over every cell
- for celly := 0; celly < game.Height; celly++ {
- for cellx := 0; cellx < game.Width; cellx++ {
-
- // if the cell is alife
- if game.Grids[game.Index].Data[celly][cellx] == 1 {
-
- /* iterate over the cell's corners:
- 0 1
-
- 2 3
- */
- for i := 0; i < 2; i++ {
- for j := 0; j < 2; j++ {
-
- // calculate the corner position
- x := (cellx * game.Cellsize) + (i * game.Cellsize) + 1
- y := (celly * game.Cellsize) + (j * game.Cellsize) + 1
-
- if i == 1 {
- x -= 1
- }
- if j == 1 {
- y -= 1
- }
-
- // setup the vertex
- game.Vertices[idx].DstX = float32(x)
- game.Vertices[idx].DstY = float32(y)
- game.Vertices[idx].SrcX = 1
- game.Vertices[idx].SrcY = 1
- game.Vertices[idx].ColorR = float32(game.Black.R)
- game.Vertices[idx].ColorG = float32(game.Black.G)
- game.Vertices[idx].ColorB = float32(game.Black.B)
- game.Vertices[idx].ColorA = 1
-
- idx++
- }
- }
- }
-
- // indices for first triangle
- game.Indices[index] = base
- game.Indices[index+1] = base + 1
- game.Indices[index+2] = base + 3
-
- // for the second one
- game.Indices[index+3] = base
- game.Indices[index+4] = base + 2
- game.Indices[index+5] = base + 3
-
- index += 6 // 3 indicies per triangle
-
- base += 4 // 4 vertices per cell
- }
- }
-}
-
-func (game *Game) Draw(screen *ebiten.Image) {
- op := &ebiten.DrawImageOptions{}
-
- op.GeoM.Translate(0, 0)
- screen.DrawImage(game.Cache, op)
-
- triop := &ebiten.DrawTrianglesOptions{}
- screen.DrawTriangles(game.Vertices, game.Indices, blackSubImage, triop)
-}
-
-func main() {
- size := 200
-
- game := &Game{
- Width: size,
- Height: size,
- Cellsize: 4,
- Density: 5,
- TPG: 5,
- Debug: true,
- }
-
- game.ScreenWidth = game.Width * game.Cellsize
- game.ScreenHeight = game.Height * game.Cellsize
-
- game.Init()
-
- ebiten.SetWindowSize(game.ScreenWidth, game.ScreenHeight)
- ebiten.SetWindowTitle("triangle conway's game of life")
- ebiten.SetWindowResizingMode(ebiten.WindowResizingModeEnabled)
-
- fd, err := os.Create("cpu.profile")
- if err != nil {
- log.Fatal(err)
- }
- defer fd.Close()
-
- pprof.StartCPUProfile(fd)
- defer pprof.StopCPUProfile()
-
- if err := ebiten.RunGame(game); err != nil {
- log.Fatal(err)
- }
-}
diff --git a/various-tests/perf-1dim/go.mod b/various-tests/perf-1dim/go.mod
deleted file mode 100644
index 400edf2..0000000
--- a/various-tests/perf-1dim/go.mod
+++ /dev/null
@@ -1,3 +0,0 @@
-module perf
-
-go 1.22
diff --git a/various-tests/perf-1dim/main.go b/various-tests/perf-1dim/main.go
deleted file mode 100644
index a9f8b74..0000000
--- a/various-tests/perf-1dim/main.go
+++ /dev/null
@@ -1,117 +0,0 @@
-package main
-
-import (
- "fmt"
- "log"
- "math/rand"
- "os"
- "runtime/pprof"
- "unsafe"
-)
-
-const (
- dim int = 1500
- loops int = 5000
- density int = 8
- debug bool = false
-)
-
-var max int
-
-// https://dev.to/chigbeef_77/bool-int-but-stupid-in-go-3jb3
-func bool2int(b bool) int {
- return int(*(*byte)(unsafe.Pointer(&b)))
-}
-
-func Count(grid []bool, x, y int) int {
- var sum int
-
- for nbgX := -1; nbgX < 2; nbgX++ {
- for nbgY := -1; nbgY < 2; nbgY++ {
- var col, row int
-
- if x+nbgX < 0 || x+nbgX >= dim || y+nbgY < 0 || y+nbgY >= dim {
- continue
- }
-
- col = x + nbgX
- row = y + nbgY
-
- state := grid[row*col]
- intstate := bool2int(state)
- sum += intstate
- }
- }
-
- sum -= bool2int(grid[y*x])
-
- return sum
-}
-
-func Init() []bool {
- max = dim * dim
-
- grid := make([]bool, max)
-
- for y := 0; y < dim; y++ {
- for x := 0; x < dim; x++ {
- if rand.Intn(density) == 1 {
- grid[y*x] = true
- }
- }
- }
-
- return grid
-}
-
-func Loop(grid []bool) {
- c := 0
- for i := 0; i < loops; i++ {
- for y := 0; y < dim; y++ {
- for x := 0; x < dim; x++ {
- state := grid[y*x]
- neighbors := Count(grid, x, y)
- if state && neighbors > 1 {
- if debug {
- fmt.Printf("Loop %d - cell at %d,%d is %t and has %d living neighbors\n", i, x, y, state, neighbors)
- }
- c = 1
- }
- }
- }
- }
-
- if c > 1 {
- c = 0
- }
-}
-
-func main() {
- grid := make([]int, 50*50)
-
- for y := 0; y < 50; y++ {
- for x := 0; x < 50; x++ {
- grid[y+50*x] = 1
- fmt.Printf("%d,%d => %d\n", x, y, x+50*y)
- }
- }
-}
-
-func xmain() {
- // enable cpu profiling. Do NOT use q to stop the game but
- // close the window to get a profile
- fd, err := os.Create("cpu.profile")
- if err != nil {
- log.Fatal(err)
- }
- defer fd.Close()
-
- pprof.StartCPUProfile(fd)
- defer pprof.StopCPUProfile()
-
- // init
- grid := Init()
-
- // main loop
- Loop(grid)
-}
diff --git a/various-tests/perf-2dim-pointers-all-array/go.mod b/various-tests/perf-2dim-pointers-all-array/go.mod
deleted file mode 100644
index 400edf2..0000000
--- a/various-tests/perf-2dim-pointers-all-array/go.mod
+++ /dev/null
@@ -1,3 +0,0 @@
-module perf
-
-go 1.22
diff --git a/various-tests/perf-2dim-pointers-all-array/main.go b/various-tests/perf-2dim-pointers-all-array/main.go
deleted file mode 100644
index 7bcb7fc..0000000
--- a/various-tests/perf-2dim-pointers-all-array/main.go
+++ /dev/null
@@ -1,141 +0,0 @@
-package main
-
-import (
- "fmt"
- "log"
- "math/rand"
- "os"
- "runtime/pprof"
- "time"
- "unsafe"
-)
-
-const (
- max int = 1500
- loops int = 5000
- density int = 8
- debug bool = false
-)
-
-type Cell struct {
- State bool
- Neighbors [8]*Cell
- NeighborCount int
-}
-
-type Grid [1500][1500]Cell
-
-// https://dev.to/chigbeef_77/bool-int-but-stupid-in-go-3jb3
-func bool2int(b bool) int {
- return int(*(*byte)(unsafe.Pointer(&b)))
-}
-
-func (cell *Cell) Count(x, y int) {
- cell.NeighborCount = 0
-
- for _, neighbor := range cell.Neighbors {
- cell.NeighborCount += bool2int(neighbor.State)
- }
-}
-
-func SetNeighbors(grid Grid, x, y int) {
- cells := []*Cell{}
- deadcell := &Cell{}
-
- for nbgX := -1; nbgX < 2; nbgX++ {
- for nbgY := -1; nbgY < 2; nbgY++ {
- var col, row int
-
- if x+nbgX < 0 || x+nbgX >= max || y+nbgY < 0 || y+nbgY >= max {
- cells = append(cells, deadcell)
- } else {
-
- col = x + nbgX
- row = y + nbgY
-
- if col == x && row == y {
- // do not add self
- continue
- }
-
- cells = append(cells, &grid[row][col])
- }
- }
- }
-
- for idx, cell := range cells {
- grid[y][x].Neighbors[idx] = cell
- }
-}
-
-func Init() Grid {
- grid := Grid{}
-
- for y := 0; y < max; y++ {
- for x := 0; x < max; x++ {
- if rand.Intn(density) == 1 {
- grid[y][x].State = true
- }
- }
- }
-
- for y := 0; y < max; y++ {
- for x := 0; x < max; x++ {
- SetNeighbors(grid, x, y)
- }
- }
-
- return grid
-}
-
-func Loop(grid Grid) {
- c := 0
- for i := 0; i < loops; i++ {
- for y := 0; y < max; y++ {
- for x := 0; x < max; x++ {
- cell := &grid[y][x]
- state := cell.State
-
- cell.Count(x, y)
-
- if state && cell.NeighborCount > 1 {
- if debug {
- fmt.Printf(
- "Loop %d - cell at %d,%d is %t and has %d living neighbors\n",
- i, x, y, state, cell.NeighborCount)
- }
- c = 1
- }
- }
- }
- }
-
- if c > 1 {
- c = 0
- }
-}
-
-func main() {
- // enable cpu profiling. Do NOT use q to stop the game but
- // close the window to get a profile
- fd, err := os.Create("cpu.profile")
- if err != nil {
- log.Fatal(err)
- }
- defer fd.Close()
-
- pprof.StartCPUProfile(fd)
- defer pprof.StopCPUProfile()
-
- // init
- grid := Init()
-
- // main loop
- loopstart := time.Now()
-
- Loop(grid)
-
- loopend := time.Now()
- diff := loopstart.Sub(loopend)
- fmt.Printf("Loop took %.04f\n", diff.Seconds())
-}
diff --git a/various-tests/perf-2dim-pointers-array/go.mod b/various-tests/perf-2dim-pointers-array/go.mod
deleted file mode 100644
index 400edf2..0000000
--- a/various-tests/perf-2dim-pointers-array/go.mod
+++ /dev/null
@@ -1,3 +0,0 @@
-module perf
-
-go 1.22
diff --git a/various-tests/perf-2dim-pointers-array/main.go b/various-tests/perf-2dim-pointers-array/main.go
deleted file mode 100644
index b03eac8..0000000
--- a/various-tests/perf-2dim-pointers-array/main.go
+++ /dev/null
@@ -1,139 +0,0 @@
-package main
-
-import (
- "fmt"
- "log"
- "math/rand"
- "os"
- "runtime/pprof"
- "time"
- "unsafe"
-)
-
-const (
- max int = 1500
- loops int = 5000
- density int = 8
- debug bool = false
-)
-
-type Cell struct {
- State bool
- Neighbors [8]*Cell
- NeighborCount int
-}
-
-// https://dev.to/chigbeef_77/bool-int-but-stupid-in-go-3jb3
-func bool2int(b bool) int {
- return int(*(*byte)(unsafe.Pointer(&b)))
-}
-
-func (cell *Cell) Count(x, y int) {
- cell.NeighborCount = 0
-
- for _, neighbor := range cell.Neighbors {
- cell.NeighborCount += bool2int(neighbor.State)
- }
-}
-
-func SetNeighbors(grid [][]Cell, x, y int) {
- cells := []*Cell{}
- deadcell := &Cell{}
-
- for nbgX := -1; nbgX < 2; nbgX++ {
- for nbgY := -1; nbgY < 2; nbgY++ {
- var col, row int
-
- if x+nbgX < 0 || x+nbgX >= max || y+nbgY < 0 || y+nbgY >= max {
- cells = append(cells, deadcell)
- } else {
-
- col = x + nbgX
- row = y + nbgY
-
- if col == x && row == y {
- // do not add self
- continue
- }
-
- cells = append(cells, &grid[row][col])
- }
- }
- }
-
- for idx, cell := range cells {
- grid[y][x].Neighbors[idx] = cell
- }
-}
-
-func Init() [][]Cell {
- grid := make([][]Cell, max)
- for y := 0; y < max; y++ {
- grid[y] = make([]Cell, max)
- for x := 0; x < max; x++ {
- if rand.Intn(density) == 1 {
- grid[y][x].State = true
- }
- }
- }
-
- for y := 0; y < max; y++ {
- for x := 0; x < max; x++ {
- SetNeighbors(grid, x, y)
- }
- }
-
- return grid
-}
-
-func Loop(grid [][]Cell) {
- c := 0
- for i := 0; i < loops; i++ {
- for y := 0; y < max; y++ {
- for x := 0; x < max; x++ {
- cell := &grid[y][x]
- state := cell.State
-
- cell.Count(x, y)
-
- if state && cell.NeighborCount > 1 {
- if debug {
- fmt.Printf(
- "Loop %d - cell at %d,%d is %t and has %d living neighbors\n",
- i, x, y, state, cell.NeighborCount)
- }
- c = 1
- }
- }
- }
- }
-
- if c > 1 {
- c = 0
- }
-}
-
-func main() {
- // enable cpu profiling. Do NOT use q to stop the game but
- // close the window to get a profile
- fd, err := os.Create("cpu.profile")
- if err != nil {
- log.Fatal(err)
- }
- defer fd.Close()
-
- pprof.StartCPUProfile(fd)
- defer pprof.StopCPUProfile()
-
- // init
- grid := Init()
-
- // main loop
- loopstart := time.Now()
-
- Loop(grid)
-
- loopend := time.Now()
- diff := loopstart.Sub(loopend)
- fmt.Printf("Loop took %.04f\n", diff.Seconds())
-}
diff --git a/various-tests/perf-2dim-pointers/go.mod b/various-tests/perf-2dim-pointers/go.mod
deleted file mode 100644
index 400edf2..0000000
--- a/various-tests/perf-2dim-pointers/go.mod
+++ /dev/null
@@ -1,3 +0,0 @@
-module perf
-
-go 1.22
diff --git a/various-tests/perf-2dim-pointers/main.go b/various-tests/perf-2dim-pointers/main.go
deleted file mode 100644
index 7faee95..0000000
--- a/various-tests/perf-2dim-pointers/main.go
+++ /dev/null
@@ -1,137 +0,0 @@
-package main
-
-import (
- "fmt"
- "log"
- "math/rand"
- "os"
- "runtime/pprof"
- "time"
- "unsafe"
-)
-
-const (
- max int = 1500
- loops int = 5000
- density int = 8
- debug bool = false
-)
-
-type Cell struct {
- State bool
- Neighbors []*Cell
- NeighborCount int
-}
-
-// https://dev.to/chigbeef_77/bool-int-but-stupid-in-go-3jb3
-func bool2int(b bool) int {
- return int(*(*byte)(unsafe.Pointer(&b)))
-}
-
-func (cell *Cell) Count(x, y int) {
- cell.NeighborCount = 0
-
- for _, neighbor := range cell.Neighbors {
- cell.NeighborCount += bool2int(neighbor.State)
- }
-}
-
-func SetNeighbors(grid [][]Cell, x, y int) {
- cells := []*Cell{}
-
- for nbgX := -1; nbgX < 2; nbgX++ {
- for nbgY := -1; nbgY < 2; nbgY++ {
- var col, row int
-
- if x+nbgX < 0 || x+nbgX >= max || y+nbgY < 0 || y+nbgY >= max {
- continue
- }
-
- col = x + nbgX
- row = y + nbgY
-
- if col == x && row == y {
- continue
- }
-
- cells = append(cells, &grid[row][col])
- }
- }
-
- grid[y][x].Neighbors = make([]*Cell, len(cells))
- for idx, cell := range cells {
- grid[y][x].Neighbors[idx] = cell
- }
-}
-
-func Init() [][]Cell {
- grid := make([][]Cell, max)
- for y := 0; y < max; y++ {
- grid[y] = make([]Cell, max)
- for x := 0; x < max; x++ {
- if rand.Intn(density) == 1 {
- grid[y][x].State = true
- }
- }
- }
-
- for y := 0; y < max; y++ {
- for x := 0; x < max; x++ {
- SetNeighbors(grid, x, y)
- }
- }
-
- return grid
-}
-
-func Loop(grid [][]Cell) {
- c := 0
- for i := 0; i < loops; i++ {
- for y := 0; y < max; y++ {
- for x := 0; x < max; x++ {
- cell := &grid[y][x]
- state := cell.State
-
- cell.Count(x, y)
-
- if state && cell.NeighborCount > 1 {
- if debug {
- fmt.Printf(
- "Loop %d - cell at %d,%d is %t and has %d living neighbors\n",
- i, x, y, state, cell.NeighborCount)
- }
- c = 1
- }
- }
- }
- }
-
- if c > 1 {
- c = 0
- }
-}
-
-func main() {
- // enable cpu profiling. Do NOT use q to stop the game but
- // close the window to get a profile
- fd, err := os.Create("cpu.profile")
- if err != nil {
- log.Fatal(err)
- }
- defer fd.Close()
-
- pprof.StartCPUProfile(fd)
- defer pprof.StopCPUProfile()
-
- // init
- grid := Init()
-
- // main loop
- loopstart := time.Now()
-
- Loop(grid)
-
- loopend := time.Now()
- diff := loopstart.Sub(loopend)
- fmt.Printf("Loop took %.04f\n", diff.Seconds())
-}
diff --git a/various-tests/perf-2dim/go.mod b/various-tests/perf-2dim/go.mod
deleted file mode 100644
index 400edf2..0000000
--- a/various-tests/perf-2dim/go.mod
+++ /dev/null
@@ -1,3 +0,0 @@
-module perf
-
-go 1.22
diff --git a/various-tests/perf-2dim/main.go b/various-tests/perf-2dim/main.go
deleted file mode 100644
index 2e7f82d..0000000
--- a/various-tests/perf-2dim/main.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package main
-
-import (
- "fmt"
- "log"
- "math/rand"
- "os"
- "runtime/pprof"
- "unsafe"
-)
-
-const (
- max int = 1500
- loops int = 5000
- density int = 8
- debug bool = false
-)
-
-// https://dev.to/chigbeef_77/bool-int-but-stupid-in-go-3jb3
-func bool2int(b bool) int {
- return int(*(*byte)(unsafe.Pointer(&b)))
-}
-
-func Count(grid [][]bool, x, y int) int {
- var sum int
-
- for nbgX := -1; nbgX < 2; nbgX++ {
- for nbgY := -1; nbgY < 2; nbgY++ {
- var col, row int
-
- if x+nbgX < 0 || x+nbgX >= max || y+nbgY < 0 || y+nbgY >= max {
- continue
- }
-
- col = x + nbgX
- row = y + nbgY
-
- state := grid[row][col]
- intstate := bool2int(state)
- sum += intstate
- }
- }
-
- sum -= bool2int(grid[y][x])
-
- return sum
-}
-
-func Init() [][]bool {
- grid := make([][]bool, max)
- for y := 0; y < max; y++ {
- grid[y] = make([]bool, max)
- for x := 0; x < max; x++ {
- if rand.Intn(density) == 1 {
- grid[y][x] = true
- }
- }
- }
-
- return grid
-}
-
-func Loop(grid [][]bool) {
- c := 0
- for i := 0; i < loops; i++ {
- for y := 0; y < max; y++ {
- for x := 0; x < max; x++ {
- state := grid[y][x]
- neighbors := Count(grid, x, y)
- if state && neighbors > 1 {
- if debug {
- fmt.Printf("Loop %d - cell at %d,%d is %t and has %d living neighbors\n", i, x, y, state, neighbors)
- }
- c = 1
- }
- }
- }
- }
-
- if c > 1 {
- c = 0
- }
-}
-
-func main() {
- // enable cpu profiling. Do NOT use q to stop the game but
- // close the window to get a profile
- fd, err := os.Create("cpu.profile")
- if err != nil {
- log.Fatal(err)
- }
- defer fd.Close()
-
- pprof.StartCPUProfile(fd)
- defer pprof.StopCPUProfile()
-
- // init
- grid := Init()
-
- // main loop
- Loop(grid)
-}
diff --git a/various-tests/perf-ecs/go.mod b/various-tests/perf-ecs/go.mod
deleted file mode 100644
index 82fd6e7..0000000
--- a/various-tests/perf-ecs/go.mod
+++ /dev/null
@@ -1,5 +0,0 @@
-module perf
-
-go 1.22
-
-require github.com/mlange-42/arche v0.13.0 // indirect
diff --git a/various-tests/perf-ecs/go.sum b/various-tests/perf-ecs/go.sum
deleted file mode 100644
index 8e955c4..0000000
--- a/various-tests/perf-ecs/go.sum
+++ /dev/null
@@ -1,2 +0,0 @@
-github.com/mlange-42/arche v0.13.0 h1:ef0fu9qC2KIr8wIlVs+CgeQ5CSUJ8A1Hut6nXYdf+xk=
-github.com/mlange-42/arche v0.13.0/go.mod h1:bFktKnvGDj2kP01xar79z0hKwGHdnoaEZR8HWmJkIyU=
diff --git a/various-tests/perf-ecs/main.go b/various-tests/perf-ecs/main.go
deleted file mode 100644
index ced59ba..0000000
--- a/various-tests/perf-ecs/main.go
+++ /dev/null
@@ -1,145 +0,0 @@
-package main
-
-import (
- "fmt"
- "log"
- "math/rand"
- "os"
- "runtime/pprof"
-
- "github.com/mlange-42/arche/ecs"
- "github.com/mlange-42/arche/generic"
-)
-
-const (
- max int = 1500
- loops int = 5000
- density int = 8
- debug bool = false
-)
-
-// components
-type Pos struct {
- X, Y, GridX, GridY int
-}
-
-type Cell struct {
- State bool
- Neighbors [8]ecs.Entity
-}
-
-type ECS struct {
- World *ecs.World
- Filter *generic.Filter2[Pos, Cell]
- Map *generic.Map2[Pos, Cell]
-}
-
-func (cell *Cell) NeighborCount(ECS *ECS) int {
- sum := 0
-
- for _, neighbor := range cell.Neighbors {
- if ECS.World.Alive(neighbor) {
- _, cel := ECS.Map.Get(neighbor)
- if cel.State {
- sum++
- }
- }
- }
-
- return sum
-}
-
-func Loop(ECS *ECS) {
- c := 0
-
- for i := 0; i < loops; i++ {
- query := ECS.Filter.Query(ECS.World)
-
- for query.Next() {
- _, cel := query.Get()
- if cel.State && cel.NeighborCount(ECS) > 1 {
- c = 1
- }
- }
- }
-
- if c > 1 {
- c = 0
- }
-}
-
-func SetupWorld() *ECS {
- world := ecs.NewWorld()
-
- builder := generic.NewMap2[Pos, Cell](&world)
-
- // we need a temporary grid in order to find out neighbors
- grid := [max][max]ecs.Entity{}
-
- // setup entities
- for y := 0; y < max; y++ {
- for x := 0; x < max; x++ {
- e := builder.New()
- pos, cell := builder.Get(e)
- pos.X = x
- pos.Y = y // pos.GridX = x*cellsize
-
- cell.State = false
- if rand.Intn(density) == 1 {
- cell.State = true
- }
-
- // store to tmp grid
- grid[y][x] = e
- }
- }
-
- // global filter
- filter := generic.NewFilter2[Pos, Cell]()
-
- query := filter.Query(&world)
-
- for query.Next() {
- pos, cel := query.Get()
-
- n := 0
- for x := -1; x < 2; x++ {
- for y := -1; y < 2; y++ {
- XX := pos.X + x
- YY := pos.Y + y
- if XX < 0 || XX >= max || YY < 0 || YY >= max {
- continue
- }
-
- if pos.X != XX || pos.Y != YY {
- cel.Neighbors[n] = grid[XX][YY]
- n++
- }
- }
- }
- }
-
- return &ECS{World: &world, Filter: filter, Map: &builder}
-}
-
-func main() {
- // enable cpu profiling. Do NOT use q to stop the game but
- // close the window to get a profile
- fd, err := os.Create("cpu.profile")
- if err != nil {
- log.Fatal(err)
- }
- defer fd.Close()
-
- pprof.StartCPUProfile(fd)
- defer pprof.StopCPUProfile()
-
- // init
- fmt.Print("Setup ... ")
- ECS := SetupWorld()
- fmt.Println("done")
- fmt.Println(ECS.World.Stats())
-
- // main loop
- Loop(ECS)
-}
diff --git a/various-tests/raygol/.gitignore b/various-tests/raygol/.gitignore
deleted file mode 100644
index 22248d7..0000000
--- a/various-tests/raygol/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-golsky
-*.o
diff --git a/various-tests/raygol/Makefile b/various-tests/raygol/Makefile
deleted file mode 100644
index dd4341b..0000000
--- a/various-tests/raygol/Makefile
+++ /dev/null
@@ -1,33 +0,0 @@
-CFLAGS = -Wall -Wextra -Werror -O2 -g
-LDFLAGS= -L/usr/local/lib -lraylib -lGL -lm -lpthread -ldl -lrt -lX11 -g
-CC = clang
-OBJS = main.o game.o grid.o
-DST = golsky
-PREFIX = /usr/local
-UID = root
-GID = 0
-MAN = udpxd.1
-
-.PHONY: all
-all: $(DST)
-
-$(DST): $(OBJS)
- $(CC) $(OBJS) $(LDFLAGS) -o $(DST)
-
-%.o: %.c
- $(CC) -c $(CFLAGS) $*.c -o $*.o
-
-.PHONY: clean
-clean:
- rm -f *.o $(DST)
-
-.PHONY: install
-install: $(DST)
- install -d -o $(UID) -g $(GID) $(PREFIX)/sbin
- install -d -o $(UID) -g $(GID) $(PREFIX)/man/man1
- install -o $(UID) -g $(GID) -m 555 $(DST) $(PREFIX)/sbin/
- install -o $(UID) -g $(GID) -m 444 $(MAN) $(PREFIX)/man/man1/
-
-.PHONY: run
-run:
- LD_LIBRARY_PATH=/usr/local/lib ./golsky
diff --git a/various-tests/raygol/game.c b/various-tests/raygol/game.c
deleted file mode 100644
index db697e4..0000000
--- a/various-tests/raygol/game.c
+++ /dev/null
@@ -1,48 +0,0 @@
-#include "game.h"
-#include
-
-Game *Init(int width, int height, int gridwidth, int gridheight, int density) {
- struct Game *game = malloc(sizeof(struct Game));
-
- game->ScreenWidth = width;
- game->ScreenHeight = height;
- game->Cellsize = width / gridwidth;
- game->Width = gridwidth;
- game->Height = gridheight;
-
- InitWindow(width, height, "golsky");
- SetTargetFPS(60);
-
- game->Grid = NewGrid(gridwidth, gridheight, density);
-
- return game;
-}
-
-void Update(Game *game) {
- if (IsKeyDown(KEY_Q)) {
- game->Done = true;
- exit(0);
- }
-}
-
-void Draw(Game *game) {
- BeginDrawing();
-
- ClearBackground(RAYWHITE);
-
- for (int y = 0; y < game->Width; y++) {
- for (int x = 0; x < game->Height; x++) {
- if (game->Grid->Data[y][x] == 1) {
- DrawRectangle(x * game->Cellsize, y * game->Cellsize, game->Cellsize,
- game->Cellsize, GREEN);
- } else {
- DrawRectangle(x * game->Cellsize, y * game->Cellsize, game->Cellsize,
- game->Cellsize, RAYWHITE);
- }
- }
- }
-
- DrawText("TEST", game->ScreenWidth / 2, 10, 20, RED);
-
- EndDrawing();
-}
diff --git a/various-tests/raygol/game.h b/various-tests/raygol/game.h
deleted file mode 100644
index 3f2b21e..0000000
--- a/various-tests/raygol/game.h
+++ /dev/null
@@ -1,25 +0,0 @@
-#ifndef _HAVE_GAME_H
-#define _HAVE_GAME_H
-
-#include "grid.h"
-#include "raylib.h"
-#include
-
-typedef struct Game {
- // Camera2D Camera;
- int ScreenWidth;
- int ScreenHeight;
- int Cellsize;
-
- // Grid dimensions
- int Width;
- int Height;
- bool Done;
- Grid *Grid;
-} Game;
-
-Game *Init(int width, int height, int gridwidth, int gridheight, int density);
-void Update(Game *game);
-void Draw(Game *game);
-
-#endif
diff --git a/various-tests/raygol/grid.c b/various-tests/raygol/grid.c
deleted file mode 100644
index d3e5321..0000000
--- a/various-tests/raygol/grid.c
+++ /dev/null
@@ -1,28 +0,0 @@
-#include "grid.h"
-
-Grid *NewGrid(int width, int height, int density) {
- Grid *grid = malloc(sizeof(struct Grid));
- grid->Width = width;
- grid->Height = height;
- grid->Density = density;
-
- grid->Data = malloc(height * sizeof(int *));
- for (int y = 0; y < grid->Height; y++) {
- grid->Data[y] = malloc(width * sizeof(int *));
- }
-
- FillRandom(grid);
-
- return grid;
-}
-
-void FillRandom(Grid *grid) {
- int r;
- for (int y = 0; y < grid->Width; y++) {
- for (int x = 0; x < grid->Height; x++) {
- r = GetRandomValue(0, grid->Density);
- if (r == 1)
- grid->Data[y][x] = r;
- }
- }
-}
diff --git a/various-tests/raygol/grid.h b/various-tests/raygol/grid.h
deleted file mode 100644
index d42d504..0000000
--- a/various-tests/raygol/grid.h
+++ /dev/null
@@ -1,18 +0,0 @@
-#ifndef _HAVE_GRID_H
-#define _HAVE_GRID_H
-
-#include "raylib.h"
-#include
-#include
-
-typedef struct Grid {
- int Width;
- int Height;
- int Density;
- int **Data;
-} Grid;
-
-Grid *NewGrid(int width, int height, int density);
-void FillRandom(Grid *grid);
-
-#endif
diff --git a/various-tests/raygol/main.c b/various-tests/raygol/main.c
deleted file mode 100644
index 48b3619..0000000
--- a/various-tests/raygol/main.c
+++ /dev/null
@@ -1,15 +0,0 @@
-#include "game.h"
-#include "raylib.h"
-
-int main(void) {
- Game *game = Init(800, 800, 10, 10, 8);
-
- while (!WindowShouldClose()) {
- Update(game);
- Draw(game);
- }
-
- CloseWindow();
- free(game);
- return 0;
-}
diff --git a/various-tests/writepixel-pointers-int/go.mod b/various-tests/writepixel-pointers-int/go.mod
deleted file mode 100644
index cde79f5..0000000
--- a/various-tests/writepixel-pointers-int/go.mod
+++ /dev/null
@@ -1,13 +0,0 @@
-module testgol
-
-go 1.22
-
-require (
- github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 // indirect
- github.com/ebitengine/hideconsole v1.0.0 // indirect
- github.com/ebitengine/purego v0.7.0 // indirect
- github.com/hajimehoshi/ebiten/v2 v2.7.4 // indirect
- github.com/jezek/xgb v1.1.1 // indirect
- golang.org/x/sync v0.7.0 // indirect
- golang.org/x/sys v0.20.0 // indirect
-)
diff --git a/various-tests/writepixel-pointers-int/go.sum b/various-tests/writepixel-pointers-int/go.sum
deleted file mode 100644
index d3c38a0..0000000
--- a/various-tests/writepixel-pointers-int/go.sum
+++ /dev/null
@@ -1,14 +0,0 @@
-github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 h1:48bCqKTuD7Z0UovDfvpCn7wZ0GUZ+yosIteNDthn3FU=
-github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895/go.mod h1:XZdLv05c5hOZm3fM2NlJ92FyEZjnslcMcNRrhxs8+8M=
-github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE=
-github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A=
-github.com/ebitengine/purego v0.7.0 h1:HPZpl61edMGCEW6XK2nsR6+7AnJ3unUxpTZBkkIXnMc=
-github.com/ebitengine/purego v0.7.0/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
-github.com/hajimehoshi/ebiten/v2 v2.7.4 h1:X+heODRQ3Ie9F9QFjm24gEZqQd5FSfR9XuT2XfHwgf8=
-github.com/hajimehoshi/ebiten/v2 v2.7.4/go.mod h1:H2pHVgq29rfm5yeQ7jzWOM3VHsjo7/AyucODNLOhsVY=
-github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4=
-github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
-golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
-golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
-golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
-golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
diff --git a/various-tests/writepixel-pointers-int/main.go b/various-tests/writepixel-pointers-int/main.go
deleted file mode 100644
index 5ea5e23..0000000
--- a/various-tests/writepixel-pointers-int/main.go
+++ /dev/null
@@ -1,306 +0,0 @@
-package main
-
-import (
- "fmt"
- "log"
- "math/rand"
- "os"
- "os/exec"
- "runtime/pprof"
- "unsafe"
-
- "github.com/hajimehoshi/ebiten/v2"
- "github.com/hajimehoshi/ebiten/v2/inpututil"
-)
-
-type Images struct {
- Black, White *ebiten.Image
-}
-
-type Cell struct {
- State uint8
- Neighbors [8]*Cell
- NeighborCount int
-}
-
-func bool2int(b bool) int {
- return int(*(*byte)(unsafe.Pointer(&b)))
-}
-
-func (cell *Cell) Count(x, y int) uint8 {
- var sum uint8
-
- for idx := 0; idx < cell.NeighborCount; idx++ {
- sum += cell.Neighbors[idx].State
- }
-
- return sum
-}
-
-func SetNeighbors(grid [][]*Cell, x, y, width, height int) {
- idx := 0
- for nbgX := -1; nbgX < 2; nbgX++ {
- for nbgY := -1; nbgY < 2; nbgY++ {
- var col, row int
-
- if x+nbgX < 0 || x+nbgX >= width || y+nbgY < 0 || y+nbgY >= height {
- continue
- }
-
- col = x + nbgX
- row = y + nbgY
-
- if col == x && row == y {
- continue
- }
-
- grid[y][x].Neighbors[idx] = grid[row][col]
- grid[y][x].NeighborCount++
- idx++
- }
- }
-}
-
-type Grid struct {
- Data [][]*Cell
- Width, Height, Density int
-}
-
-// Create new empty grid and allocate Data according to provided dimensions
-func NewGrid(width, height, density int) *Grid {
- grid := &Grid{
- Height: height,
- Width: width,
- Density: density,
- Data: make([][]*Cell, height),
- }
-
- for y := 0; y < height; y++ {
- grid.Data[y] = make([]*Cell, width)
- for x := 0; x < width; x++ {
- grid.Data[y][x] = &Cell{}
-
- if rand.Intn(density) == 1 {
- grid.Data[y][x].State = 1
- }
- }
- }
-
- for y := 0; y < height; y++ {
- for x := 0; x < width; x++ {
- SetNeighbors(grid.Data, x, y, width, height)
- }
- }
-
- return grid
-}
-
-type Game struct {
- Width, Height, Cellsize, Density int
- ScreenWidth, ScreenHeight int
- Grids []*Grid
- Index int
- Elapsed int64
- TPG int64 // adjust game speed independently of TPS
- Pause, Debug, Profile, Gridlines bool
- Pixels []byte
- OffScreen *ebiten.Image
-}
-
-func (game *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
- return game.ScreenWidth, game.ScreenHeight
-}
-
-// live console output of the grid
-func (game *Game) DebugDump() {
- cmd := exec.Command("clear")
- cmd.Stdout = os.Stdout
- cmd.Run()
-
- if game.Debug {
- for y := 0; y < game.Height; y++ {
- for x := 0; x < game.Width; x++ {
- if game.Grids[game.Index].Data[y][x].State == 1 {
- fmt.Print("XX")
- } else {
- fmt.Print(" ")
- }
- }
- fmt.Println()
- }
- }
- fmt.Printf("FPS: %0.2f\n", ebiten.ActualTPS())
-}
-
-func (game *Game) Init() {
- // setup two grids, one for display, one for next state
- grida := NewGrid(game.Width, game.Height, game.Density)
- gridb := NewGrid(game.Width, game.Height, game.Density)
-
- game.Grids = []*Grid{
- grida,
- gridb,
- }
-
- game.Pixels = make([]byte, game.ScreenWidth*game.ScreenHeight*4)
-
- game.OffScreen = ebiten.NewImage(game.ScreenWidth, game.ScreenHeight)
-}
-
-// count the living neighbors of a cell
-func (game *Game) CountNeighbors(x, y int) uint8 {
- return game.Grids[game.Index].Data[y][x].Count(x, y)
-}
-
-// the heart of the game
-func (game *Game) CheckRule(state uint8, neighbors uint8) uint8 {
- var nextstate uint8
-
- if state == 1 && neighbors == 3 {
- nextstate = 1
- } else if state == 1 && (neighbors == 2 || neighbors == 3) {
- nextstate = 1
- } else {
- nextstate = 0
- }
-
- return nextstate
-}
-
-// we only update the cells if we are not in pause state or if the
-// game timer (TPG) is elapsed.
-func (game *Game) UpdateCells() {
- if game.Pause {
- return
- }
-
- if game.Elapsed < game.TPG {
- game.Elapsed++
- return
- }
-
- // next grid index. we only have to, so we just xor it
- next := game.Index ^ 1
-
- // calculate cell life state, this is the actual game of life
- for y := 0; y < game.Height; y++ {
- for x := 0; x < game.Width; x++ {
- state := game.Grids[game.Index].Data[y][x].State
- neighbors := game.CountNeighbors(x, y)
-
- // actually apply the current rules
- nextstate := game.CheckRule(state, neighbors)
-
- // change state of current cell in next grid
- game.Grids[next].Data[y][x].State = nextstate
- }
- }
-
- // switch grid for rendering
- game.Index ^= 1
-
- game.Elapsed = 0
-
- game.UpdatePixels()
-}
-
-func (game *Game) Update() error {
- game.UpdateCells()
-
- if inpututil.IsKeyJustPressed(ebiten.KeySpace) {
- game.Pause = !game.Pause
- }
-
- return nil
-}
-
-/*
-*
-r, g, b := color(it)
-
- 78 p := 4 * (i + j*screenWidth)
- 79 gm.offscreenPix[p] = r
- 80 gm.offscreenPix[p+1] = g
- 81 gm.offscreenPix[p+2] = b
- 82 gm.offscreenPix[p+3] = 0xff
-*/
-func (game *Game) UpdatePixels() {
- var col byte
-
- gridx := 0
- gridy := 0
- idx := 0
-
- for y := 0; y < game.ScreenHeight; y++ {
- for x := 0; x < game.ScreenWidth; x++ {
- gridx = x / game.Cellsize
- gridy = y / game.Cellsize
-
- col = 0xff
- if game.Grids[game.Index].Data[gridy][gridx].State == 1 {
- col = 0x0
- }
-
- if game.Gridlines {
- if x%game.Cellsize == 0 || y%game.Cellsize == 0 {
- col = 128
- }
- }
-
- idx = 4 * (x + y*game.ScreenWidth)
-
- game.Pixels[idx] = col
- game.Pixels[idx+1] = col
- game.Pixels[idx+2] = col
- game.Pixels[idx+3] = 0xff
-
- idx++
- }
- }
-
- game.OffScreen.WritePixels(game.Pixels)
-}
-
-func (game *Game) Draw(screen *ebiten.Image) {
- screen.DrawImage(game.OffScreen, nil)
- game.DebugDump()
-}
-
-func main() {
- size := 1500
-
- game := &Game{
- Width: size,
- Height: size,
- Cellsize: 4,
- Density: 8,
- TPG: 10,
- Debug: false,
- Profile: true,
- Gridlines: false,
- }
-
- game.ScreenWidth = game.Width * game.Cellsize
- game.ScreenHeight = game.Height * game.Cellsize
-
- game.Init()
-
- ebiten.SetWindowSize(game.ScreenWidth, game.ScreenHeight)
- ebiten.SetWindowTitle("triangle conway's game of life")
- ebiten.SetWindowResizingMode(ebiten.WindowResizingModeEnabled)
-
- if game.Profile {
- fd, err := os.Create("cpu.profile")
- if err != nil {
- log.Fatal(err)
- }
- defer fd.Close()
-
- pprof.StartCPUProfile(fd)
- defer pprof.StopCPUProfile()
- }
-
- if err := ebiten.RunGame(game); err != nil {
- log.Fatal(err)
- }
-}
diff --git a/various-tests/writepixel-pointers/go.mod b/various-tests/writepixel-pointers/go.mod
deleted file mode 100644
index cde79f5..0000000
--- a/various-tests/writepixel-pointers/go.mod
+++ /dev/null
@@ -1,13 +0,0 @@
-module testgol
-
-go 1.22
-
-require (
- github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 // indirect
- github.com/ebitengine/hideconsole v1.0.0 // indirect
- github.com/ebitengine/purego v0.7.0 // indirect
- github.com/hajimehoshi/ebiten/v2 v2.7.4 // indirect
- github.com/jezek/xgb v1.1.1 // indirect
- golang.org/x/sync v0.7.0 // indirect
- golang.org/x/sys v0.20.0 // indirect
-)
diff --git a/various-tests/writepixel-pointers/go.sum b/various-tests/writepixel-pointers/go.sum
deleted file mode 100644
index d3c38a0..0000000
--- a/various-tests/writepixel-pointers/go.sum
+++ /dev/null
@@ -1,14 +0,0 @@
-github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 h1:48bCqKTuD7Z0UovDfvpCn7wZ0GUZ+yosIteNDthn3FU=
-github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895/go.mod h1:XZdLv05c5hOZm3fM2NlJ92FyEZjnslcMcNRrhxs8+8M=
-github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE=
-github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A=
-github.com/ebitengine/purego v0.7.0 h1:HPZpl61edMGCEW6XK2nsR6+7AnJ3unUxpTZBkkIXnMc=
-github.com/ebitengine/purego v0.7.0/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
-github.com/hajimehoshi/ebiten/v2 v2.7.4 h1:X+heODRQ3Ie9F9QFjm24gEZqQd5FSfR9XuT2XfHwgf8=
-github.com/hajimehoshi/ebiten/v2 v2.7.4/go.mod h1:H2pHVgq29rfm5yeQ7jzWOM3VHsjo7/AyucODNLOhsVY=
-github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4=
-github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
-golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
-golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
-golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
-golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
diff --git a/various-tests/writepixel-pointers/main.go b/various-tests/writepixel-pointers/main.go
deleted file mode 100644
index 20ed058..0000000
--- a/various-tests/writepixel-pointers/main.go
+++ /dev/null
@@ -1,306 +0,0 @@
-package main
-
-import (
- "fmt"
- "log"
- "math/rand"
- "os"
- "os/exec"
- "runtime/pprof"
- "unsafe"
-
- "github.com/hajimehoshi/ebiten/v2"
- "github.com/hajimehoshi/ebiten/v2/inpututil"
-)
-
-type Images struct {
- Black, White *ebiten.Image
-}
-
-type Cell struct {
- State bool
- Neighbors [8]*Cell
- NeighborCount int
-}
-
-func bool2int(b bool) int {
- return int(*(*byte)(unsafe.Pointer(&b)))
-}
-
-func (cell *Cell) Count(x, y int) int {
- sum := 0
-
- for idx := 0; idx < cell.NeighborCount; idx++ {
- sum += bool2int(cell.Neighbors[idx].State)
- }
-
- return sum
-}
-
-func SetNeighbors(grid [][]*Cell, x, y, width, height int) {
- idx := 0
- for nbgX := -1; nbgX < 2; nbgX++ {
- for nbgY := -1; nbgY < 2; nbgY++ {
- var col, row int
-
- if x+nbgX < 0 || x+nbgX >= width || y+nbgY < 0 || y+nbgY >= height {
- continue
- }
-
- col = x + nbgX
- row = y + nbgY
-
- if col == x && row == y {
- continue
- }
-
- grid[y][x].Neighbors[idx] = grid[row][col]
- grid[y][x].NeighborCount++
- idx++
- }
- }
-}
-
-type Grid struct {
- Data [][]*Cell
- Width, Height, Density int
-}
-
-// Create new empty grid and allocate Data according to provided dimensions
-func NewGrid(width, height, density int) *Grid {
- grid := &Grid{
- Height: height,
- Width: width,
- Density: density,
- Data: make([][]*Cell, height),
- }
-
- for y := 0; y < height; y++ {
- grid.Data[y] = make([]*Cell, width)
- for x := 0; x < width; x++ {
- grid.Data[y][x] = &Cell{}
-
- if rand.Intn(density) == 1 {
- grid.Data[y][x].State = true
- }
- }
- }
-
- for y := 0; y < height; y++ {
- for x := 0; x < width; x++ {
- SetNeighbors(grid.Data, x, y, width, height)
- }
- }
-
- return grid
-}
-
-type Game struct {
- Width, Height, Cellsize, Density int
- ScreenWidth, ScreenHeight int
- Grids []*Grid
- Index int
- Elapsed int64
- TPG int64 // adjust game speed independently of TPS
- Pause, Debug, Profile, Gridlines bool
- Pixels []byte
- OffScreen *ebiten.Image
-}
-
-func (game *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
- return game.ScreenWidth, game.ScreenHeight
-}
-
-// live console output of the grid
-func (game *Game) DebugDump() {
- cmd := exec.Command("clear")
- cmd.Stdout = os.Stdout
- cmd.Run()
-
- if game.Debug {
- for y := 0; y < game.Height; y++ {
- for x := 0; x < game.Width; x++ {
- if game.Grids[game.Index].Data[y][x].State {
- fmt.Print("XX")
- } else {
- fmt.Print(" ")
- }
- }
- fmt.Println()
- }
- }
- fmt.Printf("FPS: %0.2f\n", ebiten.ActualTPS())
-}
-
-func (game *Game) Init() {
- // setup two grids, one for display, one for next state
- grida := NewGrid(game.Width, game.Height, game.Density)
- gridb := NewGrid(game.Width, game.Height, game.Density)
-
- game.Grids = []*Grid{
- grida,
- gridb,
- }
-
- game.Pixels = make([]byte, game.ScreenWidth*game.ScreenHeight*4)
-
- game.OffScreen = ebiten.NewImage(game.ScreenWidth, game.ScreenHeight)
-}
-
-// count the living neighbors of a cell
-func (game *Game) CountNeighbors(x, y int) int {
- return game.Grids[game.Index].Data[y][x].Count(x, y)
-}
-
-// the heart of the game
-func (game *Game) CheckRule(state bool, neighbors int) bool {
- var nextstate bool
-
- if state && neighbors == 3 {
- nextstate = true
- } else if state && (neighbors == 2 || neighbors == 3) {
- nextstate = true
- } else {
- nextstate = false
- }
-
- return nextstate
-}
-
-// we only update the cells if we are not in pause state or if the
-// game timer (TPG) is elapsed.
-func (game *Game) UpdateCells() {
- if game.Pause {
- return
- }
-
- if game.Elapsed < game.TPG {
- game.Elapsed++
- return
- }
-
- // next grid index. we only have to, so we just xor it
- next := game.Index ^ 1
-
- // calculate cell life state, this is the actual game of life
- for y := 0; y < game.Height; y++ {
- for x := 0; x < game.Width; x++ {
- state := game.Grids[game.Index].Data[y][x].State
- neighbors := game.CountNeighbors(x, y)
-
- // actually apply the current rules
- nextstate := game.CheckRule(state, neighbors)
-
- // change state of current cell in next grid
- game.Grids[next].Data[y][x].State = nextstate
- }
- }
-
- // switch grid for rendering
- game.Index ^= 1
-
- game.Elapsed = 0
-
- game.UpdatePixels()
-}
-
-func (game *Game) Update() error {
- game.UpdateCells()
-
- if inpututil.IsKeyJustPressed(ebiten.KeySpace) {
- game.Pause = !game.Pause
- }
-
- return nil
-}
-
-/*
-*
-r, g, b := color(it)
-
- 78 p := 4 * (i + j*screenWidth)
- 79 gm.offscreenPix[p] = r
- 80 gm.offscreenPix[p+1] = g
- 81 gm.offscreenPix[p+2] = b
- 82 gm.offscreenPix[p+3] = 0xff
-*/
-func (game *Game) UpdatePixels() {
- var col byte
-
- gridx := 0
- gridy := 0
- idx := 0
-
- for y := 0; y < game.ScreenHeight; y++ {
- for x := 0; x < game.ScreenWidth; x++ {
- gridx = x / game.Cellsize
- gridy = y / game.Cellsize
-
- col = 0xff
- if game.Grids[game.Index].Data[gridy][gridx].State {
- col = 0x0
- }
-
- if game.Gridlines {
- if x%game.Cellsize == 0 || y%game.Cellsize == 0 {
- col = 128
- }
- }
-
- idx = 4 * (x + y*game.ScreenWidth)
-
- game.Pixels[idx] = col
- game.Pixels[idx+1] = col
- game.Pixels[idx+2] = col
- game.Pixels[idx+3] = 0xff
-
- idx++
- }
- }
-
- game.OffScreen.WritePixels(game.Pixels)
-}
-
-func (game *Game) Draw(screen *ebiten.Image) {
- screen.DrawImage(game.OffScreen, nil)
- game.DebugDump()
-}
-
-func main() {
- size := 1500
-
- game := &Game{
- Width: size,
- Height: size,
- Cellsize: 4,
- Density: 8,
- TPG: 10,
- Debug: false,
- Profile: true,
- Gridlines: false,
- }
-
- game.ScreenWidth = game.Width * game.Cellsize
- game.ScreenHeight = game.Height * game.Cellsize
-
- game.Init()
-
- ebiten.SetWindowSize(game.ScreenWidth, game.ScreenHeight)
- ebiten.SetWindowTitle("triangle conway's game of life")
- ebiten.SetWindowResizingMode(ebiten.WindowResizingModeEnabled)
-
- if game.Profile {
- fd, err := os.Create("cpu.profile")
- if err != nil {
- log.Fatal(err)
- }
- defer fd.Close()
-
- pprof.StartCPUProfile(fd)
- defer pprof.StopCPUProfile()
- }
-
- if err := ebiten.RunGame(game); err != nil {
- log.Fatal(err)
- }
-}
diff --git a/various-tests/writepixel/go.mod b/various-tests/writepixel/go.mod
deleted file mode 100644
index cde79f5..0000000
--- a/various-tests/writepixel/go.mod
+++ /dev/null
@@ -1,13 +0,0 @@
-module testgol
-
-go 1.22
-
-require (
- github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 // indirect
- github.com/ebitengine/hideconsole v1.0.0 // indirect
- github.com/ebitengine/purego v0.7.0 // indirect
- github.com/hajimehoshi/ebiten/v2 v2.7.4 // indirect
- github.com/jezek/xgb v1.1.1 // indirect
- golang.org/x/sync v0.7.0 // indirect
- golang.org/x/sys v0.20.0 // indirect
-)
diff --git a/various-tests/writepixel/go.sum b/various-tests/writepixel/go.sum
deleted file mode 100644
index d3c38a0..0000000
--- a/various-tests/writepixel/go.sum
+++ /dev/null
@@ -1,14 +0,0 @@
-github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 h1:48bCqKTuD7Z0UovDfvpCn7wZ0GUZ+yosIteNDthn3FU=
-github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895/go.mod h1:XZdLv05c5hOZm3fM2NlJ92FyEZjnslcMcNRrhxs8+8M=
-github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE=
-github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A=
-github.com/ebitengine/purego v0.7.0 h1:HPZpl61edMGCEW6XK2nsR6+7AnJ3unUxpTZBkkIXnMc=
-github.com/ebitengine/purego v0.7.0/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
-github.com/hajimehoshi/ebiten/v2 v2.7.4 h1:X+heODRQ3Ie9F9QFjm24gEZqQd5FSfR9XuT2XfHwgf8=
-github.com/hajimehoshi/ebiten/v2 v2.7.4/go.mod h1:H2pHVgq29rfm5yeQ7jzWOM3VHsjo7/AyucODNLOhsVY=
-github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4=
-github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
-golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
-golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
-golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
-golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
diff --git a/various-tests/writepixel/main.go b/various-tests/writepixel/main.go
deleted file mode 100644
index 0c61d9f..0000000
--- a/various-tests/writepixel/main.go
+++ /dev/null
@@ -1,277 +0,0 @@
-package main
-
-import (
- "fmt"
- "log"
- "math/rand"
- "os"
- "os/exec"
- "runtime/pprof"
-
- "github.com/hajimehoshi/ebiten/v2"
- "github.com/hajimehoshi/ebiten/v2/inpututil"
-)
-
-type Images struct {
- Black, White *ebiten.Image
-}
-
-type Grid struct {
- Data [][]int64
- Width, Height, Density int
-}
-
-// Create new empty grid and allocate Data according to provided dimensions
-func NewGrid(width, height, density int) *Grid {
- grid := &Grid{
- Height: height,
- Width: width,
- Density: density,
- Data: make([][]int64, height),
- }
-
- for y := 0; y < height; y++ {
- grid.Data[y] = make([]int64, width)
- }
-
- return grid
-}
-
-type Game struct {
- Width, Height, Cellsize, Density int
- ScreenWidth, ScreenHeight int
- Grids []*Grid
- Index int
- Elapsed int64
- TPG int64 // adjust game speed independently of TPS
- Pause, Debug, Profile, Gridlines bool
- Pixels []byte
- OffScreen *ebiten.Image
-}
-
-func (game *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
- return game.ScreenWidth, game.ScreenHeight
-}
-
-// live console output of the grid
-func (game *Game) DebugDump() {
- cmd := exec.Command("clear")
- cmd.Stdout = os.Stdout
- cmd.Run()
-
- if game.Debug {
- for y := 0; y < game.Height; y++ {
- for x := 0; x < game.Width; x++ {
- if game.Grids[game.Index].Data[y][x] == 1 {
- fmt.Print("XX")
- } else {
- fmt.Print(" ")
- }
- }
- fmt.Println()
- }
- }
- fmt.Printf("FPS: %0.2f\n", ebiten.ActualTPS())
-}
-
-func (game *Game) Init() {
- // setup two grids, one for display, one for next state
- grida := NewGrid(game.Width, game.Height, game.Density)
- gridb := NewGrid(game.Width, game.Height, game.Density)
-
- for y := 0; y < game.Height; y++ {
- for x := 0; x < game.Width; x++ {
- if rand.Intn(game.Density) == 1 {
- grida.Data[y][x] = 1
- }
- }
-
- }
-
- game.Grids = []*Grid{
- grida,
- gridb,
- }
-
- game.Pixels = make([]byte, game.ScreenWidth*game.ScreenHeight*4)
-
- game.OffScreen = ebiten.NewImage(game.ScreenWidth, game.ScreenHeight)
-}
-
-// count the living neighbors of a cell
-func (game *Game) CountNeighbors(x, y int) int64 {
- var sum int64
-
- for nbgX := -1; nbgX < 2; nbgX++ {
- for nbgY := -1; nbgY < 2; nbgY++ {
- var col, row int
-
- // Wrap mode we look at all the 8 neighbors surrounding
- // us. In case we are on an edge we'll look at the
- // neighbor on the other side of the grid, thus wrapping
- // lookahead around using the mod() function.
- col = (x + nbgX + game.Width) % game.Width
- row = (y + nbgY + game.Height) % game.Height
-
- sum += game.Grids[game.Index].Data[row][col]
- }
- }
-
- // don't count ourselfes though
- sum -= game.Grids[game.Index].Data[y][x]
-
- return sum
-}
-
-// the heart of the game
-func (game *Game) CheckRule(state int64, neighbors int64) int64 {
- var nextstate int64
-
- if state == 0 && neighbors == 3 {
- nextstate = 1
- } else if state == 1 && (neighbors == 2 || neighbors == 3) {
- nextstate = 1
- } else {
- nextstate = 0
- }
-
- return nextstate
-}
-
-// we only update the cells if we are not in pause state or if the
-// game timer (TPG) is elapsed.
-func (game *Game) UpdateCells() {
- if game.Pause {
- return
- }
-
- if game.Elapsed < game.TPG {
- game.Elapsed++
- return
- }
-
- // next grid index. we only have to, so we just xor it
- next := game.Index ^ 1
-
- // calculate cell life state, this is the actual game of life
- for y := 0; y < game.Height; y++ {
- for x := 0; x < game.Width; x++ {
- state := game.Grids[game.Index].Data[y][x] // 0|1 == dead or alive
- neighbors := game.CountNeighbors(x, y) // alive neighbor count
-
- // actually apply the current rules
- nextstate := game.CheckRule(state, neighbors)
-
- // change state of current cell in next grid
- game.Grids[next].Data[y][x] = nextstate
- }
- }
-
- // switch grid for rendering
- game.Index ^= 1
-
- game.Elapsed = 0
-
- game.UpdatePixels()
-}
-
-func (game *Game) Update() error {
- game.UpdateCells()
-
- if inpututil.IsKeyJustPressed(ebiten.KeySpace) {
- game.Pause = !game.Pause
- }
-
- return nil
-}
-
-/*
-*
-r, g, b := color(it)
-
- 78 p := 4 * (i + j*screenWidth)
- 79 gm.offscreenPix[p] = r
- 80 gm.offscreenPix[p+1] = g
- 81 gm.offscreenPix[p+2] = b
- 82 gm.offscreenPix[p+3] = 0xff
-*/
-func (game *Game) UpdatePixels() {
- var col byte
-
- gridx := 0
- gridy := 0
- idx := 0
-
- for y := 0; y < game.ScreenHeight; y++ {
- for x := 0; x < game.ScreenWidth; x++ {
- gridx = x / game.Cellsize
- gridy = y / game.Cellsize
-
- col = 0xff
- if game.Grids[game.Index].Data[gridy][gridx] == 1 {
- col = 0x0
- }
-
- if game.Gridlines {
- if x%game.Cellsize == 0 || y%game.Cellsize == 0 {
- col = 128
- }
- }
-
- idx = 4 * (x + y*game.ScreenWidth)
-
- game.Pixels[idx] = col
- game.Pixels[idx+1] = col
- game.Pixels[idx+2] = col
- game.Pixels[idx+3] = 0xff
-
- idx++
- }
- }
-
- game.OffScreen.WritePixels(game.Pixels)
-}
-
-func (game *Game) Draw(screen *ebiten.Image) {
- screen.DrawImage(game.OffScreen, nil)
- game.DebugDump()
-}
-
-func main() {
- size := 1500
-
- game := &Game{
- Width: size,
- Height: size,
- Cellsize: 4,
- Density: 8,
- TPG: 10,
- Debug: false,
- Profile: true,
- Gridlines: false,
- }
-
- game.ScreenWidth = game.Width * game.Cellsize
- game.ScreenHeight = game.Height * game.Cellsize
-
- game.Init()
-
- ebiten.SetWindowSize(game.ScreenWidth, game.ScreenHeight)
- ebiten.SetWindowTitle("triangle conway's game of life")
- ebiten.SetWindowResizingMode(ebiten.WindowResizingModeEnabled)
-
- if game.Profile {
- fd, err := os.Create("cpu.profile")
- if err != nil {
- log.Fatal(err)
- }
- defer fd.Close()
-
- pprof.StartCPUProfile(fd)
- defer pprof.StopCPUProfile()
- }
-
- if err := ebiten.RunGame(game); err != nil {
- log.Fatal(err)
- }
-}