Files
metro-puzzle-jp/internal/sim/fog.go
claude 915c684dc2
All checks were successful
release / build-release (push) Successful in 1m46s
M8: progression, items, bankarta och sparfil
- internal/progress: katalog (skills.json 12 noder, items.json 25 items)
  med validering; XP/nivåer (150·n(n+1)/2) och skillpoäng; skillträd med
  krav och kostnad; items per plats med buffar, genvägar (JR Pass, kartor,
  powerbank) och färgton; banresultat och upplåsning (huvudbanor i ordning,
  sidospår efter föräldern); 33 %-regeln som test
- internal/save: versionerad JSON med kunskap, plånbok, debuffar,
  progression och pågående bana + seed; atomisk skrivning; migrering;
  ~/.config/metro-puzzle-jp/save.json resp. %AppData%
- sim: RunOptions får Extra/FogRadius/Landmarks/FreeGates/RevealAll/
  InfiniteBattery; luck påverkar trängselzonen; Fog.RevealAll och extra
  landmärken
- game: bankarta i spelbrädesstil ersätter menyn (⟳ återupptar med sparad
  seed), skillträd (T), garderob (I), butik i kiosken (B), XP/nivå/items/
  upplåsningar på resultatet, spelaren tonas av skins; autospar vid banstart,
  efter banan och vid köp; flaggor -save/-nosave
- level: kind/order/parent/rewards/shop; ny sidobana 002b Kabukicho som
  belönar skinnet Kimono; 001 belönar Salaryman-skinnet; butikssortiment
  på 002/003
- tools/shots: demo-sparfil (build/, aldrig den riktiga), bilder av karta,
  skills, garderob och butik; alla körningar med -nosave
- docs/README/wiki uppdaterade

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

130 lines
3.3 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 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.
func NewFog(l *level.Level, radius int) *Fog {
return &Fog{w: l.Width(), h: l.Height(), seen: make([]bool, l.Width()*l.Height()), Radius: radius}
}
func (f *Fog) idx(p level.Point) (int, bool) {
if p.X < 0 || p.Y < 0 || p.X >= f.w || p.Y >= f.h {
return 0, false
}
return 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,
// samt väggarna som gränsar till dem (så att gångarnas form syns).
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)
for dx := -1; dx <= 1; dx++ {
for dy := -1; dy <= 1; dy++ {
n := level.Point{X: p.X + dx, Y: p.Y + dy}
if !l.At(n.X, n.Y).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}
if _, ok := dist[n]; ok || !l.At(n.X, n.Y).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.X, p.Y) {
case level.Platform, level.WrongPlatform, level.Spawn, level.Gate:
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.X, p.Y) {
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 y := 0; y < l.Height(); y++ {
for x := 0; x < l.Width(); x++ {
f.mark(level.Point{X: x, Y: y})
}
}
}
// 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 y := 0; y < l.Height(); y++ {
for x := 0; x < l.Width(); x++ {
if !l.At(x, y).Passable() {
continue
}
total++
if f.Seen(level.Point{X: x, Y: y}) {
seen++
}
}
}
if total == 0 {
return 0
}
return float64(seen) / float64(total)
}