diff --git a/.github/assets/gol.xcf b/.github/assets/gol.xcf new file mode 100644 index 0000000..88e96b1 Binary files /dev/null and b/.github/assets/gol.xcf differ diff --git a/.github/assets/golsky.png b/.github/assets/golsky.png new file mode 100644 index 0000000..ad9899d Binary files /dev/null and b/.github/assets/golsky.png differ diff --git a/.github/assets/track.pal b/.github/assets/track.pal new file mode 100644 index 0000000..a1b8e81 --- /dev/null +++ b/.github/assets/track.pal @@ -0,0 +1,7 @@ +JASC-PAL +0100 +4 +25 17 0 +43 27 0 +66 35 0 +82 38 0 diff --git a/.github/assets/track.png b/.github/assets/track.png new file mode 100644 index 0000000..e35bbb4 Binary files /dev/null and b/.github/assets/track.png differ diff --git a/.github/assets/tracklight.pal b/.github/assets/tracklight.pal new file mode 100644 index 0000000..9f07f72 --- /dev/null +++ b/.github/assets/tracklight.pal @@ -0,0 +1,7 @@ +JASC-PAL +0100 +4 +255 195 97 +255 211 140 +255 227 181 +255 240 224 diff --git a/.github/assets/tracklight.png b/.github/assets/tracklight.png new file mode 100644 index 0000000..1c286d2 Binary files /dev/null and b/.github/assets/tracklight.png differ diff --git a/.gitignore b/.gitignore index 9e13a6c..7fc9a42 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -gameoflife +golsky bak dump* diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..17ee469 --- /dev/null +++ b/Makefile @@ -0,0 +1,87 @@ +# 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 config.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) + +all: buildlocal + + +buildlocal: + go build -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 v$(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 diff --git a/README.md b/README.md index 357889c..2ddb0d2 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,21 @@ -# Conway's game of life +# golsky - Conway's game of life written in GO -I wanted to play around a little bit with GOL in golang and here's the -result. It's a simple game using +![Golsky Logo](https://github.com/TLINDEN/golsky/blob/main/.github/assets/golsky.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 [ebitengine](https://github.com/hajimehoshi/ebiten/). # Features * flexible parameters as grid and cell size * colors can be inverted +* evolution tracks can be shown, with age the cells color fades and + old life cells will be drawn in red * game grid lines can be enabled or disabled * game speed can be adjusted on startup and in-game * you can zoom in and out of the canvas and move it around @@ -19,7 +27,12 @@ result. It's a simple game using * you can paint your own patterns in the game * the game can also be started with an empty grid, which is easier to paint patterns -# Build and install +# Install + +In the github releases page you can find ready to use binaries for +your OS. Just download the one you need and use it. + +# Build from source Just execute: `go build .` and use the resulting executable. @@ -30,7 +43,7 @@ You'll need the golang toolchain. The game has a couple of commandline options: ```default -Usage of ./gameoflife: +Usage of ./golsky: -c, --cellsize int cell size in pixels (default 8) -d, --debug show debug info -D, --density int density of random cells (default 10) @@ -64,7 +77,7 @@ While it runs, there are a couple of commands you can use: # Report bugs -[Please open an issue](https://github.com/TLINDEN/gameoflife/issues). Thanks! +[Please open an issue](https://github.com/TLINDEN/golsky/issues). Thanks! # License diff --git a/game.go b/game.go index 2c359b0..0704c64 100644 --- a/game.go +++ b/game.go @@ -11,35 +11,36 @@ import ( "github.com/hajimehoshi/ebiten/v2/ebitenutil" "github.com/hajimehoshi/ebiten/v2/inpututil" "github.com/hajimehoshi/ebiten/v2/vector" - "github.com/tlinden/gameoflife/rle" + "github.com/tlinden/golsky/rle" "golang.org/x/image/math/f64" ) type Images struct { - Black, White, Beige *ebiten.Image + Black, White, Age1, Age2, Age3, Age4, Old *ebiten.Image } type Game struct { - Grids []*Grid // 2 grids: one current, one next - History *Grid // holds state of past dead cells for evolution tracks - Index int // points to current grid - Width, Height, Cellsize, Density int // measurements - ScreenWidth, ScreenHeight int - Generations int64 // Stats - Black, White, Grey, Beige color.RGBA - TPG int // ticks per generation/game speed, 1==max - TicksElapsed int // tick counter for game speed - Debug, Paused, Empty, Invert bool // game modi - ShowEvolution, NoGrid, RunOneStep bool // flags - Rule *Rule // which rule to use, default: B3/S23 - Tiles Images // pre-computed tiles for dead and alife cells - RLE *rle.RLE // loaded GOL pattern from RLE file - Camera Camera // for zoom+move - World *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 []int // used to check if the user is dragging - Statefile string // load game state from it if non-nil + Grids []*Grid // 2 grids: one current, one next + History *Grid // holds state of past dead cells for evolution tracks + Index int // points to current grid + Width, Height, Cellsize, Density int // measurements + ScreenWidth, ScreenHeight int + Generations int64 // Stats + Black, White, Grey, Old color.RGBA + AgeColor1, AgeColor2, AgeColor3, AgeColor4 color.RGBA + TPG int // ticks per generation/game speed, 1==max + TicksElapsed int // tick counter for game speed + Debug, Paused, Empty, Invert bool // game modi + ShowEvolution, NoGrid, RunOneStep bool // flags + Rule *Rule // which rule to use, default: B3/S23 + Tiles Images // pre-computed tiles for dead and alife cells + RLE *rle.RLE // loaded GOL pattern from RLE file + Camera Camera // for zoom+move + World *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 []int // used to check if the user is dragging + Statefile string // load game state from it if non-nil } func (game *Game) Layout(outsideWidth, outsideHeight int) (int, int) { @@ -91,7 +92,10 @@ func (game *Game) UpdateCells() { // change state of current cell in next grid game.Grids[next].Data[y][x] = nextstate - if state == Alive && nextstate == Dead { + // 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 tracking is enabled + if state != nextstate { game.History.Data[y][x] = game.Generations } } @@ -272,13 +276,27 @@ func (game *Game) Draw(screen *ebiten.Image) { op.GeoM.Reset() op.GeoM.Translate(float64(x*game.Cellsize), float64(y*game.Cellsize)) + age := game.Generations - game.History.Data[y][x] + switch game.Grids[game.Index].Data[y][x] { case 1: - - game.World.DrawImage(game.Tiles.Black, op) + if age > 50 && game.ShowEvolution { + game.World.DrawImage(game.Tiles.Old, op) + } else { + game.World.DrawImage(game.Tiles.Black, op) + } case 0: - if game.History.Data[y][x] == 1 && game.ShowEvolution { - game.World.DrawImage(game.Tiles.Beige, op) + if game.History.Data[y][x] > 1 && game.ShowEvolution { + switch { + case age < 10: + game.World.DrawImage(game.Tiles.Age1, op) + case age < 20: + game.World.DrawImage(game.Tiles.Age2, op) + case age < 30: + game.World.DrawImage(game.Tiles.Age3, op) + default: + game.World.DrawImage(game.Tiles.Age4, op) + } } else { game.World.DrawImage(game.Tiles.White, op) } @@ -365,25 +383,43 @@ func (game *Game) InitGrid(grid *Grid) { // prepare tile images func (game *Game) InitTiles() { + game.Grey = color.RGBA{128, 128, 128, 0xff} + game.Old = color.RGBA{255, 30, 30, 0xff} + game.Black = color.RGBA{0, 0, 0, 0xff} game.White = color.RGBA{200, 200, 200, 0xff} - game.Grey = color.RGBA{128, 128, 128, 0xff} - game.Beige = color.RGBA{0xff, 0xf8, 0xdc, 0xff} + game.AgeColor1 = color.RGBA{255, 195, 97, 0xff} // FIXME: use slice! + game.AgeColor2 = color.RGBA{255, 211, 140, 0xff} + game.AgeColor3 = color.RGBA{255, 227, 181, 0xff} + game.AgeColor4 = color.RGBA{255, 240, 224, 0xff} if game.Invert { game.White = color.RGBA{0, 0, 0, 0xff} game.Black = color.RGBA{200, 200, 200, 0xff} - game.Beige = color.RGBA{0x30, 0x1c, 0x11, 0xff} + + game.AgeColor1 = color.RGBA{82, 38, 0, 0xff} + game.AgeColor2 = color.RGBA{66, 35, 0, 0xff} + game.AgeColor3 = color.RGBA{43, 27, 0, 0xff} + game.AgeColor4 = color.RGBA{25, 17, 0, 0xff} } - game.Tiles.Beige = ebiten.NewImage(game.Cellsize, game.Cellsize) game.Tiles.Black = ebiten.NewImage(game.Cellsize, game.Cellsize) game.Tiles.White = ebiten.NewImage(game.Cellsize, game.Cellsize) + game.Tiles.Old = ebiten.NewImage(game.Cellsize, game.Cellsize) + game.Tiles.Age1 = ebiten.NewImage(game.Cellsize, game.Cellsize) + game.Tiles.Age2 = ebiten.NewImage(game.Cellsize, game.Cellsize) + game.Tiles.Age3 = ebiten.NewImage(game.Cellsize, game.Cellsize) + game.Tiles.Age4 = ebiten.NewImage(game.Cellsize, game.Cellsize) cellsize := game.ScreenWidth / game.Cellsize - FillCell(game.Tiles.Beige, cellsize, game.Beige) + FillCell(game.Tiles.Black, cellsize, game.Black) FillCell(game.Tiles.White, cellsize, game.White) + FillCell(game.Tiles.Old, cellsize, game.Old) + FillCell(game.Tiles.Age1, cellsize, game.AgeColor1) + FillCell(game.Tiles.Age2, cellsize, game.AgeColor2) + FillCell(game.Tiles.Age3, cellsize, game.AgeColor3) + FillCell(game.Tiles.Age4, cellsize, game.AgeColor4) } func (game *Game) Init() { diff --git a/go.mod b/go.mod index e918b52..d93f07f 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/tlinden/gameoflife +module github.com/tlinden/golsky go 1.22 diff --git a/main.go b/main.go index 5faabbd..22405f9 100644 --- a/main.go +++ b/main.go @@ -5,14 +5,14 @@ import ( "log" "os" - "github.com/tlinden/gameoflife/rle" + "github.com/tlinden/golsky/rle" "github.com/hajimehoshi/ebiten/v2" "github.com/spf13/pflag" ) const ( - VERSION = "v0.0.4" + VERSION = "v0.0.5" Alive = 1 Dead = 0 ) @@ -64,7 +64,7 @@ func main() { pflag.Parse() if showversion { - fmt.Printf("This is gameoflife version %s\n", VERSION) + fmt.Printf("This is golsky version %s\n", VERSION) os.Exit(0) }