ebiten/examples/life/main.go
dericmiller 7ee8d1aa5f examples/life: Fix initial world randomization (#1045)
Because golang executes the init function after establishing
the variables in the var block, the random number seed wasn't
initializing until after the initial world state had gotten
established (leading to an identical game of Life on every run).
To fix this, we establish an empty world in the var block,
and then populate it in the init function after the random
number generator has been seeded.
2020-01-06 13:09:51 +09:00

181 lines
4.0 KiB
Go

// The MIT License (MIT)
//
// Copyright (c) 2015-2016 Martin Lindhe
// Copyright (c) 2016 Hajime Hoshi
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
// +build example jsgo
package main
import (
"log"
"math/rand"
"time"
"github.com/hajimehoshi/ebiten"
)
// World represents the game state.
type World struct {
area []bool
width int
height int
}
// NewWorld creates a new world.
func NewWorld(width, height int, maxInitLiveCells int) *World {
w := &World{
area: make([]bool, width*height),
width: width,
height: height,
}
w.init(maxInitLiveCells)
return w
}
func init() {
rand.Seed(time.Now().UnixNano())
world = NewWorld(screenWidth, screenHeight, int((screenWidth*screenHeight)/10))
}
// init inits world with a random state.
func (w *World) init(maxLiveCells int) {
for i := 0; i < maxLiveCells; i++ {
x := rand.Intn(w.width)
y := rand.Intn(w.height)
w.area[y*w.width+x] = true
}
}
// Update game state by one tick.
func (w *World) Update() {
width := w.width
height := w.height
next := make([]bool, width*height)
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
pop := neighbourCount(w.area, width, height, x, y)
switch {
case pop < 2:
// rule 1. Any live cell with fewer than two live neighbours
// dies, as if caused by under-population.
next[y*width+x] = false
case (pop == 2 || pop == 3) && w.area[y*width+x]:
// rule 2. Any live cell with two or three live neighbours
// lives on to the next generation.
next[y*width+x] = true
case pop > 3:
// rule 3. Any live cell with more than three live neighbours
// dies, as if by over-population.
next[y*width+x] = false
case pop == 3:
// rule 4. Any dead cell with exactly three live neighbours
// becomes a live cell, as if by reproduction.
next[y*width+x] = true
}
}
}
w.area = next
}
// Draw paints current game state.
func (w *World) Draw(pix []byte) {
for i, v := range w.area {
if v {
pix[4*i] = 0xff
pix[4*i+1] = 0xff
pix[4*i+2] = 0xff
pix[4*i+3] = 0xff
} else {
pix[4*i] = 0
pix[4*i+1] = 0
pix[4*i+2] = 0
pix[4*i+3] = 0
}
}
}
func max(a, b int) int {
if a < b {
return b
}
return a
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
// neighbourCount calculates the Moore neighborhood of (x, y).
func neighbourCount(a []bool, width, height, x, y int) int {
c := 0
for j := -1; j <= 1; j++ {
for i := -1; i <= 1; i++ {
if i == 0 && j == 0 {
continue
}
x2 := x + i
y2 := y + j
if x2 < 0 || y2 < 0 || width <= x2 || height <= y2 {
continue
}
if a[y2*width+x2] {
c++
}
}
}
return c
}
const (
screenWidth = 320
screenHeight = 240
)
var (
world *World
pixels = make([]byte, screenWidth*screenHeight*4)
)
func update(screen *ebiten.Image) error {
world.Update()
if ebiten.IsDrawingSkipped() {
return nil
}
world.Draw(pixels)
screen.ReplacePixels(pixels)
return nil
}
func main() {
if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Game of Life (Ebiten Demo)"); err != nil {
log.Fatal(err)
}
}