Files
metro-puzzle-jp/internal/sim/fog.go
claude 8fca14e42d
All checks were successful
release / build-release (push) Successful in 1m34s
M9a: stationer i flera våningar med trappor
- level: Point har Z; banor kan ha floors (lika stora rutnät med namn),
  gamla grid-banor läses som en våning; trappor U/D på samma x,y en våning
  upp/ner valideras parvis; Neighbors ger grannar över våningar så att
  Path, lösbarhet, spärregel och Unreachable fungerar; skyltar har z
- sim: att kliva på en trappa flyttar spelaren till andra änden
  (FloorChanges); dimman är per våning och avslöjar trappans andra ände;
  tåg hörs/syns bara på egen våning
- game: bara spelarens våning ritas (följare på andra våningar döljs),
  HUD och minikarta visar våningens namn, trappgrafik i genart och färg på
  minikartan
- bana 004 Tokyo (svårighet 3, tre i sällskapet): start under mark på B1,
  trappa upp till 1F med perronger; generatorn gör våningar med trappor
  som flaskhalsar; ordboken utökad
- testbotar och tools/shots förstår trappor; två våningsbilder
- docs/README/wiki uppdaterade

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-24 11:54:07 +02:00

143 lines
3.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}