tins2021/level.go

221 lines
4.5 KiB
Go

package tins2021
import (
"math/rand"
"sort"
"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)
}
type MonsterHit struct {
Position geom.Point
Type MonsterType
}
func (l *Level) MovePlayer(dir Direction) (bool, *MonsterHit) {
towards, allowed := l.CanPlayerMove(dir)
if !allowed {
return false, nil
}
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
}
var hit *MonsterHit
if l.Monsters[towards] != nil {
hit = &MonsterHit{
Position: towards,
Type: l.Monsters[towards].Type(),
}
l.DecrementLive()
l.DestroyMonster(towards)
l.Score -= 5
}
l.Score -= 1 // for every move
return true, hit
}
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)
}
distances := l.Tiles.Distances(l.Player)
sort.Slice(positions, func(i, j int) bool {
return distances[positions[i]] < distances[positions[j]]
})
pick := func(bias int) geom.Point { // bias towards far away [0..100]
if bias > 100 {
bias = 100
}
if bias < 0 {
bias = 0
}
n := len(positions)
const min = 4
from := min + bias*(n-min)/(2*(n-min)) // never pick first
return positions[from+rand.Intn(n-from)]
}
flip := difficulty * len(l.Tiles) / 200
if flip > len(l.Tiles)/2 {
flip = len(l.Tiles) / 2
}
for ; flip > 0; flip-- {
for {
pos := pick(0)
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 {
pos := pick(difficulty)
if l.Tiles[pos].Occupied() {
continue
}
l.Tiles[pos].Star = true
stars--
}
hearts := rand.Intn(2 + (100-difficulty)/50) // [3..1] (only difficulty 0 has 3 hearts)
for hearts > 0 {
pos := pick(difficulty)
if l.Tiles[pos].Occupied() {
continue
}
l.Tiles[pos].Heart = true
hearts--
}
monsters := 5 + (25 * difficulty / 100)
minRandomMonster := (100-difficulty)*60/100 + 10 // [70..10]
minChaserMonster := (100-difficulty)*70/100 + 25 // [95..25]
for monsters > 0 {
pos := pick(100 - difficulty)
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--
}
}