6 Commits

10 changed files with 140 additions and 97 deletions

27
TODO.md
View File

@@ -1,16 +1,17 @@
- add all other options like size etc
- Clear screen problem:
- it works when hitting the K key, immediately
- its being turned off correctly when entering menu and on when leaving it
- but regardless of the setting, after turning it off, the engine
seems to run a couple of ticks with the old setting before switching
scenes
- looks like a race condition
- obviously with K there are more loops before actually switching
scenes, which doesn't happen with ESC
- if grid lines is disabled, they appear anyway in the first frame (then disappear)
- changing options mid-game has no effect in most cases, even after a restart
- Statefile loading does not work correclty anymore. With larger grids
everything is empty. With square grids part of the grid is cut
off. Smaller grids load though
- Also when loading a state file, centering doesn't work anymore, I
think the geom calculation is overthrown by the parser func. So, put
this calc into its own func and always call. Or - as stated below -
put it onto camera.go and call from Init().
- Zoom 0 on reset only works when world<screen. otherwise zoom would
be negative So, on Init() memoize centered camera position or add a
Center() function to camera.go. Then on reset calculate the zoom
level so that the world fits into the screen.

View File

@@ -3,12 +3,12 @@ package main
import (
"errors"
"fmt"
"math"
"os"
"runtime/pprof"
"strconv"
"strings"
"github.com/alecthomas/repr"
"github.com/spf13/pflag"
"github.com/tlinden/golsky/rle"
)
@@ -30,6 +30,8 @@ type Config struct {
Restart, RestartGrid, RestartCache bool
StartWithMenu bool
Zoomfactor int
InitialCamPos []float64
DelayedStart bool // if true game, we wait. like pause but program induced
// for internal profiling
ProfileFile string
@@ -38,26 +40,19 @@ type Config struct {
}
const (
VERSION = "v0.0.7"
VERSION = "v0.0.8"
Alive = 1
Dead = 0
DEFAULT_WIDTH = 600
DEFAULT_HEIGHT = 400
DEFAULT_CELLSIZE = 4
DEFAULT_ZOOMFACTOR = 150 // FIXME, doesn't work?
DEFAULT_GEOM = "640x384"
DEFAULT_GRID_WIDTH = 600
DEFAULT_GRID_HEIGHT = 400
DEFAULT_CELLSIZE = 4
DEFAULT_ZOOMFACTOR = 150
DEFAULT_GEOM = "640x384"
)
// parse given window geometry and adjust game settings according to it
func (config *Config) ParseGeom(geom string) error {
if geom == "" {
config.ScreenWidth = config.Cellsize * config.Width
config.ScreenHeight = config.Cellsize * config.Height
config.Zoomfactor = 0
return nil
}
// force a geom
geometry := strings.Split(geom, "x")
if len(geometry) != 2 {
@@ -74,25 +69,27 @@ func (config *Config) ParseGeom(geom string) error {
return errors.New("failed to parse height, expecting integer")
}
/*
// adjust dimensions, account for grid width+height so that cells
// fit into window
config.ScreenWidth = width - (width % config.Width)
config.ScreenHeight = height - (height % config.Height)
if config.ScreenWidth == 0 || config.ScreenHeight == 0 {
return errors.New("the number of requested cells don't fit into the requested window size")
}
*/
config.ScreenWidth = width
config.ScreenHeight = height
//config.Cellsize = config.ScreenWidth / config.Width
config.Cellsize = DEFAULT_CELLSIZE
config.Zoomfactor = DEFAULT_ZOOMFACTOR
repr.Println(config)
// calculate the initial cam pos. It is negative if the total grid
// size is smaller than the screen in a centered position, but
// it's zero if it's equal or larger than the screen.
config.InitialCamPos = make([]float64, 2)
config.InitialCamPos[0] = float64(((config.ScreenWidth - (config.Width * config.Cellsize)) / 2) * -1)
if config.Width*config.Cellsize >= config.ScreenWidth {
// must be positive if world wider than screen
config.InitialCamPos[0] = math.Abs(config.InitialCamPos[0])
}
if config.Height*config.Cellsize > config.ScreenHeight {
config.InitialCamPos[1] = math.Abs(float64(((config.ScreenHeight - (config.Height * config.Cellsize)) / 2)))
}
return nil
}
@@ -121,6 +118,9 @@ func (config *Config) ParseRLE(rlefile string) error {
config.Cellsize = config.ScreenWidth / config.Width
}
fmt.Printf("width: %d, screenwidth: %d, rlewidth: %d, cellsize: %d\n",
config.Width, config.ScreenWidth, config.RLE.Width, config.Cellsize)
// RLE needs an empty grid
config.Empty = true
@@ -133,7 +133,7 @@ func (config *Config) ParseRLE(rlefile string) error {
}
// parse a state file, if given, and adjust game settings accordingly
func (config *Config) ParseStatefile(statefile string) error {
func (config *Config) ParseStatefile() error {
if config.Statefile == "" {
return nil
}
@@ -175,8 +175,8 @@ func ParseCommandline() (*Config, error) {
)
// commandline params, most configure directly config flags
pflag.IntVarP(&config.Width, "width", "W", DEFAULT_WIDTH, "grid width in cells")
pflag.IntVarP(&config.Height, "height", "H", DEFAULT_HEIGHT, "grid height in cells")
pflag.IntVarP(&config.Width, "width", "W", DEFAULT_GRID_WIDTH, "grid width in cells")
pflag.IntVarP(&config.Height, "height", "H", DEFAULT_GRID_HEIGHT, "grid height in cells")
pflag.IntVarP(&config.Cellsize, "cellsize", "c", 8, "cell size in pixels")
pflag.StringVarP(&geom, "geom", "G", DEFAULT_GEOM, "window geometry in WxH in pixels, overturns -c")
@@ -191,7 +191,7 @@ func ParseCommandline() (*Config, error) {
pflag.BoolVarP(&config.ShowVersion, "version", "v", false, "show version")
pflag.BoolVarP(&config.Paused, "paused", "p", false, "do not start simulation (use space to start)")
pflag.BoolVarP(&config.Debug, "debug", "d", false, "show debug info")
pflag.BoolVarP(&config.ShowGrid, "show-grid", "g", true, "draw grid lines")
pflag.BoolVarP(&config.ShowGrid, "show-grid", "g", false, "draw grid lines")
pflag.BoolVarP(&config.Empty, "empty", "e", false, "start with an empty screen")
pflag.BoolVarP(&config.Invert, "invert", "i", false, "invert colors (dead cell: black)")
pflag.BoolVarP(&config.ShowEvolution, "show-evolution", "s", false, "show evolution traces")
@@ -214,12 +214,18 @@ func ParseCommandline() (*Config, error) {
return nil, err
}
err = config.ParseStatefile()
if err != nil {
return nil, err
}
// load rule from commandline when no rule came from RLE file,
// default is B3/S23, aka conways game of life
if config.Rule == nil {
config.Rule = ParseGameRule(rule)
}
//repr.Println(config)
return &config, nil
}

35
game.go
View File

@@ -1,8 +1,6 @@
package main
import (
"fmt"
"github.com/hajimehoshi/ebiten/v2"
)
@@ -53,26 +51,29 @@ func (game *Game) Update() error {
scene := game.GetCurrentScene()
scene.Update()
fmt.Printf("Clear Screen: %t\n", ebiten.IsScreenClearedEveryFrame())
next := scene.GetNext()
if next != game.CurrentScene {
scene.ResetNext()
game.CurrentScene = next
}
return nil
}
func (game *Game) Draw(screen *ebiten.Image) {
var nextscene Scene
// first draw primary scene[s], although there are only 1
for current, scene := range game.Scenes {
if scene.IsPrimary() {
// primary scenes always draw
scene.Draw(screen)
if current == game.CurrentScene {
// avoid to redraw it in the next step
return
}
}
}
scene := game.GetCurrentScene()
next := scene.GetNext()
if next != game.CurrentScene {
scene.ResetNext()
game.CurrentScene = next
nextscene = game.GetCurrentScene()
ebiten.SetScreenClearedEveryFrame(nextscene.Clearscreen())
}
scene.Draw(screen)
if nextscene != nil {
nextscene.Draw(screen)
}
}

15
grid.go
View File

@@ -78,6 +78,19 @@ 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] == 1 {
fmt.Print("XX")
} else {
fmt.Print(" ")
}
}
fmt.Println()
}
}
// initialize using a given RLE pattern
func (grid *Grid) LoadRLE(pattern *rle.RLE) {
if pattern != nil {
@@ -95,6 +108,8 @@ func (grid *Grid) LoadRLE(pattern *rle.RLE) {
}
}
}
//grid.Dump()
}
}

