M0: skelett, byggkedja och dokumentation för JP Subway Pussel
- Go 1.27 + Ebitengine v2.10, intern upplösning 320x180 med heltalsskalning - internal/res (resursuppslag), internal/rng (seedad slump), internal/level (banformat, validering, BFS-lösbarhet) med tester; internal/archtest kontrollerar att bara presentationslagret beror på Ebitengine - tools/build: debug -> build/debug/, release -> build/release/<os>-<arch>/ med resources/ bredvid binären; Windows korsbyggs utan C-verktyg - VS Code tasks (build/test/release) och launch (dlv) - Första banan 001-flygplatsen, sv/en-texter, skyltordbok, placeholder-sprite - doc/: spelbeskrivning (renskriven), mål och tester, roadmap med teknikval (M0–M12), plan.md; README med bygg- och beroendeinformation Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
48
internal/rng/rng.go
Normal file
48
internal/rng/rng.go
Normal file
@@ -0,0 +1,48 @@
|
||||
// Package rng ger seedade slumptalsgeneratorer.
|
||||
//
|
||||
// Allt som ska vara reproducerbart (minispel, oändligt läge, event-timing)
|
||||
// måste ta sin slump härifrån med en känd seed, aldrig från den globala
|
||||
// generatorn. Seeden sparas i sparfilen så att en laddad omgång ger samma
|
||||
// utfall som den avbrutna.
|
||||
package rng
|
||||
|
||||
import (
|
||||
crand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"math/rand/v2"
|
||||
)
|
||||
|
||||
// Seed är en 64-bitars seed. Två värden för att kunna avleda del-seeds
|
||||
// (t.ex. per bana eller per minispel) utan att de påverkar varandra.
|
||||
type Seed struct {
|
||||
A, B uint64
|
||||
}
|
||||
|
||||
// NewSeed drar en färsk slumpmässig seed från operativsystemet.
|
||||
func NewSeed() Seed {
|
||||
var b [16]byte
|
||||
if _, err := crand.Read(b[:]); err != nil {
|
||||
panic("rng: kunde inte läsa slump från OS: " + err.Error())
|
||||
}
|
||||
return Seed{binary.LittleEndian.Uint64(b[:8]), binary.LittleEndian.Uint64(b[8:])}
|
||||
}
|
||||
|
||||
// FromInt skapar en seed från ett enkelt heltal (praktiskt i tester och för
|
||||
// seeds som spelaren skriver in manuellt).
|
||||
func FromInt(n uint64) Seed { return Seed{A: n, B: n ^ 0x9E3779B97F4A7C15} }
|
||||
|
||||
// Derive avleder en deterministisk del-seed för en namngiven kontext, så att
|
||||
// t.ex. biljettautomatens minispel på bana 3 alltid får samma slump oavsett
|
||||
// vad spelaren gjort innan.
|
||||
func (s Seed) Derive(context string) Seed {
|
||||
h := rand.New(rand.NewPCG(s.A, s.B))
|
||||
a, b := h.Uint64(), h.Uint64()
|
||||
for _, c := range context {
|
||||
a ^= uint64(c) * 0x100000001B3
|
||||
b = b*31 + uint64(c)
|
||||
}
|
||||
return Seed{A: a, B: b}
|
||||
}
|
||||
|
||||
// New returnerar en generator för seeden. Samma seed ger alltid samma sekvens.
|
||||
func (s Seed) New() *rand.Rand { return rand.New(rand.NewPCG(s.A, s.B)) }
|
||||
38
internal/rng/rng_test.go
Normal file
38
internal/rng/rng_test.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package rng
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestDeterministic(t *testing.T) {
|
||||
a := FromInt(42).New()
|
||||
b := FromInt(42).New()
|
||||
for i := 0; i < 1000; i++ {
|
||||
if x, y := a.Uint64(), b.Uint64(); x != y {
|
||||
t.Fatalf("sekvensen skiljer sig vid %d: %d != %d", i, x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDifferentSeedsDiffer(t *testing.T) {
|
||||
if FromInt(1).New().Uint64() == FromInt(2).New().Uint64() {
|
||||
t.Fatal("olika seeds gav samma första värde")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeriveIsStableAndContextual(t *testing.T) {
|
||||
s := FromInt(7)
|
||||
if s.Derive("ticket") != s.Derive("ticket") {
|
||||
t.Fatal("Derive är inte deterministisk")
|
||||
}
|
||||
if s.Derive("ticket") == s.Derive("toilet") {
|
||||
t.Fatal("olika kontexter gav samma del-seed")
|
||||
}
|
||||
if s.Derive("ticket") == FromInt(8).Derive("ticket") {
|
||||
t.Fatal("olika rot-seeds gav samma del-seed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewSeedIsRandom(t *testing.T) {
|
||||
if NewSeed() == NewSeed() {
|
||||
t.Fatal("två NewSeed() gav samma värde")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user