Compare commits

..

No commits in common. "c926ae2ef93e6a0dba1ab884f772c022922158ce" and "c27d43e3232ef68eecea4a0a73ea5e0196e2615e" have entirely different histories.

15 changed files with 61 additions and 223 deletions

View File

@ -23,21 +23,13 @@ In Botanim you play the role of botanist and your goal is to cultivate flowers i
Flowers can only grow (well) in certain climates based on two properties: humidity and temperature. Watch out for existing vegetation to get an idea how humid the land is and check the appearance of the tile to see how hot it is. When well placed your planted flower will spread soon but an odd choice might kill your flower almost instantly. So choose carefully. When the flower spread significantly you can dig up flowers again to collect more money. Flowers can only grow (well) in certain climates based on two properties: humidity and temperature. Watch out for existing vegetation to get an idea how humid the land is and check the appearance of the tile to see how hot it is. When well placed your planted flower will spread soon but an odd choice might kill your flower almost instantly. So choose carefully. When the flower spread significantly you can dig up flowers again to collect more money.
**Controls:** **Controls:**
- H: Selects shovel - D: Selects shovel
- R: Selects research - R: Selects research
- Spacebar: pauses game - Spacebar: pauses game
- 1: runs game at normal speed - 1: runs game at normal speed
- 2: runs game extra fast - 2: runs game extra fast
- Mouse wheel or plus/minus: zooms landscape - Mouse wheel or plus/minus: zooms landscape
- W, A, S, D keys or CTRL + left mouse button or middle mouse button: pans landscape - CTRL + left mouse button or middle mouse button: pans landscape
** On screen **
On the left side of the playing screen you'll find several buttons:
- Settings: disabled.
- Save: saves the game instantly, only single slot.
- Load: loads previously saved game instantly.
- New: starts a new game with a different terrain.
- Information: shows the intro/information screen (again, also accessible with the Escape key).
Have fun playing! Have fun playing!

View File

@ -21,7 +21,7 @@ type BuyFlowerButton struct {
func NewBuyFlowerButton(icon, iconDisabled, flowerID string, flower FlowerDescriptor, onClick EventContextFn) *BuyFlowerButton { func NewBuyFlowerButton(icon, iconDisabled, flowerID string, flower FlowerDescriptor, onClick EventContextFn) *BuyFlowerButton {
return &BuyFlowerButton{ return &BuyFlowerButton{
IconButton: *NewIconButtonConfigure(icon, onClick, func(b *IconButton) { IconButton: *NewIconButtonConfig(icon, onClick, func(b *IconButton) {
b.IconDisabled = iconDisabled b.IconDisabled = iconDisabled
b.IsDisabled = !flower.Unlocked b.IsDisabled = !flower.Unlocked
}), }),

View File

@ -136,8 +136,6 @@ func run() error {
app.Arrange(ctx, tins2020.RectAbs(0, 0, w, h)) app.Arrange(ctx, tins2020.RectAbs(0, 0, w, h))
ctx.Settings.Window.Size = tins2020.PtPtr(w, h) ctx.Settings.Window.Size = tins2020.PtPtr(w, h)
} }
case *sdl.MouseMotionEvent:
ctx.MousePosition = tins2020.Pt(e.X, e.Y)
} }
app.Handle(ctx, event) app.Handle(ctx, event)
} }

View File

