sdl2-life

Conway's Game of Life with go-sdl2
git clone https://www.brianlane.com/git/sdl2-life
Log | Files | Refs | README

commit 19471e734f4b05345c9014468e7c0a6d560b0a4f
parent e53b6ca9c41e190991ddc75bb8cb7bd10a788158
Author: Brian C. Lane <bcl@brianlane.com>
Date:   Thu, 24 Nov 2022 09:08:01 -0800

Add an optional server on port 3051 to receive patterns

This adds a server that listens for POST on port 3051 by default. It
accepts Live 1.05 and plain text formatted pattern files (the same as
the -pattern cmdline) and it adds them to the currently running world.

eg. run `sdl2-life -empty -server` and then:

curl --data-binary @./examples/glider-gun-1.05.life http://127.0.0.1:3051/

to add the glider gun example.

Diffstat:
Mmain.go | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 63 insertions(+), 0 deletions(-)

diff --git a/main.go b/main.go @@ -8,6 +8,7 @@ import ( "fmt" "log" "math/rand" + "net/http" "os" "strconv" "strings" @@ -36,6 +37,9 @@ type cmdlineArgs struct { PatternFile string // File with initial pattern Pause bool // Start the game paused Empty bool // Start with empty world + Port int // Port to listen to + Host string // Host IP to bind to + Server bool // Launch an API server when true } /* commandline defaults */ @@ -53,6 +57,9 @@ var cfg = cmdlineArgs{ PatternFile: "", Pause: false, Empty: false, + Port: 3051, + Host: "127.0.0.1", + Server: false, } /* parseArgs handles parsing the cmdline args and setting values in the global cfg struct */ @@ -70,6 +77,9 @@ func parseArgs() { flag.StringVar(&cfg.PatternFile, "pattern", cfg.PatternFile, "File with initial pattern to load") flag.BoolVar(&cfg.Pause, "pause", cfg.Pause, "Start the game paused") flag.BoolVar(&cfg.Empty, "empty", cfg.Empty, "Start with empty world") + flag.IntVar(&cfg.Port, "port", cfg.Port, "Port to listen to") + flag.StringVar(&cfg.Host, "host", cfg.Host, "Host IP to bind to") + flag.BoolVar(&cfg.Server, "server", cfg.Server, "Launch an API server") flag.Parse() } @@ -93,6 +103,9 @@ type Cell struct { y int } +// Pattern is used to pass patterns from the API to the game +type Pattern []string + // LifeGame holds all the global state of the game and the methods to operate on it type LifeGame struct { mp bool @@ -111,6 +124,7 @@ type LifeGame struct { fg RGBAColor cellWidth int32 cellHeight int32 + pChan <-chan Pattern } // cleanup will handle cleanup of allocated resources @@ -505,6 +519,22 @@ func (g *LifeGame) Run() { oneStep = false } } + + if g.pChan != nil { + select { + case pattern := <-g.pChan: + var err error + if strings.HasPrefix(pattern[0], "#Life 1.05") { + err = g.ParseLife105(pattern) + } else { + err = g.ParsePlaintext(pattern) + } + if err != nil { + log.Printf("Pattern error: %s\n", err) + } + default: + } + } } } @@ -640,6 +670,33 @@ func ParseRulestring(rule string) (birth map[int]bool, stayAlive map[int]bool, e return birth, stayAlive, nil } +// Server starts an API server to receive patterns +func Server(host string, port int, pChan chan<- Pattern) { + + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + http.Error(w, "", http.StatusMethodNotAllowed) + return + } + + scanner := bufio.NewScanner(r.Body) + var pattern Pattern + for scanner.Scan() { + pattern = append(pattern, scanner.Text()) + } + if len(pattern) == 0 { + http.Error(w, "Empty pattern", http.StatusServiceUnavailable) + return + } + + // Splat this pattern onto the world + pChan <- pattern + }) + + log.Printf("Starting server on %s:%d", host, port) + log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%d", host, port), nil)) +} + func main() { parseArgs() @@ -670,5 +727,11 @@ func main() { ShowKeysHelp() + if cfg.Server { + ch := make(chan Pattern, 2) + game.pChan = ch + go Server(cfg.Host, cfg.Port, ch) + } + game.Run() }