Private
Public Access
1
0

fix README, SYNC, DATENSCHUTZ
Some checks failed
Dynamic Branch Deploy / build-and-deploy (push) Has been cancelled

This commit is contained in:
Sebastian Unterschütz
2025-11-30 19:33:20 +01:00
parent 56dd8db9a3
commit 8950b70378
18 changed files with 280 additions and 512 deletions

View File

@@ -12,8 +12,8 @@ import (
const (
ServerTickRate = 50 * time.Millisecond
BufferAhead = 60 // Puffergröße (Zukunft)
SpawnXStart = 2000.0 // Spawn Abstand
BufferAhead = 60
SpawnXStart = 2000.0
)
var upgrader = websocket.Upgrader{
@@ -24,7 +24,7 @@ var upgrader = websocket.Upgrader{
type WSInputMsg struct {
Type string `json:"type"`
Input string `json:"input"`
Tick int `json:"tick"` // Optional: Client Timestamp für Ping
Tick int `json:"tick"`
PosY float64 `json:"y"`
VelY float64 `json:"vy"`
}
@@ -37,7 +37,7 @@ type WSServerMsg struct {
Score int `json:"score"`
PowerUps PowerUpState `json:"powerups"`
SessionID string `json:"sessionId"`
Ts int `json:"ts,omitempty"` // Für Pong
Ts int `json:"ts,omitempty"`
CurrentSpeed float64 `json:"currentSpeed"`
}
@@ -107,15 +107,7 @@ func handleWebSocket(w http.ResponseWriter, r *http.Request) {
case <-closeChan:
return // Client weg
// WICHTIG: Wir verarbeiten Inputs hier NICHT einzeln,
// sondern sammeln sie im Default-Case (siehe unten),
// oder nutzen eine nicht-blockierende Schleife.
// Aber für einfache Logik reicht select.
// Um "Input Lag" zu verhindern, lesen wir den Channel leer:
case <-ticker.C:
// A. INPUTS VERARBEITEN (Alle die angekommen sind!)
// Wir loopen solange durch den Channel, bis er leer ist.
InputLoop:
for {
select {
@@ -136,7 +128,6 @@ func handleWebSocket(w http.ResponseWriter, r *http.Request) {
}
if msg.Type == "ping" {
// Sofort Pong zurück (Performance wichtig!)
conn.WriteJSON(WSServerMsg{Type: "pong", Ts: msg.Tick})
}
@@ -163,25 +154,21 @@ func handleWebSocket(w http.ResponseWriter, r *http.Request) {
log.Printf("🐞 Debug Snapshot an Client gesendet (Tick %d)", state.Ticks)
}
default:
// Channel leer, weiter zur Physik
break InputLoop
}
}
// B. LIVE SIMULATION (1 Tick)
// Jetzt simulieren wir genau EINEN Frame (16ms)
state.Ticks++
state.Score++ // Score wächst mit der Zeit
state.Score++
currentSpeed := calculateSpeed(state.Ticks)
updatePhysics(&state, pendingJump, isCrouching, currentSpeed)
pendingJump = false // Jump Trigger reset
pendingJump = false
checkCollisions(&state, isCrouching, currentSpeed)
if state.IsDead {
// Score Persistieren
rdb.HSet(ctx, "session:"+sessionID, map[string]interface{}{
"score": state.Score,
"is_dead": 1,
@@ -194,13 +181,10 @@ func handleWebSocket(w http.ResponseWriter, r *http.Request) {
moveWorld(&state, currentSpeed)
// C. STREAMING (Zukunft)
// Wir generieren nur, wenn der Puffer zur Neige geht
targetTick := state.Ticks + BufferAhead
var newObs []ActiveObstacle
var newPlats []ActivePlatform
// Um CPU zu sparen, generieren wir max 10 Ticks pro Frame nach
loops := 0
for generatedHeadTick < targetTick && loops < 10 {
generatedHeadTick++
@@ -216,8 +200,6 @@ func handleWebSocket(w http.ResponseWriter, r *http.Request) {
}
}
// D. SENDEN (Effizienz)
// Nur senden wenn Daten da sind ODER alle 15 Frames (Heartbeat/Score Sync)
if len(newObs) > 0 || len(newPlats) > 0 || state.Ticks%15 == 0 {
msg := WSServerMsg{
Type: "chunk",
@@ -235,7 +217,6 @@ func handleWebSocket(w http.ResponseWriter, r *http.Request) {
}
}
// Hilfsfunktion: Generiert Objekte für EINEN Tick in der Zukunft
func generateFutureObjects(s *SimState, tick int, speed float64) ([]ActiveObstacle, []ActivePlatform) {
var createdObs []ActiveObstacle
var createdPlats []ActivePlatform
@@ -249,19 +230,12 @@ func generateFutureObjects(s *SimState, tick int, speed float64) ([]ActiveObstac
if tick >= s.NextSpawnTick {
spawnX := SpawnXStart
// --- ENTSCHEIDUNG: CHUNK vs RANDOM ---
// Wir nutzen die globalen Chunks (da Read-Only während des Spiels, ist Zugriff sicher)
chunkCount := len(defaultConfig.Chunks)
if chunkCount > 0 && s.RNG.NextFloat() > 0.8 {
// =================================================
// OPTION A: CHUNK SPAWNING
// =================================================
idx := int(s.RNG.NextRange(0, float64(chunkCount)))
chunk := defaultConfig.Chunks[idx]
// 1. Plattformen übernehmen
for _, p := range chunk.Platforms {
createdPlats = append(createdPlats, ActivePlatform{
X: spawnX + p.X,
@@ -271,21 +245,18 @@ func generateFutureObjects(s *SimState, tick int, speed float64) ([]ActiveObstac
})
}
// 2. Hindernisse übernehmen & Speech berechnen
for _, o := range chunk.Obstacles {
// Speech-Logik: Wir müssen die Original-Def finden, um zu wissen, ob er sprechen kann
speech := ""
for _, def := range defaultConfig.Obstacles {
if def.ID == o.ID {
// Wenn gefunden, würfeln wir
if def.CanTalk && len(def.SpeechLines) > 0 {
if s.RNG.NextFloat() > 0.7 { // 30% Wahrscheinlichkeit
sIdx := int(s.RNG.NextRange(0, float64(len(def.SpeechLines))))
speech = def.SpeechLines[sIdx]
}
}
break // Def gefunden, Loop abbrechen
break
}
}
@@ -296,11 +267,10 @@ func generateFutureObjects(s *SimState, tick int, speed float64) ([]ActiveObstac
Y: o.Y,
Width: o.Width,
Height: o.Height,
Speech: speech, // <--- HIER wird der Text gesetzt
Speech: speech,
})
}
// Timer setzen (Länge des Chunks)
width := float64(chunk.TotalWidth)
if width == 0 {
width = 2000.0
@@ -308,15 +278,10 @@ func generateFutureObjects(s *SimState, tick int, speed float64) ([]ActiveObstac
s.NextSpawnTick = tick + int(width/speed)
} else {
// =================================================
// OPTION B: RANDOM SPAWNING
// =================================================
// Lücke berechnen
gap := 400 + int(s.RNG.NextRange(0, 500))
s.NextSpawnTick = tick + int(float64(gap)/speed)
// Pool bilden (Boss Phase?)
defs := defaultConfig.Obstacles
if len(defs) > 0 {
isBoss := (tick % 1500) > 1200
@@ -334,11 +299,9 @@ func generateFutureObjects(s *SimState, tick int, speed float64) ([]ActiveObstac
}
}
// Objekt auswählen
def := s.RNG.PickDef(pool)
if def != nil {
// Powerup Rarity (90% Chance, dass es NICHT spawnt)
if def.Type == "powerup" && s.RNG.NextFloat() > 0.1 {
def = nil
}
@@ -353,7 +316,6 @@ func generateFutureObjects(s *SimState, tick int, speed float64) ([]ActiveObstac
}
}
// Y-Position berechnen (Boden - Höhe - Offset)
spawnY := GroundY - def.Height - def.YOffset
createdObs = append(createdObs, ActiveObstacle{
@@ -363,7 +325,7 @@ func generateFutureObjects(s *SimState, tick int, speed float64) ([]ActiveObstac
Y: spawnY,
Width: def.Width,
Height: def.Height,
Speech: speech, // <--- HIER wird der Text gesetzt
Speech: speech,
})
}
}