Private
Public Access
1
0

add offline mode for solo play with local game state simulation
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 1m47s

This commit is contained in:
Sebastian Unterschütz
2026-04-22 12:37:52 +02:00
parent e295d1ad61
commit de87b76005
4 changed files with 147 additions and 6 deletions

128
cmd/client/offline_logic.go Normal file
View File

@@ -0,0 +1,128 @@
package main
import (
"log"
"math/rand"
"time"
"git.zb-server.de/ZB-Server/EscapeFromTeacher/pkg/config"
"git.zb-server.de/ZB-Server/EscapeFromTeacher/pkg/game"
)
// startOfflineGame initialisiert eine lokale Spielrunde ohne Server
func (g *Game) startOfflineGame() {
g.resetForNewGame()
g.isOffline = true
g.connected = false // Explizit offline
g.appState = StateGame
// Initialen GameState lokal erstellen
g.stateMutex.Lock()
g.gameState = game.GameState{
Status: "RUNNING",
RoomID: "offline_solo",
Players: make(map[string]game.PlayerState),
WorldChunks: []game.ActiveChunk{{ChunkID: "start", X: 0}},
CurrentSpeed: config.RunSpeed,
DifficultyFactor: 0,
}
// Lokalen Spieler hinzufügen
g.gameState.Players[g.playerName] = game.PlayerState{
ID: g.playerName,
Name: g.playerName,
X: 100,
Y: 200,
IsAlive: true,
}
g.stateMutex.Unlock()
// Initialer Chunk-Library Check
if len(g.world.ChunkLibrary) == 0 {
log.Println("⚠️ Warnung: Keine Chunks in Library geladen!")
}
g.roundStartTime = time.Now()
g.predictedX = 100
g.predictedY = 200
g.currentSpeed = config.RunSpeed
g.audio.PlayMusic()
g.notifyGameStarted()
log.Println("🕹️ Offline-Modus gestartet")
}
// updateOfflineLoop simuliert die Server-Logik lokal
func (g *Game) updateOfflineLoop() {
if !g.isOffline || g.gameState.Status != "RUNNING" {
return
}
g.stateMutex.Lock()
defer g.stateMutex.Unlock()
elapsed := time.Since(g.roundStartTime).Seconds()
// 1. Schwierigkeit & Speed (analog zu pkg/server/room.go)
g.gameState.DifficultyFactor = elapsed / config.MaxDifficultySeconds
if g.gameState.DifficultyFactor > 1.0 {
g.gameState.DifficultyFactor = 1.0
}
speedIncrease := g.gameState.DifficultyFactor * g.gameState.DifficultyFactor * 18.0
g.gameState.CurrentSpeed = config.RunSpeed + speedIncrease
g.currentSpeed = g.gameState.CurrentSpeed
// 2. Scrolling
g.gameState.ScrollX += g.currentSpeed
// 3. Chunks nachladen
// Wenn das Ende der Map nah am rechten Rand ist, neuen Chunk spawnen
mapEnd := 0.0
for _, c := range g.gameState.WorldChunks {
chunkDef := g.world.ChunkLibrary[c.ChunkID]
end := c.X + float64(chunkDef.Width*config.TileSize)
if end > mapEnd {
mapEnd = end
}
}
if mapEnd < g.gameState.ScrollX+2500 {
g.spawnOfflineChunk(mapEnd)
}
// 4. Entferne alte Chunks (links aus dem Bild)
if len(g.gameState.WorldChunks) > 5 {
// Behalte immer mindestens die letzten paar Chunks
if g.gameState.WorldChunks[0].X < g.gameState.ScrollX-2000 {
g.gameState.WorldChunks = g.gameState.WorldChunks[1:]
}
}
// 5. Score Update (Distanz)
p, ok := g.gameState.Players[g.playerName]
if ok && p.IsAlive {
// Grobe Score-Simulation
p.Score = int(g.gameState.ScrollX / 10)
g.gameState.Players[g.playerName] = p
}
}
func (g *Game) spawnOfflineChunk(atX float64) {
// Zufälligen Chunk wählen
var pool []string
for id := range g.world.ChunkLibrary {
if id != "start" {
pool = append(pool)
pool = append(pool, id)
}
}
if len(pool) > 0 {
randomID := pool[rand.Intn(len(pool))]
g.gameState.WorldChunks = append(g.gameState.WorldChunks, game.ActiveChunk{
ChunkID: randomID,
X: atX,
})
}
}