10 Commits

42 changed files with 266 additions and 1078 deletions

View File

@@ -1,65 +0,0 @@
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
version: 2
before:
hooks:
- go mod tidy
gitea_urls:
api: https://codeberg.org/api/v1
download: https://codeberg.org
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- freebsd
archives:
- formats: [tar.gz]
# this name template makes the OS and Arch compatible with the results of `uname`.
name_template: >-
{{ .ProjectName }}_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }}_{{ .Tag }}
# use zip for windows archives
format_overrides:
- goos: windows
formats: [zip]
- goos: linux
formats: [tar.gz,binary]
files:
- src: "*.md"
strip_parent: true
- src: Makefile.dist
dst: Makefile
wrap_in_directory: true
changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"
groups:
- title: Improved
regexp: '^.*?(feat|add|new)(\([[:word:]]+\))??!?:.+$'
order: 0
- title: Fixed
regexp: '^.*?(bug|fix)(\([[:word:]]+\))??!?:.+$'
order: 1
- title: Changed
order: 999
release:
header: "# Release Notes"
footer: >-
---
Full Changelog: [{{ .PreviousTag }}...{{ .Tag }}](https://codeberg.org/scip/golsky/compare/{{ .PreviousTag }}...{{ .Tag }})

108
Makefile
View File

@@ -1,103 +1,9 @@
# Copyright © 2024 Thomas von Dein .PHONY: all
all:
# This program is free software: you can redistribute it and/or modify make -C src
# it under the terms of the GNU General Public License as published by mv src/golsky .
# 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 <http://www.gnu.org/licenses/>.
#
# 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/
.PHONY: clean
clean: clean:
rm -rf $(tool) coverage.out testdata t/out make -C src clean
rm -f dump* rect*
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
.PHONY: profile
profile: build
./golsky -W 1500 -H 1500 -d --profile-file cpu.profile
go tool pprof --http localhost:8888 golsky cpu.profile

View File

@@ -1,18 +0,0 @@
# -*-make-*-
.PHONY: install all
tool = rpn
PREFIX = /usr/local
UID = root
GID = 0
all:
@echo "Type 'sudo make install' to install the tool."
@echo "To change prefix, type 'sudo make install PREFIX=/opt'"
install:
install -d -o $(UID) -g $(GID) $(PREFIX)/bin
install -d -o $(UID) -g $(GID) $(PREFIX)/share/doc
install -o $(UID) -g $(GID) -m 555 $(tool) $(PREFIX)/sbin/
install -o $(UID) -g $(GID) -m 444 *.md $(PREFIX)/share/doc/

View File

@@ -1,9 +1,9 @@
# golsky - Conway's game of life written in GO # golsky - Conway's game of life written in GO
![Golsky Logo](https://codeberg.org/scip/golsky/raw/branch/main/.github/assets/golskylogo.png) ![Golsky Logo](https://github.com/TLINDEN/golsky/blob/main/.github/assets/golskylogo.png)
[![License](https://img.shields.io/badge/license-GPL-blue.svg)](https://codeberg.org/scip/golsky/blob/master/LICENSE) [![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/codeberg.org/scip/golsky)](https://goreportcard.com/report/codeberg.org/scip/golsky) [![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/) 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 in golang and here's the result. It's a simple game using
@@ -14,15 +14,15 @@ John Conway himself: https://youtu.be/R9Plq-D1gEk?si=yYxs77e9yXxeSNbL
Based on: https://youtu.be/FWSR_7kZuYg?si=ix1dmo76D8AmF25F Based on: https://youtu.be/FWSR_7kZuYg?si=ix1dmo76D8AmF25F
# Screenshots # Screenshots
[![golsky-mainmenu.png](https://codeberg.org/scip/golsky/raw/branch/main/.github/assets/screenshots/256_golsky-mainmenu.png)](https://codeberg.org/scip/golsky/raw/branch/main/.github/assets/screenshots/golsky-mainmenu.png) [![golsky-mainmenu.png](https://github.com/TLINDEN/golsky/blob/main/.github/assets/screenshots/256_golsky-mainmenu.png)](https://github.com/TLINDEN/golsky/blob/main/.github/assets/screenshots/golsky-mainmenu.png)
[![golsky-options.png](https://codeberg.org/scip/golsky/raw/branch/main/.github/assets/screenshots/256_golsky-options.png)](https://codeberg.org/scip/golsky/raw/branch/main/.github/assets/screenshots/golsky-options.png) [![golsky-options.png](https://github.com/TLINDEN/golsky/blob/main/.github/assets/screenshots/256_golsky-options.png)](https://github.com/TLINDEN/golsky/blob/main/.github/assets/screenshots/golsky-options.png)
[![golsky-bindings.png](https://codeberg.org/scip/golsky/raw/branch/main/.github/assets/screenshots/256_golsky-bindings.png)](https://codeberg.org/scip/golsky/raw/branch/main/.github/assets/screenshots/golsky-bindings.png) [![golsky-bindings.png](https://github.com/TLINDEN/golsky/blob/main/.github/assets/screenshots/256_golsky-bindings.png)](https://github.com/TLINDEN/golsky/blob/main/.github/assets/screenshots/golsky-bindings.png)
[![golsky-evolution-trace.png](https://codeberg.org/scip/golsky/raw/branch/main/.github/assets/screenshots/256_golsky-evolution-trace.png)](https://codeberg.org/scip/golsky/raw/branch/main/.github/assets/screenshots/golsky-evolution-trace.png) [![golsky-evolution-trace.png](https://github.com/TLINDEN/golsky/blob/main/.github/assets/screenshots/256_golsky-evolution-trace.png)](https://github.com/TLINDEN/golsky/blob/main/.github/assets/screenshots/golsky-evolution-trace.png)
[![golsky-zoom.png](https://codeberg.org/scip/golsky/raw/branch/main/.github/assets/screenshots/256_golsky-zoom.png)](https://codeberg.org/scip/golsky/raw/branch/main/.github/assets/screenshots/golsky-zoom.png) [![golsky-zoom.png](https://github.com/TLINDEN/golsky/blob/main/.github/assets/screenshots/256_golsky-zoom.png)](https://github.com/TLINDEN/golsky/blob/main/.github/assets/screenshots/golsky-zoom.png)
[![golsky-debug.png](https://codeberg.org/scip/golsky/raw/branch/main/.github/assets/screenshots/256_golsky-debug.png)](https://codeberg.org/scip/golsky/raw/branch/main/.github/assets/screenshots/golsky-debug.png) [![golsky-debug.png](https://github.com/TLINDEN/golsky/blob/main/.github/assets/screenshots/256_golsky-debug.png)](https://github.com/TLINDEN/golsky/blob/main/.github/assets/screenshots/golsky-debug.png)
[![golsky-capture.png](https://codeberg.org/scip/golsky/raw/branch/main/.github/assets/screenshots/256_golsky-capture.png)](https://codeberg.org/scip/golsky/raw/branch/main/.github/assets/screenshots/golsky-capture.png) [![golsky-capture.png](https://github.com/TLINDEN/golsky/blob/main/.github/assets/screenshots/256_golsky-capture.png)](https://github.com/TLINDEN/golsky/blob/main/.github/assets/screenshots/golsky-capture.png)
[![golsky-captured.png](https://codeberg.org/scip/golsky/raw/branch/main/.github/assets/screenshots/256_golsky-captured.png)](https://codeberg.org/scip/golsky/raw/branch/main/.github/assets/screenshots/golsky-captured.png) [![golsky-captured.png](https://github.com/TLINDEN/golsky/blob/main/.github/assets/screenshots/256_golsky-captured.png)](https://github.com/TLINDEN/golsky/blob/main/.github/assets/screenshots/golsky-captured.png)
[![golsky-dark-theme.png](https://codeberg.org/scip/golsky/raw/branch/main/.github/assets/screenshots/256_golsky-dark-theme.png)](https://codeberg.org/scip/golsky/raw/branch/main/.github/assets/screenshots/golsky-dark-theme.png) [![golsky-dark-theme.png](https://github.com/TLINDEN/golsky/blob/main/.github/assets/screenshots/256_golsky-dark-theme.png)](https://github.com/TLINDEN/golsky/blob/main/.github/assets/screenshots/golsky-dark-theme.png)
[Youtube video game preview](https://www.youtube.com/watch?v=xEto6Oew16I) [Youtube video game preview](https://www.youtube.com/watch?v=xEto6Oew16I)
@@ -82,16 +82,17 @@ Usage of ./golsky:
While it runs, there are a couple of commands you can use: While it runs, there are a couple of commands you can use:
* left mouse click: set a cell to alife (also pauses the game)
* right mouse click: set a cell to dead
* space: pause or resume the game * space: pause or resume the game
* while game is paused: press n to forward one step * while game is paused: press n to forward one step
* page up: speed up * page up: speed up
* page down: slow down * page down: slow down
* Mouse wheel: zoom in or out * Mouse wheel: zoom in or out
* move mouse while left mouse button pressed: move canvas * move mouse while left mouse button pressed: move canvas
* i: enter "insert" (draw) mode: use left mouse to toggle cells alife state. * i: enter "insert" (draw) mode: use left mouse to set cells alife and right
Leave with insert mode "space". While in insert mode, use middle mouse button to dead. Leave with "space". While in insert mode, use middle mouse
button to drag the grid. button to drag grid.
* r: reset to 1:1 zoom * r: reset to 1:1 zoom
* escape: open menu * escape: open menu
* s: save game state to file (can be loaded with -l) * s: save game state to file (can be loaded with -l)
@@ -102,7 +103,7 @@ While it runs, there are a couple of commands you can use:
# Report bugs # Report bugs
[Please open an issue](https://codeberg.org/scip/golsky/issues). Thanks! [Please open an issue](https://github.com/TLINDEN/golsky/issues). Thanks!
# License # License

10
TODO.md
View File

@@ -11,8 +11,8 @@
- try arche ecs variant with either a component of the cells neighbors or using relations. - try arche ecs variant with either a component of the cells neighbors or using relations.
- https://mattnakama.com/blog/go-branchless-coding/ - https://mattnakama.com/blog/go-branchless-coding/
- add performance measurements, see: - add performance measurements, see:
DrawTriangles: https://codeberg.org/scip/testgol DrawTriangles: https://github.com/TLINDEN/testgol
WritePixels: https://codeberg.org/scip/testgol/tree/wrpixels WritePixels: https://github.com/TLINDEN/testgol/tree/wrpixels
https://www.tasnimzotder.com/blog/optimizing-game-of-life-algorithm https://www.tasnimzotder.com/blog/optimizing-game-of-life-algorithm
- pre-draw the grid separately to a cache grid image, then during - pre-draw the grid separately to a cache grid image, then during
rendering, first draw the dead background, then the life cells, and rendering, first draw the dead background, then the life cells, and
@@ -20,11 +20,7 @@ https://www.tasnimzotder.com/blog/optimizing-game-of-life-algorithm
the cells anymore. the cells anymore.
- Speed - Speed
https://conwaylife.com/forums/viewtopic.php?f=7&t=3237 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: - Patterns:
A Catagolue textcensus of, say, period-2 oscillators from A Catagolue textcensus of, say, period-2 oscillators from

2
go.mod
View File

@@ -1,4 +1,4 @@
module codeberg.org/scip/golsky module github.com/tlinden/golsky
go 1.22 go 1.22

97
src/Makefile Normal file
View File

@@ -0,0 +1,97 @@
# 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 <http://www.gnu.org/licenses/>.
#
# 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

View File

Before

Width:  |  Height:  |  Size: 269 B

After

Width:  |  Height:  |  Size: 269 B

View File

Before

Width:  |  Height:  |  Size: 263 B

After

Width:  |  Height:  |  Size: 263 B

View File

Before

Width:  |  Height:  |  Size: 269 B

After

Width:  |  Height:  |  Size: 269 B

View File

Before

Width:  |  Height:  |  Size: 289 B

After

Width:  |  Height:  |  Size: 289 B

View File

Before

Width:  |  Height:  |  Size: 271 B

After

Width:  |  Height:  |  Size: 271 B

View File

@@ -1,6 +1,6 @@
// this comes from the camera example but I enhanced it a little bit // this comes from the camera example but I enhanced it a little bit
package cmd package main
import ( import (
"fmt" "fmt"

View File

@@ -1,4 +1,4 @@
package cmd package main
import ( import (
"errors" "errors"
@@ -10,7 +10,7 @@ import (
"strings" "strings"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"codeberg.org/scip/golsky/rle" "github.com/tlinden/golsky/rle"
) )
// all the settings comming from commandline, but maybe tweaked later from the UI // all the settings comming from commandline, but maybe tweaked later from the UI
@@ -52,7 +52,7 @@ const (
DEFAULT_CELLSIZE = 4 DEFAULT_CELLSIZE = 4
DEFAULT_ZOOMFACTOR = 400 DEFAULT_ZOOMFACTOR = 400
DEFAULT_GEOM = "640x384" DEFAULT_GEOM = "640x384"
DEFAULT_THEME = "standard" DEFAULT_THEME = "standard" // "light" // inverse => "dark"
) )
const KEYBINDINGS string = ` const KEYBINDINGS string = `
@@ -62,9 +62,9 @@ const KEYBINDINGS string = `
- PAGE DOWN: slow down - PAGE DOWN: slow down
- MOUSE WHEEL: zoom in or out - MOUSE WHEEL: zoom in or out
- LEFT MOUSE BUTTON: use to drag canvas, keep clicked and move mouse - 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. - I: enter "insert" (draw) mode: use left mouse to set cells alife and right
Leave with insert mode with "space". While in insert mode, use middle mouse button to dead. Leave with "space". While in insert mode, use middle mouse
button to drag the grid. button to drag grid.
- R: reset to 1:1 zoom - R: reset to 1:1 zoom
- ESCAPE: open menu, o: open options menu - ESCAPE: open menu, o: open options menu
- S: save game state to file (can be loaded with -l) - S: save game state to file (can be loaded with -l)

View File

@@ -1,4 +1,4 @@
package cmd package main
import ( import (
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"

View File

@@ -1,4 +1,4 @@
package cmd package main
// find an item in a list, generic variant // find an item in a list, generic variant
func Contains[E comparable](s []E, v E) bool { func Contains[E comparable](s []E, v E) bool {

View File

@@ -1,4 +1,4 @@
package cmd package main
import ( import (
"bufio" "bufio"
@@ -9,70 +9,60 @@ import (
"strings" "strings"
"time" "time"
"codeberg.org/scip/golsky/rle" "github.com/tlinden/golsky/rle"
) )
// equals grid height, is being used to access grid elements and must be global type Cell struct {
var STRIDE int State uint8
Neighbors [8]*Cell
NeighborCount int
}
type Neighbor struct { func (cell *Cell) Count() uint8 {
X, Y int var count uint8
for idx := 0; idx < cell.NeighborCount; idx++ {
count += cell.Neighbors[idx].State
}
return count
} }
type Grid struct { type Grid struct {
Data []uint8 Data [][]*Cell
NeighborCount []int Empty bool
Neighbors [][]Neighbor Config *Config
Empty bool
Config *Config
Counter func(x, y int) uint8
} }
// Create new empty grid and allocate Data according to provided dimensions // Create new empty grid and allocate Data according to provided dimensions
func NewGrid(config *Config) *Grid { func NewGrid(config *Config) *Grid {
STRIDE = config.Height
if config.Width > config.Height {
STRIDE = config.Width
}
size := STRIDE * STRIDE
grid := &Grid{ grid := &Grid{
Data: make([]uint8, size), Data: make([][]*Cell, config.Height),
NeighborCount: make([]int, size), Empty: config.Empty,
Neighbors: make([][]Neighbor, size), Config: config,
Empty: config.Empty,
Config: config,
} }
// first setup the cells // first setup the cells
for y := 0; y < config.Height; y++ { for y := 0; y < config.Height; y++ {
grid.Data[y] = make([]*Cell, config.Width)
for x := 0; x < config.Width; x++ { for x := 0; x < config.Width; x++ {
grid.Data[y+STRIDE*x] = 0 grid.Data[y][x] = &Cell{}
} }
} }
// in a second pass, collect positions to the neighbors of each cell // in a second pass, collect pointers to the neighbors of each cell
for y := 0; y < config.Height; y++ { for y := 0; y < config.Height; y++ {
for x := 0; x < config.Width; x++ { for x := 0; x < config.Width; x++ {
grid.SetupNeighbors(x, y) grid.SetupNeighbors(x, y)
} }
} }
if grid.Config.Wrap {
grid.Counter = grid.CountNeighborsWrap
} else {
grid.Counter = grid.CountNeighbors
}
return grid return grid
} }
func (grid *Grid) SetupNeighbors(x, y int) { func (grid *Grid) SetupNeighbors(x, y int) {
idx := 0 idx := 0
var neighbors []Neighbor
for nbgY := -1; nbgY < 2; nbgY++ { for nbgY := -1; nbgY < 2; nbgY++ {
for nbgX := -1; nbgX < 2; nbgX++ { for nbgX := -1; nbgX < 2; nbgX++ {
var col, row int var col, row int
@@ -99,83 +89,16 @@ func (grid *Grid) SetupNeighbors(x, y int) {
continue continue
} }
neighbors = append(neighbors, Neighbor{X: col, Y: row}) grid.Data[y][x].Neighbors[idx] = grid.Data[row][col]
grid.NeighborCount[y+STRIDE*x]++ grid.Data[y][x].NeighborCount++
idx++ 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 // count the living neighbors of a cell
func (grid *Grid) _CountNeighbors(x, y int) uint8 { func (grid *Grid) CountNeighbors(x, y int) uint8 {
var count uint8 return grid.Data[y][x].Count()
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 // Create a new 1:1 instance
@@ -189,30 +112,30 @@ func (grid *Grid) Clone() *Grid {
} }
// copy data // copy data
// func (grid *Grid) Copy(other *Grid) { func (grid *Grid) Copy(other *Grid) {
// for y := range grid.Data { for y := range grid.Data {
// for x := range grid.Data[y] { for x := range grid.Data[y] {
// other.Data[y+STRIDE*x] = grid.Data[y+STRIDE*x] other.Data[y][x] = grid.Data[y][x]
// } }
// } }
// } }
// delete all contents // delete all contents
// func (grid *Grid) Clear() { func (grid *Grid) Clear() {
// for y := range grid.Data { for y := range grid.Data {
// for x := range grid.Data[y] { for x := range grid.Data[y] {
// grid.Data[y+STRIDE*x] = 0 grid.Data[y][x].State = 0
// } }
// } }
// } }
// initialize with random life cells using the given density // initialize with random life cells using the given density
func (grid *Grid) FillRandom() { func (grid *Grid) FillRandom() {
if !grid.Empty { if !grid.Empty {
for y := 0; y < grid.Config.Height; y++ { for y := range grid.Data {
for x := 0; x < grid.Config.Width; x++ { for x := range grid.Data[y] {
if rand.Intn(grid.Config.Density) == 1 { if rand.Intn(grid.Config.Density) == 1 {
grid.Data[y+STRIDE*x] = 1 grid.Data[y][x].State = 1
} }
} }
} }
@@ -222,7 +145,7 @@ func (grid *Grid) FillRandom() {
func (grid *Grid) Dump() { func (grid *Grid) Dump() {
for y := 0; y < grid.Config.Height; y++ { for y := 0; y < grid.Config.Height; y++ {
for x := 0; x < grid.Config.Width; x++ { for x := 0; x < grid.Config.Width; x++ {
if grid.Data[y+STRIDE*x] == 1 { if grid.Data[y][x].State == 1 {
fmt.Print("XX") fmt.Print("XX")
} else { } else {
fmt.Print(" ") fmt.Print(" ")
@@ -245,7 +168,7 @@ func (grid *Grid) LoadRLE(pattern *rle.RLE) {
x = colIndex + startX x = colIndex + startX
y = rowIndex + startY y = rowIndex + startY
grid.Data[y+STRIDE*x] = 1 grid.Data[y][x].State = 1
} }
} }
} }
@@ -353,10 +276,10 @@ func (grid *Grid) SaveState(filename, rule string) error {
fmt.Fprintf(file, "#Life 1.05\n#R %s\n#D golsky state file\n#P -1 -1\n", rule) 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 y := range grid.Data {
for x := 0; x < grid.Config.Width; x++ { for _, cell := range grid.Data[y] {
row := "." row := "."
if grid.Data[y+STRIDE*x] == 1 { if cell.State == 1 {
row = "o" row = "o"
} }

View File

@@ -1,4 +1,4 @@
package cmd package main
import ( import (
"image/color" "image/color"

View File

@@ -1,4 +1,4 @@
package cmd package main
import ( import (
"log" "log"

View File

@@ -1,4 +1,4 @@
package cmd package main
import ( import (
"bytes" "bytes"

View File

@@ -1,4 +1,4 @@
package cmd package main
import ( import (
"embed" "embed"

View File

@@ -8,7 +8,6 @@ import (
_ "net/http/pprof" _ "net/http/pprof"
"codeberg.org/scip/golsky/cmd"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
) )
@@ -19,22 +18,22 @@ func main() {
directstart = true directstart = true
} }
config, err := cmd.ParseCommandline() config, err := ParseCommandline()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
if config.ShowVersion { if config.ShowVersion {
fmt.Printf("This is golsky version %s\n", cmd.VERSION) fmt.Printf("This is golsky version %s\n", VERSION)
os.Exit(0) os.Exit(0)
} }
start := cmd.Play start := Play
if !directstart { if !directstart {
start = cmd.Menu start = Menu
config.DelayedStart = true config.DelayedStart = true
} }
game := cmd.NewGame(config, cmd.SceneName(start)) game := NewGame(config, SceneName(start))
if config.ProfileFile != "" { if config.ProfileFile != "" {
// enable cpu profiling. Do NOT use q to stop the game but // enable cpu profiling. Do NOT use q to stop the game but

View File

@@ -1,4 +1,4 @@
package cmd package main
import ( import (
"image/color" "image/color"

View File

@@ -1,4 +1,4 @@
package cmd package main
import ( import (
"image/color" "image/color"

View File

@@ -1,4 +1,4 @@
package cmd package main
import ( import (
"fmt" "fmt"
@@ -10,7 +10,7 @@ import (
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/inpututil" "github.com/hajimehoshi/ebiten/v2/inpututil"
"github.com/hajimehoshi/ebiten/v2/vector" "github.com/hajimehoshi/ebiten/v2/vector"
"codeberg.org/scip/golsky/rle" "github.com/tlinden/golsky/rle"
"golang.org/x/image/math/f64" "golang.org/x/image/math/f64"
) )
@@ -100,35 +100,34 @@ func (scene *ScenePlay) SetNext(next SceneName) {
scene.Next = next 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 { func (scene *ScenePlay) CheckRuleB3S23(state uint8, neighbors uint8) uint8 {
switch (9 * state) + neighbors { var nextstate uint8
check := (9 * state) + neighbors
switch check {
case 11: case 11:
fallthrough fallthrough
case 12: case 12:
fallthrough fallthrough
case 3: case 3:
return Alive nextstate = Alive
default:
nextstate = Dead
} }
return Dead return nextstate
} }
/*
* 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 { func (scene *ScenePlay) CheckRuleGeneric(state uint8, neighbors uint8) uint8 {
var nextstate uint8 var nextstate uint8
// 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. The first number, or list of numbers, is
// what is required for a dead cell to be born.
if state != 1 && Contains(scene.Config.Rule.Birth, neighbors) { if state != 1 && Contains(scene.Config.Rule.Birth, neighbors) {
nextstate = Alive nextstate = Alive
} else if state == 1 && Contains(scene.Config.Rule.Death, neighbors) { } else if state == 1 && Contains(scene.Config.Rule.Death, neighbors) {
@@ -156,24 +155,21 @@ func (scene *ScenePlay) UpdateCells() {
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(scene.Config.Height) wg.Add(scene.Config.Height)
width := scene.Config.Width
height := scene.Config.Height
// compute life status of cells // compute life status of cells
for y := 0; y < height; y++ { for y := 0; y < scene.Config.Height; y++ {
go func() { go func() {
defer wg.Done() defer wg.Done()
for x := 0; x < width; x++ { for x := 0; x < scene.Config.Width; x++ {
state := scene.Grids[scene.Index].Data[y+STRIDE*x] // 0|1 == dead or alive state := scene.Grids[scene.Index].Data[y][x].State // 0|1 == dead or alive
neighbors := scene.Grids[scene.Index].Counter(x, y) neighbors := scene.Grids[scene.Index].CountNeighbors(x, y)
// actually apply the current rules // actually apply the current rules
nextstate := scene.RuleCheckFunc(state, neighbors) nextstate := scene.RuleCheckFunc(state, neighbors)
// change state of current cell in next grid // change state of current cell in next grid
scene.Grids[next].Data[y+STRIDE*x] = nextstate scene.Grids[next].Data[y][x].State = nextstate
if scene.Config.ShowEvolution { if scene.Config.ShowEvolution {
// set history to current generation so we can infer the // set history to current generation so we can infer the
@@ -268,8 +264,10 @@ func (scene *ScenePlay) CheckInput() {
func (scene *ScenePlay) CheckDrawingInput() { func (scene *ScenePlay) CheckDrawingInput() {
if scene.Config.Drawmode { if scene.Config.Drawmode {
switch { switch {
case inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft): case ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft):
scene.ToggleCellOnCursorPos() scene.ToggleCellOnCursorPos(Alive)
case ebiten.IsMouseButtonPressed(ebiten.MouseButtonRight):
scene.ToggleCellOnCursorPos(Dead)
case inpututil.IsKeyJustPressed(ebiten.KeyEscape): case inpututil.IsKeyJustPressed(ebiten.KeyEscape):
scene.Config.Drawmode = false scene.Config.Drawmode = false
} }
@@ -421,7 +419,7 @@ func (scene *ScenePlay) SaveRectRLE() {
grid[y] = make([]uint8, width) grid[y] = make([]uint8, width)
for x := 0; x < width; x++ { for x := 0; x < width; x++ {
grid[y][x] = scene.Grids[scene.Index].Data[(y+starty)+STRIDE*(x+startx)] grid[y][x] = scene.Grids[scene.Index].Data[y+starty][x+startx].State
} }
} }
@@ -467,14 +465,14 @@ func (scene *ScenePlay) Update() error {
} }
// set a cell to alive or dead // set a cell to alive or dead
func (scene *ScenePlay) ToggleCellOnCursorPos() { func (scene *ScenePlay) ToggleCellOnCursorPos(alive uint8) {
// use cursor pos relative to the world // use cursor pos relative to the world
worldX, worldY := scene.Camera.ScreenToWorld(ebiten.CursorPosition()) worldX, worldY := scene.Camera.ScreenToWorld(ebiten.CursorPosition())
x := int(worldX) / scene.Config.Cellsize x := int(worldX) / scene.Config.Cellsize
y := int(worldY) / scene.Config.Cellsize y := int(worldY) / scene.Config.Cellsize
if x > -1 && y > -1 && x < scene.Config.Width && y < scene.Config.Height { if x > -1 && y > -1 && x < scene.Config.Width && y < scene.Config.Height {
scene.Grids[scene.Index].Data[y+STRIDE*x] ^= 1 scene.Grids[scene.Index].Data[y][x].State = alive
scene.History.Age[y][x] = 1 scene.History.Age[y][x] = 1
} }
} }
@@ -500,7 +498,7 @@ func (scene *ScenePlay) Draw(screen *ebiten.Image) {
if scene.Config.ShowEvolution { if scene.Config.ShowEvolution {
scene.DrawEvolution(screen, x, y, op) scene.DrawEvolution(screen, x, y, op)
} else { } else {
if scene.Grids[scene.Index].Data[y+STRIDE*x] == 1 { if scene.Grids[scene.Index].Data[y][x].State == 1 {
scene.World.DrawImage(scene.Theme.Tile(ColLife), op) scene.World.DrawImage(scene.Theme.Tile(ColLife), op)
} }
} }
@@ -517,7 +515,7 @@ func (scene *ScenePlay) Draw(screen *ebiten.Image) {
func (scene *ScenePlay) DrawEvolution(screen *ebiten.Image, x, y int, op *ebiten.DrawImageOptions) { func (scene *ScenePlay) DrawEvolution(screen *ebiten.Image, x, y int, op *ebiten.DrawImageOptions) {
age := scene.Generations - scene.History.Age[y][x] age := scene.Generations - scene.History.Age[y][x]
switch scene.Grids[scene.Index].Data[y+STRIDE*x] { switch scene.Grids[scene.Index].Data[y][x].State {
case Alive: case Alive:
if age > 50 && scene.Config.ShowEvolution { if age > 50 && scene.Config.ShowEvolution {
scene.World.DrawImage(scene.Theme.Tile(ColOld), op) scene.World.DrawImage(scene.Theme.Tile(ColOld), op)

View File

@@ -1,4 +1,4 @@
package cmd package main
import ( import (
"log" "log"

View File

@@ -1,4 +1,4 @@
package cmd package main
import "github.com/hajimehoshi/ebiten/v2" import "github.com/hajimehoshi/ebiten/v2"

View File

@@ -1,4 +1,4 @@
package cmd package main
import "runtime" import "runtime"

View File

@@ -1,4 +1,4 @@
package cmd package main
import ( import (
"fmt" "fmt"
@@ -42,10 +42,10 @@ var THEMES = map[string]ThemeDef{
dead: "5a5a5a", dead: "5a5a5a",
old: "ff1e1e", old: "ff1e1e",
grid: "808080", grid: "808080",
age3: "6c6059", age1: "735f52",
age2: "735f52", age2: "6c6059",
age1: "7b5e4b", age3: "635d59",
age4: "635d59", age4: "7b5e4b",
}, },
"dark": { "dark": {
life: "c8c8c8", life: "c8c8c8",

View File

@@ -1,4 +1,4 @@
package cmd package main
import ( import (
"image/color" "image/color"

View File

@@ -87,17 +87,6 @@ func Loop(grid []bool) {
} }
func main() { 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 // enable cpu profiling. Do NOT use q to stop the game but
// close the window to get a profile // close the window to get a profile
fd, err := os.Create("cpu.profile") fd, err := os.Create("cpu.profile")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -238,7 +238,35 @@ func (game *Game) Draw(screen *ebiten.Image) {
} }
func main() { func main() {
size := 1500 //x := 1
//y := 0
col := 1 >> 0xff
fmt.Printf("col: %d\n", col)
x := 1
y := 2
c := 4
xm := x & (c - 1)
ym := y & (c - 1)
fmt.Println(xm & ym)
a := 1
b := 1
//gen := 100
hist := 0
for gen := 0; gen < 50; gen++ {
fmt.Println((a ^ (1 ^ b)) * (gen - hist))
if gen == 25 {
a = 0
}
}
}
func _main() {
size := 800
game := &Game{ game := &Game{
Width: size, Width: size,
@@ -247,7 +275,7 @@ func main() {
Density: 8, Density: 8,
TPG: 10, TPG: 10,
Debug: false, Debug: false,
Profile: true, Profile: false,
Gridlines: false, Gridlines: false,
} }