mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-27 12:18:55 +01:00
7ee8d1aa5f
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.
181 lines
4.0 KiB
Go
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)
|
|
}
|
|
}
|