@ -6,13 +6,12 @@ import (
) )
type Context struct { type Context struct {
Renderer *sdl.Renderer Renderer *sdl.Renderer
Fonts Fonts Fonts Fonts
Resources Resources Resources Resources
Textures Textures Textures Textures
Settings Settings Settings Settings
MousePosition Point ShouldQuit bool
ShouldQuit bool
} }
func NewContext(res *rice.Box) (*Context, error) { func NewContext(res *rice.Box) (*Context, error) {

View File

@ -22,35 +22,12 @@ func EmptyEvent(fn EventFn) EventContextFn {
type ControlBase struct { type ControlBase struct {
Bounds Rectangle Bounds Rectangle
FontName string
Foreground sdl.Color
IsDisabled bool IsDisabled bool
IsMouseOver bool IsMouseOver bool
OnLeftMouseButtonClick EventContextFn OnLeftMouseButtonClick EventContextFn
} }
func (c *ControlBase) ActualForeground() sdl.Color {
var none sdl.Color
if c.Foreground == none {
return MustHexColor("#ffffff")
}
return c.Foreground
}
func (c *ControlBase) ActualFont(ctx *Context) *Font {
name := c.ActualFontName()
return ctx.Fonts.Font(name)
}
func (c *ControlBase) ActualFontName() string {
if len(c.FontName) == 0 {
return "default"
}
return c.FontName
}
func (b *ControlBase) Arrange(ctx *Context, bounds Rectangle) { b.Bounds = bounds } func (b *ControlBase) Arrange(ctx *Context, bounds Rectangle) { b.Bounds = bounds }
func (b *ControlBase) Init(*Context) error { return nil } func (b *ControlBase) Init(*Context) error { return nil }

10
game.go
View File

@ -114,15 +114,7 @@ func (g *Game) Dig(tile Point) {
if !ok { if !ok {
return return
} }
adjacent := g.Terrain.FlowersOnAdjacentTiles(tile) g.Balance += desc.SellPrice
switch adjacent {
case 3:
g.Balance += (desc.SellPrice * 3 / 2) // 50% bonus
case 4:
g.Balance += (desc.SellPrice * 2) // 100% bonus
default:
g.Balance += desc.SellPrice
}
} }
func (g *Game) New() { func (g *Game) New() {

View File

@ -100,61 +100,44 @@ func (c *GameControls) Init(ctx *Context) error {
} }
c.top.Orientation = OrientationHorizontal c.top.Orientation = OrientationHorizontal
c.pause = NewIconButtonConfigure("control-pause", EmptyEvent(func() { c.pause = NewIconButtonConfig("control-pause", EmptyEvent(func() {
c.game.Pause() c.game.Pause()
}), func(b *IconButton) { }), func(b *IconButton) {
b.IconDisabled = "control-pause-disabled" b.IconDisabled = "control-pause-disabled"
b.Tooltip.Text = "Pause game"
}) })
c.run = NewIconButtonConfigure("control-run", EmptyEvent(func() { c.run = NewIconButtonConfig("control-run", EmptyEvent(func() {
c.game.Run() c.game.Run()
}), func(b *IconButton) { }), func(b *IconButton) {
b.IconDisabled = "control-run-disabled" b.IconDisabled = "control-run-disabled"
b.Tooltip.Text = "Run game at normal speed"
}) })
c.runFast = NewIconButtonConfigure("control-run-fast", EmptyEvent(func() { c.runFast = NewIconButtonConfig("control-run-fast", EmptyEvent(func() {
c.game.RunFast() c.game.RunFast()
}), func(b *IconButton) { }), func(b *IconButton) {
b.IconDisabled = "control-run-fast-disabled" b.IconDisabled = "control-run-fast-disabled"
b.Tooltip.Text = "Run game at fast speed"
}) })
c.speedChanged(c.game.Speed) c.speedChanged(c.game.Speed)
c.top.Buttons = []Control{c.pause, c.run, c.runFast} c.top.Buttons = []Control{c.pause, c.run, c.runFast}
c.menu.Background = MustHexColor("#356dad") c.menu.Background = MustHexColor("#356dad")
c.menu.Buttons = []Control{ c.menu.Buttons = []Control{
NewIconButtonConfigure("control-settings", c.dialogs.ShowSettings, func(b *IconButton) { NewIconButtonConfig("control-settings", c.dialogs.ShowSettings, func(b *IconButton) {
b.IsDisabled = true b.IsDisabled = true
b.IconDisabled = "#afafaf" b.IconDisabled = "#afafaf"
}), }),
NewIconButtonConfigure("control-save", func(*Context) { c.game.Save() }, func(b *IconButton) { NewIconButton("control-save", func(*Context) { c.game.Save() }),
b.Tooltip.Text = "Save game (overwrites previous save; no confirmation)" NewIconButton("control-load", func(ctx *Context) {
}),
NewIconButtonConfigure("control-load", func(ctx *Context) {
c.game.Load() c.game.Load()
c.updateFlowerControls(ctx) c.updateFlowerControls(ctx)
}, func(b *IconButton) {
b.Tooltip.Text = "Load last saved game (no confirmation)"
}), }),
NewIconButtonConfigure("control-new", func(ctx *Context) { NewIconButton("control-new", func(ctx *Context) {
c.game.New() c.game.New()
c.updateFlowerControls(ctx) c.updateFlowerControls(ctx)
}, func(b *IconButton) {
b.Tooltip.Text = "Start new game (no confirmation)"
}),
NewIconButtonConfigure("control-information", c.dialogs.ShowIntro, func(b *IconButton) {
b.Tooltip.Text = "Show information/intro"
}), }),
NewIconButton("control-information", c.dialogs.ShowIntro),
} }
c.shovel = NewIconButtonConfigure("control-shovel", func(*Context) { c.game.SelectShovel() }, func(b *IconButton) { c.shovel = NewIconButtonConfig("control-shovel", func(*Context) { c.game.SelectShovel() }, func(b *IconButton) { b.IconHeight = 32 })
b.IconHeight = 32 c.research = NewIconButtonConfig("control-research", c.dialogs.ShowResearch, func(b *IconButton) { b.IconHeight = 32 })
b.Tooltip.Text = "Select harvest tool (key: H)"
})
c.research = NewIconButtonConfigure("control-research", c.dialogs.ShowResearch, func(b *IconButton) {
b.IconHeight = 32
b.Tooltip.Text = "Conduct research (key: R)"
})
c.otherTools.Buttons = []Control{c.shovel, c.research} c.otherTools.Buttons = []Control{c.shovel, c.research}
c.Container.AddChild(&c.menu) c.Container.AddChild(&c.menu)
@ -180,7 +163,7 @@ func (c *GameControls) Handle(ctx *Context, event sdl.Event) bool {
c.game.Run() c.game.Run()
case sdl.K_2: case sdl.K_2:
c.game.RunFast() c.game.RunFast()
case sdl.K_h: case sdl.K_d:
c.game.SelectShovel() c.game.SelectShovel()
case sdl.K_r: case sdl.K_r:
c.dialogs.ShowResearch(ctx) c.dialogs.ShowResearch(ctx)

View File

@ -1,9 +1,5 @@
package tins2020 package tins2020
import (
"github.com/veandco/go-sdl2/sdl"
)
type HoverEffect int type HoverEffect int
const ( const (
@ -23,7 +19,6 @@ type IconButton struct {
IconActive HoverEffect IconActive HoverEffect
IconHover HoverEffect IconHover HoverEffect
Tooltip Tooltip
IsActive bool IsActive bool
} }
@ -36,7 +31,7 @@ func NewIconButton(icon string, onClick EventContextFn) *IconButton {
} }
} }
func NewIconButtonConfigure(icon string, onClick EventContextFn, configure func(*IconButton)) *IconButton { func NewIconButtonConfig(icon string, onClick EventContextFn, configure func(*IconButton)) *IconButton {
button := NewIconButton(icon, onClick) button := NewIconButton(icon, onClick)
configure(button) configure(button)
return button return button
@ -62,31 +57,6 @@ func (b *IconButton) activeTexture(ctx *Context) *Texture {
return ctx.Textures.Texture(b.Icon) return ctx.Textures.Texture(b.Icon)
} }
func (b *IconButton) Arrange(ctx *Context, bounds Rectangle) {
b.ControlBase.Arrange(ctx, bounds)
b.Tooltip.Arrange(ctx, bounds)
}
func (b *IconButton) Handle(ctx *Context, event sdl.Event) bool {
if b.ControlBase.Handle(ctx, event) {
return true
}
if b.Tooltip.Handle(ctx, event) {
return true
}
return false
}
func (b *IconButton) Init(ctx *Context) error {
if err := b.ControlBase.Init(ctx); err != nil {
return err
}
if err := b.Tooltip.Init(ctx); err != nil {
return err
}
return nil
}
func (b *IconButton) Render(ctx *Context) { func (b *IconButton) Render(ctx *Context) {
iconTexture := b.activeTexture(ctx) iconTexture := b.activeTexture(ctx)
@ -111,10 +81,6 @@ func (b *IconButton) Render(ctx *Context) {
ctx.Renderer.FillRect(b.Bounds.SDLPtr()) ctx.Renderer.FillRect(b.Bounds.SDLPtr())
} }
iconTexture.SetColor(White) iconTexture.SetColor(White)
if len(b.Tooltip.Text) > 0 && b.IsMouseOver {
b.Tooltip.Render(ctx)
}
} }
type Scale int type Scale int

View File

@ -12,13 +12,13 @@ func (i *Intro) Init(ctx *Context) error {
"In Botanim you play the role of botanist and your goal is to cultivate flowers in an open landscape.\n\n" + "In Botanim you play the role of botanist and your goal is to cultivate flowers in an open landscape.\n\n" +
"Flowers can only grow (well) in certain climates based on two properties: humidity and temperature. Watch out for existing vegetation to get an idea how humid the land is and check the appearance of the tile to see how hot it is. When well placed your planted flower will spread soon but an odd choice might kill your flower almost instantly. So choose carefully. When the flower spread significantly you can dig up flowers again to collect more money.\n\n" + "Flowers can only grow (well) in certain climates based on two properties: humidity and temperature. Watch out for existing vegetation to get an idea how humid the land is and check the appearance of the tile to see how hot it is. When well placed your planted flower will spread soon but an odd choice might kill your flower almost instantly. So choose carefully. When the flower spread significantly you can dig up flowers again to collect more money.\n\n" +
"Controls:\n" + "Controls:\n" +
" - H: Selects shovel\n" + " - D: Selects shovel\n" +
" - R: Selects research\n" + " - R: Selects research\n" +
" - Spacebar: pauses game\n" + " - Spacebar: pauses game\n" +
" - 1: runs game at normal speed\n" + " - 1: runs game at normal speed\n" +
" - 2: runs game extra fast\n" + " - 2: runs game extra fast\n" +
" - Mouse wheel or plus/minus: zooms landscape\n" + " - Mouse wheel or plus/minus: zooms landscape\n" +
" - W, A, S, D keys or CTRL + left mouse button or middle mouse button: pans landscape\n" + " - CTRL + left mouse button or middle mouse button: pans landscape\n" +
"\n" + "\n" +
"Have fun playing!" "Have fun playing!"
i.SetContent(&i.welcome) i.SetContent(&i.welcome)

View File

@ -2,18 +2,37 @@ package tins2020
import ( import (
"strings" "strings"
"github.com/veandco/go-sdl2/sdl"
) )
type Label struct { type Label struct {
ControlBase ControlBase
FontColor sdl.Color
FontName string
Text string Text string
Alignment TextAlignment Alignment TextAlignment
} }
func (l *Label) fontColor() sdl.Color {
var none sdl.Color
if l.FontColor == none {
return MustHexColor("#ffffff")
}
return l.FontColor
}
func (l *Label) fontName() string {
if len(l.FontName) == 0 {
return "default"
}
return l.FontName
}
func (l *Label) Render(ctx *Context) { func (l *Label) Render(ctx *Context) {
font := ctx.Fonts.Font(l.ActualFontName()) font := ctx.Fonts.Font(l.fontName())
color := l.ActualForeground() color := l.fontColor()
bottom := l.Bounds.Y + l.Bounds.H bottom := l.Bounds.Y + l.Bounds.H
switch l.Alignment { switch l.Alignment {
case TextAlignmentCenter: case TextAlignmentCenter:
@ -30,8 +49,8 @@ type Paragraph struct {
} }
func (p *Paragraph) Render(ctx *Context) { func (p *Paragraph) Render(ctx *Context) {
font := ctx.Fonts.Font(p.ActualFontName()) font := ctx.Fonts.Font(p.fontName())
color := p.ActualForeground() color := p.fontColor()
fontHeight := int32(font.Height()) fontHeight := int32(font.Height())
lines := strings.Split(p.Text, "\n") lines := strings.Split(p.Text, "\n")

View File

@ -62,10 +62,11 @@ func (d *LargeDialog) Arrange(ctx *Context, bounds Rectangle) {
} }
func (d *LargeDialog) Init(ctx *Context) error { func (d *LargeDialog) Init(ctx *Context) error {
d.title.Text = "Botanim" d.title = Label{
d.title.FontName = "title" Text: "Botanim",
d.title.Alignment = TextAlignmentCenter FontName: "title",
Alignment: TextAlignmentCenter,
}
d.close = IconButton{ d.close = IconButton{
Icon: "control-cancel", Icon: "control-cancel",
IconHover: HoverEffectColor, IconHover: HoverEffectColor,

17
map.go
View File

@ -15,23 +15,6 @@ func (m *Map) AddFlower(pos Point, id string, traits FlowerTraits) {
m.Flowers[pos] = m.NewFlower(pos, id, traits) m.Flowers[pos] = m.NewFlower(pos, id, traits)
} }
func (m *Map) FlowersOnAdjacentTiles(pos Point) int {
var count int
if _, ok := m.Flowers[Pt(pos.X+1, pos.Y)]; ok {
count++
}
if _, ok := m.Flowers[Pt(pos.X-1, pos.Y)]; ok {
count++
}
if _, ok := m.Flowers[Pt(pos.X, pos.Y+1)]; ok {
count++
}
if _, ok := m.Flowers[Pt(pos.X, pos.Y-1)]; ok {
count++
}
return count
}
func (m *Map) DigFlower(pos Point) string { func (m *Map) DigFlower(pos Point) string {
flower, ok := m.Flowers[pos] flower, ok := m.Flowers[pos]
if !ok { if !ok {

View File

@ -101,20 +101,6 @@ func (p *projection) visibleTiles(action func(int32, int32, Point)) {
} }
} }
func (p *projection) Pan(ctx *Context, delta PointF) {
p.center = p.center.Add(delta.Mul(p.zoomInv))
p.update(ctx.Renderer)
}
func (p *projection) SetZoom(ctx *Context, center PointF, zoom float32) {
if p.zoom == zoom {
return
}
p.center = center.Sub(center.Sub(p.center).Mul(p.zoom / zoom))
p.zoom = zoom
p.update(ctx.Renderer)
}
func (p *projection) ZoomOut(ctx *Context, center PointF) { func (p *projection) ZoomOut(ctx *Context, center PointF) {
if p.zoom <= .25 { if p.zoom <= .25 {
return return
@ -128,3 +114,12 @@ func (p *projection) ZoomIn(ctx *Context, center PointF) {
} }
p.SetZoom(ctx, center, 2*p.zoom) p.SetZoom(ctx, center, 2*p.zoom)
} }
func (p *projection) SetZoom(ctx *Context, center PointF, zoom float32) {
if p.zoom == zoom {
return
}
p.center = center.Sub(center.Sub(p.center).Mul(p.zoom / zoom))
p.zoom = zoom
p.update(ctx.Renderer)
}

View File

@ -101,14 +101,6 @@ func (r *terrainRenderer) Handle(ctx *Context, event sdl.Event) bool {
r.project.ZoomOut(ctx, r.project.center) r.project.ZoomOut(ctx, r.project.center)
case sdl.K_KP_MINUS: case sdl.K_KP_MINUS:
r.project.ZoomOut(ctx, r.project.center) r.project.ZoomOut(ctx, r.project.center)
case sdl.K_w:
r.project.Pan(ctx, PtF(-1, -1))
case sdl.K_a:
r.project.Pan(ctx, PtF(-1, 1))
case sdl.K_s:
r.project.Pan(ctx, PtF(1, 1))
case sdl.K_d:
r.project.Pan(ctx, PtF(1, -1))
} }
} }
} }

View File

@ -1,59 +0,0 @@
package tins2020
import "github.com/veandco/go-sdl2/sdl"
type Tooltip struct {
ControlBase
Text string
}
const tooltipBorderThickness = 1
const tooltipHorizontalPadding = 4
const tooltipMouseDistance = 12
func (t *Tooltip) Handle(ctx *Context, event sdl.Event) bool {
if len(t.Text) == 0 {
return false
}
font := ctx.Fonts.Font(t.ActualFontName())
windowW, windowH, err := ctx.Renderer.GetOutputSize()
if err != nil {
return false
}
labelW, labelH, err := font.SizeUTF8(t.Text)
if err != nil {
return false
}
mouse := ctx.MousePosition
width := int32(labelW) + 2*tooltipBorderThickness + 2*tooltipHorizontalPadding
height := int32(labelH) + 2*tooltipBorderThickness
left := mouse.X + tooltipMouseDistance
top := mouse.Y + tooltipMouseDistance
if left+width > windowW {
left = mouse.X - tooltipMouseDistance - width
}
if top+height > windowH {
top = mouse.Y - tooltipMouseDistance - height
}
t.Bounds = Rect(left, top, width, height)
return false
}
func (t *Tooltip) Render(ctx *Context) {
SetDrawColor(ctx.Renderer, Black)
ctx.Renderer.FillRect(t.Bounds.SDLPtr())
SetDrawColor(ctx.Renderer, White)
ctx.Renderer.DrawRect(t.Bounds.SDLPtr())
font := t.ActualFont(ctx)
bottomLeft := Pt(t.Bounds.X+tooltipBorderThickness+tooltipHorizontalPadding, t.Bounds.Y+t.Bounds.H-tooltipBorderThickness)
font.RenderCopy(ctx.Renderer, t.Text, bottomLeft, White)
}