diff --git a/doodle.go b/doodle.go index 36f8228..422187c 100644 --- a/doodle.go +++ b/doodle.go @@ -33,8 +33,7 @@ type Doodle struct { width int32 height int32 - nextSecond time.Time - canvas Grid + scene Scene window *sdl.Window renderer *sdl.Renderer @@ -49,9 +48,6 @@ func New(debug bool) *Doodle { running: true, width: 800, height: 600, - canvas: Grid{}, - - nextSecond: time.Now().Add(1 * time.Second), } if !debug { @@ -102,13 +98,25 @@ func (d *Doodle) Run() error { render.Renderer = renderer defer renderer.Destroy() + // Set up the default scene. + if d.scene == nil { + d.Goto(&EditorScene{}) + } + log.Info("Enter Main Loop") for d.running { d.ticks++ + // Poll for events. + _, err := d.events.Poll(d.ticks) + if err != nil { + log.Error("event poll error: %s", err) + return err + } + // Draw a frame and log how long it took. start := time.Now() - err = d.Loop() + err = d.scene.Loop(d) if err != nil { return err } @@ -131,6 +139,18 @@ func (d *Doodle) Run() error { return nil } +// LoadLevel loads a map from JSON into the EditorScene. +func (d *Doodle) LoadLevel(filename string) error { + log.Info("Loading level from file: %s", filename) + scene := &EditorScene{} + err := scene.LoadLevel(filename) + if err != nil { + return err + } + d.Goto(scene) + return nil +} + // TODO: not a global type Pixel struct { start bool @@ -149,78 +169,3 @@ func (p Pixel) String() string { // Grid is a 2D grid of pixels in X,Y notation. type Grid map[Pixel]interface{} - -// TODO: a linked list instead of a slice -var pixelHistory []Pixel - -// Loop runs one loop of the game engine. -func (d *Doodle) Loop() error { - // Poll for events. - ev, err := d.events.Poll(d.ticks) - if err != nil { - log.Error("event poll error: %s", err) - return err - } - - // Taking a screenshot? - if ev.ScreenshotKey.Pressed() { - log.Info("Taking a screenshot") - d.Screenshot() - d.SaveLevel() - } - - // Clear the canvas and fill it with white. - d.renderer.SetDrawColor(255, 255, 255, 255) - d.renderer.Clear() - - // Clicking? Log all the pixels while doing so. - if ev.Button1.Now { - pixel := Pixel{ - start: ev.Button1.Pressed(), - x: ev.CursorX.Now, - y: ev.CursorY.Now, - dx: ev.CursorX.Now, - dy: ev.CursorY.Now, - } - - // Append unique new pixels. - if len(pixelHistory) == 0 || pixelHistory[len(pixelHistory)-1] != pixel { - // If not a start pixel, make the delta coord the previous one. - if !pixel.start && len(pixelHistory) > 0 { - prev := pixelHistory[len(pixelHistory)-1] - pixel.dx = prev.x - pixel.dy = prev.y - } - - pixelHistory = append(pixelHistory, pixel) - - // Save in the pixel canvas map. - d.canvas[pixel] = nil - } - } - - d.renderer.SetDrawColor(0, 0, 0, 255) - for i, pixel := range pixelHistory { - if !pixel.start && i > 0 { - prev := pixelHistory[i-1] - if prev.x == pixel.x && prev.y == pixel.y { - d.renderer.DrawPoint(pixel.x, pixel.y) - } else { - d.renderer.DrawLine( - pixel.x, - pixel.y, - prev.x, - prev.y, - ) - } - } - d.renderer.DrawPoint(pixel.x, pixel.y) - } - - // Draw the FPS. - d.DrawDebugOverlay() - - d.renderer.Present() - - return nil -} diff --git a/editor_scene.go b/editor_scene.go new file mode 100644 index 0000000..1d26a24 --- /dev/null +++ b/editor_scene.go @@ -0,0 +1,221 @@ +package doodle + +import ( + "fmt" + "image" + "image/png" + "io/ioutil" + "os" + "time" + + "git.kirsle.net/apps/doodle/draw" + "git.kirsle.net/apps/doodle/level" +) + +// EditorScene manages the "Edit Level" game mode. +type EditorScene struct { + // History of all the pixels placed by the user. + pixelHistory []Pixel + canvas Grid + + // Canvas size + width int32 + height int32 +} + +// Setup the editor scene. +func (e *EditorScene) Setup(d *Doodle) error { + if e.pixelHistory == nil { + e.pixelHistory = []Pixel{} + } + if e.canvas == nil { + e.canvas = Grid{} + } + e.width = d.width // TODO: canvas width = copy the window size + e.height = d.height + return nil +} + +// Loop the editor scene. +func (e *EditorScene) Loop(d *Doodle) error { + ev := d.events + + // Taking a screenshot? + if ev.ScreenshotKey.Pressed() { + log.Info("Taking a screenshot") + e.Screenshot() + e.SaveLevel() + } + + // Clear the canvas and fill it with white. + d.renderer.SetDrawColor(255, 255, 255, 255) + d.renderer.Clear() + + // Clicking? Log all the pixels while doing so. + if ev.Button1.Now { + pixel := Pixel{ + start: ev.Button1.Pressed(), + x: ev.CursorX.Now, + y: ev.CursorY.Now, + dx: ev.CursorX.Now, + dy: ev.CursorY.Now, + } + + // Append unique new pixels. + if len(e.pixelHistory) == 0 || e.pixelHistory[len(e.pixelHistory)-1] != pixel { + // If not a start pixel, make the delta coord the previous one. + if !pixel.start && len(e.pixelHistory) > 0 { + prev := e.pixelHistory[len(e.pixelHistory)-1] + pixel.dx = prev.x + pixel.dy = prev.y + } + + e.pixelHistory = append(e.pixelHistory, pixel) + + // Save in the pixel canvas map. + e.canvas[pixel] = nil + } + } + + d.renderer.SetDrawColor(0, 0, 0, 255) + for i, pixel := range e.pixelHistory { + if !pixel.start && i > 0 { + prev := e.pixelHistory[i-1] + if prev.x == pixel.x && prev.y == pixel.y { + d.renderer.DrawPoint(pixel.x, pixel.y) + } else { + d.renderer.DrawLine( + pixel.x, + pixel.y, + prev.x, + prev.y, + ) + } + } + d.renderer.DrawPoint(pixel.x, pixel.y) + } + + // Draw the FPS. + d.DrawDebugOverlay() + + d.renderer.Present() + + return nil +} + +// LoadLevel loads a level from disk. +func (e *EditorScene) LoadLevel(filename string) error { + e.pixelHistory = []Pixel{} + e.canvas = Grid{} + + m, err := level.LoadJSON(filename) + if err != nil { + return err + } + + for _, point := range m.Pixels { + pixel := Pixel{ + start: true, + x: point.X, + y: point.Y, + dx: point.X, + dy: point.Y, + } + e.pixelHistory = append(e.pixelHistory, pixel) + e.canvas[pixel] = nil + } + + return nil +} + +// SaveLevel saves the level to disk. +func (e *EditorScene) SaveLevel() { + m := level.Level{ + Version: 1, + Title: "Alpha", + Author: os.Getenv("USER"), + Width: e.width, + Height: e.height, + Palette: []level.Palette{ + level.Palette{ + Color: "#000000", + Solid: true, + }, + }, + Pixels: []level.Pixel{}, + } + + for pixel := range e.canvas { + for point := range draw.Line(pixel.x, pixel.y, pixel.dx, pixel.dy) { + m.Pixels = append(m.Pixels, level.Pixel{ + X: point.X, + Y: point.Y, + Palette: 0, + }) + } + } + + json, err := m.ToJSON() + if err != nil { + log.Error("SaveLevel error: %s", err) + return + } + + filename := fmt.Sprintf("./map-%s.json", + time.Now().Format("2006-01-02T15-04-05"), + ) + err = ioutil.WriteFile(filename, json, 0644) + if err != nil { + log.Error("Create map file error: %s", err) + return + } +} + +// Screenshot saves the level canvas to disk as a PNG image. +func (e *EditorScene) Screenshot() { + screenshot := image.NewRGBA(image.Rect(0, 0, int(e.width), int(e.height))) + + // White-out the image. + for x := 0; x < int(e.width); x++ { + for y := 0; y < int(e.height); y++ { + screenshot.Set(x, y, image.White) + } + } + + // Fill in the dots we drew. + for pixel := range e.canvas { + // A line or a dot? + if pixel.x == pixel.dx && pixel.y == pixel.dy { + screenshot.Set(int(pixel.x), int(pixel.y), image.Black) + } else { + for point := range draw.Line(pixel.x, pixel.y, pixel.dx, pixel.dy) { + screenshot.Set(int(point.X), int(point.Y), image.Black) + } + } + } + + // Create the screenshot directory. + if _, err := os.Stat("./screenshots"); os.IsNotExist(err) { + log.Info("Creating directory: ./screenshots") + err = os.Mkdir("./screenshots", 0755) + if err != nil { + log.Error("Can't create ./screenshots: %s", err) + return + } + } + + filename := fmt.Sprintf("./screenshots/screenshot-%s.png", + time.Now().Format("2006-01-02T15-04-05"), + ) + fh, err := os.Create(filename) + if err != nil { + log.Error(err.Error()) + return + } + defer fh.Close() + + if err := png.Encode(fh, screenshot); err != nil { + log.Error(err.Error()) + return + } +} diff --git a/fps.go b/fps.go index e7f7d85..a624a22 100644 --- a/fps.go +++ b/fps.go @@ -27,12 +27,11 @@ func (d *Doodle) DrawDebugOverlay() { } text := fmt.Sprintf( - "FPS: %d (%dms) (%d,%d) size=%d F12=screenshot", + "FPS: %d (%dms) (%d,%d) F12=screenshot", fpsCurrent, fpsSkipped, d.events.CursorX.Now, d.events.CursorY.Now, - len(pixelHistory), ) render.StrokedText(render.TextConfig{ Text: text, diff --git a/scene.go b/scene.go new file mode 100644 index 0000000..8493a02 --- /dev/null +++ b/scene.go @@ -0,0 +1,17 @@ +package doodle + +// Scene is an abstraction for a game mode in Doodle. The app points to one +// scene at a time and that scene has control over the main loop, and its own +// state information. +type Scene interface { + Setup(*Doodle) error + Loop(*Doodle) error +} + +// Goto a scene. First it unloads the current scene. +func (d *Doodle) Goto(scene Scene) error { + // d.scene.Destroy() + log.Info("Goto Scene") + d.scene = scene + return d.scene.Setup(d) +} diff --git a/screenshot.go b/screenshot.go deleted file mode 100644 index 67b2905..0000000 --- a/screenshot.go +++ /dev/null @@ -1,131 +0,0 @@ -package doodle - -import ( - "fmt" - "image" - "image/png" - "io/ioutil" - "os" - "time" - - "git.kirsle.net/apps/doodle/draw" - "git.kirsle.net/apps/doodle/level" -) - -// SaveLevel saves the level to disk. -func (d *Doodle) SaveLevel() { - m := level.Level{ - Version: 1, - Title: "Alpha", - Author: os.Getenv("USER"), - Width: d.width, - Height: d.height, - Palette: []level.Palette{ - level.Palette{ - Color: "#000000", - Solid: true, - }, - }, - Pixels: []level.Pixel{}, - } - - for pixel := range d.canvas { - for point := range draw.Line(pixel.x, pixel.y, pixel.dx, pixel.dy) { - m.Pixels = append(m.Pixels, level.Pixel{ - X: point.X, - Y: point.Y, - Palette: 0, - }) - } - } - - json, err := m.ToJSON() - if err != nil { - log.Error("SaveLevel error: %s", err) - return - } - - filename := fmt.Sprintf("./map-%s.json", - time.Now().Format("2006-01-02T15-04-05"), - ) - err = ioutil.WriteFile(filename, json, 0644) - if err != nil { - log.Error("Create map file error: %s", err) - return - } -} - -// LoadLevel loads a map from JSON. -func (d *Doodle) LoadLevel(filename string) error { - log.Info("Loading level from file: %s", filename) - pixelHistory = []Pixel{} - d.canvas = Grid{} - - m, err := level.LoadJSON(filename) - if err != nil { - return err - } - - for _, point := range m.Pixels { - pixel := Pixel{ - start: true, - x: point.X, - y: point.Y, - dx: point.X, - dy: point.Y, - } - pixelHistory = append(pixelHistory, pixel) - d.canvas[pixel] = nil - } - - return nil -} - -// Screenshot saves the level canvas to disk as a PNG image. -func (d *Doodle) Screenshot() { - screenshot := image.NewRGBA(image.Rect(0, 0, int(d.width), int(d.height))) - - // White-out the image. - for x := 0; x < int(d.width); x++ { - for y := 0; y < int(d.height); y++ { - screenshot.Set(x, y, image.White) - } - } - - // Fill in the dots we drew. - for pixel := range d.canvas { - // A line or a dot? - if pixel.x == pixel.dx && pixel.y == pixel.dy { - screenshot.Set(int(pixel.x), int(pixel.y), image.Black) - } else { - for point := range draw.Line(pixel.x, pixel.y, pixel.dx, pixel.dy) { - screenshot.Set(int(point.X), int(point.Y), image.Black) - } - } - } - - // Create the screenshot directory. - if _, err := os.Stat("./screenshots"); os.IsNotExist(err) { - log.Info("Creating directory: ./screenshots") - err = os.Mkdir("./screenshots", 0755) - if err != nil { - log.Error("Can't create ./screenshots: %s", err) - return - } - } - - filename := fmt.Sprintf("./screenshots/screenshot-%s.png", - time.Now().Format("2006-01-02T15-04-05"), - ) - fh, err := os.Create(filename) - if err != nil { - log.Error(err.Error()) - return - } - defer fh.Close() - - if err := png.Encode(fh, screenshot); err != nil { - log.Error(err.Error()) - return - } -}