Basic Collision Detection, Toggle Between Play/Edit

Known bugs:
* The Pixel format in the Grid has DX and DY attributes and
  it wreaks havoc on collision detection in Play Mode when you
  come straight from the editor. Reloading the map from disk to
  play is OK cuz it lacks these attrs.
This commit is contained in:
Noah 2018-07-23 20:10:53 -07:00
parent e13dd62309
commit e141203c4b
13 changed files with 474 additions and 106 deletions

View File

@ -8,10 +8,10 @@ var (
ShellBackgroundColor = render.Color{0, 10, 20, 128} ShellBackgroundColor = render.Color{0, 10, 20, 128}
ShellForegroundColor = render.White ShellForegroundColor = render.White
ShellPadding int32 = 8 ShellPadding int32 = 8
ShellFontSize = 14 ShellFontSize = 16
ShellCursorBlinkRate uint64 = 20 ShellCursorBlinkRate uint64 = 20
ShellHistoryLineCount = 8 ShellHistoryLineCount = 8
// Ticks that a flashed message persists for. // Ticks that a flashed message persists for.
FlashTTL uint64 = 200 FlashTTL uint64 = 400
) )

113
doodads/doodads.go Normal file
View File

@ -0,0 +1,113 @@
package doodads
import (
"git.kirsle.net/apps/doodle/render"
)
// Doodad is a reusable drawing component used in Doodle. Doodads are buttons,
// doors, switches, the player characters themselves, anything that isn't a part
// of the level geometry.
type Doodad interface {
ID() string
// Position and velocity, not saved to disk.
Position() render.Point
Velocity() render.Point
Size() render.Rect
// Movement commands.
MoveBy(render.Point) // Add {X,Y} to current Position.
MoveTo(render.Point) // Set current Position to {X,Y}.
// Implement the Draw function.
Draw(render.Engine)
}
// Collide describes how a collision occurred.
type Collide struct {
X int32
Y int32
W int32
H int32
Top bool
Left bool
Right bool
Bottom bool
}
// CollidesWithGrid checks if a Doodad collides with level geometry.
func CollidesWithGrid(d Doodad, grid *render.Grid) (Collide, bool) {
var (
P = d.Position()
S = d.Size()
topLeft = P
topRight = render.Point{
X: P.X + S.W,
Y: P.Y,
}
bottomLeft = render.Point{
X: P.X,
Y: P.Y + S.H,
}
bottomRight = render.Point{
X: bottomLeft.X + S.W,
Y: P.Y + S.H,
}
)
// Bottom edge.
for point := range render.IterLine2(bottomLeft, bottomRight) {
if grid.Exists(render.Pixel{
X: point.X,
Y: point.Y,
}) {
return Collide{
Bottom: true,
X: point.X,
Y: point.Y,
}, true
}
}
// Top edge.
for point := range render.IterLine2(topLeft, topRight) {
if grid.Exists(render.Pixel{
X: point.X,
Y: point.Y,
}) {
return Collide{
Top: true,
X: point.X,
Y: point.Y,
}, true
}
}
for point := range render.IterLine2(topLeft, bottomLeft) {
if grid.Exists(render.Pixel{
X: point.X,
Y: point.Y,
}) {
return Collide{
Left: true,
X: point.X,
Y: point.Y,
}, true
}
}
for point := range render.IterLine2(topRight, bottomRight) {
if grid.Exists(render.Pixel{
X: point.X,
Y: point.Y,
}) {
return Collide{
Right: true,
X: point.X,
Y: point.Y,
}, true
}
}
return Collide{}, false
}

70
doodads/player.go Normal file
View File

