package main import ( "encoding/json" "html" "math/rand" "net/http" "strconv" "time" "github.com/google/uuid" "github.com/redis/go-redis/v9" ) func handleConfig(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(defaultConfig) } func handleStart(w http.ResponseWriter, r *http.Request) { sessionID := uuid.New().String() rawSeed := time.Now().UnixNano() seed32 := uint32(rawSeed) emptyObs, _ := json.Marshal([]ActiveObstacle{}) err := rdb.HSet(ctx, "session:"+sessionID, map[string]interface{}{ "seed": seed32, "rng_state": seed32, "score": 0, "is_dead": 0, "pos_y": PlayerYBase, "vel_y": 0.0, "obstacles": string(emptyObs), }).Err() if err != nil { http.Error(w, "DB Error", 500) return } rdb.Expire(ctx, "session:"+sessionID, 4000*time.Hour) json.NewEncoder(w).Encode(StartResponse{SessionID: sessionID, Seed: seed32}) } func handleValidate(w http.ResponseWriter, r *http.Request) { var req ValidateRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Bad Request", 400) return } key := "session:" + req.SessionID vals, err := rdb.HGetAll(ctx, key).Result() if err != nil || len(vals) == 0 { http.Error(w, "Session invalid", 401) return } if vals["is_dead"] == "1" { json.NewEncoder(w).Encode(ValidateResponse{Status: "dead", VerifiedScore: 0}) return } // ---> HIER RUFEN WIR JETZT DIE SIMULATION AUF <--- isDead, score, obstacles := simulateChunk(req.SessionID, req.Inputs, req.TotalTicks, vals) status := "alive" if isDead { status = "dead" rdb.HSet(ctx, key, "is_dead", 1) } rdb.Expire(ctx, key, 4000*time.Hour) json.NewEncoder(w).Encode(ValidateResponse{ Status: status, VerifiedScore: score, ServerObs: obstacles, }) } func handleSubmitName(w http.ResponseWriter, r *http.Request) { var req SubmitNameRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Bad Request", 400) return } safeName := html.EscapeString(req.Name) sessionKey := "session:" + req.SessionID scoreVal, err := rdb.HGet(ctx, sessionKey, "score").Result() if err != nil { http.Error(w, "Session expired", 404) return } scoreInt, _ := strconv.Atoi(scoreVal) claimCode := generateClaimCode() timestamp := time.Now().Format("02.01.2006 15:04") rdb.HSet(ctx, sessionKey, map[string]interface{}{ "name": safeName, "claim_code": claimCode, "created_at": timestamp, }) rdb.ZAdd(ctx, "leaderboard:unverified", redis.Z{ Score: float64(scoreInt), Member: req.SessionID, }) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(SubmitResponse{ClaimCode: claimCode}) } func handleLeaderboard(w http.ResponseWriter, r *http.Request) { mySessionID := r.URL.Query().Get("sessionId") targetKey := "leaderboard:public" var entries []LeaderboardEntry top3, _ := rdb.ZRevRangeWithScores(ctx, targetKey, 0, 2).Result() for i, z := range top3 { rank := int64(i + 1) sid := z.Member.(string) name, _ := rdb.HGet(ctx, "session:"+sid, "name").Result() if name == "" { name = "Unbekannt" } entries = append(entries, LeaderboardEntry{ Rank: rank, Name: name, Score: int(z.Score), IsMe: (sid == mySessionID), }) } if mySessionID != "" { myRank, err := rdb.ZRevRank(ctx, targetKey, mySessionID).Result() if err == nil && myRank > 2 { start := myRank - 1 stop := myRank + 1 neighbors, _ := rdb.ZRevRangeWithScores(ctx, targetKey, start, stop).Result() for i, z := range neighbors { rank := start + int64(i) + 1 sid := z.Member.(string) name, _ := rdb.HGet(ctx, "session:"+sid, "name").Result() if name == "" { name = "Unbekannt" } entries = append(entries, LeaderboardEntry{ Rank: rank, Name: name, Score: int(z.Score), IsMe: (sid == mySessionID), }) } } } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(entries) } func handleAdminPage(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "./secure/admin.html") } func handleAdminList(w http.ResponseWriter, r *http.Request) { listType := r.URL.Query().Get("type") redisKey := "leaderboard:unverified" if listType == "public" { redisKey = "leaderboard:public" } vals, _ := rdb.ZRevRangeWithScores(ctx, redisKey, 0, -1).Result() var adminList []AdminEntry for _, z := range vals { sid := z.Member.(string) info, _ := rdb.HGetAll(ctx, "session:"+sid).Result() name := info["name"] if name == "" { name = "Unbekannt" } adminList = append(adminList, AdminEntry{ SessionID: sid, Name: name, Score: int(z.Score), Code: info["claim_code"], Time: info["created_at"], }) } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(adminList) } func handleAdminAction(w http.ResponseWriter, r *http.Request) { var req AdminActionRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Bad Request", 400) return } if req.Action == "approve" { score, err := rdb.ZScore(ctx, "leaderboard:unverified", req.SessionID).Result() if err == nil { rdb.ZAdd(ctx, "leaderboard:public", redis.Z{Score: score, Member: req.SessionID}) rdb.ZRem(ctx, "leaderboard:unverified", req.SessionID) } } else if req.Action == "delete" { rdb.ZRem(ctx, "leaderboard:unverified", req.SessionID) rdb.ZRem(ctx, "leaderboard:public", req.SessionID) } w.WriteHeader(http.StatusOK) } func handleClaimDelete(w http.ResponseWriter, r *http.Request) { var req ClaimDeleteRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Bad Request", 400) return } sessionKey := "session:" + req.SessionID realCode, err := rdb.HGet(ctx, sessionKey, "claim_code").Result() if err != nil || realCode != req.ClaimCode { http.Error(w, "Error", 403) return } rdb.ZRem(ctx, "leaderboard:unverified", req.SessionID) rdb.ZRem(ctx, "leaderboard:public", req.SessionID) rdb.HDel(ctx, sessionKey, "name") w.WriteHeader(http.StatusOK) } func generateClaimCode() string { const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" b := make([]byte, 8) for i := range b { b[i] = charset[rand.Intn(len(charset))] } return string(b) }