View File

@@ -12,11 +12,12 @@ import (
)
func main() {
dau := true
var directstart bool
if len(os.Args) > 1 {
dau = false
directstart = true
}
config, err := ParseCommandline()
if err != nil {
log.Fatal(err)
@@ -28,8 +29,9 @@ func main() {
}
start := Play
if dau {
if !directstart {
start = Menu
config.DelayedStart = true
}
game := NewGame(config, SceneName(start))

View File

@@ -46,14 +46,11 @@ func (scene *SceneMenu) SetNext(next SceneName) {
scene.Next = next
}
func (scene *SceneMenu) Clearscreen() bool {
return false
}
func (scene *SceneMenu) Update() error {
scene.Ui.Update()
if inpututil.IsKeyJustPressed(ebiten.KeyEscape) || inpututil.IsKeyJustPressed(ebiten.KeyQ) {
scene.Config.DelayedStart = false
scene.Leave()
}
@@ -61,6 +58,10 @@ func (scene *SceneMenu) Update() error {
}
func (scene *SceneMenu) IsPrimary() bool {
return false
}
func (scene *SceneMenu) Draw(screen *ebiten.Image) {
scene.Ui.Draw(screen)
}
@@ -102,7 +103,7 @@ func (scene *SceneMenu) Init() {
separator2 := NewSeparator()
separator3 := NewSeparator()
cancel := NewMenuButton("Close Window",
cancel := NewMenuButton("Back",
func(args *widget.ButtonClickedEventArgs) {
scene.Leave()
})

View File

@@ -44,7 +44,7 @@ func (scene *SceneOptions) SetNext(next SceneName) {
scene.Next = next
}
func (scene *SceneOptions) Clearscreen() bool {
func (scene *SceneOptions) IsPrimary() bool {
return false
}

View File

@@ -18,6 +18,10 @@ type Images struct {
Black, White, Age1, Age2, Age3, Age4, Old *ebiten.Image
}
const (
DEBUG_FORMAT = "FPS: %0.2f, TPG: %d, M: %0.2fMB, Generations: %d\nScale: %.02f, Zoom: %d, Cam: %.02f,%.02f Cursor: %d,%d %s"
)
type ScenePlay struct {
Game *Game
Config *Config
@@ -61,6 +65,10 @@ func NewPlayScene(game *Game, config *Config) Scene {
return scene
}
func (scene *ScenePlay) IsPrimary() bool {
return true
}
func (scene *ScenePlay) GetNext() SceneName {
return scene.Next
}
@@ -73,10 +81,6 @@ func (scene *ScenePlay) SetNext(next SceneName) {
scene.Next = next
}
func (scene *ScenePlay) Clearscreen() bool {
return true
}
func (scene *ScenePlay) CheckRule(state int64, neighbors int64) int64 {
var nextstate int64
@@ -366,7 +370,8 @@ func (scene *ScenePlay) SaveRectRLE() {
func (scene *ScenePlay) Update() error {
if scene.Config.Restart {
scene.Config.Restart = false
scene.Init()
scene.InitGrid(nil)
scene.InitCache()
return nil
}
@@ -395,7 +400,7 @@ func (scene *ScenePlay) ToggleCellOnCursorPos(alive int64) {
x := int(worldX) / scene.Config.Cellsize
y := int(worldY) / scene.Config.Cellsize
if x > -1 && y > -1 {
if x > -1 && y > -1 && x < scene.Config.Width && y < scene.Config.Height {
scene.Grids[scene.Index].Data[y][x] = alive
scene.History.Data[y][x] = 1
}
@@ -484,10 +489,14 @@ func (scene *ScenePlay) DrawDebug(screen *ebiten.Image) {
paused = "-- paused --"
}
x, y := ebiten.CursorPosition()
debug := fmt.Sprintf(
"FPS: %0.2f, TPG: %d, Mem: %0.2fMB, Gen: %d, Scale: %.02f, Z: %d, Clear: %t %s",
DEBUG_FORMAT,
ebiten.ActualTPS(), scene.TPG, GetMem(), scene.Generations,
scene.Game.Scale, scene.Camera.ZoomFactor, ebiten.IsScreenClearedEveryFrame(), paused)
scene.Game.Scale, scene.Camera.ZoomFactor,
scene.Camera.Position[0], scene.Camera.Position[1],
x, y,
paused)
FontRenderer.Renderer.SetSizePx(10 + int(scene.Game.Scale*10))
FontRenderer.Renderer.SetTarget(screen)
@@ -500,6 +509,7 @@ func (scene *ScenePlay) DrawDebug(screen *ebiten.Image) {
fmt.Println(debug)
}
}
// load a pre-computed pattern from RLE file
@@ -549,6 +559,7 @@ func (scene *ScenePlay) InitGrid(grid *Grid) {
gridb := NewGrid(scene.Config.Width, scene.Config.Height, scene.Config.Density, scene.Config.Empty)
history := NewGrid(scene.Config.Width, scene.Config.Height, scene.Config.Density, scene.Config.Empty)
// startup is delayed until user has selected options
grida.FillRandom()
grida.Copy(history)
@@ -607,7 +618,6 @@ func (scene *ScenePlay) Init() {
if scene.Config.StateGrid != nil {
grid = scene.Config.StateGrid
}
scene.Camera = Camera{
@@ -617,12 +627,27 @@ func (scene *ScenePlay) Init() {
},
}
scene.World = ebiten.NewImage(scene.Config.ScreenWidth, scene.Config.ScreenHeight)
scene.Cache = ebiten.NewImage(scene.Config.ScreenWidth, scene.Config.ScreenHeight)
scene.World = ebiten.NewImage(
scene.Config.Width*scene.Config.Cellsize,
scene.Config.Height*scene.Config.Cellsize,
)
scene.Cache = ebiten.NewImage(
scene.Config.Width*scene.Config.Cellsize,
scene.Config.Height*scene.Config.Cellsize,
)
scene.InitTiles()
scene.InitCache()
scene.InitGrid(grid)
if scene.Config.DelayedStart && !scene.Config.Empty {
scene.Config.Empty = true
scene.InitGrid(grid)
scene.Config.Empty = false
} else {
scene.InitGrid(grid)
}
scene.InitPattern()
scene.Index = 0
@@ -633,6 +658,9 @@ func (scene *ScenePlay) Init() {
if scene.Config.Zoomfactor < 0 || scene.Config.Zoomfactor > 0 {
scene.Camera.ZoomFactor = scene.Config.Zoomfactor
}
scene.Camera.Position[0] = scene.Config.InitialCamPos[0]
scene.Camera.Position[1] = scene.Config.InitialCamPos[1]
}
// count the living neighbors of a cell

View File

@@ -14,9 +14,9 @@ type Scene interface {
SetNext(SceneName)
GetNext() SceneName
ResetNext()
Clearscreen() bool
Update() error
Draw(screen *ebiten.Image)
IsPrimary() bool // if true, this scene will be always drawn
}
const (

View File

@@ -69,23 +69,12 @@ func NewSeparator() widget.PreferredSizeLocateableWidget {
widget.ContainerOpts.Layout(widget.NewRowLayout(
widget.RowLayoutOpts.Direction(widget.DirectionVertical),
widget.RowLayoutOpts.Padding(widget.Insets{
Top: 20,
Bottom: 20,
Top: 3,
Bottom: 0,
}))),
widget.ContainerOpts.WidgetOpts(
widget.WidgetOpts.LayoutData(
widget.RowLayoutData{Stretch: true})))
c.AddChild(widget.NewGraphic(
widget.GraphicOpts.WidgetOpts(widget.WidgetOpts.LayoutData(widget.RowLayoutData{
Stretch: true,
MaxHeight: 2,
})),
widget.GraphicOpts.ImageNineSlice(
image.NewNineSliceColor(
color.NRGBA{0xdf, 0xf4, 0xff, 0xff})),
))
return c
}
@@ -125,7 +114,7 @@ func NewRowContainer(title string) *RowContainer {
),
widget.ContainerOpts.Layout(widget.NewRowLayout(
widget.RowLayoutOpts.Direction(widget.DirectionVertical),
widget.RowLayoutOpts.Padding(widget.NewInsetsSimple(20)),
widget.RowLayoutOpts.Padding(widget.NewInsetsSimple(8)),
widget.RowLayoutOpts.Spacing(0),
)),
widget.ContainerOpts.BackgroundImage(buttonImageHover),