tins2020/game.go

274 lines
5.9 KiB
Go
Raw Normal View History

2020-05-08 16:38:26 +00:00
package tins2020
import (
"log"
2020-05-08 16:38:26 +00:00
"math/rand"
"time"
2020-05-08 16:38:26 +00:00
)
type Game struct {
Debug bool
Balance int
Speed GameSpeed
SpeedBeforePause GameSpeed
Herbarium Herbarium
Terrain *Map
2020-05-11 01:09:01 +00:00
tool Tool
centerChanged *Events
toolChanged *Events
speedChanged *Events
simulation Animation
2020-05-08 16:38:26 +00:00
}
type GameSpeed string
const (
GameSpeedNormal GameSpeed = "normal"
GameSpeedFast = "fast"
GameSpeedPaused = "paused"
)
const simulationInterval = 120 * time.Millisecond
const fastSimulationInterval = 20 * time.Millisecond
2020-05-08 16:38:26 +00:00
func NewGame() *Game {
2020-05-11 09:53:15 +00:00
game := &Game{
2020-05-11 01:09:01 +00:00
centerChanged: NewEvents(),
speedChanged: NewEvents(),
toolChanged: NewEvents(),
simulation: NewAnimation(time.Millisecond * 10),
}
2020-05-11 09:53:15 +00:00
game.Reset()
return game
2020-05-08 16:38:26 +00:00
}
func (g *Game) selectTool(t Tool) {
g.tool = t
g.toolChanged.Notify(t)
}
func (g *Game) setSpeed(speed GameSpeed) {
if speed == g.Speed {
return
}
if speed == GameSpeedPaused {
g.SpeedBeforePause = g.Speed
}
g.Speed = speed
g.speedChanged.Notify(speed)
switch speed {
case GameSpeedPaused:
g.simulation.Pause()
case GameSpeedNormal:
g.simulation.SetInterval(simulationInterval)
g.simulation.Run()
case GameSpeedFast:
g.simulation.SetInterval(fastSimulationInterval)
g.simulation.Run()
}
}
func (g *Game) tick() {
randomNeighbor := func(pos Point) Point {
switch rand.Intn(4) {
case 0:
return Pt(pos.X-1, pos.Y)
case 1:
return Pt(pos.X, pos.Y-1)
case 2:
return Pt(pos.X+1, pos.Y)
case 3:
return Pt(pos.X, pos.Y+1)
}
return pos
}
flowers := map[Point]Flower{}
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 {
flowers[dst] = g.Terrain.NewFlower(dst, flower.ID, flower.Traits)
}
}
}
if Sqr32(rand.Float32()) < flower.Traits.Life*flower.Traits.LifeModifier {
flowers[pos] = flower
}
}
g.Terrain.Flowers = flowers
2020-05-08 16:38:26 +00:00
}
func (g *Game) CancelTool() {
g.selectTool(nil)
}
2020-05-11 01:09:01 +00:00
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)
if !ok {
return
}
g.Balance += desc.SellPrice
}
2020-05-11 09:53:15 +00:00
func (g *Game) New() {
g.Pause()
g.Reset()
}
2020-05-11 01:09:01 +00:00
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) {
flower, ok := g.Herbarium.Find(id)
if !ok {
log.Println("user was able to plant a flower that doesn't exist")
return
}
if flower.BuyPrice > g.Balance {
// TODO: notify user of insufficient balance?
return
}
g.Balance -= flower.BuyPrice
g.Terrain.AddFlower(tile, id, flower.Traits)
}
2020-05-11 09:53:15 +00:00
func (g *Game) Reset() {
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()),
Flowers: map[Point]Flower{},
}
g.CancelTool()
g.setSpeed(GameSpeedNormal)
}
func (g *Game) Resume() { g.setSpeed(g.SpeedBeforePause) }
func (g *Game) Run() { g.setSpeed(GameSpeedNormal) }
func (g *Game) RunFast() { g.setSpeed(GameSpeedFast) }
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)
}
}
func (g *Game) SelectPlantFlowerTool(id string) {
g.selectTool(&PlantFlowerTool{FlowerID: id})
}
func (g *Game) SelectShovel() {
g.selectTool(&ShovelTool{})
}
func (g *Game) SpeedChanged() EventHandler { return g.speedChanged }
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
}
func (g *Game) TogglePause() {
if g.Speed == GameSpeedPaused {
g.Resume()
} else {
g.Pause()
}
}
func (g *Game) Tool() Tool { return g.tool }
func (g *Game) ToolChanged() EventHandler { return g.toolChanged }
func (g *Game) UnlockNextFlower() {
g.Herbarium.UnlockNext()
g.selectTool(nil)
}
func (g *Game) Update() {
for g.simulation.Animate() {
g.tick()
}
}
func (g *Game) UserClickedTile(pos Point) {
if g.tool == nil {
return
}
g.tool.ClickedTile(g, pos)
}