package tins2021 import ( "math/rand" "opslag.de/schobers/geom" ) type Level struct { Player geom.Point Lives int Stars int StarsCollected int Tiles Tiles Monsters Monsters MonsterTargets map[geom.Point]geom.Point GameOver bool Difficulty int Score int Bounds geom.Rectangle } func NewLevel() *Level { const dims = 12 f := &Level{ Player: geom.Pt(1, 1), Lives: 3, Stars: 0, StarsCollected: 0, Tiles: Tiles{}, Monsters: Monsters{}, MonsterTargets: map[geom.Point]geom.Point{}, Bounds: geom.Rect(1, 1, dims+1, dims+1), } for y := 1; y <= dims; y++ { endX := dims if y%2 == 0 { endX-- } for x := 1; x <= endX; x++ { f.Tiles[geom.Pt(x, y)] = &Tile{} } } return f } func (l Level) CanPlayerMove(dir Direction) (geom.Point, bool) { return l.Tiles.CanMove(l.Player, dir) } func (l Level) CanMonsterMove(p geom.Point, dir Direction) (geom.Point, bool) { q, ok := l.Tiles.CanMove(p, dir) if !ok { return geom.Point{}, false } if l.CanMonsterMoveTo(q) { return q, true } return geom.Point{}, false } func (l Level) CanMonsterMoveTo(p geom.Point) bool { if l.Tiles[p].Occupied() { return false } if _, ok := l.Monsters[p]; ok { return false } for _, target := range l.MonsterTargets { if p == target { return false } } return true } func (l *Level) DecrementLive() { l.Lives-- if l.Lives == 0 { l.GameOver = true } } func (l *Level) DestroyMonster(pos geom.Point) { delete(l.MonsterTargets, pos) delete(l.Monsters, pos) } func (l *Level) MoveMonster(target, source geom.Point) { l.Monsters[target] = l.Monsters[source] l.DestroyMonster(source) } func (l *Level) MovePlayer(dir Direction) bool { towards, allowed := l.CanPlayerMove(dir) if !allowed { return false } l.Player = towards tile := l.Tiles[towards] if tile.Heart { l.Lives++ tile.Heart = false l.Score += 10 } if tile.Star { l.StarsCollected++ tile.Star = false l.Score += 25 } if l.Monsters[towards] != nil { l.DecrementLive() l.DestroyMonster(towards) l.Score -= 5 } l.Score -= 1 // for every move return true } func (l *Level) Randomize(difficulty int, stars int) { if difficulty < 0 { difficulty = 0 } l.Difficulty = difficulty positions := make([]geom.Point, 0, len(l.Tiles)) for pos := range l.Tiles { positions = append(positions, pos) } flip := difficulty * len(l.Tiles) / 200 if flip > len(l.Tiles)/2 { flip = len(l.Tiles) / 2 } for ; flip > 0; flip-- { for { i := rand.Intn(len(positions)) pos := positions[i] if l.Tiles[pos].Inversed { continue } l.Tiles[pos].Invert() if l.Tiles.AllReachable(l.Player) { break } l.Tiles[pos].Invert() } } l.Stars = stars for stars > 0 { i := rand.Intn(len(positions)) pos := positions[i] if l.Tiles[pos].Occupied() { continue } l.Tiles[pos].Star = true stars-- } hearts := 1 + (100-difficulty)/50 // [3..1] (only difficulty has 3 hearts) for hearts > 0 { i := rand.Intn(len(positions)) pos := positions[i] if l.Tiles[pos].Occupied() { continue } l.Tiles[pos].Heart = true hearts-- } monsters := 2 + (8 * difficulty / 100) minRandomMonster := (100-difficulty)*80/100 + 10 // [90..10] minChaserMonster := (100-difficulty)*50/100 + 50 // [100..50] for monsters > 0 { i := rand.Intn(len(positions)) pos := positions[i] curr := l.Monsters[pos] if l.Tiles[pos].Occupied() || curr != nil { continue } monster := MonsterTypeStraight m := rand.Intn(100) if m >= minChaserMonster { monster = MonsterTypeChaser } else if m >= minRandomMonster { monster = MonsterTypeRandom } switch monster { case MonsterTypeStraight: l.Monsters[pos] = &StraightWalkingMonster{Direction: RandomDirection()} case MonsterTypeRandom: l.Monsters[pos] = &RandomWalkingMonster{} case MonsterTypeChaser: l.Monsters[pos] = &ChasingMonster{} default: panic("not implemented") } monsters-- } }