Private
Public Access
1
0
Files
EscapeFromTeacher/pkg/server/gateway.go

139 lines
3.6 KiB
Go

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)
}