From 7b0a74fb9346dc3983fef8544cea8563cf3a71da Mon Sep 17 00:00:00 2001 From: Thomas von Dein Date: Fri, 14 Jun 2024 17:53:58 +0200 Subject: [PATCH] use cells instead of only bools, use pointer list to all neighbors --- src/grid.go | 111 +++++++++++++++++++++++++++++++++++++++++----------- src/play.go | 57 +++++---------------------- 2 files changed, 97 insertions(+), 71 deletions(-) diff --git a/src/grid.go b/src/grid.go index d35765e..6f8af69 100644 --- a/src/grid.go +++ b/src/grid.go @@ -12,35 +12,100 @@ import ( "github.com/tlinden/golsky/rle" ) +type Cell struct { + State bool + Neighbors [8]*Cell + NeighborCount int +} + +func (cell *Cell) Count() int { + count := 0 + + for idx := 0; idx < cell.NeighborCount; idx++ { + count += bool2int(cell.Neighbors[idx].State) + } + + return count +} + type Grid struct { - Data [][]bool - Width, Height, Density int - Empty bool + Data [][]*Cell + Empty bool + Config *Config } // Create new empty grid and allocate Data according to provided dimensions -func NewGrid(width, height, density int, empty bool) *Grid { +func NewGrid(config *Config) *Grid { grid := &Grid{ - Height: height, - Width: width, - Density: density, - Data: make([][]bool, height), - Empty: empty, + Data: make([][]*Cell, config.Height), + Empty: config.Empty, + Config: config, } - for y := 0; y < height; y++ { - grid.Data[y] = make([]bool, width) + // first setup the cells + for y := 0; y < config.Height; y++ { + grid.Data[y] = make([]*Cell, config.Width) + for x := 0; x < config.Width; x++ { + grid.Data[y][x] = &Cell{} + } + } + + // in a second pass, collect pointers to the neighbors of each cell + for y := 0; y < config.Height; y++ { + for x := 0; x < config.Width; x++ { + grid.SetupNeighbors(x, y) + } } return grid } +func (grid *Grid) SetupNeighbors(x, y int) { + idx := 0 + + for nbgY := -1; nbgY < 2; nbgY++ { + for nbgX := -1; nbgX < 2; nbgX++ { + var col, row int + + if grid.Config.Wrap { + // In wrap mode we look at all the 8 neighbors surrounding us. + // In case we are on an edge we'll look at the neighbor on the + // other side of the grid, thus wrapping lookahead around + // using the mod() function. + col = (x + nbgX + grid.Config.Width) % grid.Config.Width + row = (y + nbgY + grid.Config.Height) % grid.Config.Height + + } else { + // In traditional grid mode the edges are deadly + if x+nbgX < 0 || x+nbgX >= grid.Config.Width || y+nbgY < 0 || y+nbgY >= grid.Config.Height { + continue + } + + col = x + nbgX + row = y + nbgY + } + + if col == x && row == y { + continue + } + + grid.Data[y][x].Neighbors[idx] = grid.Data[row][col] + grid.Data[y][x].NeighborCount++ + idx++ + } + } +} + +// count the living neighbors of a cell +func (grid *Grid) CountNeighbors(x, y int) int { + return grid.Data[y][x].Count() +} + // Create a new 1:1 instance func (grid *Grid) Clone() *Grid { newgrid := &Grid{} - newgrid.Width = grid.Width - newgrid.Height = grid.Height + newgrid.Config = grid.Config newgrid.Data = grid.Data return newgrid @@ -59,7 +124,7 @@ func (grid *Grid) Copy(other *Grid) { func (grid *Grid) Clear() { for y := range grid.Data { for x := range grid.Data[y] { - grid.Data[y][x] = false + grid.Data[y][x].State = false } } } @@ -69,8 +134,8 @@ func (grid *Grid) FillRandom() { if !grid.Empty { for y := range grid.Data { for x := range grid.Data[y] { - if rand.Intn(grid.Density) == 1 { - grid.Data[y][x] = true + if rand.Intn(grid.Config.Density) == 1 { + grid.Data[y][x].State = true } } } @@ -78,9 +143,9 @@ func (grid *Grid) FillRandom() { } func (grid *Grid) Dump() { - for y := 0; y < grid.Height; y++ { - for x := 0; x < grid.Width; x++ { - if grid.Data[y][x] { + for y := 0; y < grid.Config.Height; y++ { + for x := 0; x < grid.Config.Width; x++ { + if grid.Data[y][x].State { fmt.Print("XX") } else { fmt.Print(" ") @@ -93,8 +158,8 @@ func (grid *Grid) Dump() { // initialize using a given RLE pattern func (grid *Grid) LoadRLE(pattern *rle.RLE) { if pattern != nil { - startX := (grid.Width / 2) - (pattern.Width / 2) - startY := (grid.Height / 2) - (pattern.Height / 2) + startX := (grid.Config.Width / 2) - (pattern.Width / 2) + startY := (grid.Config.Height / 2) - (pattern.Height / 2) var y, x int for rowIndex, patternRow := range pattern.Pattern { @@ -103,7 +168,7 @@ func (grid *Grid) LoadRLE(pattern *rle.RLE) { x = colIndex + startX y = rowIndex + startY - grid.Data[y][x] = true + grid.Data[y][x].State = true } } } @@ -214,7 +279,7 @@ func (grid *Grid) SaveState(filename, rule string) error { for y := range grid.Data { for _, cell := range grid.Data[y] { row := "." - if cell { + if cell.State { row = "o" } diff --git a/src/play.go b/src/play.go index edc50ee..ce27b73 100644 --- a/src/play.go +++ b/src/play.go @@ -119,14 +119,14 @@ func (scene *ScenePlay) UpdateCells() { // compute life status of cells for y := 0; y < scene.Config.Height; y++ { for x := 0; x < scene.Config.Width; x++ { - state := scene.Grids[scene.Index].Data[y][x] // 0|1 == dead or alive - neighbors := scene.CountNeighbors(x, y) // alive neighbor count + state := scene.Grids[scene.Index].Data[y][x].State // 0|1 == dead or alive + neighbors := scene.Grids[scene.Index].CountNeighbors(x, y) // actually apply the current rules nextstate := scene.CheckRule(state, neighbors) // change state of current cell in next grid - scene.Grids[next].Data[y][x] = nextstate + scene.Grids[next].Data[y][x].State = nextstate if scene.Config.ShowEvolution { // set history to current generation so we can infer the @@ -136,9 +136,6 @@ func (scene *ScenePlay) UpdateCells() { if state != nextstate { scene.History[y][x] = scene.Generations } - - // 10FPS: - //scene.History.Data[y][x] = (state ^ (1 ^ nextstate)) * (scene.Generations - scene.History.Data[y][x]) } } } @@ -376,7 +373,7 @@ func (scene *ScenePlay) SaveRectRLE() { grid[y] = make([]bool, width) for x := 0; x < width; x++ { - grid[y][x] = scene.Grids[scene.Index].Data[y+starty][x+startx] + grid[y][x] = scene.Grids[scene.Index].Data[y+starty][x+startx].State } } @@ -429,7 +426,7 @@ func (scene *ScenePlay) ToggleCellOnCursorPos(alive bool) { y := int(worldY) / scene.Config.Cellsize if x > -1 && y > -1 && x < scene.Config.Width && y < scene.Config.Height { - scene.Grids[scene.Index].Data[y][x] = alive + scene.Grids[scene.Index].Data[y][x].State = alive scene.History[y][x] = 1 } } @@ -455,7 +452,7 @@ func (scene *ScenePlay) Draw(screen *ebiten.Image) { if scene.Config.ShowEvolution { scene.DrawEvolution(screen, x, y, op) } else { - if scene.Grids[scene.Index].Data[y][x] { + if scene.Grids[scene.Index].Data[y][x].State { scene.World.DrawImage(scene.Theme.Tile(ColLife), op) } } @@ -472,7 +469,7 @@ func (scene *ScenePlay) Draw(screen *ebiten.Image) { func (scene *ScenePlay) DrawEvolution(screen *ebiten.Image, x, y int, op *ebiten.DrawImageOptions) { age := scene.Generations - scene.History[y][x] - switch scene.Grids[scene.Index].Data[y][x] { + switch scene.Grids[scene.Index].Data[y][x].State { case Alive: if age > 50 && scene.Config.ShowEvolution { scene.World.DrawImage(scene.Theme.Tile(ColOld), op) @@ -580,8 +577,8 @@ func (scene *ScenePlay) InitCache() { // initialize grid[s], either using pre-computed from state or rle file, or random func (scene *ScenePlay) InitGrid() { - grida := NewGrid(scene.Config.Width, scene.Config.Height, scene.Config.Density, scene.Config.Empty) - gridb := NewGrid(scene.Config.Width, scene.Config.Height, scene.Config.Density, scene.Config.Empty) + grida := NewGrid(scene.Config) + gridb := NewGrid(scene.Config) // startup is delayed until user has selected options grida.FillRandom() @@ -650,39 +647,3 @@ func (scene *ScenePlay) Init() { func bool2int(b bool) int { return int(*(*byte)(unsafe.Pointer(&b))) } - -// count the living neighbors of a cell -func (scene *ScenePlay) CountNeighbors(x, y int) int { - var sum int - - grid := scene.Grids[scene.Index].Data - - for nbgX := -1; nbgX < 2; nbgX++ { - for nbgY := -1; nbgY < 2; nbgY++ { - var col, row int - if scene.Config.Wrap { - // In wrap mode we look at all the 8 neighbors surrounding us. - // In case we are on an edge we'll look at the neighbor on the - // other side of the grid, thus wrapping lookahead around - // using the mod() function. - col = (x + nbgX + scene.Config.Width) % scene.Config.Width - row = (y + nbgY + scene.Config.Height) % scene.Config.Height - - } else { - // In traditional grid mode the edges are deadly - if x+nbgX < 0 || x+nbgX >= scene.Config.Width || y+nbgY < 0 || y+nbgY >= scene.Config.Height { - continue - } - col = x + nbgX - row = y + nbgY - } - - sum += bool2int(grid[row][col]) - } - } - - // don't count ourselfes though - sum -= bool2int(grid[y][x]) - - return sum -}