Compare commits

..

4 Commits

Author SHA1 Message Date
c926ae2ef9 Added bonusses when flower is adjacent to more flowers 2020-05-14 08:39:42 +02:00
013fe4bdb6 Added tooltips. 2020-05-14 08:29:23 +02:00
f57a9dd845 Extended README. 2020-05-14 07:47:45 +02:00
76ac685cbb Added panning with keyboard.
Changed D for digging into H for harvesting.
2020-05-14 07:47:29 +02:00
15 changed files with 223 additions and 61 deletions

View File

@ -23,13 +23,21 @@ 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.
**Controls:**
- D: Selects shovel
- H: Selects shovel
- R: Selects research
- Spacebar: pauses game
- 1: runs game at normal speed
- 2: runs game extra fast
- Mouse wheel or plus/minus: zooms landscape
- CTRL + left mouse button or middle mouse button: pans landscape
- W, A, S, D keys or 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!

View File

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

View File

@ -136,6 +136,8 @@ func run() error {
app.Arrange(ctx, tins2020.RectAbs(0, 0, 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)
}

View File

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

View File

@ -22,12 +22,35 @@ func EmptyEvent(fn EventFn) EventContextFn {
type ControlBase struct {
Bounds Rectangle
FontName string
Foreground sdl.Color
IsDisabled bool
IsMouseOver bool
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) Init(*Context) error { return nil }

10
game.go
View File

@ -114,7 +114,15 @@ func (g *Game) Dig(tile Point) {
if !ok {
return
}
g.Balance += desc.SellPrice
adjacent := g.Terrain.FlowersOnAdjacentTiles(tile)
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() {

View File

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

View File

@ -1,5 +1,9 @@
package tins2020
import (
"github.com/veandco/go-sdl2/sdl"
)
type HoverEffect int
const (
@ -19,6 +23,7 @@ type IconButton struct {
IconActive HoverEffect
IconHover HoverEffect
Tooltip Tooltip
IsActive bool
}
@ -31,7 +36,7 @@ func NewIconButton(icon string, onClick EventContextFn) *IconButton {
}
}
func NewIconButtonConfig(icon string, onClick EventContextFn, configure func(*IconButton)) *IconButton {
func NewIconButtonConfigure(icon string, onClick EventContextFn, configure func(*IconButton)) *IconButton {
button := NewIconButton(icon, onClick)
configure(button)
return button
@ -57,6 +62,31 @@ func (b *IconButton) activeTexture(ctx *Context) *Texture {
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) {
iconTexture := b.activeTexture(ctx)
@ -81,6 +111,10 @@ func (b *IconButton) Render(ctx *Context) {
ctx.Renderer.FillRect(b.Bounds.SDLPtr())
}
iconTexture.SetColor(White)
if len(b.Tooltip.Text) > 0 && b.IsMouseOver {
b.Tooltip.Render(ctx)
}
}
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" +
"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" +
" - D: Selects shovel\n" +
" - H: Selects shovel\n" +
" - R: Selects research\n" +
" - Spacebar: pauses game\n" +
" - 1: runs game at normal speed\n" +
" - 2: runs game extra fast\n" +
" - Mouse wheel or plus/minus: zooms landscape\n" +
" - CTRL + left mouse button or middle mouse button: pans landscape\n" +
" - W, A, S, D keys or CTRL + left mouse button or middle mouse button: pans landscape\n" +
"\n" +
"Have fun playing!"
i.SetContent(&i.welcome)

View File

@ -2,37 +2,18 @@ package tins2020
import (
"strings"
"github.com/veandco/go-sdl2/sdl"
)
type Label struct {
ControlBase
FontColor sdl.Color
FontName string
Text string
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) {
font := ctx.Fonts.Font(l.fontName())
color := l.fontColor()
font := ctx.Fonts.Font(l.ActualFontName())
color := l.ActualForeground()
bottom := l.Bounds.Y + l.Bounds.H
switch l.Alignment {
case TextAlignmentCenter:
@ -49,8 +30,8 @@ type Paragraph struct {
}
func (p *Paragraph) Render(ctx *Context) {
font := ctx.Fonts.Font(p.fontName())
color := p.fontColor()
font := ctx.Fonts.Font(p.ActualFontName())
color := p.ActualForeground()
fontHeight := int32(font.Height())
lines := strings.Split(p.Text, "\n")

View File

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

17
map.go
View File

@ -15,6 +15,23 @@ func (m *Map) AddFlower(pos Point, id string, traits FlowerTraits) {
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 {
flower, ok := m.Flowers[pos]
if !ok {

View File

@ -101,6 +101,20 @@ 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) {
if p.zoom <= .25 {
return
@ -114,12 +128,3 @@ func (p *projection) ZoomIn(ctx *Context, center PointF) {
}
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,6 +101,14 @@ func (r *terrainRenderer) Handle(ctx *Context, event sdl.Event) bool {
r.project.ZoomOut(ctx, r.project.center)
case sdl.K_KP_MINUS:
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))
}
}
}

59
tooltip.go Normal file
View File

@ -0,0 +1,59 @@
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)
}