package server import ( "encoding/json" "fmt" "log" "math/rand" "net/http" "time" "git.zb-server.de/ZB-Server/EscapeFromTeacher/pkg/game" "github.com/gorilla/websocket" "github.com/nats-io/nats.go" ) var upgrader = websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }} type Gateway struct { NC *nats.Conn World *game.World // Lokale Referenz auf Räume, die DIESER Server verwaltet // In einer echten Microservice Welt wäre das separat, // aber hier hostet der Gateway auch Räume. LocalRooms map[string]*Room } func NewGateway(nc *nats.Conn, w *game.World) *Gateway { return &Gateway{ NC: nc, World: w, LocalRooms: make(map[string]*Room), } } func (gw *Gateway) HandleWS(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { return } defer conn.Close() // 1. HANDSHAKE (Login warten) // Der Client muss als allererstes JSON senden: {action: "CREATE"|"JOIN", name: "Hans"} var login game.LoginPayload _, msg, err := conn.ReadMessage() if err != nil { return } if err := json.Unmarshal(msg, &login); err != nil { log.Println("Ungültiger Login:", err) return } // IDs generieren playerID := fmt.Sprintf("p_%d", time.Now().UnixNano()) roomID := login.RoomID // 2. RAUM LOGIK if login.Action == "CREATE" { // Raum ID generieren (4 Zeichen Random) roomID = GenerateRoomCode() // Neuen Raum starten (auf diesem Server) newRoom := NewRoom(roomID, gw.NC, gw.World) gw.LocalRooms[roomID] = newRoom go newRoom.RunLoop() // Spieler lokal hinzufügen (Hack für Demo, sauberer wäre via NATS Event) newRoom.AddPlayer(playerID, login.Name) } else if login.Action == "JOIN" { // Wir müssen dem Raum (egal wo er läuft) sagen: Hier ist ein Neuer! // Da wir hier keine verteilte DB haben, tricksen wir: // Wir gehen davon aus, dass wir den Raum "finden" müssen. // Für dieses Tutorial: Wir prüfen ob er lokal ist. // Wenn er auf einem anderen Server wäre, bräuchten wir ein "PlayerJoin" Subject. if room, ok := gw.LocalRooms[roomID]; ok { room.AddPlayer(playerID, login.Name) } else { // Falls Raum nicht lokal: Senden wir ein "JOIN REQUEST" über NATS? // Für jetzt: Wir lassen es simpel. Wenn Raum nicht auf diesem Server -> Error. // (Für echtes Scaling bräuchten wir Redis oder NATS Request/Reply zur Raumsuche) log.Println("Raum nicht gefunden (oder auf anderem Node):", roomID) // Optional: Error an Client senden return } } log.Printf("Player %s (%s) joined Room %s", playerID, login.Name, roomID) // 3. PROXY LOOP // A) NATS -> WebSocket (State Updates empfangen) sub, _ := gw.NC.Subscribe(fmt.Sprintf("game.room.%s.state", roomID), func(m *nats.Msg) { conn.WriteMessage(websocket.TextMessage, m.Data) }) defer sub.Unsubscribe() // B) WebSocket -> NATS (Input senden) for { _, data, err := conn.ReadMessage() if err != nil { break } // Wir parsen kurz, um den Typ zu prüfen, oder leiten blind weiter? // Besser: Wir wrappen es in ClientInput struct var raw map[string]interface{} json.Unmarshal(data, &raw) inputType, _ := raw["type"].(string) input := game.ClientInput{ Type: inputType, RoomID: roomID, PlayerID: playerID, } bytes, _ := json.Marshal(input) gw.NC.Publish(fmt.Sprintf("game.room.%s.input", roomID), bytes) } // Cleanup beim Disconnect if room, ok := gw.LocalRooms[roomID]; ok { room.RemovePlayer(playerID) // Wenn leer -> Raum löschen? } } func GenerateRoomCode() string { chars := "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" b := make([]byte, 4) for i := range b { b[i] = chars[rand.Intn(len(chars))] } return string(b) }