package sim import "gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/level" // FogRadius är hur många steg från spelaren som avslöjas (gångavstånd, så // väggar skymmer – man ser längs gången, inte genom väggen). const FogRadius = 3 // Fog håller reda på vilka rutor spelaren har sett i den här omgången. // Minikartan ritar bara sedda rutor plus riktmärken som alltid syns. type Fog struct { w, h, floors int seen []bool Radius int count int ExtraLandmarks bool // toaletter, kiosker och automater syns alltid (skillen Lokalkännedom) } // NewFog skapar en tom dimma för banan (alla våningar). func NewFog(l *level.Level, radius int) *Fog { n := max(1, l.NumFloors()) return &Fog{w: l.Width(), h: l.Height(), floors: n, seen: make([]bool, l.Width()*l.Height()*n), Radius: radius} } func (f *Fog) idx(p level.Point) (int, bool) { if p.X < 0 || p.Y < 0 || p.Z < 0 || p.X >= f.w || p.Y >= f.h || p.Z >= f.floors { return 0, false } return (p.Z*f.h+p.Y)*f.w + p.X, true } // Seen rapporterar om rutan har setts. func (f *Fog) Seen(p level.Point) bool { i, ok := f.idx(p) return ok && f.seen[i] } func (f *Fog) mark(p level.Point) { if i, ok := f.idx(p); ok && !f.seen[i] { f.seen[i] = true f.count++ } } // Reveal avslöjar allt inom Radius steg från from via passerbara rutor på // samma våning, samt väggarna som gränsar till dem (så att gångarnas form // syns). Trappans andra ände avslöjas också. func (f *Fog) Reveal(l *level.Level, from level.Point) { dist := map[level.Point]int{from: 0} queue := []level.Point{from} for len(queue) > 0 { p := queue[0] queue = queue[1:] f.mark(p) switch l.At(p) { case level.StairsUp: f.mark(p.Up()) case level.StairsDown: f.mark(p.Down()) } for dx := -1; dx <= 1; dx++ { for dy := -1; dy <= 1; dy++ { n := level.Point{X: p.X + dx, Y: p.Y + dy, Z: p.Z} if !l.At(n).Passable() { f.mark(n) // gränsande vägg } } } if dist[p] >= f.Radius { continue } for _, d := range [...]level.Point{{X: 1}, {X: -1}, {Y: 1}, {Y: -1}} { n := level.Point{X: p.X + d.X, Y: p.Y + d.Y, Z: p.Z} if _, ok := dist[n]; ok || !l.At(n).Passable() { continue } dist[n] = dist[p] + 1 queue = append(queue, n) } } } // Landmark är rutor som alltid syns på minikartan: perrongerna (avgångarna), // starten och spärrarna – det man orienterar sig efter. func Landmark(l *level.Level, p level.Point) bool { switch l.At(p) { case level.Platform, level.WrongPlatform, level.Spawn, level.Gate, level.StairsUp, level.StairsDown: return true } return false } // Visible rapporterar om rutan ska ritas på minikartan. func (f *Fog) Visible(l *level.Level, p level.Point) bool { if f.Seen(p) || Landmark(l, p) { return true } if f.ExtraLandmarks { switch l.At(p) { case level.Toilet, level.Kiosk, level.TicketMachine: return true } } return false } // RevealAll avslöjar hela banan (kartan köpt som item). func (f *Fog) RevealAll(l *level.Level) { for z := 0; z < l.NumFloors(); z++ { for y := 0; y < l.Height(); y++ { for x := 0; x < l.Width(); x++ { f.mark(level.Point{X: x, Y: y, Z: z}) } } } } // Count är antalet sedda rutor (inkl. väggar). func (f *Fog) Count() int { return f.count } // Explored är andelen passerbara rutor som setts, 0..1. func (f *Fog) Explored(l *level.Level) float64 { total, seen := 0, 0 for z := 0; z < l.NumFloors(); z++ { for y := 0; y < l.Height(); y++ { for x := 0; x < l.Width(); x++ { p := level.Point{X: x, Y: y, Z: z} if !l.At(p).Passable() { continue } total++ if f.Seen(p) { seen++ } } } } if total == 0 { return 0 } return float64(seen) / float64(total) }