Compare commits

..

No commits in common. "a72f4166503c4e2c5e6eb4164fa3be73b133c549" and "3a8ad733a4564167716a65cca56e8ec3ce832747" have entirely different histories.

13 changed files with 131 additions and 337 deletions

View File

@ -5,10 +5,9 @@ import "github.com/veandco/go-sdl2/sdl"
type ButtonBar struct {
Container
Background sdl.Color
ButtonLength int32
Orientation Orientation
Buttons []Control
Background sdl.Color
Orientation Orientation
Buttons []Control
}
const buttonBarWidth = 96
@ -22,24 +21,19 @@ func (b *ButtonBar) Init(ctx *Context) error {
func (b *ButtonBar) Arrange(ctx *Context, bounds Rectangle) {
b.Container.Arrange(ctx, bounds)
length := b.ButtonLength
switch b.Orientation {
case OrientationHorizontal:
if length == 0 {
length = bounds.H
}
length := bounds.H
offset := bounds.X
for i := range b.Buttons {
b.Buttons[i].Arrange(ctx, RectSize(offset, bounds.Y, length, bounds.H))
b.Buttons[i].Arrange(ctx, RectSize(offset, bounds.Y, length, length))
offset += length
}
default:
if length == 0 {
length = bounds.W
}
length := bounds.W
offset := bounds.Y
for i := range b.Buttons {
b.Buttons[i].Arrange(ctx, RectSize(bounds.X, offset, bounds.W, length))
b.Buttons[i].Arrange(ctx, RectSize(bounds.X, offset, length, length))
offset += length
}
}

View File

@ -38,39 +38,29 @@ func (b *BuyFlowerButton) animate() {
}
func (b *BuyFlowerButton) fmtTooltipText() string {
if !b.Flower.Unlocked {
if b.Flower.Unlocked {
return fmt.Sprintf("%s - %s - %s", FmtMoney(b.Flower.BuyPrice), b.Flower.Name, "Traits are not known yet.")
}
return fmt.Sprintf("%s - %s - %s", FmtMoney(b.Flower.BuyPrice), b.Flower.Name, b.Flower.Description)
}
func (b *BuyFlowerButton) updateTexts(ctx *Context) error {
text := b.fmtTooltipText()
func (b *BuyFlowerButton) Init(ctx *Context) error {
text := fmt.Sprintf("%s - %s - %s", FmtMoney(b.Flower.BuyPrice), b.Flower.Name, b.Flower.Description)
font := ctx.Fonts.Font("small")
color := MustHexColor("#ffffff")
texture, err := font.Render(ctx.Renderer, text, color)
if err != nil {
return err
}
if b.hoverTexture != nil {
b.hoverTexture.Destroy()
}
b.hoverTexture = texture
texture, err = font.Render(ctx.Renderer, FmtMoney(b.Flower.BuyPrice), color)
if err != nil {
return err
}
if b.priceTexture != nil {
b.priceTexture.Destroy()
}
b.priceTexture = texture
return nil
}
func (b *BuyFlowerButton) Init(ctx *Context) error {
return b.updateTexts(ctx)
}
func (b *BuyFlowerButton) Handle(ctx *Context, event sdl.Event) {
b.IconButton.Handle(ctx, event)
if b.IsMouseOver && b.hoverAnimation == nil {
@ -86,9 +76,9 @@ func (b *BuyFlowerButton) Render(ctx *Context) {
mouseOverTexture := ctx.Textures.Texture("control-hover")
pos := Pt(b.Bounds.X, b.Bounds.Y)
iconTexture.CopyResize(ctx.Renderer, RectSize(pos.X, pos.Y-60, b.Bounds.W, 120))
iconTexture.CopyResize(ctx.Renderer, RectSize(pos.X, pos.Y-40, buttonBarWidth, 120))
if (b.IsMouseOver && !b.IsDisabled) || b.IsActive {
mouseOverTexture.CopyResize(ctx.Renderer, b.Bounds)
mouseOverTexture.Copy(ctx.Renderer, pos)
}
if b.hoverAnimation != nil {
@ -96,8 +86,8 @@ func (b *BuyFlowerButton) Render(ctx *Context) {
}
if b.IsMouseOver {
left := b.Bounds.W - 8 - b.hoverOffset
top := pos.Y + b.Bounds.H - 20
left := buttonBarWidth - 8 - b.hoverOffset
top := pos.Y + buttonBarWidth - 20
if left < 0 {
part := Rect(-left, 0, b.hoverTexture.Size().X, b.hoverTexture.Size().Y)
b.hoverTexture.CopyPart(ctx.Renderer, part, Pt(pos.X, top))
@ -105,11 +95,6 @@ func (b *BuyFlowerButton) Render(ctx *Context) {
b.hoverTexture.Copy(ctx.Renderer, Pt(pos.X+left, top))
}
} else {
b.priceTexture.Copy(ctx.Renderer, Pt(pos.X+b.Bounds.W-8-b.priceTexture.Size().X, pos.Y+b.Bounds.H-20))
b.priceTexture.Copy(ctx.Renderer, Pt(pos.X+buttonBarWidth-8-b.priceTexture.Size().X, pos.Y+buttonBarWidth-20))
}
}
func (b *BuyFlowerButton) Update(ctx *Context, desc FlowerDescriptor) {
b.Flower = desc
b.updateTexts(ctx)
}

View File

@ -39,10 +39,6 @@ func (b *ControlBase) Handle(ctx *Context, event sdl.Event) {
if b.IsMouseOver && e.Button == sdl.BUTTON_LEFT && e.Type == sdl.MOUSEBUTTONDOWN {
b.Invoke(ctx, b.OnLeftMouseButtonClick)
}
case *sdl.WindowEvent:
if e.Event == sdl.WINDOWEVENT_LEAVE {
b.IsMouseOver = false
}
}
}

View File

@ -1,24 +0,0 @@
package tins2020
type Drageable struct {
start *Point
dragged bool
}
func (d *Drageable) Cancel() { d.start = nil }
func (d *Drageable) IsDragging() bool { return d.start != nil }
func (d *Drageable) HasDragged() bool { return d.dragged }
func (d *Drageable) Move(p Point) Point {
delta := p.Sub(*d.start)
d.start = &p
d.dragged = true
return delta
}
func (d *Drageable) Start(p Point) {
d.start = &p
d.dragged = false
}

136
game.go
View File

@ -13,11 +13,10 @@ type Game struct {
Herbarium Herbarium
Terrain *Map
tool Tool
centerChanged *Events
toolChanged *Events
speedChanged *Events
simulation Animation
tool Tool
toolChanged *Events
speedChanged *Events
simulation Animation
}
type GameSpeed string
@ -41,16 +40,60 @@ func NewGame() *Game {
Flowers: map[Point]Flower{},
}
herbarium := NewHerbarium()
herbarium.Add("anemone", FlowerDescriptor{
Name: "Anemone",
Description: "A very generic flower that thrives in a temperate climate.",
IconTemplate: "flower-anemone-%s",
BuyPrice: 10,
SellPrice: 3,
Traits: NewAnemoneTraits(),
Unlocked: true,
})
herbarium.Add("loosestrife", FlowerDescriptor{
Name: "Loosestrife",
Description: "A simple flower that will spread in temperate and wet climates.",
IconTemplate: "flower-loosestrife-%s",
BuyPrice: 100,
SellPrice: 20,
Traits: NewLoosestrifeTraits(),
Unlocked: false,
})
herbarium.Add("coneflower", FlowerDescriptor{
Name: "Coneflower",
Description: "A beautifull flower that can withstand hotter climates.",
IconTemplate: "flower-coneflower-%s",
BuyPrice: 500,
SellPrice: 100,
Traits: NewConeflowerTraits(),
Unlocked: false,
})
herbarium.Add("tulip", FlowerDescriptor{
Name: "Tulip",
Description: "A lovely flower that prefers a bit humid and colder climates.",
IconTemplate: "flower-tulip-%s",
BuyPrice: 20000,
SellPrice: 5000,
Traits: NewTulipTraits(),
Unlocked: false,
})
herbarium.Add("ajuga", FlowerDescriptor{
Name: "Ajuga",
Description: "A flower that is resitant to cold climates and spreads very easily.",
IconTemplate: "flower-ajuga-%s",
BuyPrice: 100000,
SellPrice: 10000,
Traits: NewAjugaTraits(),
Unlocked: false,
})
return &Game{
Speed: GameSpeedNormal,
Balance: 100,
Terrain: terrain,
Herbarium: herbarium,
centerChanged: NewEvents(),
speedChanged: NewEvents(),
toolChanged: NewEvents(),
simulation: NewAnimation(time.Millisecond * 10),
speedChanged: NewEvents(),
toolChanged: NewEvents(),
simulation: NewAnimation(time.Millisecond * 10),
}
}
@ -116,8 +159,6 @@ func (g *Game) CancelTool() {
g.selectTool(nil)
}
func (g *Game) CenterChanged() EventHandler { return g.centerChanged }
func (g *Game) Dig(tile Point) {
id := g.Terrain.DigFlower(tile)
desc, ok := g.Herbarium.Find(id)
@ -127,44 +168,6 @@ func (g *Game) Dig(tile Point) {
g.Balance += desc.SellPrice
}
func (g *Game) Load() {
g.CancelTool()
g.Pause()
var state GameState
err := state.Deserialize(SaveGameName())
if err != nil {
log.Println("failed to load; error:", err)
return
}
g.Herbarium = NewHerbarium()
for _, flower := range state.Herbarium.Flowers {
g.Herbarium.Update(flower.ID, func(desc *FlowerDescriptor) {
desc.Unlocked = flower.Unlocked
})
}
g.Balance = state.Balance
g.Terrain = &Map{
Temp: NewNoiseMap(state.Terrain.Temperature),
Humid: NewNoiseMap(state.Terrain.Humidity),
Variant: NewRandomNoiseMap(state.Terrain.Variant),
PlaceX: NewRandomNoiseMap(state.Terrain.PlaceX),
PlaceY: NewRandomNoiseMap(state.Terrain.PlaceY),
Flowers: map[Point]Flower{},
}
for _, flower := range state.Terrain.Flowers {
desc, _ := g.Herbarium.Find(flower.ID)
g.Terrain.AddFlower(flower.Location, flower.ID, desc.Traits)
}
g.Terrain.Center = state.View.Center
g.centerChanged.Notify(g.Terrain.Center)
g.CancelTool()
g.setSpeed(state.Speed)
}
func (g *Game) Pause() { g.setSpeed(GameSpeedPaused) }
func (g *Game) PlantFlower(id string, tile Point) {
@ -187,14 +190,6 @@ func (g *Game) Run() { g.setSpeed(GameSpeedNormal) }
func (g *Game) RunFast() { g.setSpeed(GameSpeedFast) }
func (g *Game) Save() {
state := g.State()
err := state.Serialize(SaveGameName())
if err != nil {
log.Println("failed to save; error:", err)
}
}
func (g *Game) SelectPlantFlowerTool(id string) {
g.selectTool(&PlantFlowerTool{FlowerID: id})
}
@ -209,33 +204,6 @@ func (g *Game) SelectResearch() {
func (g *Game) SpeedChanged() EventHandler { return g.speedChanged }
func (g *Game) State() GameState {
var state GameState
state.Balance = g.Balance
state.Speed = g.Speed
for _, id := range g.Herbarium.Flowers() {
flower, _ := g.Herbarium.Find(id)
state.Herbarium.Flowers = append(state.Herbarium.Flowers, HerbariumFlowerState{
ID: id,
Unlocked: flower.Unlocked,
})
}
flowers := make([]FlowerState, 0, len(g.Terrain.Flowers))
for pos, flower := range g.Terrain.Flowers {
flowers = append(flowers, FlowerState{ID: flower.ID, Location: pos})
}
state.Terrain = TerrainState{
Temperature: g.Terrain.Temp.Seed(),
Humidity: g.Terrain.Humid.Seed(),
Variant: g.Terrain.Variant.Seed(),
PlaceX: g.Terrain.PlaceX.Seed(),
PlaceY: g.Terrain.PlaceY.Seed(),
Flowers: flowers,
}
state.View.Center = g.Terrain.Center
return state
}
func (g *Game) TogglePause() {
if g.Speed == GameSpeedPaused {
g.Resume()

View File

@ -58,7 +58,6 @@ func (c *GameControls) toolChanged(state interface{}) {
for _, control := range c.flowers.Buttons {
button := control.(*BuyFlowerButton)
button.IsActive = button.FlowerID == flowerID
button.IsDisabled = !c.game.Herbarium.IsUnlocked(button.FlowerID)
}
_, shovel := tool.(*ShovelTool)
c.shovel.IsActive = shovel
@ -76,8 +75,7 @@ func (c *GameControls) Init(ctx *Context) error {
c.game.SpeedChanged().RegisterItf(c.speedChanged)
c.game.ToolChanged().RegisterItf(c.toolChanged)
c.flowers.Background = MustHexColor("#356dad")
c.flowers.ButtonLength = 64
c.flowers.Background = MustHexColor("#356dad") // brown alternative? #4ac69a
for _, id := range c.game.Herbarium.Flowers() {
c.flowers.Buttons = append(c.flowers.Buttons, c.createBuyFlowerButton(id))
@ -104,22 +102,13 @@ func (c *GameControls) Init(ctx *Context) error {
c.menu.Background = MustHexColor("#356dad")
c.menu.Buttons = []Control{
NewIconButton("control-settings", func(*Context) {}),
NewIconButton("control-save", func(*Context) { c.game.Save() }),
NewIconButton("control-load", func(ctx *Context) {
c.game.Load()
for _, b := range c.flowers.Buttons {
button := b.(*BuyFlowerButton)
flower, ok := c.game.Herbarium.Find(button.FlowerID)
if ok {
button.Update(ctx, flower)
}
}
}),
NewIconButton("control-settings", EmptyEvent(func() {})),
NewIconButton("control-save", EmptyEvent(func() {})),
NewIconButton("control-load", EmptyEvent(func() {})),
}
c.shovel = NewIconButtonConfig("control-shovel", func(*Context) { c.game.SelectShovel() }, func(b *IconButton) { b.IconHeight = 32 })
c.research = NewIconButtonConfig("control-research", func(*Context) { c.game.SelectResearch() }, func(b *IconButton) { b.IconHeight = 32 })
c.shovel = NewIconButtonConfig("control-shovel", func(*Context) { c.game.SelectShovel() }, func(b *IconButton) { b.IconHeight = 48 })
c.research = NewIconButtonConfig("control-research", func(*Context) { c.game.SelectResearch() }, func(b *IconButton) { b.IconHeight = 48 })
c.otherTools.Buttons = []Control{c.shovel, c.research}
c.Container.AddChild(&c.menu)

View File

@ -1,54 +0,0 @@
package tins2020
type FlowerState struct {
ID string
Location Point
}
type GameState struct {
Balance int
Speed GameSpeed
Herbarium HerbariumState
Terrain TerrainState
View ViewState
}
type HerbariumState struct {
Flowers []HerbariumFlowerState
}
type HerbariumFlowerState struct {
ID string
Unlocked bool
}
type TerrainState struct {
Temperature int64
Humidity int64
Variant int64
PlaceX int64
PlaceY int64
Flowers []FlowerState
}
type ViewState struct {
Center Point
}
func (s *GameState) Serialize(name string) error {
path, err := UserFile(name)
if err != nil {
return err
}
return EncodeJSON(path, &s)
}
func (s *GameState) Deserialize(name string) error {
path, err := UserFile(name)
if err != nil {
return err
}
return DecodeJSON(path, &s)
}
func SaveGameName() string { return "savegame.json" }

View File

@ -11,69 +11,7 @@ type Herbarium struct {
}
func NewHerbarium() Herbarium {
h := Herbarium{map[string]FlowerDescriptor{}, nil}
h.Reset()
return h
}
func (h *Herbarium) Reset() {
h.flowers = map[string]FlowerDescriptor{}
h.order = nil
h.Add("anemone", FlowerDescriptor{
Name: "Anemone",
Description: "A very generic flower that thrives in a temperate climate.",
IconTemplate: "flower-anemone-%s",
BuyPrice: 10,
SellPrice: 3,
Traits: NewAnemoneTraits(),
Unlocked: true,
})
h.Add("loosestrife", FlowerDescriptor{
Name: "Loosestrife",
Description: "A simple flower that will spread in temperate and wet climates.",
IconTemplate: "flower-loosestrife-%s",
BuyPrice: 100,
SellPrice: 20,
Traits: NewLoosestrifeTraits(),
Unlocked: true,
})
h.Add("coneflower", FlowerDescriptor{
Name: "Coneflower",
Description: "A beautifull flower that can withstand hotter climates.",
IconTemplate: "flower-coneflower-%s",
BuyPrice: 500,
SellPrice: 100,
Traits: NewConeflowerTraits(),
Unlocked: true,
})
h.Add("tulip", FlowerDescriptor{
Name: "Tulip",
Description: "A lovely flower that prefers a bit humid and colder climates.",
IconTemplate: "flower-tulip-%s",
BuyPrice: 20000,
SellPrice: 5000,
Traits: NewTulipTraits(),
Unlocked: true,
})
h.Add("ajuga", FlowerDescriptor{
Name: "Ajuga",
Description: "A flower that is resitant to cold climates and spreads very easily.",
IconTemplate: "flower-ajuga-%s",
BuyPrice: 100000,
SellPrice: 10000,
Traits: NewAjugaTraits(),
Unlocked: true,
})
}
func (h *Herbarium) Update(id string, update func(*FlowerDescriptor)) {
flower, ok := h.flowers[id]
if !ok {
return
}
update(&flower)
h.flowers[id] = flower
return Herbarium{map[string]FlowerDescriptor{}, nil}
}
type FlowerDescriptor struct {
@ -106,11 +44,3 @@ func (h *Herbarium) Find(id string) (FlowerDescriptor, bool) {
}
func (h *Herbarium) Flowers() []string { return h.order }
func (h *Herbarium) IsUnlocked(id string) bool {
flower, ok := h.flowers[id]
if !ok {
return false
}
return flower.Unlocked
}

2
io.go
View File

@ -33,7 +33,7 @@ func UserDir() (string, error) {
if err != nil {
return "", err
}
dir := filepath.Join(config, "tins2020_flowers_sim")
dir := filepath.Join(config, "tins2020")
err = os.MkdirAll(dir, 0777)
if err != nil {
return "", err

1
map.go
View File

@ -7,7 +7,6 @@ type Map struct {
PlaceX NoiseMap // displacement map of props
PlaceY NoiseMap
Center Point
Flowers map[Point]Flower
}

View File

@ -2,18 +2,7 @@ package tins2020
import "opslag.de/schobers/geom/noise"
func clipNormalized(x float64) float64 {
if x < 0 {
return 0
}
if x > 1 {
return 1
}
return x
}
type NoiseMap interface {
Seed() int64
Value(x, y int32) float64
}
@ -26,22 +15,30 @@ func NewNoiseMap(seed int64) NoiseMap {
}
}
func NewRandomNoiseMap(seed int64) NoiseMap {
return &randomNoiseMap{noise.NewPerlin(seed)}
}
type noiseMap struct {
noise *noise.Perlin
alpha, beta float64
harmonics int
}
// Value generates the noise value for an x/y pair.
func (m noiseMap) Value(x, y int32) float64 {
value := m.noise.Noise2D(float64(x)*.01, float64(y)*.01, m.alpha, m.beta, m.harmonics)*.565 + .5
return clipNormalized(value)
func clipNormalized(x float64) float64 {
if x < 0 {
return 0
}
if x > 1 {
return 1
}
return x
}
func (m noiseMap) Seed() int64 { return m.noise.Seed }
func NewRandomNoiseMap(seed int64) NoiseMap {
return &randomNoiseMap{noise.NewPerlin(seed)}
// Value generates the noise value for an x/y pair.
func (m *noiseMap) Value(x, y int32) float64 {
value := m.noise.Noise2D(float64(x)*.01, float64(y)*.01, m.alpha, m.beta, m.harmonics)*.565 + .5
return clipNormalized(value)
}
type randomNoiseMap struct {
@ -53,5 +50,3 @@ func (m randomNoiseMap) Value(x, y int32) float64 {
value := m.Noise2D(float64(x)*.53, float64(y)*.53, 1.01, 2, 2)*.5 + .5
return clipNormalized(value)
}
func (m randomNoiseMap) Seed() int64 { return m.Perlin.Seed }

View File

@ -6,10 +6,6 @@ import (
"github.com/veandco/go-sdl2/sdl"
)
func mapToTile(q PointF) Point {
return Pt(int32(Round32(q.X)), int32(Round32(q.Y)))
}
type projection struct {
center PointF
zoom float32
@ -45,7 +41,7 @@ func (p *projection) screenToMap(x, y int32) PointF {
func (p *projection) screenToMapInt(x, y int32) Point {
pos := p.screenToMap(x, y)
return mapToTile(pos)
return Pt(int32(Round32(pos.X)), int32(Round32(pos.Y)))
}
func (p *projection) screenToMapRel(x, y int32) PointF {

View File

@ -8,14 +8,38 @@ import (
type terrainRenderer struct {
game *Game
terrain *Map
hover *Point
project projection
drag Drageable
}
type Drageable struct {
start *Point
dragged bool
}
func (d *Drageable) Cancel() { d.start = nil }
func (d *Drageable) IsDragging() bool { return d.start != nil }
func (d *Drageable) HasDragged() bool { return d.dragged }
func (d *Drageable) Move(p Point) Point {
delta := p.Sub(*d.start)
d.start = &p
d.dragged = true
return delta
}
func (d *Drageable) Start(p Point) {
d.start = &p
d.dragged = false
}
func NewTerrainRenderer(game *Game) Control {
return &terrainRenderer{game: game, project: newProjection()}
return &terrainRenderer{game: game, terrain: game.Terrain, project: newProjection()}
}
func (r *terrainRenderer) Arrange(ctx *Context, _ Rectangle) {
@ -23,11 +47,6 @@ func (r *terrainRenderer) Arrange(ctx *Context, _ Rectangle) {
}
func (r *terrainRenderer) Init(ctx *Context) error {
r.game.CenterChanged().RegisterItf(func(state interface{}) {
center := state.(Point)
r.project.center = center.ToPtF()
r.project.update(ctx.Renderer)
})
r.project.update(ctx.Renderer)
return nil
}
@ -59,7 +78,6 @@ func (r *terrainRenderer) Handle(ctx *Context, event sdl.Event) {
}
if e.Type == sdl.MOUSEBUTTONUP {
if r.drag.IsDragging() {
r.game.Terrain.Center = mapToTile(r.project.center)
r.drag.Cancel()
}
}
@ -91,18 +109,12 @@ func (r *terrainRenderer) Handle(ctx *Context, event sdl.Event) {
r.project.update(ctx.Renderer)
}
}
case *sdl.WindowEvent:
if e.Event == sdl.WINDOWEVENT_LEAVE {
r.hover = nil
r.project.update(ctx.Renderer)
}
}
}
func (r *terrainRenderer) Render(ctx *Context) {
terrain := r.game.Terrain
toTileTexture := func(x, y int32) *Texture {
temp := terrain.Temp.Value(x, y)
temp := r.terrain.Temp.Value(x, y)
if temp < .35 {
return ctx.Textures.Texture("tile-snow")
}
@ -166,14 +178,14 @@ func (r *terrainRenderer) Render(ctx *Context) {
}
toItemTexture := func(x, y int32) *Texture {
variant := terrain.Variant.Value(x, y)
flower, ok := terrain.Flowers[Pt(x, y)]
variant := r.terrain.Variant.Value(x, y)
flower, ok := r.terrain.Flowers[Pt(x, y)]
if ok {
desc, _ := r.game.Herbarium.Find(flower.ID)
return ctx.Textures.Texture(desc.IconTemplate.Variant(variantToInt(variant)))
}
temp := terrain.Temp.Value(x, y)
humid := terrain.Humid.Value(x, y)
temp := r.terrain.Temp.Value(x, y)
humid := r.terrain.Humid.Value(x, y)
return toPropTexture(temp, humid, variant)
}
@ -197,8 +209,16 @@ func (r *terrainRenderer) Render(ctx *Context) {
return
}
placeX, placeY := terrain.PlaceX.Value(x, y), terrain.PlaceY.Value(x, y)
placeX, placeY := r.terrain.PlaceX.Value(x, y), r.terrain.PlaceY.Value(x, y)
pos = r.project.mapToScreenF(float32(x)-.2+float32(.9*placeX-.45), float32(y)-.2+float32(.9*placeY-.45))
text.CopyResize(ctx.Renderer, r.project.screenToTileRect(pos))
})
// gfx.RectangleColor(ctx.Renderer, r.project.windowRect.X, r.project.windowRect.Y, r.project.windowRect.X+r.project.windowRect.W, r.project.windowRect.Y+r.project.windowRect.H, sdl.Color{R: 255, A: 255})
// for y := int32(-40); y < 40; y++ {
// for x := int32(-40); x < 40; x++ {
// pos := r.project.mapToScreen(x, y)
// ctx.Fonts.Font("debug").RenderCopy(ctx.Renderer, fmt.Sprintf("(%d, %d)", x, y), pos, sdl.Color{R: 255, A: 255})
// }
// }
}