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 @@ +[![License](https://img.shields.io/badge/license-GPL-blue.svg)](https://github.com/tlinden/golsky/blob/master/LICENSE) +[![Go Report Card](https://goreportcard.com/badge/github.com/tlinden/golsky)](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/). + ![Golsky Logo](https://github.com/TLINDEN/golsky/blob/main/.github/assets/golskylogo.png) -[![License](https://img.shields.io/badge/license-GPL-blue.svg)](https://github.com/tlinden/golsky/blob/master/LICENSE) -[![Go Report Card](https://goreportcard.com/badge/github.com/tlinden/golsky)](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) - } -}