170 lines
3.6 KiB
Go
170 lines
3.6 KiB
Go
package main
|
|
|
|
import (
|
|
"image/color"
|
|
"log"
|
|
"time"
|
|
|
|
"github.com/nmoniz/game-of-life/grid"
|
|
"github.com/nmoniz/game-of-life/math"
|
|
"github.com/nmoniz/game-of-life/ui"
|
|
|
|
"github.com/ebitenui/ebitenui"
|
|
"github.com/ebitenui/ebitenui/input"
|
|
"github.com/ebitenui/ebitenui/widget"
|
|
"github.com/hajimehoshi/ebiten/v2"
|
|
"github.com/hajimehoshi/ebiten/v2/vector"
|
|
)
|
|
|
|
type Game struct {
|
|
ui *ebitenui.UI
|
|
|
|
Grid grid.HexLayout
|
|
Alive map[grid.Hex]struct{}
|
|
Dead map[grid.Hex]struct{}
|
|
Ticker Ticker
|
|
Speed int
|
|
Paused bool
|
|
|
|
UnderPopThreshold int
|
|
OverPopThreshold int
|
|
ReproductionTarget int
|
|
}
|
|
|
|
func NewGame() *Game {
|
|
g := &Game{
|
|
ui: &ebitenui.UI{Container: ui.Root()},
|
|
|
|
Grid: grid.HexLayout{
|
|
Orientation: grid.PointyTopLayout(),
|
|
Origin: math.Pair[int]{X: 4, Y: 4},
|
|
Radius: math.Pair[int]{X: 8, Y: 8},
|
|
},
|
|
Alive: make(map[grid.Hex]struct{}),
|
|
Dead: make(map[grid.Hex]struct{}),
|
|
|
|
Ticker: *NewTicker(1, time.Second),
|
|
Speed: 0,
|
|
|
|
OverPopThreshold: 5,
|
|
UnderPopThreshold: 2,
|
|
ReproductionTarget: 2,
|
|
}
|
|
|
|
_ = ui.EventBus().Listen(ui.SpeedSliderEvent, func(e ui.Event) {
|
|
args, ok := e.Data().(*widget.SliderChangedEventArgs)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
g.Speed = args.Current
|
|
|
|
if g.Speed > 0 {
|
|
g.Ticker.Reset(g.Speed, time.Second)
|
|
}
|
|
})
|
|
|
|
_ = ui.EventBus().Listen(ui.OverPopulationSliderEvent, func(e ui.Event) {
|
|
args, ok := e.Data().(*widget.SliderChangedEventArgs)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
g.OverPopThreshold = args.Current
|
|
})
|
|
|
|
_ = ui.EventBus().Listen(ui.UnderPopulationSliderEvent, func(e ui.Event) {
|
|
args, ok := e.Data().(*widget.SliderChangedEventArgs)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
g.UnderPopThreshold = args.Current
|
|
})
|
|
|
|
_ = ui.EventBus().Listen(ui.ReproductionTargetEvent, func(e ui.Event) {
|
|
args, ok := e.Data().(*widget.SliderChangedEventArgs)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
g.ReproductionTarget = args.Current
|
|
})
|
|
|
|
_ = ui.EventBus().Listen(ui.ResetButtonEvent, func(ui.Event) {
|
|
g.Alive = make(map[grid.Hex]struct{})
|
|
g.Dead = make(map[grid.Hex]struct{})
|
|
})
|
|
|
|
return g
|
|
}
|
|
|
|
func (g *Game) Update() error {
|
|
g.ui.Update()
|
|
|
|
if !input.UIHovered && input.MouseButtonJustPressed(ebiten.MouseButtonLeft) {
|
|
x, y := ebiten.CursorPosition()
|
|
hexPos := g.Grid.PixToHex(math.NewPair(x, y))
|
|
if _, ok := g.Alive[hexPos]; ok {
|
|
delete(g.Alive, hexPos)
|
|
} else {
|
|
g.Alive[hexPos] = struct{}{}
|
|
}
|
|
}
|
|
|
|
if g.Speed == 0 {
|
|
return nil
|
|
}
|
|
|
|
if len(g.Alive) > 0 && g.Ticker.HasTicked() {
|
|
g.Dead = make(map[grid.Hex]struct{})
|
|
|
|
neighbourhood := make(map[grid.Hex]int)
|
|
for hex := range g.Alive {
|
|
neighbourhood[hex] = 0
|
|
for _, neighbourHex := range hex.AllNeighbors() {
|
|
neighbourhood[neighbourHex]++
|
|
}
|
|
}
|
|
|
|
for hex, count := range neighbourhood {
|
|
if _, ok := g.Alive[hex]; ok {
|
|
if count < g.UnderPopThreshold || count > g.OverPopThreshold {
|
|
g.Dead[hex] = struct{}{}
|
|
delete(g.Alive, hex)
|
|
}
|
|
} else if count == g.ReproductionTarget {
|
|
g.Alive[hex] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (g *Game) Draw(screen *ebiten.Image) {
|
|
for k := range g.Dead {
|
|
pixPosition := g.Grid.HexToPix(k)
|
|
vector.FillCircle(screen, float32(pixPosition.X), float32(pixPosition.Y), 5, color.RGBA{128, 128, 128, 0}, true)
|
|
}
|
|
|
|
for k := range g.Alive {
|
|
pixPosition := g.Grid.HexToPix(k)
|
|
vector.FillCircle(screen, float32(pixPosition.X), float32(pixPosition.Y), 5, color.RGBA{0, 255, 0, 0}, true)
|
|
}
|
|
|
|
g.ui.Draw(screen)
|
|
}
|
|
|
|
func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
|
|
return 800, 600
|
|
}
|
|
|
|
func main() {
|
|
ebiten.SetWindowSize(800, 600)
|
|
ebiten.SetWindowTitle("Game of Life with hexagons")
|
|
if err := ebiten.RunGame(NewGame()); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|