@ -0,0 +1,70 @@
package doodads
import (
"git.kirsle.net/apps/doodle/render"
)
// PlayerID is the Doodad ID for the player character.
const PlayerID = "PLAYER"
// Player is a special doodad for the player character.
type Player struct {
point render.Point
velocity render.Point
size render.Rect
}
// NewPlayer creates the special Player Character doodad.
func NewPlayer() *Player {
return &Player{
point: render.Point{
X: 100,
Y: 100,
},
size: render.Rect{
W: 16,
H: 16,
},
}
}
// ID of the Player singleton.
func (p *Player) ID() string {
return PlayerID
}
// Position of the player.
func (p *Player) Position() render.Point {
return p.point
}
// MoveBy a relative delta position.
func (p *Player) MoveBy(by render.Point) {
p.point.X += by.X
p.point.Y += by.Y
}
// MoveTo an absolute position.
func (p *Player) MoveTo(to render.Point) {
p.point = to
}
// Velocity returns the player's current velocity.
func (p *Player) Velocity() render.Point {
return p.velocity
}
// Size returns the player's size.
func (p *Player) Size() render.Rect {
return p.size
}
// Draw the player sprite.
func (p *Player) Draw(e render.Engine) {
e.DrawRect(render.Magenta, render.Rect{
X: p.point.X,
Y: p.point.Y,
W: p.size.W,
H: p.size.H,
})
}

View File

