2020-05-08 16:38:26 +00:00
|
|
|
package tins2020
|
|
|
|
|
|
|
|
import (
|
2020-05-10 18:44:20 +00:00
|
|
|
"log"
|
2020-05-08 16:38:26 +00:00
|
|
|
"math/rand"
|
2020-05-09 17:34:43 +00:00
|
|
|
"time"
|
2020-05-17 08:56:56 +00:00
|
|
|
|
|
|
|
"opslag.de/schobers/geom"
|
|
|
|
"opslag.de/schobers/zntg"
|
|
|
|
"opslag.de/schobers/zntg/ui"
|
2020-05-08 16:38:26 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type Game struct {
|
2020-05-11 09:44:50 +00:00
|
|
|
Debug bool
|
|
|
|
|
2020-05-10 18:44:20 +00:00
|
|
|
Balance int
|
|
|
|
Speed GameSpeed
|
|
|
|
SpeedBeforePause GameSpeed
|
|
|
|
Herbarium Herbarium
|
|
|
|
Terrain *Map
|
|
|
|
|
2020-05-11 01:09:01 +00:00
|
|
|
tool Tool
|
2020-05-17 08:56:56 +00:00
|
|
|
centerChanged ui.Events
|
|
|
|
toolChanged ui.Events
|
|
|
|
speedChanged ui.Events
|
|
|
|
simulation zntg.Animation
|
2020-05-08 16:38:26 +00:00
|
|
|
}
|
|
|
|
|
2020-05-10 15:16:18 +00:00
|
|
|
type GameSpeed string
|
|
|
|
|
|
|
|
const (
|
|
|
|
GameSpeedNormal GameSpeed = "normal"
|
|
|
|
GameSpeedFast = "fast"
|
|
|
|
GameSpeedPaused = "paused"
|
|
|
|
)
|
|
|
|
|
|
|
|
const simulationInterval = 120 * time.Millisecond
|
2020-05-11 09:44:50 +00:00
|
|
|
const fastSimulationInterval = 20 * time.Millisecond
|
2020-05-10 15:16:18 +00:00
|
|
|
|
2020-05-08 16:38:26 +00:00
|
|
|
func NewGame() *Game {
|
2020-05-11 09:53:15 +00:00
|
|
|
game := &Game{
|
2020-05-17 08:56:56 +00:00
|
|
|
simulation: zntg.Animation{Interval: time.Millisecond * 10},
|
2020-05-09 17:34:43 +00:00
|
|
|
}
|
2020-05-11 09:53:15 +00:00
|
|
|
return game
|
2020-05-08 16:38:26 +00:00
|
|
|
}
|
|
|
|
|
2020-05-17 08:56:56 +00:00
|
|
|
func (g *Game) selectTool(ctx ui.Context, t Tool) {
|
2020-05-10 18:44:20 +00:00
|
|
|
g.tool = t
|
2020-05-17 08:56:56 +00:00
|
|
|
g.toolChanged.Notify(ctx, t)
|
2020-05-10 18:44:20 +00:00
|
|
|
}
|
|
|
|
|
2020-05-17 08:56:56 +00:00
|
|
|
func (g *Game) setSpeed(ctx ui.Context, speed GameSpeed) {
|
2020-05-10 21:00:19 +00:00
|
|
|
if speed == g.Speed {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if speed == GameSpeedPaused {
|
|
|
|
g.SpeedBeforePause = g.Speed
|
|
|
|
}
|
|
|
|
g.Speed = speed
|
2020-05-17 08:56:56 +00:00
|
|
|
g.speedChanged.Notify(ctx, speed)
|
2020-05-10 21:00:19 +00:00
|
|
|
|
|
|
|
switch speed {
|
|
|
|
case GameSpeedPaused:
|
|
|
|
g.simulation.Pause()
|
|
|
|
case GameSpeedNormal:
|
2020-05-17 08:56:56 +00:00
|
|
|
g.simulation.Interval = simulationInterval
|
|
|
|
g.simulation.Start()
|
2020-05-10 21:00:19 +00:00
|
|
|
case GameSpeedFast:
|
2020-05-17 08:56:56 +00:00
|
|
|
g.simulation.Interval = fastSimulationInterval
|
|
|
|
g.simulation.Start()
|
2020-05-10 21:00:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-09 17:34:43 +00:00
|
|
|
func (g *Game) tick() {
|
2020-05-17 08:56:56 +00:00
|
|
|
randomNeighbor := func(pos geom.Point) geom.Point {
|
2020-05-09 17:34:43 +00:00
|
|
|
switch rand.Intn(4) {
|
|
|
|
case 0:
|
2020-05-17 08:56:56 +00:00
|
|
|
return geom.Pt(pos.X-1, pos.Y)
|
2020-05-09 17:34:43 +00:00
|
|
|
case 1:
|
2020-05-17 08:56:56 +00:00
|
|
|
return geom.Pt(pos.X, pos.Y-1)
|
2020-05-09 17:34:43 +00:00
|
|
|
case 2:
|
2020-05-17 08:56:56 +00:00
|
|
|
return geom.Pt(pos.X+1, pos.Y)
|
2020-05-09 17:34:43 +00:00
|
|
|
case 3:
|
2020-05-17 08:56:56 +00:00
|
|
|
return geom.Pt(pos.X, pos.Y+1)
|
2020-05-09 17:34:43 +00:00
|
|
|
}
|
|
|
|
return pos
|
|
|
|
}
|
2020-05-17 08:56:56 +00:00
|
|
|
flowers := map[geom.Point]Flower{}
|
2020-05-09 17:34:43 +00:00
|
|
|
for pos, flower := range g.Terrain.Flowers {
|
|
|
|
if rand.Float32() < flower.Traits.Spread {
|
|
|
|
dst := randomNeighbor(pos)
|
|
|
|
if _, ok := g.Terrain.Flowers[dst]; !ok {
|
|
|
|
if _, ok := flowers[dst]; !ok {
|
2020-05-10 21:00:19 +00:00
|
|
|
flowers[dst] = g.Terrain.NewFlower(dst, flower.ID, flower.Traits)
|
2020-05-09 17:34:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-05-10 21:00:19 +00:00
|
|
|
if Sqr32(rand.Float32()) < flower.Traits.Life*flower.Traits.LifeModifier {
|
2020-05-09 17:34:43 +00:00
|
|
|
flowers[pos] = flower
|
|
|
|
}
|
|
|
|
}
|
|
|
|
g.Terrain.Flowers = flowers
|
2020-05-08 16:38:26 +00:00
|
|
|
}
|
2020-05-10 15:16:18 +00:00
|
|
|
|
2020-05-17 08:56:56 +00:00
|
|
|
func (g *Game) CancelTool(ctx ui.Context) {
|
|
|
|
g.selectTool(ctx, nil)
|
2020-05-10 18:44:20 +00:00
|
|
|
}
|
|
|
|
|
2020-05-17 08:56:56 +00:00
|
|
|
func (g *Game) CenterChanged() ui.EventHandler { return &g.centerChanged }
|
2020-05-11 01:09:01 +00:00
|
|
|
|
2020-05-17 08:56:56 +00:00
|
|
|
func (g *Game) Dig(tile geom.Point) {
|
2020-05-10 21:00:19 +00:00
|
|
|
id := g.Terrain.DigFlower(tile)
|
|
|
|
desc, ok := g.Herbarium.Find(id)
|
|
|
|
if !ok {
|
2020-05-10 18:44:20 +00:00
|
|
|
return
|
|
|
|
}
|
2020-05-14 06:39:42 +00:00
|
|
|
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
|
|
|
|
}
|
2020-05-10 15:16:18 +00:00
|
|
|
}
|
|
|
|
|
2020-05-17 08:56:56 +00:00
|
|
|
func (g *Game) New(ctx ui.Context) {
|
|
|
|
g.Pause(ctx)
|
|
|
|
g.Reset(ctx)
|
2020-05-11 09:53:15 +00:00
|
|
|
}
|
|
|
|
|
2020-05-17 08:56:56 +00:00
|
|
|
func (g *Game) Load(ctx ui.Context) {
|
|
|
|
g.CancelTool(ctx)
|
|
|
|
g.Pause(ctx)
|
2020-05-11 01:09:01 +00:00
|
|
|
|
|
|
|
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),
|
2020-05-17 08:56:56 +00:00
|
|
|
Flowers: map[geom.Point]Flower{},
|
2020-05-11 01:09:01 +00:00
|
|
|
}
|
|
|
|
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
|
2020-05-17 08:56:56 +00:00
|
|
|
g.centerChanged.Notify(ctx, g.Terrain.Center)
|
2020-05-23 08:18:37 +00:00
|
|
|
g.setSpeed(ctx, state.Speed)
|
2020-05-11 01:09:01 +00:00
|
|
|
}
|
|
|
|
|
2020-05-17 08:56:56 +00:00
|
|
|
func (g *Game) Pause(ctx ui.Context) { g.setSpeed(ctx, GameSpeedPaused) }
|
2020-05-10 21:00:19 +00:00
|
|
|
|
2020-05-17 08:56:56 +00:00
|
|
|
func (g *Game) PlantFlower(id string, tile geom.Point) {
|
2020-05-11 13:27:22 +00:00
|
|
|
if g.Terrain.HasFlower(tile) {
|
|
|
|
// TODO: notify user it tried to plant on tile with flower?
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-05-10 18:44:20 +00:00
|
|
|
flower, ok := g.Herbarium.Find(id)
|
|
|
|
if !ok {
|
|
|
|
log.Println("user was able to plant a flower that doesn't exist")
|
|
|
|
return
|
|
|
|
}
|
2020-05-10 21:00:19 +00:00
|
|
|
if flower.BuyPrice > g.Balance {
|
2020-05-10 18:44:20 +00:00
|
|
|
// TODO: notify user of insufficient balance?
|
|
|
|
return
|
|
|
|
}
|
2020-05-10 21:00:19 +00:00
|
|
|
g.Balance -= flower.BuyPrice
|
|
|
|
g.Terrain.AddFlower(tile, id, flower.Traits)
|
2020-05-10 18:44:20 +00:00
|
|
|
}
|
|
|
|
|
2020-05-17 08:56:56 +00:00
|
|
|
func (g *Game) Reset(ctx ui.Context) {
|
2020-05-11 09:53:15 +00:00
|
|
|
g.Balance = 100
|
|
|
|
g.Herbarium = NewHerbarium()
|
|
|
|
g.Terrain = &Map{
|
|
|
|
Temp: NewNoiseMap(rand.Int63()),
|
|
|
|
Humid: NewNoiseMap(rand.Int63()),
|
|
|
|
Variant: NewRandomNoiseMap(rand.Int63()),
|
|
|
|
PlaceX: NewRandomNoiseMap(rand.Int63()),
|
|
|
|
PlaceY: NewRandomNoiseMap(rand.Int63()),
|
2020-05-17 08:56:56 +00:00
|
|
|
Flowers: map[geom.Point]Flower{},
|
2020-05-11 09:53:15 +00:00
|
|
|
}
|
2020-05-17 08:56:56 +00:00
|
|
|
g.CancelTool(ctx)
|
|
|
|
g.setSpeed(ctx, GameSpeedNormal)
|
2020-05-11 09:53:15 +00:00
|
|
|
}
|
|
|
|
|
2020-05-17 08:56:56 +00:00
|
|
|
func (g *Game) Resume(ctx ui.Context) { g.setSpeed(ctx, g.SpeedBeforePause) }
|
2020-05-10 21:00:19 +00:00
|
|
|
|
2020-05-17 08:56:56 +00:00
|
|
|
func (g *Game) Run(ctx ui.Context) { g.setSpeed(ctx, GameSpeedNormal) }
|
2020-05-10 21:00:19 +00:00
|
|
|
|
2020-05-17 08:56:56 +00:00
|
|
|
func (g *Game) RunFast(ctx ui.Context) { g.setSpeed(ctx, GameSpeedFast) }
|
2020-05-10 21:00:19 +00:00
|
|
|
|
2020-05-11 01:09:01 +00:00
|
|
|
func (g *Game) Save() {
|
|
|
|
state := g.State()
|
|
|
|
err := state.Serialize(SaveGameName())
|
|
|
|
if err != nil {
|
|
|
|
log.Println("failed to save; error:", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-17 08:56:56 +00:00
|
|
|
func (g *Game) SelectPlantFlowerTool(ctx ui.Context, id string) {
|
|
|
|
g.selectTool(ctx, &PlantFlowerTool{FlowerID: id})
|
2020-05-10 18:44:20 +00:00
|
|
|
}
|
|
|
|
|
2020-05-17 08:56:56 +00:00
|
|
|
func (g *Game) SelectShovel(ctx ui.Context) {
|
|
|
|
g.selectTool(ctx, &ShovelTool{})
|
2020-05-10 15:16:18 +00:00
|
|
|
}
|
|
|
|
|
2020-05-17 08:56:56 +00:00
|
|
|
func (g *Game) SpeedChanged() ui.EventHandler { return &g.speedChanged }
|
2020-05-10 21:00:19 +00:00
|
|
|
|
2020-05-11 01:09:01 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-05-17 08:56:56 +00:00
|
|
|
func (g *Game) TogglePause(ctx ui.Context) {
|
2020-05-10 21:00:19 +00:00
|
|
|
if g.Speed == GameSpeedPaused {
|
2020-05-17 08:56:56 +00:00
|
|
|
g.Resume(ctx)
|
2020-05-10 21:00:19 +00:00
|
|
|
} else {
|
2020-05-17 08:56:56 +00:00
|
|
|
g.Pause(ctx)
|
2020-05-10 21:00:19 +00:00
|
|
|
}
|
2020-05-10 18:44:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (g *Game) Tool() Tool { return g.tool }
|
|
|
|
|
2020-05-17 08:56:56 +00:00
|
|
|
func (g *Game) ToolChanged() ui.EventHandler { return &g.toolChanged }
|
2020-05-10 18:44:20 +00:00
|
|
|
|
2020-05-17 08:56:56 +00:00
|
|
|
func (g *Game) UnlockNextFlower(ctx ui.Context) {
|
2020-05-11 12:41:42 +00:00
|
|
|
price := g.Herbarium.UnlockNext()
|
|
|
|
g.Balance -= price
|
2020-05-17 08:56:56 +00:00
|
|
|
g.selectTool(ctx, nil)
|
2020-05-11 12:41:42 +00:00
|
|
|
}
|
|
|
|
|
2020-05-10 15:16:18 +00:00
|
|
|
func (g *Game) Update() {
|
|
|
|
for g.simulation.Animate() {
|
|
|
|
g.tick()
|
|
|
|
}
|
|
|
|
}
|
2020-05-10 18:44:20 +00:00
|
|
|
|
2020-05-17 08:56:56 +00:00
|
|
|
func (g *Game) UserClickedTile(pos geom.Point) {
|
2020-05-10 18:44:20 +00:00
|
|
|
if g.tool == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
g.tool.ClickedTile(g, pos)
|
|
|
|
}
|