//go:build !wasm // +build !wasm package main import ( "fmt" "image/color" "sort" "time" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/text" "github.com/hajimehoshi/ebiten/v2/vector" "golang.org/x/image/font/basicfont" "git.zb-server.de/ZB-Server/EscapeFromTeacher/pkg/game" ) // sendGameOverToJS ist ein Stub für Native (kein HTML) func (g *Game) sendGameOverToJS(score int) { // Native hat kein HTML-Overlay, nichts zu tun } // drawGameOverScreen zeigt Leaderboard mit Team-Name-Eingabe func (g *Game) drawGameOverScreen(screen *ebiten.Image, myScore int) { screen.Fill(color.RGBA{20, 20, 30, 255}) // Leaderboard immer beim ersten Mal anfordern (ohne Lock hier!) if !g.scoreSubmitted && g.gameMode == "solo" && !g.isOffline { g.submitScore() // submitScore() ruft requestLeaderboard() auf } else if !g.scoreSubmitted && g.gameMode == "solo" && g.isOffline { // Offline-Solo: Keine automatische Submission } else { // Für Coop: Nur Leaderboard anfordern, nicht submitten g.leaderboardMutex.Lock() needsLeaderboard := len(g.leaderboard) == 0 && g.connected g.leaderboardMutex.Unlock() if needsLeaderboard { g.requestLeaderboard() } } // Großes GAME OVER text.Draw(screen, "GAME OVER", basicfont.Face7x13, ScreenWidth/2-50, 60, color.RGBA{255, 0, 0, 255}) // Highscore prüfen und aktualisieren if myScore > g.localHighscore { g.localHighscore = myScore g.saveHighscore(myScore) } // Persönlicher Highscore anzeigen if myScore == g.localHighscore && myScore > 0 { text.Draw(screen, fmt.Sprintf("★ NEUER REKORD: %d ★", g.localHighscore), basicfont.Face7x13, ScreenWidth/2-80, 85, color.RGBA{255, 215, 0, 255}) } else { text.Draw(screen, fmt.Sprintf("Persönlicher Highscore: %d", g.localHighscore), basicfont.Face7x13, ScreenWidth/2-80, 85, color.Gray{Y: 180}) } // Linke Seite: Raum-Ergebnisse - Daten KOPIEREN mit Lock, dann außerhalb zeichnen text.Draw(screen, "=== RAUM ERGEBNISSE ===", basicfont.Face7x13, 50, 120, color.RGBA{255, 255, 0, 255}) type playerScore struct { name string score int } // Lock NUR für Datenkopie g.stateMutex.Lock() players := make([]playerScore, 0, len(g.gameState.Players)) for _, p := range g.gameState.Players { players = append(players, playerScore{name: p.Name, score: p.Score}) } g.stateMutex.Unlock() // Sortieren und Zeichnen OHNE Lock sort.Slice(players, func(i, j int) bool { return players[i].score > players[j].score }) y := 150 for i, p := range players { medal := "" if i == 0 { medal = "🥇 " } else if i == 1 { medal = "🥈 " } else if i == 2 { medal = "🥉 " } scoreMsg := fmt.Sprintf("%d. %s%s: %d pts", i+1, medal, p.name, p.score) text.Draw(screen, scoreMsg, basicfont.Face7x13, 50, y, color.White) y += 20 } // Rechte Seite: Global Leaderboard - Daten KOPIEREN mit Lock, dann außerhalb zeichnen text.Draw(screen, "=== TOP 10 BESTENLISTE ===", basicfont.Face7x13, 650, 120, color.RGBA{255, 215, 0, 255}) // Lock NUR für Datenkopie g.leaderboardMutex.Lock() leaderboardCopy := make([]game.LeaderboardEntry, len(g.leaderboard)) copy(leaderboardCopy, g.leaderboard) g.leaderboardMutex.Unlock() // Zeichnen OHNE Lock ly := 150 if len(leaderboardCopy) == 0 { text.Draw(screen, "Laden...", basicfont.Face7x13, 700, ly, color.Gray{150}) } else { for i, entry := range leaderboardCopy { if i >= 10 { break } var col color.Color = color.White marker := "" if entry.PlayerCode == g.playerCode { col = color.RGBA{0, 255, 0, 255} marker = " ← DU" } medal := "" if i == 0 { medal = "🥇 " } else if i == 1 { medal = "🥈 " } else if i == 2 { medal = "🥉 " } leaderMsg := fmt.Sprintf("%d. %s%s: %d%s", i+1, medal, entry.PlayerName, entry.Score, marker) text.Draw(screen, leaderMsg, basicfont.Face7x13, 650, ly, col) ly += 20 } } // Team-Name-Eingabe nur für Coop-Host (in der Mitte unten) if g.gameMode == "coop" && g.isHost { text.Draw(screen, "Host: Gib Team-Namen ein", basicfont.Face7x13, ScreenWidth/2-100, ScreenHeight-180, color.RGBA{255, 215, 0, 255}) // Team-Name Feld fieldW := 300 fieldX := ScreenWidth/2 - fieldW/2 fieldY := ScreenHeight - 140 col := color.RGBA{70, 70, 80, 255} if g.activeField == "teamname" { col = color.RGBA{90, 90, 100, 255} } vector.DrawFilledRect(screen, float32(fieldX), float32(fieldY), float32(fieldW), 40, col, false) vector.StrokeRect(screen, float32(fieldX), float32(fieldY), float32(fieldW), 40, 2, color.RGBA{255, 215, 0, 255}, false) display := g.teamName if g.activeField == "teamname" && (time.Now().UnixMilli()/500)%2 == 0 { display += "|" } if display == "" { display = "Team Name..." } text.Draw(screen, display, basicfont.Face7x13, fieldX+10, fieldY+25, color.White) // Submit Button submitBtnY := ScreenHeight - 85 submitBtnW := 200 submitBtnX := ScreenWidth/2 - submitBtnW/2 btnCol := color.RGBA{0, 150, 0, 255} if g.teamName == "" { btnCol = color.RGBA{100, 100, 100, 255} // Grau wenn kein Name } vector.DrawFilledRect(screen, float32(submitBtnX), float32(submitBtnY), float32(submitBtnW), 40, btnCol, false) vector.StrokeRect(screen, float32(submitBtnX), float32(submitBtnY), float32(submitBtnW), 40, 2, color.White, false) text.Draw(screen, "SUBMIT SCORE", basicfont.Face7x13, submitBtnX+50, submitBtnY+25, color.White) } else if g.gameMode == "solo" && g.scoreSubmitted { // Solo: Zeige Bestätigungsmeldung text.Draw(screen, "✓ Runde verifiziert & Score eingereicht!", basicfont.Face7x13, ScreenWidth/2-110, ScreenHeight-100, color.RGBA{0, 255, 0, 255}) } else if g.gameMode == "solo" && g.isOffline { // Offline Solo text.Draw(screen, "Offline-Modus: Score lokal gespeichert.", basicfont.Face7x13, ScreenWidth/2-110, ScreenHeight-100, color.RGBA{200, 200, 0, 255}) } else if g.gameMode == "coop" && !g.isHost { // Coop Non-Host: Warten auf Host text.Draw(screen, "Warte auf Host...", basicfont.Face7x13, ScreenWidth/2-70, ScreenHeight-100, color.Gray{180}) } // Back Button (oben links) backBtnW, backBtnH := 120, 40 backBtnX, backBtnY := 20, 20 vector.DrawFilledRect(screen, float32(backBtnX), float32(backBtnY), float32(backBtnW), float32(backBtnH), color.RGBA{150, 0, 0, 255}, false) vector.StrokeRect(screen, float32(backBtnX), float32(backBtnY), float32(backBtnW), float32(backBtnH), 2, color.White, false) text.Draw(screen, "< ZURÜCK", basicfont.Face7x13, backBtnX+20, backBtnY+25, color.White) // Unten: Anleitung text.Draw(screen, "ESC oder ZURÜCK-Button = Menü", basicfont.Face7x13, ScreenWidth/2-110, ScreenHeight-30, color.Gray{180}) }