From 162d141b34f7429a12b8b505da75a884ad6c2bf7 Mon Sep 17 00:00:00 2001 From: Thomas von Dein Date: Wed, 5 Jun 2024 16:33:35 +0200 Subject: [PATCH] reorganized source, added test cases for tuning --- Makefile | 101 +---- src/Makefile | 97 +++++ .../assets}/fonts/NotoSans-Regular.ttf | Bin {assets => src/assets}/shaders/row.kg | 0 .../assets}/sprites/button-9slice1.png | Bin .../assets}/sprites/button-9slice2.png | Bin .../assets}/sprites/button-9slice3.png | Bin .../assets}/sprites/checkbox-9slice1.png | Bin .../assets}/sprites/checkbox-9slice2.png | Bin {assets => src/assets}/src/button-9slice.ase | Bin .../assets}/src/checkbox-9slice.ase | Bin camera.go => src/camera.go | 0 config.go => src/config.go | 0 game.go => src/game.go | 0 generics.go => src/generics.go | 0 grid.go => src/grid.go | 0 loader-fonts.go => src/loader-fonts.go | 0 loader-shaders.go => src/loader-shaders.go | 0 loader-sprites.go => src/loader-sprites.go | 0 main.go => src/main.go | 0 menu.go => src/menu.go | 0 options.go => src/options.go | 0 play.go => src/play.go | 0 rule.go => src/rule.go | 0 scene.go => src/scene.go | 0 system.go => src/system.go | 0 widgets.go => src/widgets.go | 0 various-tests/drawtriangles/go.mod | 13 + various-tests/drawtriangles/go.sum | 14 + various-tests/drawtriangles/main.go | 362 ++++++++++++++++++ various-tests/writepixel/go.mod | 13 + various-tests/writepixel/go.sum | 14 + various-tests/writepixel/main.go | 294 ++++++++++++++ 33 files changed, 811 insertions(+), 97 deletions(-) create mode 100644 src/Makefile rename {assets => src/assets}/fonts/NotoSans-Regular.ttf (100%) rename {assets => src/assets}/shaders/row.kg (100%) rename {assets => src/assets}/sprites/button-9slice1.png (100%) rename {assets => src/assets}/sprites/button-9slice2.png (100%) rename {assets => src/assets}/sprites/button-9slice3.png (100%) rename {assets => src/assets}/sprites/checkbox-9slice1.png (100%) rename {assets => src/assets}/sprites/checkbox-9slice2.png (100%) rename {assets => src/assets}/src/button-9slice.ase (100%) rename {assets => src/assets}/src/checkbox-9slice.ase (100%) rename camera.go => src/camera.go (100%) rename config.go => src/config.go (100%) rename game.go => src/game.go (100%) rename generics.go => src/generics.go (100%) rename grid.go => src/grid.go (100%) rename loader-fonts.go => src/loader-fonts.go (100%) rename loader-shaders.go => src/loader-shaders.go (100%) rename loader-sprites.go => src/loader-sprites.go (100%) rename main.go => src/main.go (100%) rename menu.go => src/menu.go (100%) rename options.go => src/options.go (100%) rename play.go => src/play.go (100%) rename rule.go => src/rule.go (100%) rename scene.go => src/scene.go (100%) rename system.go => src/system.go (100%) rename widgets.go => src/widgets.go (100%) create mode 100644 various-tests/drawtriangles/go.mod create mode 100644 various-tests/drawtriangles/go.sum create mode 100644 various-tests/drawtriangles/main.go create mode 100644 various-tests/writepixel/go.mod create mode 100644 various-tests/writepixel/go.sum create mode 100644 various-tests/writepixel/main.go diff --git a/Makefile b/Makefile index c3beded..9f212ba 100644 --- a/Makefile +++ b/Makefile @@ -1,97 +1,4 @@ -# 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 +.PHONY: all +all: + make -C src + mv src/golsky . diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..c3beded --- /dev/null +++ b/src/Makefile @@ -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 . + + +# +# 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/assets/fonts/NotoSans-Regular.ttf b/src/assets/fonts/NotoSans-Regular.ttf similarity index 100% rename from assets/fonts/NotoSans-Regular.ttf rename to src/assets/fonts/NotoSans-Regular.ttf diff --git a/assets/shaders/row.kg b/src/assets/shaders/row.kg similarity index 100% rename from assets/shaders/row.kg rename to src/assets/shaders/row.kg diff --git a/assets/sprites/button-9slice1.png b/src/assets/sprites/button-9slice1.png similarity index 100% rename from assets/sprites/button-9slice1.png rename to src/assets/sprites/button-9slice1.png diff --git a/assets/sprites/button-9slice2.png b/src/assets/sprites/button-9slice2.png similarity index 100% rename from assets/sprites/button-9slice2.png rename to src/assets/sprites/button-9slice2.png diff --git a/assets/sprites/button-9slice3.png b/src/assets/sprites/button-9slice3.png similarity index 100% rename from assets/sprites/button-9slice3.png rename to src/assets/sprites/button-9slice3.png diff --git a/assets/sprites/checkbox-9slice1.png b/src/assets/sprites/checkbox-9slice1.png similarity index 100% rename from assets/sprites/checkbox-9slice1.png rename to src/assets/sprites/checkbox-9slice1.png diff --git a/assets/sprites/checkbox-9slice2.png b/src/assets/sprites/checkbox-9slice2.png similarity index 100% rename from assets/sprites/checkbox-9slice2.png rename to src/assets/sprites/checkbox-9slice2.png diff --git a/assets/src/button-9slice.ase b/src/assets/src/button-9slice.ase similarity index 100% rename from assets/src/button-9slice.ase rename to src/assets/src/button-9slice.ase diff --git a/assets/src/checkbox-9slice.ase b/src/assets/src/checkbox-9slice.ase similarity index 100% rename from assets/src/checkbox-9slice.ase rename to src/assets/src/checkbox-9slice.ase diff --git a/camera.go b/src/camera.go similarity index 100% rename from camera.go rename to src/camera.go diff --git a/config.go b/src/config.go similarity index 100% rename from config.go rename to src/config.go diff --git a/game.go b/src/game.go similarity index 100% rename from game.go rename to src/game.go diff --git a/generics.go b/src/generics.go similarity index 100% rename from generics.go rename to src/generics.go diff --git a/grid.go b/src/grid.go similarity index 100% rename from grid.go rename to src/grid.go diff --git a/loader-fonts.go b/src/loader-fonts.go similarity index 100% rename from loader-fonts.go rename to src/loader-fonts.go diff --git a/loader-shaders.go b/src/loader-shaders.go similarity index 100% rename from loader-shaders.go rename to src/loader-shaders.go diff --git a/loader-sprites.go b/src/loader-sprites.go similarity index 100% rename from loader-sprites.go rename to src/loader-sprites.go diff --git a/main.go b/src/main.go similarity index 100% rename from main.go rename to src/main.go diff --git a/menu.go b/src/menu.go similarity index 100% rename from menu.go rename to src/menu.go diff --git a/options.go b/src/options.go similarity index 100% rename from options.go rename to src/options.go diff --git a/play.go b/src/play.go similarity index 100% rename from play.go rename to src/play.go diff --git a/rule.go b/src/rule.go similarity index 100% rename from rule.go rename to src/rule.go diff --git a/scene.go b/src/scene.go similarity index 100% rename from scene.go rename to src/scene.go diff --git a/system.go b/src/system.go similarity index 100% rename from system.go rename to src/system.go diff --git a/widgets.go b/src/widgets.go similarity index 100% rename from widgets.go rename to src/widgets.go diff --git a/various-tests/drawtriangles/go.mod b/various-tests/drawtriangles/go.mod new file mode 100644 index 0000000..7a41db3 --- /dev/null +++ b/various-tests/drawtriangles/go.mod @@ -0,0 +1,13 @@ +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 new file mode 100644 index 0000000..d3c38a0 --- /dev/null +++ b/various-tests/drawtriangles/go.sum @@ -0,0 +1,14 @@ +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 new file mode 100644 index 0000000..259ef74 --- /dev/null +++ b/various-tests/drawtriangles/main.go @@ -0,0 +1,362 @@ +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/writepixel/go.mod b/various-tests/writepixel/go.mod new file mode 100644 index 0000000..cde79f5 --- /dev/null +++ b/various-tests/writepixel/go.mod @@ -0,0 +1,13 @@ +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 new file mode 100644 index 0000000..d3c38a0 --- /dev/null +++ b/various-tests/writepixel/go.sum @@ -0,0 +1,14 @@ +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 new file mode 100644 index 0000000..edc7497 --- /dev/null +++ b/various-tests/writepixel/main.go @@ -0,0 +1,294 @@ +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() { + //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) +} + +func _main() { + size := 800 + + game := &Game{ + Width: size, + Height: size, + Cellsize: 4, + Density: 8, + TPG: 10, + Debug: false, + Profile: false, + 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) + } +}