//go:build !wasm // +build !wasm package main import ( "encoding/json" "log" "net/url" "time" "github.com/gorilla/websocket" "git.zb-server.de/ZB-Server/EscapeFromTeacher/pkg/game" ) // wsConn verwaltet die WebSocket-Verbindung (Native Desktop) type wsConn struct { conn *websocket.Conn send chan []byte stopChan chan struct{} } // WebSocketMessage ist das Format für WebSocket-Nachrichten type WebSocketMessage struct { Type string `json:"type"` Payload interface{} `json:"payload"` } // connectToServer verbindet sich über WebSocket mit dem Gateway (Native Desktop) func (g *Game) connectToServer() { serverURL := "ws://localhost:8080/ws" log.Printf("🔌 Verbinde zu WebSocket-Gateway: %s", serverURL) u, err := url.Parse(serverURL) if err != nil { log.Printf("❌ URL Parse Fehler: %v", err) return } conn, _, err := websocket.DefaultDialer.Dial(u.String(), nil) if err != nil { log.Printf("❌ WebSocket Connect Fehler: %v", err) return } wsConn := &wsConn{ conn: conn, send: make(chan []byte, 100), stopChan: make(chan struct{}), } g.wsConn = wsConn log.Println("✅ WebSocket verbunden!") g.connected = true // Goroutines für Lesen und Schreiben starten go g.wsReadPump() go g.wsWritePump() // JOIN senden g.sendJoinRequest() } // wsReadPump liest Nachrichten vom WebSocket func (g *Game) wsReadPump() { defer func() { g.wsConn.conn.Close() g.connected = false }() for { var msg WebSocketMessage err := g.wsConn.conn.ReadJSON(&msg) if err != nil { if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { log.Printf("⚠️ WebSocket Fehler: %v", err) } break } switch msg.Type { case "game_update": // GameState Update payloadBytes, _ := json.Marshal(msg.Payload) var state game.GameState if err := json.Unmarshal(payloadBytes, &state); err == nil { // Server Reconciliation für lokalen Spieler (VOR dem Lock) for _, p := range state.Players { if p.Name == g.playerName { g.ReconcileWithServer(p) break } } g.stateMutex.Lock() g.gameState = state g.stateMutex.Unlock() } case "leaderboard_response": // Leaderboard Response payloadBytes, _ := json.Marshal(msg.Payload) var resp game.LeaderboardResponse if err := json.Unmarshal(payloadBytes, &resp); err == nil { g.leaderboardMutex.Lock() g.leaderboard = resp.Entries g.leaderboardMutex.Unlock() log.Printf("📊 Leaderboard empfangen: %d Einträge", len(resp.Entries)) } } } } // wsWritePump sendet Nachrichten zum WebSocket func (g *Game) wsWritePump() { defer g.wsConn.conn.Close() for { select { case message, ok := <-g.wsConn.send: if !ok { g.wsConn.conn.WriteMessage(websocket.CloseMessage, []byte{}) return } err := g.wsConn.conn.WriteMessage(websocket.TextMessage, message) if err != nil { log.Printf("⚠️ Fehler beim Senden: %v", err) return } case <-g.wsConn.stopChan: return } } } // sendWebSocketMessage sendet eine Nachricht über WebSocket func (g *Game) sendWebSocketMessage(msg WebSocketMessage) { if g.wsConn == nil || !g.connected { log.Println("⚠️ WebSocket nicht verbunden") return } data, err := json.Marshal(msg) if err != nil { log.Printf("❌ Fehler beim Marshallen: %v", err) return } select { case g.wsConn.send <- data: default: log.Println("⚠️ Send channel voll, Nachricht verworfen") } } // sendJoinRequest sendet Join-Request über WebSocket func (g *Game) sendJoinRequest() { msg := WebSocketMessage{ Type: "join", Payload: game.JoinRequest{ Name: g.playerName, RoomID: g.roomID, GameMode: g.gameMode, IsHost: g.isHost, TeamName: g.teamName, }, } g.sendWebSocketMessage(msg) log.Printf("➡️ JOIN gesendet über WebSocket: Name=%s, RoomID=%s", g.playerName, g.roomID) } // sendStartRequest sendet Start-Request über WebSocket func (g *Game) sendStartRequest() { msg := WebSocketMessage{ Type: "start", Payload: game.StartRequest{ RoomID: g.roomID, }, } g.sendWebSocketMessage(msg) log.Printf("▶️ START gesendet über WebSocket: RoomID=%s", g.roomID) } // publishInput sendet Input über WebSocket func (g *Game) publishInput(input game.ClientInput) { msg := WebSocketMessage{ Type: "input", Payload: input, } g.sendWebSocketMessage(msg) } // connectForLeaderboard verbindet für Leaderboard (Native) func (g *Game) connectForLeaderboard() { if g.wsConn != nil && g.connected { // Bereits verbunden g.requestLeaderboard() return } // Neue Verbindung aufbauen g.connectToServer() // Kurz warten und dann Leaderboard anfragen time.Sleep(500 * time.Millisecond) g.requestLeaderboard() } // requestLeaderboard fordert Leaderboard an (Native) func (g *Game) requestLeaderboard() { mode := "solo" if g.gameMode == "coop" { mode = "coop" } msg := WebSocketMessage{ Type: "leaderboard_request", Payload: game.LeaderboardRequest{ Mode: mode, }, } g.sendWebSocketMessage(msg) log.Printf("🏆 Leaderboard-Request gesendet: Mode=%s", mode) } // submitScore sendet Score ans Leaderboard (Native) func (g *Game) submitScore() { if g.scoreSubmitted { return } g.stateMutex.Lock() score := 0 for _, p := range g.gameState.Players { if p.Name == g.playerName { score = p.Score break } } g.stateMutex.Unlock() if score == 0 { log.Println("⚠️ Score ist 0, überspringe Submission") return } name := g.playerName if g.gameMode == "coop" && g.teamName != "" { name = g.teamName } // Verwende Team-Name für Coop-Mode, sonst Player-Name displayName := name teamName := "" if g.gameMode == "coop" { g.stateMutex.Lock() teamName = g.gameState.TeamName g.stateMutex.Unlock() if teamName != "" { displayName = teamName } } msg := WebSocketMessage{ Type: "score_submit", Payload: game.ScoreSubmission{ PlayerName: displayName, PlayerCode: g.playerCode, Name: displayName, // Für Kompatibilität Score: score, Mode: g.gameMode, TeamName: teamName, // Team-Name für Coop }, } g.sendWebSocketMessage(msg) g.scoreSubmitted = true log.Printf("📊 Score submitted: %s = %d (TeamName: %s)", displayName, score, teamName) }