package tins2020 import ( "log" "math/rand" "time" "opslag.de/schobers/geom" "opslag.de/schobers/zntg" "opslag.de/schobers/zntg/ui" ) type Game struct { Debug bool Balance int Speed GameSpeed SpeedBeforePause GameSpeed Herbarium Herbarium Terrain *Map tool Tool centerChanged ui.Events toolChanged ui.Events speedChanged ui.Events simulation zntg.Animation } type GameSpeed string const ( GameSpeedNormal GameSpeed = "normal" GameSpeedFast = "fast" GameSpeedPaused = "paused" ) const simulationInterval = 120 * time.Millisecond const fastSimulationInterval = 20 * time.Millisecond func NewGame() *Game { game := &Game{ simulation: zntg.Animation{Interval: time.Millisecond * 10}, } return game } func (g *Game) selectTool(ctx ui.Context, t Tool) { g.tool = t g.toolChanged.Notify(ctx, t) } func (g *Game) setSpeed(ctx ui.Context, speed GameSpeed) { if speed == g.Speed { return } if speed == GameSpeedPaused { g.SpeedBeforePause = g.Speed } g.Speed = speed g.speedChanged.Notify(ctx, speed) switch speed { case GameSpeedPaused: g.simulation.Pause() case GameSpeedNormal: g.simulation.Interval = simulationInterval g.simulation.Start() case GameSpeedFast: g.simulation.Interval = fastSimulationInterval g.simulation.Start() } } func (g *Game) tick() { randomNeighbor := func(pos geom.Point) geom.Point { switch rand.Intn(4) { case 0: return geom.Pt(pos.X-1, pos.Y) case 1: return geom.Pt(pos.X, pos.Y-1) case 2: return geom.Pt(pos.X+1, pos.Y) case 3: return geom.Pt(pos.X, pos.Y+1) } return pos } flowers := map[geom.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 } func (g *Game) CancelTool(ctx ui.Context) { g.selectTool(ctx, nil) } func (g *Game) CenterChanged() ui.EventHandler { return &g.centerChanged } func (g *Game) Dig(tile geom.Point) { id := g.Terrain.DigFlower(tile) desc, ok := g.Herbarium.Find(id) if !ok { return } 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(ctx ui.Context) { g.Pause(ctx) g.Reset(ctx) } func (g *Game) Load(ctx ui.Context) { g.CancelTool(ctx) g.Pause(ctx) 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[geom.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(ctx, g.Terrain.Center) g.setSpeed(ctx, state.Speed) } func (g *Game) Pause(ctx ui.Context) { g.setSpeed(ctx, GameSpeedPaused) } func (g *Game) PlantFlower(id string, tile geom.Point) { if g.Terrain.HasFlower(tile) { // TODO: notify user it tried to plant on tile with flower? return } 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) } func (g *Game) Reset(ctx ui.Context) { 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[geom.Point]Flower{}, } g.CancelTool(ctx) g.setSpeed(ctx, GameSpeedNormal) } func (g *Game) Resume(ctx ui.Context) { g.setSpeed(ctx, g.SpeedBeforePause) } func (g *Game) Run(ctx ui.Context) { g.setSpeed(ctx, GameSpeedNormal) } func (g *Game) RunFast(ctx ui.Context) { g.setSpeed(ctx, 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(ctx ui.Context, id string) { g.selectTool(ctx, &PlantFlowerTool{FlowerID: id}) } func (g *Game) SelectShovel(ctx ui.Context) { g.selectTool(ctx, &ShovelTool{}) } func (g *Game) SpeedChanged() ui.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(ctx ui.Context) { if g.Speed == GameSpeedPaused { g.Resume(ctx) } else { g.Pause(ctx) } } func (g *Game) Tool() Tool { return g.tool } func (g *Game) ToolChanged() ui.EventHandler { return &g.toolChanged } func (g *Game) UnlockNextFlower(ctx ui.Context) { price := g.Herbarium.UnlockNext() g.Balance -= price g.selectTool(ctx, nil) } func (g *Game) Update() { for g.simulation.Animate() { g.tick() } } func (g *Game) UserClickedTile(pos geom.Point) { if g.tool == nil { return } g.tool.ClickedTile(g, pos) }