Private
Public Access
1
0
Files
it232Abschied/simulation.go
Sebastian Unterschütz 95119cdf98
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 3m42s
bug fixes
2025-11-26 10:59:05 +01:00

247 lines
5.5 KiB
Go

package main
import (
"encoding/json"
"fmt"
"strconv"
)
func simulateChunk(sessionID string, inputs []Input, totalTicks int, vals map[string]string) (bool, int, []ActiveObstacle) {
// State laden
posY := parseOr(vals["pos_y"], PlayerYBase)
velY := parseOr(vals["vel_y"], 0.0)
score := int(parseOr(vals["score"], 0))
// NEU: Wir laden die bisher vergangene Zeit (Ticks)
ticksAlive := int(parseOr(vals["total_ticks"], 0))
rngStateVal, _ := strconv.ParseInt(vals["rng_state"], 10, 64)
godLives := int(parseOr(vals["p_god_lives"], 0))
hasBat := vals["p_has_bat"] == "1"
bootTicks := int(parseOr(vals["p_boot_ticks"], 0))
rng := NewRNG(rngStateVal)
var obstacles []ActiveObstacle
if val, ok := vals["obstacles"]; ok && val != "" {
json.Unmarshal([]byte(val), &obstacles)
} else {
obstacles = []ActiveObstacle{}
}
playerDead := false
for i := 0; i < totalTicks; i++ {
// WICHTIG: Wir erhöhen die Zeit
ticksAlive++
// LOGIK FIX: Geschwindigkeit basiert jetzt auf ZEIT (Ticks), nicht Score!
// 3000 Ticks = ca. 50 Sekunden. Da wird es schneller.
currentSpeed := BaseSpeed + (float64(ticksAlive)/3000.0)*0.5
if currentSpeed > 12.0 {
currentSpeed = 12.0
}
currentJumpPower := JumpPower
if bootTicks > 0 {
currentJumpPower = HighJumpPower
bootTicks--
}
// Input Verarbeitung
didJump := false
isCrouching := false
for _, inp := range inputs {
if inp.Tick == i {
if inp.Act == "JUMP" {
didJump = true
}
if inp.Act == "DUCK" {
isCrouching = true
}
}
}
// Physik
isGrounded := posY >= PlayerYBase-1.0
currentHeight := PlayerHeight
if isCrouching {
currentHeight = PlayerHeight / 2
if !isGrounded {
velY += 2.0
}
}
if didJump && isGrounded && !isCrouching {
velY = currentJumpPower
}
velY += Gravity
posY += velY
if posY > PlayerYBase {
posY = PlayerYBase
velY = 0
}
hitboxY := posY
if isCrouching {
hitboxY = posY + (PlayerHeight - currentHeight)
}
// Obstacles
nextObstacles := []ActiveObstacle{}
rightmostX := 0.0
for _, obs := range obstacles {
obs.X -= currentSpeed
if obs.X+obs.Width < -50.0 {
continue
}
// Passed Check (Verhindert Hängenbleiben an "toten" Objekten)
paddingX := 10.0
if obs.X+obs.Width-paddingX < 55.0 {
// Schon vorbei -> Keine Kollision mehr prüfen
nextObstacles = append(nextObstacles, obs)
if obs.X+obs.Width > rightmostX {
rightmostX = obs.X + obs.Width
}
continue
}
paddingY_Top := 10.0
if obs.Type == "teacher" {
paddingY_Top = 25.0
}
pLeft, pRight := 50.0+paddingX, 50.0+30.0-paddingX
pTop, pBottom := hitboxY+paddingY_Top, hitboxY+currentHeight-5.0
oLeft, oRight := obs.X+paddingX, obs.X+obs.Width-paddingX
oTop, oBottom := obs.Y+paddingY_Top, obs.Y+obs.Height-5.0
isCollision := pRight > oLeft && pLeft < oRight && pBottom > oTop && pTop < oBottom
if isCollision {
if obs.Type == "coin" {
score += 2000 // Score Bonus macht das Spiel nicht mehr kaputt!
continue
} else if obs.Type == "powerup" {
if obs.ID == "p_god" {
godLives = 3
}
if obs.ID == "p_bat" {
hasBat = true
}
if obs.ID == "p_boot" {
bootTicks = 600
}
continue
} else {
if hasBat && obs.Type == "teacher" {
hasBat = false
continue
}
if godLives > 0 {
godLives--
continue
}
playerDead = true
}
}
nextObstacles = append(nextObstacles, obs)
if obs.X+obs.Width > rightmostX {
rightmostX = obs.X + obs.Width
}
}
obstacles = nextObstacles
// Spawning
if rightmostX < GameWidth-10.0 {
gap := float64(int(400.0 + rng.NextRange(0, 500)))
spawnX := rightmostX + gap
if spawnX < GameWidth {
spawnX = GameWidth
}
// LOGIK FIX: Boss Phase basiert auf ZEIT (Ticks)
isBossPhase := (ticksAlive % 1500) > 1200
var possibleDefs []ObstacleDef
for _, d := range defaultConfig.Obstacles {
if isBossPhase {
if d.ID == "principal" || d.ID == "trashcan" {
possibleDefs = append(possibleDefs, d)
}
} else {
if d.ID == "principal" {
continue
}
// Eraser kommt ab Tick 3000 (ca. 50 sekunden)
if d.ID == "eraser" && ticksAlive < 3000 {
continue
}
possibleDefs = append(possibleDefs, d)
}
}
def := rng.PickDef(possibleDefs)
if def != nil && def.CanTalk {
if rng.NextFloat() > 0.7 {
rng.NextFloat()
}
}
if def != nil {
if def.Type == "powerup" && rng.NextFloat() > 0.1 {
def = nil
}
if def != nil {
spawnY := GroundY - def.Height - def.YOffset
obstacles = append(obstacles, ActiveObstacle{
ID: def.ID, Type: def.Type, X: spawnX, Y: spawnY, Width: def.Width, Height: def.Height,
})
}
}
}
if !playerDead {
score++ // Basis-Score läuft normal weiter
} else {
break
}
}
obsJson, _ := json.Marshal(obstacles)
batStr := "0"
if hasBat {
batStr = "1"
}
rdb.HSet(ctx, "session:"+sessionID, map[string]interface{}{
"score": score,
"total_ticks": ticksAlive, // NEU: Speichern
"pos_y": fmt.Sprintf("%f", posY),
"vel_y": fmt.Sprintf("%f", velY),
"rng_state": rng.State,
"obstacles": string(obsJson),
"p_god_lives": godLives,
"p_has_bat": batStr,
"p_boot_ticks": bootTicks,
})
return playerDead, score, obstacles
}
func parseOr(s string, def float64) float64 {
if s == "" {
return def
}
v, err := strconv.ParseFloat(s, 64)
if err != nil {
return def
}
return v
}