Initial commit
implemented the game of life variation with hexagonal grid
This commit is contained in:
169
main.go
Normal file
169
main.go
Normal file
@@ -0,0 +1,169 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user