From 7f66e4ca1bedfeb9813fd858bd30442a1dcb0562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Untersch=C3=BCtz?= Date: Mon, 24 Nov 2025 23:23:43 +0100 Subject: [PATCH] add a lot debug --- main.go | 185 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 144 insertions(+), 41 deletions(-) diff --git a/main.go b/main.go index 41783a2..751bd38 100644 --- a/main.go +++ b/main.go @@ -16,16 +16,23 @@ import ( "github.com/redis/go-redis/v9" ) +// ========================================== +// 1. KONSTANTEN +// ========================================== const ( Gravity = 0.6 JumpPower = -12.0 GroundY = 350.0 PlayerHeight = 50.0 - PlayerYBase = GroundY - PlayerHeight - GameSpeed = 5.0 - GameWidth = 800.0 + PlayerYBase = GroundY - PlayerHeight // 300.0 + + GameSpeed = 5.0 + GameWidth = 800.0 ) +// ========================================== +// 2. GLOBALE VARIABLEN +// ========================================== var ( ctx = context.Background() rdb *redis.Client @@ -34,6 +41,10 @@ var ( adminPass string ) +// ========================================== +// 3. STRUCTS +// ========================================== + type ObstacleDef struct { ID string `json:"id"` Width float64 `json:"width"` @@ -114,11 +125,16 @@ type ClaimDeleteRequest struct { ClaimCode string `json:"claimCode"` } +// ========================================== +// 4. PSEUDO RNG +// ========================================== type PseudoRNG struct { State uint32 } func NewRNG(seed int64) *PseudoRNG { + // DEBUG: RNG Init + // log.Printf("[RNG] Init mit Seed: %d", seed) return &PseudoRNG{State: uint32(seed)} } @@ -140,6 +156,9 @@ func (r *PseudoRNG) PickDef(defs []ObstacleDef) *ObstacleDef { return &defs[idx] } +// ========================================== +// 5. HELPER +// ========================================== func getEnv(key, fallback string) string { if value, ok := os.LookupEnv(key); ok { return value @@ -147,6 +166,27 @@ func getEnv(key, fallback string) string { return fallback } +func parseOr(s string, def float64) float64 { + if s == "" { + return def + } + v, err := strconv.ParseFloat(s, 64) + if err != nil { + return def + } + return v +} + +func generateClaimCode() string { + const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + b := make([]byte, 8) + for i := range b { + b[i] = charset[rand.Intn(len(charset))] + } + return string(b) +} + +// Middleware für Basic Auth func BasicAuth(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { user, pass, ok := r.BasicAuth() @@ -159,45 +199,60 @@ func BasicAuth(next http.HandlerFunc) http.HandlerFunc { } } +// Middleware für Request Logging (DAMIT DU SIEHST OB REQUESTS ANKOMMEN) +func LogRequest(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + log.Printf("➡ [REQ] %s %s von %s", r.Method, r.URL.Path, r.RemoteAddr) + next(w, r) + log.Printf("⬅ [RES] %s %s fertig in %v", r.Method, r.URL.Path, time.Since(start)) + } +} + +// ========================================== +// 6. MAIN +// ========================================== func main() { + // Config laden redisAddr := getEnv("REDIS_ADDR", "localhost:6379") adminUser = getEnv("ADMIN_USER", "lehrer") adminPass = getEnv("ADMIN_PASS", "geheim123") + log.Printf("🔧 CONFIG: Redis=%s | AdminUser=%s", redisAddr, adminUser) + + // Redis verbinden + log.Println("🔌 Verbinde zu Redis...") rdb = redis.NewClient(&redis.Options{Addr: redisAddr}) - if _, err := rdb.Ping(ctx).Result(); err != nil { - log.Fatal(err) + + pong, err := rdb.Ping(ctx).Result() + if err != nil { + log.Fatalf("❌ REDIS ERROR: %v", err) } + log.Printf("✅ Redis verbunden: %s", pong) initGameConfig() + // File Server fs := http.FileServer(http.Dir("./static")) http.Handle("/", fs) - http.HandleFunc("/api/config", handleConfig) - http.HandleFunc("/api/start", handleStart) - http.HandleFunc("/api/validate", handleValidate) - http.HandleFunc("/api/submit-name", handleSubmitName) - http.HandleFunc("/api/leaderboard", handleLeaderboard) - http.HandleFunc("/api/claim/delete", handleClaimDelete) + // API Routes (mit Logging Wrapper) + http.HandleFunc("/api/config", LogRequest(handleConfig)) + http.HandleFunc("/api/start", LogRequest(handleStart)) + http.HandleFunc("/api/validate", LogRequest(handleValidate)) // Hier passiert die Magie + http.HandleFunc("/api/submit-name", LogRequest(handleSubmitName)) + http.HandleFunc("/api/leaderboard", LogRequest(handleLeaderboard)) + http.HandleFunc("/api/claim/delete", LogRequest(handleClaimDelete)) + // Admin Routes http.HandleFunc("/admin", BasicAuth(handleAdminPage)) http.HandleFunc("/api/admin/list", BasicAuth(handleAdminList)) http.HandleFunc("/api/admin/action", BasicAuth(handleAdminAction)) - log.Println("Server läuft auf Port 8080") + log.Println("🦖 Server läuft auf Port 8080") log.Fatal(http.ListenAndServe(":8080", nil)) } -func generateClaimCode() string { - const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - b := make([]byte, 8) - for i := range b { - b[i] = charset[rand.Intn(len(charset))] - } - return string(b) -} - func initGameConfig() { defaultConfig = GameConfig{ Obstacles: []ObstacleDef{ @@ -206,10 +261,15 @@ func initGameConfig() { {ID: "trashcan", Width: 25, Height: 35, Color: "#555", Image: "trash.png"}, {ID: "eraser", Width: 30, Height: 20, Color: "#fff", Image: "eraser.png", YOffset: 45.0}, }, - Backgrounds: []string{"background.jpg"}, + Backgrounds: []string{"background.png"}, } + log.Println("✅ Game Config initialisiert") } +// ========================================== +// 7. HANDLER (MIT DEBUG LOGS) +// ========================================== + func handleConfig(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(defaultConfig) @@ -217,25 +277,34 @@ func handleConfig(w http.ResponseWriter, r *http.Request) { func handleStart(w http.ResponseWriter, r *http.Request) { sessionID := uuid.New().String() + + // Seed erstellen (32-Bit sicher) rawSeed := time.Now().UnixNano() seed32 := uint32(rawSeed) + // Initiale Hindernisse (leer) emptyObs, _ := json.Marshal([]ActiveObstacle{}) + log.Printf("🆕 START SESSION: %s | Seed: %d", sessionID, seed32) + + // In Redis speichern err := rdb.HSet(ctx, "session:"+sessionID, map[string]interface{}{ "seed": seed32, "rng_state": seed32, "score": 0, "is_dead": 0, - "pos_y": PlayerYBase, + "pos_y": PlayerYBase, // WICHTIG: Start auf dem Boden (300.0) "vel_y": 0.0, "obstacles": string(emptyObs), }).Err() if err != nil { + log.Printf("❌ REDIS WRITE ERROR bei Start: %v", err) http.Error(w, "DB Error", 500) return } + + // Expire setzen rdb.Expire(ctx, "session:"+sessionID, 1*time.Hour) json.NewEncoder(w).Encode(StartResponse{SessionID: sessionID, Seed: seed32}) @@ -244,29 +313,38 @@ func handleStart(w http.ResponseWriter, r *http.Request) { func handleValidate(w http.ResponseWriter, r *http.Request) { var req ValidateRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + log.Printf("⚠️ Validate Decode Error: %v", err) http.Error(w, "Bad Request", 400) return } key := "session:" + req.SessionID + + // Alle Daten holen vals, err := rdb.HGetAll(ctx, key).Result() if err != nil || len(vals) == 0 { + log.Printf("⚠️ Session nicht gefunden oder leer: %s", req.SessionID) http.Error(w, "Session invalid", 401) return } + // Check ob schon tot if vals["is_dead"] == "1" { + // log.Printf("💀 Validierung abgelehnt: Spieler %s ist bereits tot.", req.SessionID) json.NewEncoder(w).Encode(ValidateResponse{Status: "dead", VerifiedScore: 0}) return } + // State parsen posY := parseOr(vals["pos_y"], PlayerYBase) velY := parseOr(vals["vel_y"], 0.0) score := int(parseOr(vals["score"], 0)) - rngStateVal, _ := strconv.ParseInt(vals["rng_state"], 10, 64) + + // RNG initialisieren rng := NewRNG(rngStateVal) + // Hindernisse laden var obstacles []ActiveObstacle if val, ok := vals["obstacles"]; ok && val != "" { json.Unmarshal([]byte(val), &obstacles) @@ -274,12 +352,16 @@ func handleValidate(w http.ResponseWriter, r *http.Request) { obstacles = []ActiveObstacle{} } + // log.Printf("🎮 SIMULATION START (%s) | Ticks: %d | PosY: %.2f | Obstacles: %d", req.SessionID, req.TotalTicks, posY, len(obstacles)) + playerDead := false + // --- SIMULATION LOOP --- for i := 0; i < req.TotalTicks; i++ { + + // A. INPUT didJump := false isCrouching := false - for _, inp := range req.Inputs { if inp.Tick == i { if inp.Act == "JUMP" { @@ -291,6 +373,8 @@ func handleValidate(w http.ResponseWriter, r *http.Request) { } } + // B. PHYSIK + // Toleranz 1.0px isGrounded := posY >= PlayerYBase-1.0 currentHeight := PlayerHeight @@ -298,7 +382,7 @@ func handleValidate(w http.ResponseWriter, r *http.Request) { currentHeight = PlayerHeight / 2 if !isGrounded { velY += 2.0 - } + } // Fast fall } if didJump && isGrounded && !isCrouching { @@ -313,21 +397,26 @@ func handleValidate(w http.ResponseWriter, r *http.Request) { velY = 0 } + // Hitbox Y berechnen hitboxY := posY if isCrouching { hitboxY = posY + (PlayerHeight - currentHeight) } + // C. OBSTACLES & KOLLISION nextObstacles := []ActiveObstacle{} rightmostX := 0.0 for _, obs := range obstacles { obs.X -= GameSpeed + // Ist Hindernis schon vorbei? (Hinter Spieler) if obs.X+obs.Width < 50.0 { - continue + // log.Printf("🗑️ Obstacle %s passed safely", obs.ID) + continue // Nicht in nextObstacles aufnehmen -> wird gelöscht } + // Kollisions-Logik paddingX := 10.0 paddingY_Top := 25.0 paddingY_Bottom := 5.0 @@ -339,9 +428,12 @@ func handleValidate(w http.ResponseWriter, r *http.Request) { oTop, oBottom := obs.Y+paddingY_Top, obs.Y+obs.Height-paddingY_Bottom if pRight > oLeft && pLeft < oRight && pBottom > oTop && pTop < oBottom { + log.Printf("💥 KOLLISION! Session: %s | Obs: %s at %.2f | PlayerY: %.2f | Crouch: %v", req.SessionID, obs.ID, obs.X, posY, isCrouching) playerDead = true } + // Behalten wenn noch relevant (sichtbar oder kurz davor) + // Wir behalten es etwas länger, damit Client Synchronisation nicht springt if obs.X+obs.Width > -100 { nextObstacles = append(nextObstacles, obs) if obs.X+obs.Width > rightmostX { @@ -351,6 +443,7 @@ func handleValidate(w http.ResponseWriter, r *http.Request) { } obstacles = nextObstacles + // D. SPAWNING if rightmostX < GameWidth-10.0 { rawGap := 400.0 + rng.NextRange(0, 500) gap := float64(int(rawGap)) @@ -380,6 +473,8 @@ func handleValidate(w http.ResponseWriter, r *http.Request) { if def != nil { spawnY := GroundY - def.Height - def.YOffset + // log.Printf("✨ SPAWN: %s at %.2f (Y: %.2f)", def.ID, spawnX, spawnY) + obstacles = append(obstacles, ActiveObstacle{ ID: def.ID, X: spawnX, @@ -392,9 +487,15 @@ func handleValidate(w http.ResponseWriter, r *http.Request) { if !playerDead { score++ + } else { + // Wenn tot, brechen wir die Loop ab, um Rechenzeit zu sparen + // und senden den Dead-Status zurück + break } } + // --- SPEICHERN --- + status := "alive" if playerDead { status = "dead" @@ -403,13 +504,18 @@ func handleValidate(w http.ResponseWriter, r *http.Request) { obsJson, _ := json.Marshal(obstacles) - rdb.HSet(ctx, key, map[string]interface{}{ + err = rdb.HSet(ctx, key, map[string]interface{}{ "score": score, "pos_y": fmt.Sprintf("%f", posY), "vel_y": fmt.Sprintf("%f", velY), "rng_state": rng.State, "obstacles": string(obsJson), - }) + }).Err() + + if err != nil { + log.Printf("❌ REDIS SAVE ERROR: %v", err) + } + rdb.Expire(ctx, key, 1*time.Hour) w.Header().Set("Content-Type", "application/json") @@ -427,11 +533,14 @@ func handleSubmitName(w http.ResponseWriter, r *http.Request) { return } - safeName := html.EscapeString(req.Name) + log.Printf("📝 SUBMIT NAME: %s für Session %s", req.Name, req.SessionID) + safeName := html.EscapeString(req.Name) sessionKey := "session:" + req.SessionID + scoreVal, err := rdb.HGet(ctx, sessionKey, "score").Result() if err != nil { + log.Printf("⚠️ Submit fehlgeschlagen: Session nicht gefunden %s", req.SessionID) http.Error(w, "Session expired", 404) return } @@ -547,6 +656,8 @@ func handleAdminAction(w http.ResponseWriter, r *http.Request) { return } + log.Printf("👮 ADMIN ACTION: %s on %s", req.Action, req.SessionID) + if req.Action == "approve" { score, err := rdb.ZScore(ctx, "leaderboard:unverified", req.SessionID).Result() if err != nil { @@ -575,29 +686,21 @@ func handleClaimDelete(w http.ResponseWriter, r *http.Request) { realCode, err := rdb.HGet(ctx, sessionKey, "claim_code").Result() if err != nil || realCode == "" { + log.Printf("⚠️ Claim Delete Failed: Session/Code missing %s", req.SessionID) http.Error(w, "Not found", 404) return } if realCode != req.ClaimCode { + log.Printf("⛔ Claim Delete Denied: Wrong Code %s vs %s", req.ClaimCode, realCode) http.Error(w, "Wrong Code", 403) return } + log.Printf("🗑️ USER DELETE: Session %s deleted via code", req.SessionID) rdb.ZRem(ctx, "leaderboard:unverified", req.SessionID) rdb.ZRem(ctx, "leaderboard:public", req.SessionID) rdb.HDel(ctx, sessionKey, "name") w.WriteHeader(http.StatusOK) } - -func parseOr(s string, def float64) float64 { - if s == "" { - return def - } - v, err := strconv.ParseFloat(s, 64) - if err != nil { - return def - } - return v -}