@ -1,7 +1,6 @@
package doodle package doodle
import ( import (
"fmt"
"time" "time"
"git.kirsle.net/apps/doodle/render" "git.kirsle.net/apps/doodle/render"
@ -167,30 +166,9 @@ func (d *Doodle) EditLevel(filename string) error {
// PlayLevel loads a map from JSON into the PlayScene. // PlayLevel loads a map from JSON into the PlayScene.
func (d *Doodle) PlayLevel(filename string) error { func (d *Doodle) PlayLevel(filename string) error {
log.Info("Loading level from file: %s", filename) log.Info("Loading level from file: %s", filename)
scene := &PlayScene{} scene := &PlayScene{
err := scene.LoadLevel(filename) Filename: filename,
if err != nil {
return err
} }
d.Goto(scene) d.Goto(scene)
return nil return nil
} }
// Pixel TODO: not a global
type Pixel struct {
start bool
x int32
y int32
dx int32
dy int32
}
func (p Pixel) String() string {
return fmt.Sprintf("(%d,%d) delta (%d,%d)",
p.x, p.y,
p.dx, p.dy,
)
}
// Grid is a 2D grid of pixels in X,Y notation.
type Grid map[Pixel]interface{}

View File

@ -3,13 +3,13 @@ package draw
import ( import (
"math" "math"
"git.kirsle.net/apps/doodle/types" "git.kirsle.net/apps/doodle/render"
) )
// Line is a generator that returns the X,Y coordinates to draw a line. // Line is a generator that returns the X,Y coordinates to draw a line.
// https://en.wikipedia.org/wiki/Digital_differential_analyzer_(graphics_algorithm) // https://en.wikipedia.org/wiki/Digital_differential_analyzer_(graphics_algorithm)
func Line(x1, y1, x2, y2 int32) chan types.Point { func Line(x1, y1, x2, y2 int32) chan render.Point {
generator := make(chan types.Point) generator := make(chan render.Point)
go func() { go func() {
var ( var (
@ -28,7 +28,7 @@ func Line(x1, y1, x2, y2 int32) chan types.Point {
x := float64(x1) x := float64(x1)
y := float64(y1) y := float64(y1)
for i := 0; i <= int(step); i++ { for i := 0; i <= int(step); i++ {
generator <- types.Point{ generator <- render.Point{
X: int32(x), X: int32(x),
Y: int32(y), Y: int32(y),
} }

View File

@ -5,7 +5,7 @@ import (
"testing" "testing"
"git.kirsle.net/apps/doodle/draw" "git.kirsle.net/apps/doodle/draw"
"git.kirsle.net/apps/doodle/types" "git.kirsle.net/apps/doodle/render"
) )
func TestLine(t *testing.T) { func TestLine(t *testing.T) {
@ -14,7 +14,7 @@ func TestLine(t *testing.T) {
X2 int32 X2 int32
Y1 int32 Y1 int32
Y2 int32 Y2 int32
Expect []types.Point Expect []render.Point
} }
toString := func(t task) string { toString := func(t task) string {
return fmt.Sprintf("Line<%d,%d -> %d,%d>", return fmt.Sprintf("Line<%d,%d -> %d,%d>",
@ -29,7 +29,7 @@ func TestLine(t *testing.T) {
Y1: 0, Y1: 0,
X2: 0, X2: 0,
Y2: 10, Y2: 10,
Expect: []types.Point{ Expect: []render.Point{
{X: 0, Y: 0}, {X: 0, Y: 0},
{X: 0, Y: 1}, {X: 0, Y: 1},
{X: 0, Y: 2}, {X: 0, Y: 2},
@ -48,7 +48,7 @@ func TestLine(t *testing.T) {
Y1: 10, Y1: 10,
X2: 15, X2: 15,
Y2: 15, Y2: 15,
Expect: []types.Point{ Expect: []render.Point{
{X: 10, Y: 10}, {X: 10, Y: 10},
{X: 11, Y: 11}, {X: 11, Y: 11},
{X: 12, Y: 12}, {X: 12, Y: 12},

View File

@ -16,9 +16,14 @@ import (
// EditorScene manages the "Edit Level" game mode. // EditorScene manages the "Edit Level" game mode.
type EditorScene struct { type EditorScene struct {
// Configuration for the scene initializer.
OpenFile bool
Filename string
Canvas render.Grid
// History of all the pixels placed by the user. // History of all the pixels placed by the user.
pixelHistory []Pixel pixelHistory []render.Pixel
canvas Grid canvas render.Grid
filename string // Last saved filename. filename string // Last saved filename.
// Canvas size // Canvas size
@ -33,11 +38,32 @@ func (s *EditorScene) Name() string {
// Setup the editor scene. // Setup the editor scene.
func (s *EditorScene) Setup(d *Doodle) error { func (s *EditorScene) Setup(d *Doodle) error {
// Were we given configuration data?
if s.Filename != "" {
log.Debug("EditorScene: Set filename to %s", s.Filename)
s.filename = s.Filename
s.Filename = ""
if s.OpenFile {
log.Debug("EditorScene: Loading map from filename at %s", s.filename)
if err := s.LoadLevel(s.filename); err != nil {
return err
}
}
}
if s.Canvas != nil {
log.Debug("EditorScene: Received Canvas from caller")
s.canvas = s.Canvas
s.Canvas = nil
}
d.Flash("Editor Mode. Press 'P' to play this map.")
if s.pixelHistory == nil { if s.pixelHistory == nil {
s.pixelHistory = []Pixel{} s.pixelHistory = []render.Pixel{}
} }
if s.canvas == nil { if s.canvas == nil {
s.canvas = Grid{} log.Debug("EditorScene: Setting default canvas to an empty grid")
s.canvas = render.Grid{}
} }
s.width = d.width // TODO: canvas width = copy the window size s.width = d.width // TODO: canvas width = copy the window size
s.height = d.height s.height = d.height
@ -52,31 +78,45 @@ func (s *EditorScene) Loop(d *Doodle, ev *events.State) error {
s.Screenshot() s.Screenshot()
} }
// Switching to Play Mode?
if ev.KeyName.Read() == "p" {
log.Info("Play Mode, Go!")
d.Goto(&PlayScene{
Canvas: s.canvas,
})
return nil
}
// Clear the canvas and fill it with white. // Clear the canvas and fill it with white.
d.Engine.Clear(render.White) d.Engine.Clear(render.White)
// Clicking? Log all the pixels while doing so. // Clicking? Log all the pixels while doing so.
if ev.Button1.Now { if ev.Button1.Now {
pixel := Pixel{ log.Warn("Button1: %+v", ev.Button1)
start: ev.Button1.Pressed(), pixel := render.Pixel{
x: ev.CursorX.Now, Start: ev.Button1.Pressed(),
y: ev.CursorY.Now, X: ev.CursorX.Now,
dx: ev.CursorX.Now, Y: ev.CursorY.Now,
dy: ev.CursorY.Now, DX: ev.CursorX.Last,
DY: ev.CursorY.Last,
}
if pixel.Start {
log.Warn("START PIXEL %+v", pixel)
} }
// Append unique new pixels. // Append unique new pixels.
if len(s.pixelHistory) == 0 || s.pixelHistory[len(s.pixelHistory)-1] != pixel { if len(s.pixelHistory) == 0 || s.pixelHistory[len(s.pixelHistory)-1] != pixel {
// If not a start pixel, make the delta coord the previous one. // If not a start pixel, make the delta coord the previous one.
if !pixel.start && len(s.pixelHistory) > 0 { if !pixel.Start && len(s.pixelHistory) > 0 {
prev := s.pixelHistory[len(s.pixelHistory)-1] prev := s.pixelHistory[len(s.pixelHistory)-1]
pixel.dx = prev.x pixel.DY = prev.Y
pixel.dy = prev.y pixel.DX = prev.X
} }
s.pixelHistory = append(s.pixelHistory, pixel) s.pixelHistory = append(s.pixelHistory, pixel)
// Save in the pixel canvas map. // Save in the pixel canvas map.
fmt.Printf("%+v", pixel)
s.canvas[pixel] = nil s.canvas[pixel] = nil
} }
} }
@ -86,24 +126,26 @@ func (s *EditorScene) Loop(d *Doodle, ev *events.State) error {
// Draw the current frame. // Draw the current frame.
func (s *EditorScene) Draw(d *Doodle) error { func (s *EditorScene) Draw(d *Doodle) error {
for i, pixel := range s.pixelHistory { // for i, pixel := range s.pixelHistory {
if !pixel.start && i > 0 { // if !pixel.Start && i > 0 {
prev := s.pixelHistory[i-1] // prev := s.pixelHistory[i-1]
if prev.x == pixel.x && prev.y == pixel.y { // if prev.X == pixel.X && prev.Y == pixel.Y {
d.Engine.DrawPoint( // d.Engine.DrawPoint(
render.Black, // render.Black,
render.Point{pixel.x, pixel.y}, // render.Point{pixel.X, pixel.Y},
) // )
} else { // } else {
d.Engine.DrawLine( // d.Engine.DrawLine(
render.Black, // render.Black,
render.Point{pixel.x, pixel.y}, // render.Point{pixel.X, pixel.Y},
render.Point{prev.x, prev.y}, // render.Point{prev.X, prev.Y},
) // )
} // }
} // }
d.Engine.DrawPoint(render.Black, render.Point{pixel.x, pixel.y}) // d.Engine.DrawPoint(render.Black, render.Point{pixel.X, pixel.Y})
} // }
s.canvas.Draw(d.Engine)
return nil return nil
} }
@ -111,8 +153,8 @@ func (s *EditorScene) Draw(d *Doodle) error {
// LoadLevel loads a level from disk. // LoadLevel loads a level from disk.
func (s *EditorScene) LoadLevel(filename string) error { func (s *EditorScene) LoadLevel(filename string) error {
s.filename = filename s.filename = filename
s.pixelHistory = []Pixel{} s.pixelHistory = []render.Pixel{}
s.canvas = Grid{} s.canvas = render.Grid{}
m, err := level.LoadJSON(filename) m, err := level.LoadJSON(filename)
if err != nil { if err != nil {
@ -120,12 +162,12 @@ func (s *EditorScene) LoadLevel(filename string) error {
} }
for _, point := range m.Pixels { for _, point := range m.Pixels {
pixel := Pixel{ pixel := render.Pixel{
start: true, Start: true,
x: point.X, X: point.X,
y: point.Y, Y: point.Y,
dx: point.X, DX: point.X,
dy: point.Y, DY: point.Y,
} }
s.pixelHistory = append(s.pixelHistory, pixel) s.pixelHistory = append(s.pixelHistory, pixel)
s.canvas[pixel] = nil s.canvas[pixel] = nil
@ -153,12 +195,20 @@ func (s *EditorScene) SaveLevel(filename string) {
} }
for pixel := range s.canvas { for pixel := range s.canvas {
for point := range draw.Line(pixel.x, pixel.y, pixel.dx, pixel.dy) { if pixel.DX == 0 && pixel.DY == 0 {
m.Pixels = append(m.Pixels, level.Pixel{ m.Pixels = append(m.Pixels, level.Pixel{
X: point.X, X: pixel.X,
Y: point.Y, Y: pixel.Y,
Palette: 0, Palette: 0,
}) })
} else {
for point := range render.IterLine(pixel.X, pixel.Y, pixel.DX, pixel.DY) {
m.Pixels = append(m.Pixels, level.Pixel{
X: point.X,
Y: point.Y,
Palette: 0,
})
}
} }
} }
@ -189,10 +239,10 @@ func (s *EditorScene) Screenshot() {
// Fill in the dots we drew. // Fill in the dots we drew.
for pixel := range s.canvas { for pixel := range s.canvas {
// A line or a dot? // A line or a dot?
if pixel.x == pixel.dx && pixel.y == pixel.dy { if pixel.DX == 0 && pixel.DY == 0 {
screenshot.Set(int(pixel.x), int(pixel.y), image.Black) screenshot.Set(int(pixel.X), int(pixel.Y), image.Black)
} else { } else {
for point := range draw.Line(pixel.x, pixel.y, pixel.dx, pixel.dy) { for point := range draw.Line(pixel.X, pixel.Y, pixel.DX, pixel.DY) {
screenshot.Set(int(point.X), int(point.Y), image.Black) screenshot.Set(int(point.X), int(point.Y), image.Black)
} }
} }
@ -223,3 +273,8 @@ func (s *EditorScene) Screenshot() {
return return
} }
} }
// Destroy the scene.
func (s *EditorScene) Destroy() error {
return nil
}

View File

@ -7,11 +7,12 @@ import (
// Level is the container format for Doodle map drawings. // Level is the container format for Doodle map drawings.
type Level struct { type Level struct {
Version int32 `json:"version"` // File format version spec. Version int32 `json:"version"` // File format version spec.
Title string `json:"title"` GameVersion string `json:"gameVersion"` // Game version that created the level.
Author string `json:"author"` Title string `json:"title"`
Password string `json:"passwd"` Author string `json:"author"`
Locked bool `json:"locked"` Password string `json:"passwd"`
Locked bool `json:"locked"`
// Level size. // Level size.
Width int32 `json:"w"` Width int32 `json:"w"`

View File

@ -1,6 +1,7 @@
package doodle package doodle
import ( import (
"git.kirsle.net/apps/doodle/doodads"
"git.kirsle.net/apps/doodle/events" "git.kirsle.net/apps/doodle/events"
"git.kirsle.net/apps/doodle/level" "git.kirsle.net/apps/doodle/level"
"git.kirsle.net/apps/doodle/render" "git.kirsle.net/apps/doodle/render"
@ -8,17 +9,19 @@ import (
// PlayScene manages the "Edit Level" game mode. // PlayScene manages the "Edit Level" game mode.
type PlayScene struct { type PlayScene struct {
canvas Grid // Configuration attributes.
Filename string
Canvas render.Grid
// Private variables.
canvas render.Grid
// Canvas size // Canvas size
width int32 width int32
height int32 height int32
// Player position and velocity. // Player character
x int32 player doodads.Doodad
y int32
vx int32
vy int32
} }
// Name of the scene. // Name of the scene.
@ -28,19 +31,43 @@ func (s *PlayScene) Name() string {
// Setup the play scene. // Setup the play scene.
func (s *PlayScene) Setup(d *Doodle) error { func (s *PlayScene) Setup(d *Doodle) error {
s.x = 10 // Given a filename or map data to play?
s.y = 10 if s.Canvas != nil {
log.Debug("PlayScene.Setup: loading map from given canvas")
s.canvas = s.Canvas
} else if s.Filename != "" {
log.Debug("PlayScene.Setup: loading map from file %s", s.Filename)
s.LoadLevel(s.Filename)
s.Filename = ""
}
s.player = doodads.NewPlayer()
if s.canvas == nil { if s.canvas == nil {
s.canvas = Grid{} log.Debug("PlayScene.Setup: no grid given, initializing empty grid")
s.canvas = render.Grid{}
} }
s.width = d.width // TODO: canvas width = copy the window size s.width = d.width // TODO: canvas width = copy the window size
s.height = d.height s.height = d.height
d.Flash("Entered Play Mode. Press 'E' to edit this map.")
return nil return nil
} }
// Loop the editor scene. // Loop the editor scene.
func (s *PlayScene) Loop(d *Doodle, ev *events.State) error { func (s *PlayScene) Loop(d *Doodle, ev *events.State) error {
// Switching to Edit Mode?
if ev.KeyName.Read() == "e" {
log.Info("Edit Mode, Go!")
d.Goto(&EditorScene{
Canvas: s.canvas,
})
return nil
}
s.movePlayer(ev) s.movePlayer(ev)
return nil return nil
} }
@ -50,35 +77,53 @@ func (s *PlayScene) Draw(d *Doodle) error {
// Clear the canvas and fill it with white. // Clear the canvas and fill it with white.
d.Engine.Clear(render.White) d.Engine.Clear(render.White)
for pixel := range s.canvas { s.canvas.Draw(d.Engine)
d.Engine.DrawPoint(render.Black, render.Point{pixel.x, pixel.y})
}
// Draw our hero. // Draw our hero.
d.Engine.DrawRect(render.Magenta, render.Rect{s.x, s.y, 16, 16}) s.player.Draw(d.Engine)
return nil return nil
} }
// movePlayer updates the player's X,Y coordinate based on key pressed. // movePlayer updates the player's X,Y coordinate based on key pressed.
func (s *PlayScene) movePlayer(ev *events.State) { func (s *PlayScene) movePlayer(ev *events.State) {
delta := s.player.Position()
var playerSpeed int32 = 8
var gravity int32 = 2
if ev.Down.Now { if ev.Down.Now {
s.y += 4 delta.Y += playerSpeed
} }
if ev.Left.Now { if ev.Left.Now {
s.x -= 4 delta.X -= playerSpeed
} }
if ev.Right.Now { if ev.Right.Now {
s.x += 4 delta.X += playerSpeed
} }
if ev.Up.Now { if ev.Up.Now {
s.y -= 4 delta.Y -= playerSpeed
} }
// Apply gravity.
delta.Y += gravity
// Draw a ray and check for collision.
var lastOk = s.player.Position()
for point := range render.IterLine2(s.player.Position(), delta) {
s.player.MoveTo(point)
if _, ok := doodads.CollidesWithGrid(s.player, &s.canvas); ok {
s.player.MoveTo(lastOk)
} else {
lastOk = s.player.Position()
}
}
s.player.MoveTo(lastOk)
} }
// LoadLevel loads a level from disk. // LoadLevel loads a level from disk.
func (s *PlayScene) LoadLevel(filename string) error { func (s *PlayScene) LoadLevel(filename string) error {
s.canvas = Grid{} s.canvas = render.Grid{}
m, err := level.LoadJSON(filename) m, err := level.LoadJSON(filename)
if err != nil { if err != nil {
@ -86,12 +131,17 @@ func (s *PlayScene) LoadLevel(filename string) error {
} }
for _, point := range m.Pixels { for _, point := range m.Pixels {
pixel := Pixel{ pixel := render.Pixel{
x: point.X, X: point.X,
y: point.Y, Y: point.Y,
} }
s.canvas[pixel] = nil s.canvas[pixel] = nil
} }
return nil return nil
} }
// Destroy the scene.
func (s *PlayScene) Destroy() error {
return nil
}

49
render/grid.go Normal file
View File

@ -0,0 +1,49 @@
package render
import (
"fmt"
)
// Pixel TODO: not a global
// TODO get rid of this ugly thing.
type Pixel struct {
Start bool
X int32
Y int32
DX int32
DY int32
}
func (p Pixel) String() string {
return fmt.Sprintf("(%d,%d) delta (%d,%d)",
p.X, p.Y,
p.DX, p.DY,
)
}
// Grid is a 2D grid of pixels in X,Y notation.
type Grid map[Pixel]interface{}
// Exists returns true if the point exists on the grid.
func (g *Grid) Exists(p Pixel) bool {
if _, ok := (*g)[p]; ok {
return true
}
return false
}
// Draw the grid efficiently.
func (g *Grid) Draw(e Engine) {
for pixel := range *g {
if pixel.DX == 0 && pixel.DY == 0 {
e.DrawPoint(Black, Point{
X: pixel.X,
Y: pixel.Y,
})
} else {
for point := range IterLine(pixel.X, pixel.Y, pixel.DX, pixel.DY) {
e.DrawPoint(Black, point)
}
}
}
}

View File

@ -2,6 +2,7 @@ package render
import ( import (
"fmt" "fmt"
"math"
"git.kirsle.net/apps/doodle/events" "git.kirsle.net/apps/doodle/events"
) )
@ -103,3 +104,43 @@ var (
Magenta = Color{255, 0, 255, 255} Magenta = Color{255, 0, 255, 255}
Pink = Color{255, 153, 255, 255} Pink = Color{255, 153, 255, 255}
) )
// IterLine is a generator that returns the X,Y coordinates to draw a line.
// https://en.wikipedia.org/wiki/Digital_differential_analyzer_(graphics_algorithm)
func IterLine(x1, y1, x2, y2 int32) chan Point {
generator := make(chan Point)
go func() {
var (
dx = float64(x2 - x1)
dy = float64(y2 - y1)
)
var step float64
if math.Abs(dx) >= math.Abs(dy) {
step = math.Abs(dx)
} else {
step = math.Abs(dy)
}
dx = dx / step
dy = dy / step
x := float64(x1)
y := float64(y1)
for i := 0; i <= int(step); i++ {
generator <- Point{
X: int32(x),
Y: int32(y),
}
x += dx
y += dy
}
close(generator)
}()
return generator
}
func IterLine2(p1 Point, p2 Point) chan Point {
return IterLine(p1.X, p1.Y, p2.X, p2.Y)
}

View File

@ -8,6 +8,7 @@ import "git.kirsle.net/apps/doodle/events"
type Scene interface { type Scene interface {
Name() string Name() string
Setup(*Doodle) error Setup(*Doodle) error
Destroy() error
// Loop should update the scene's state but not draw anything. // Loop should update the scene's state but not draw anything.
Loop(*Doodle, *events.State) error Loop(*Doodle, *events.State) error
@ -19,7 +20,11 @@ type Scene interface {
// Goto a scene. First it unloads the current scene. // Goto a scene. First it unloads the current scene.
func (d *Doodle) Goto(scene Scene) error { func (d *Doodle) Goto(scene Scene) error {
// d.scene.Destroy() // Teardown existing scene.
if d.scene != nil {
d.scene.Destroy()
}
log.Info("Goto Scene") log.Info("Goto Scene")
d.scene = scene d.scene = scene
return d.scene.Setup(d) return d.scene.Setup(d)

View File

@ -2,6 +2,7 @@ package doodle
import ( import (
"bytes" "bytes"
"fmt"
"strings" "strings"
"git.kirsle.net/apps/doodle/balance" "git.kirsle.net/apps/doodle/balance"
@ -9,6 +10,11 @@ import (
"git.kirsle.net/apps/doodle/render" "git.kirsle.net/apps/doodle/render"
) )
// Flash a message to the user.
func (d *Doodle) Flash(template string, v ...interface{}) {
d.shell.Write(fmt.Sprintf(template, v...))
}
// Shell implements the developer console in-game. // Shell implements the developer console in-game.
type Shell struct { type Shell struct {
parent *Doodle parent *Doodle