All checks were successful
release / build-release (push) Successful in 1m34s
internal/nav med resources/data/network.json (76 stationer, 11 linjer, Yamanote som ringlinje). Tåg bedöms från lineId + terminus mot banans station/to: rätt, fel håll, för långt bort (fler än 12 stationer) eller ingen trafik; correct räknas ut för nätkopplade banor, 1-4 rätta tåg. Tågresan visar linjens riktiga stationer. Nya förlustorsaker "Fel håll" och "För långt bort". Listläge (nästa tåg nere till vänster) och skyltläge (+50 % poäng) växlas med N på bankartan och sparas; -mode. Terminalen: biljettautomaten visar linjetavlan på nätbanor. Perronger P p Q q R r. Bana 006 Ikebukuro med fyra perronger. Banorna 001-005 kopplade till nätet. Skript: waitover och pick calm; poängraden radbryts; skärmdumpar uppdaterade. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
143 lines
3.7 KiB
Go
143 lines
3.7 KiB
Go
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.Platform2, level.Platform3, level.Platform4, level.Platform5, 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)
|
||
}
|