mirror of
https://codeberg.org/scip/golsky.git
synced 2025-12-16 20:20:57 +01:00
Compare commits
1 Commits
v0.0.8-cod
...
dimensions
| Author | SHA1 | Date | |
|---|---|---|---|
| 01eeab86f7 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
||||
golsky
|
||||
bak
|
||||
dump*
|
||||
rect*
|
||||
*profile
|
||||
|
||||
164
config.go
164
config.go
@@ -1,9 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"runtime/pprof"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/tlinden/golsky/rle"
|
||||
@@ -22,42 +25,136 @@ type Config struct {
|
||||
StateGrid *Grid // a grid from a statefile
|
||||
Wrap bool // wether wraparound mode is in place or not
|
||||
ShowVersion bool
|
||||
|
||||
// for internal profiling
|
||||
ProfileFile string
|
||||
ProfileDraw bool
|
||||
ProfileMaxLoops int64
|
||||
}
|
||||
|
||||
const (
|
||||
VERSION = "v0.0.6"
|
||||
VERSION = "v0.0.7"
|
||||
Alive = 1
|
||||
Dead = 0
|
||||
)
|
||||
|
||||
func GetRLE(filename string) *rle.RLE {
|
||||
// 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
|
||||
return nil
|
||||
}
|
||||
|
||||
// force a geom
|
||||
geometry := strings.Split(geom, "x")
|
||||
if len(geometry) != 2 {
|
||||
return errors.New("failed to parse -g parameters, expecting WIDTHxHEIGHT")
|
||||
}
|
||||
|
||||
width, err := strconv.Atoi(geometry[0])
|
||||
if err != nil {
|
||||
return errors.New("failed to parse width, expecting integer")
|
||||
}
|
||||
|
||||
height, err := strconv.Atoi(geometry[1])
|
||||
if err != nil {
|
||||
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)
|
||||
config.Cellsize = config.ScreenWidth / config.Width
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// check if we have been given an RLE file to load, then load it and
|
||||
// adjust game settings accordingly
|
||||
func (config *Config) ParseRLE(rlefile string) error {
|
||||
if rlefile == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
rleobj, err := rle.GetRLE(rlefile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rleobj == nil {
|
||||
return errors.New("failed to load RLE file (uncatched module error)")
|
||||
}
|
||||
|
||||
config.RLE = rleobj
|
||||
|
||||
// adjust geometry if needed
|
||||
if config.RLE.Width > config.Width || config.RLE.Height > config.Height {
|
||||
config.Width = config.RLE.Width * 2
|
||||
config.Height = config.RLE.Height * 2
|
||||
config.Cellsize = config.ScreenWidth / config.Width
|
||||
}
|
||||
|
||||
// RLE needs an empty grid
|
||||
config.Empty = true
|
||||
|
||||
// it may come with its own rule
|
||||
if config.RLE.Rule != "" {
|
||||
config.Rule = ParseGameRule(config.RLE.Rule)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parse a state file, if given, and adjust game settings accordingly
|
||||
func (config *Config) ParseStatefile(statefile string) error {
|
||||
if config.Statefile == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
grid, err := LoadState(config.Statefile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load game state: %s", err)
|
||||
}
|
||||
|
||||
config.Width = grid.Width
|
||||
config.Height = grid.Height
|
||||
config.Cellsize = config.ScreenWidth / config.Width
|
||||
config.StateGrid = grid
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (config *Config) EnableCPUProfiling(filename string) error {
|
||||
if filename == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(filename)
|
||||
fd, err := os.Create(filename)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
|
||||
parsedRle, err := rle.Parse(string(content))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to load RLE pattern file: %s", err)
|
||||
}
|
||||
pprof.StartCPUProfile(fd)
|
||||
defer pprof.StopCPUProfile()
|
||||
|
||||
return &parsedRle
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseCommandline() *Config {
|
||||
func ParseCommandline() (*Config, error) {
|
||||
config := Config{}
|
||||
|
||||
var rule string
|
||||
var rlefile string
|
||||
var (
|
||||
rule, rlefile, geom string
|
||||
)
|
||||
|
||||
// commandline params, most configure directly config flags
|
||||
pflag.IntVarP(&config.Width, "width", "W", 40, "grid width in cells")
|
||||
pflag.IntVarP(&config.Height, "height", "H", 40, "grid height in cells")
|
||||
pflag.IntVarP(&config.Cellsize, "cellsize", "c", 8, "cell size in pixels")
|
||||
pflag.StringVarP(&geom, "geom", "g", "", "window geometry in WxH in pixels, overturns -c")
|
||||
|
||||
pflag.IntVarP(&config.Density, "density", "D", 10, "density of random cells")
|
||||
pflag.IntVarP(&config.TPG, "ticks-per-generation", "t", 10,
|
||||
"game speed: the higher the slower (default: 10)")
|
||||
@@ -75,38 +172,21 @@ func ParseCommandline() *Config {
|
||||
pflag.BoolVarP(&config.ShowEvolution, "show-evolution", "s", false, "show evolution tracks")
|
||||
pflag.BoolVarP(&config.Wrap, "wrap-around", "w", false, "wrap around grid mode")
|
||||
|
||||
pflag.StringVarP(&config.ProfileFile, "profile-file", "", "", "enable profiling")
|
||||
pflag.BoolVarP(&config.ProfileDraw, "profile-draw", "", false, "profile draw method (default false)")
|
||||
pflag.Int64VarP(&config.ProfileMaxLoops, "profile-max-loops", "", 10, "how many loops to execute (default 10)")
|
||||
|
||||
pflag.Parse()
|
||||
|
||||
// check if we have been given an RLE file to load
|
||||
config.RLE = GetRLE(rlefile)
|
||||
if config.RLE != nil {
|
||||
if config.RLE.Width > config.Width || config.RLE.Height > config.Height {
|
||||
config.Width = config.RLE.Width * 2
|
||||
config.Height = config.RLE.Height * 2
|
||||
fmt.Printf("rlew: %d, rleh: %d, w: %d, h: %d\n",
|
||||
config.RLE.Width, config.RLE.Height, config.Width, config.Height)
|
||||
}
|
||||
|
||||
// RLE needs an empty grid
|
||||
config.Empty = true
|
||||
|
||||
// it may come with its own rule
|
||||
if config.RLE.Rule != "" {
|
||||
config.Rule = ParseGameRule(config.RLE.Rule)
|
||||
}
|
||||
} else if config.Statefile != "" {
|
||||
grid, err := LoadState(config.Statefile)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to load game state: %s", err)
|
||||
}
|
||||
|
||||
config.Width = grid.Width
|
||||
config.Height = grid.Height
|
||||
config.StateGrid = grid
|
||||
err := config.ParseGeom(geom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config.ScreenWidth = config.Cellsize * config.Width
|
||||
config.ScreenHeight = config.Cellsize * config.Height
|
||||
err = config.ParseRLE(rlefile)
|
||||
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
|
||||
@@ -114,5 +194,5 @@ func ParseCommandline() *Config {
|
||||
config.Rule = ParseGameRule(rule)
|
||||
}
|
||||
|
||||
return &config
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
70
main.go
70
main.go
@@ -4,35 +4,77 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"runtime/pprof"
|
||||
"time"
|
||||
|
||||
_ "net/http/pprof"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := ParseCommandline()
|
||||
config, err := ParseCommandline()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if config.ShowVersion {
|
||||
fmt.Printf("This is golsky version %s\n", VERSION)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// grid := [][]int64{
|
||||
// {0, 1, 1},
|
||||
// {0, 1, 0},
|
||||
// {1, 1, 0},
|
||||
// }
|
||||
|
||||
// err := rle.StoreGridToRLE(grid, "test.rle", "B3/S23", 3, 3)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
|
||||
// os.Exit(0)
|
||||
|
||||
game := NewGame(config, Play)
|
||||
|
||||
if config.ProfileFile != "" {
|
||||
// enable cpu profiling and use fake game loop
|
||||
fd, err := os.Create(config.ProfileFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
pprof.StartCPUProfile(fd)
|
||||
defer pprof.StopCPUProfile()
|
||||
|
||||
Ebitfake(game)
|
||||
|
||||
pprof.StopCPUProfile()
|
||||
fd.Close()
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// main loop
|
||||
if err := ebiten.RunGame(game); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// fake game loop, required to be able to profile the program using
|
||||
// pprof. Otherwise any kind of program exit leads to an empty profile
|
||||
// file.
|
||||
func Ebitfake(game *Game) {
|
||||
screen := ebiten.NewImage(game.ScreenWidth, game.ScreenHeight)
|
||||
|
||||
var loops int64
|
||||
|
||||
for {
|
||||
err := game.Update()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if game.Config.ProfileDraw {
|
||||
game.Draw(screen)
|
||||
}
|
||||
|
||||
fmt.Print(".")
|
||||
time.Sleep(16 * time.Millisecond) // around 60 TPS
|
||||
|
||||
if loops >= game.Config.ProfileMaxLoops {
|
||||
break
|
||||
}
|
||||
|
||||
loops++
|
||||
}
|
||||
}
|
||||
|
||||
19
rle/rle.go
19
rle/rle.go
@@ -20,6 +20,25 @@ type RLE struct {
|
||||
patternLineIndex int
|
||||
}
|
||||
|
||||
// wrapper to load a RLE file
|
||||
func GetRLE(filename string) (*RLE, error) {
|
||||
if filename == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parsedRle, err := Parse(string(content))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load RLE pattern file: %s", err)
|
||||
}
|
||||
|
||||
return &parsedRle, nil
|
||||
}
|
||||
|
||||
func Parse(input string) (RLE, error) {
|
||||
rle := RLE{
|
||||
inputLines: strings.Split(input, "\n"),
|
||||
|
||||
Reference in New Issue
Block a user