package handler import ( "Embalming_Girl_Server/game" "Embalming_Girl_Server/room" "encoding/json" "log" "math/rand" "net/http" "sync" "time" "github.com/gorilla/websocket" ) var upgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, } type Client struct { ID string RoomID string Conn *websocket.Conn Send chan []byte mu sync.Mutex } type Hub struct { RoomMgr *room.Manager clients map[string]*Client engines map[string]*game.Engine mu sync.RWMutex } func NewHub() *Hub { return &Hub{ RoomMgr: room.NewManager(), clients: make(map[string]*Client), engines: make(map[string]*game.Engine), } } func (h *Hub) InitTestRooms() { for _, n := range []int{3, 4, 5, 6} { rid := "TEST" + string(rune('0'+n)) h.RoomMgr.CreateTestRoom(rid, n) log.Printf("Test room: %s (%d players, %d bots)", rid, n, n-1) } } func (h *Hub) ServeWS(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Println("Upgrade:", err) return } clientID := generateClientID() client := &Client{ID: clientID, Conn: conn, Send: make(chan []byte, 256)} h.mu.Lock() h.clients[clientID] = client h.mu.Unlock() log.Printf("Connected: %s", clientID) go h.writePump(client) go h.readPump(client) } func (h *Hub) readPump(c *Client) { defer func() { h.removeClient(c); c.Conn.Close() }() for { msgType, msg, err := c.Conn.ReadMessage() if err != nil { break } if msgType == websocket.BinaryMessage { h.relayAudio(c, msg) continue } var m map[string]interface{} if json.Unmarshal(msg, &m) == nil { h.handleMessage(c, m) } } } func (h *Hub) writePump(c *Client) { defer c.Conn.Close() for data := range c.Send { c.mu.Lock() err := c.Conn.WriteMessage(websocket.TextMessage, data) c.mu.Unlock() if err != nil { break } } } func (h *Hub) removeClient(c *Client) { h.mu.Lock() delete(h.clients, c.ID) h.mu.Unlock() if c.RoomID != "" { h.RoomMgr.LeaveRoom(c.RoomID, c.ID) rm := h.RoomMgr.GetRoom(c.RoomID) if rm != nil { h.broadcastRoomState(rm) } } close(c.Send) log.Printf("Disconnected: %s", c.ID) } func (h *Hub) handleMessage(c *Client, msg map[string]interface{}) { t, _ := msg["type"].(string) p, _ := msg["payload"].(map[string]interface{}) switch t { case "create_room": h.handleCreateRoom(c, p) case "join_room": h.handleJoinRoom(c, p) case "leave_room": h.handleLeaveRoom(c) case "set_ready": h.handleSetReady(c, p) case "start_game": h.handleStartGame(c) case "list_rooms": h.handleListRooms(c) case "select_card": h.handleSelectCard(c, p) case "confirm_action": h.handleConfirmAction(c, p) case "effect_response": h.handleEffectResponse(c, p) case "send_chat": h.handleChat(c, p) case "heartbeat": h.sendTo(c, "heartbeat_ack", nil) } } // ---- Room handlers ---- func (h *Hub) handleListRooms(c *Client) { h.sendTo(c, "room_list", map[string]interface{}{"rooms": h.RoomMgr.ListRooms()}) } func (h *Hub) handleCreateRoom(c *Client, p map[string]interface{}) { nick, _ := p["nickname"].(string) mp := 4 if v, ok := p["max_players"].(float64); ok { mp = int(v) } if nick == "" { nick = "Player" } rm := h.RoomMgr.CreateRoom(c.ID, nick, mp) c.RoomID = rm.ID rm.Players[c.ID].Send = c.Send h.sendTo(c, "room_created", map[string]interface{}{"room_id": rm.ID}) h.broadcastRoomState(rm) } func (h *Hub) handleJoinRoom(c *Client, p map[string]interface{}) { rid, _ := p["room_id"].(string) nick, _ := p["nickname"].(string) if nick == "" { nick = "Player" } rm, ok := h.RoomMgr.JoinRoom(rid, c.ID, nick) if !ok { h.sendTo(c, "error", map[string]interface{}{"message": "Cannot join room"}) return } c.RoomID = rm.ID rm.Players[c.ID].Send = c.Send h.sendTo(c, "room_joined", map[string]interface{}{"room_id": rm.ID}) h.broadcastRoomState(rm) } func (h *Hub) handleLeaveRoom(c *Client) { if c.RoomID == "" { return } rid := c.RoomID h.RoomMgr.LeaveRoom(rid, c.ID) c.RoomID = "" h.sendTo(c, "room_left", nil) if rm := h.RoomMgr.GetRoom(rid); rm != nil { h.broadcastRoomState(rm) } } func (h *Hub) handleChat(c *Client, p map[string]interface{}) { if c.RoomID == "" { return } rm := h.RoomMgr.GetRoom(c.RoomID) if rm == nil { return } text, _ := p["text"].(string) if text == "" { return } nick := "" if pl, ok := rm.Players[c.ID]; ok { nick = pl.Nickname } msg := map[string]interface{}{ "sender_id": c.ID, "nickname": nick, "text": text, } for _, pid := range rm.PlayerOrder { pl := rm.Players[pid] if pl.IsBot || pl.Send == nil { continue } h.sendToChannel(pl.Send, "chat", msg) } } func (h *Hub) handleSetReady(c *Client, p map[string]interface{}) { ready, _ := p["ready"].(bool) if c.RoomID == "" { return } h.RoomMgr.SetReady(c.RoomID, c.ID, ready) if rm := h.RoomMgr.GetRoom(c.RoomID); rm != nil { h.broadcastRoomState(rm) } } // ---- Game start ---- func (h *Hub) handleStartGame(c *Client) { if c.RoomID == "" { return } rm := h.RoomMgr.GetRoom(c.RoomID) if rm == nil || !rm.Players[c.ID].IsHost { h.sendTo(c, "error", map[string]interface{}{"message": "Only host can start"}) return } if len(rm.Players) < 3 { h.sendTo(c, "error", map[string]interface{}{"message": "Need 3+ players"}) return } var ids, nicks []string var bots []bool for _, pid := range rm.PlayerOrder { p := rm.Players[pid] ids = append(ids, p.ID) nicks = append(nicks, p.Nickname) bots = append(bots, p.IsBot) } eng := game.NewEngine(ids, nicks, bots, len(ids)) eng.Deal() h.mu.Lock() h.engines[rm.ID] = eng h.mu.Unlock() rm.State = "playing" rm.HarmonyTarget = eng.HarmonyTarget for _, pid := range rm.PlayerOrder { p := rm.Players[pid] if p.IsBot || p.Send == nil { continue } h.sendToChannel(p.Send, "game_start", map[string]interface{}{ "your_player_id": pid, "harmony_target": eng.HarmonyTarget, "player_count": len(ids), }) } log.Printf("Game started in %s, %d players, target=%d, first=%s", rm.ID, len(ids), eng.HarmonyTarget, eng.CurrentPlayer().ID) h.broadcastSnapshots(rm.ID) h.processBotTurns(rm.ID) } // ---- Game action handlers ---- func (h *Hub) handleSelectCard(c *Client, p map[string]interface{}) { h.mu.RLock() eng := h.engines[c.RoomID] h.mu.RUnlock() if eng == nil { return } uid, _ := p["card_uid"].(string) if err := eng.SelectCard(c.ID, uid); err != nil { h.sendTo(c, "error", map[string]interface{}{"message": err.Error()}) return } h.broadcastSnapshots(c.RoomID) } func (h *Hub) handleConfirmAction(c *Client, p map[string]interface{}) { h.mu.RLock() eng := h.engines[c.RoomID] h.mu.RUnlock() if eng == nil { return } action, _ := p["action"].(string) target, _ := p["target_player_id"].(string) if err := eng.ConfirmAction(c.ID, action, target); err != nil { h.sendTo(c, "error", map[string]interface{}{"message": err.Error()}) return } h.broadcastSnapshots(c.RoomID) if eng.IsEffectResolving() { h.processBotEffectResponses(c.RoomID) return } if eng.Phase == "all_exited" { h.handleAllExited(c.RoomID) return } h.processBotTurns(c.RoomID) } func (h *Hub) handleEffectResponse(c *Client, p map[string]interface{}) { h.mu.RLock() eng := h.engines[c.RoomID] h.mu.RUnlock() if eng == nil || !eng.IsEffectResolving() { return } if err := eng.HandleEffectResponse(c.ID, p); err != nil { h.sendTo(c, "error", map[string]interface{}{"message": err.Error()}) return } h.broadcastSnapshots(c.RoomID) if eng.IsEffectResolving() { h.processBotEffectResponses(c.RoomID) return } if eng.Phase == "all_exited" { h.handleAllExited(c.RoomID) return } h.processBotTurns(c.RoomID) } func (h *Hub) processBotEffectResponses(roomID string) { h.mu.RLock() eng := h.engines[roomID] h.mu.RUnlock() if eng == nil { return } for eng.IsEffectResolving() { ef := eng.Effect if ef == nil { break } if ef.ChoiceType == "all_select_card" { anyBotLeft := false for _, p := range eng.Players { if p.IsBot && eng.EffectNeedsResponseFrom(p.ID) { time.Sleep(200 * time.Millisecond) resp := eng.BotAutoEffectResponse(p.ID) if resp != nil { eng.HandleEffectResponse(p.ID, resp) } anyBotLeft = true } } if !anyBotLeft { break } h.broadcastSnapshots(roomID) continue } waitFor := ef.WaitingFor bot := eng.GetPlayer(waitFor) if bot == nil || !bot.IsBot { break } time.Sleep(200 * time.Millisecond) resp := eng.BotAutoEffectResponse(waitFor) if resp != nil { eng.HandleEffectResponse(waitFor, resp) } h.broadcastSnapshots(roomID) } } func (h *Hub) processBotTurns(roomID string) { h.mu.RLock() eng := h.engines[roomID] h.mu.RUnlock() if eng == nil { return } for eng.Phase == "playing" && !eng.IsEffectResolving() { cp := eng.CurrentPlayer() if cp == nil || !cp.IsBot { break } time.Sleep(300 * time.Millisecond) action, target := eng.BotAutoPlay(cp.ID) if action == "" { break } eng.ConfirmAction(cp.ID, action, target) h.broadcastSnapshots(roomID) if eng.IsEffectResolving() { h.processBotEffectResponses(roomID) if eng.IsEffectResolving() { break } h.broadcastSnapshots(roomID) } if eng.Phase == "all_exited" { h.handleAllExited(roomID) return } } } func (h *Hub) handleAllExited(roomID string) { rm := h.RoomMgr.GetRoom(roomID) if rm == nil { return } h.mu.RLock() eng := h.engines[roomID] h.mu.RUnlock() if eng == nil { return } result := eng.RunSettlement() log.Printf("Settlement in room %s: %s", roomID, result["victory"].(map[string]interface{})["end_type"]) for _, pid := range rm.PlayerOrder { p := rm.Players[pid] if p.IsBot || p.Send == nil { continue } h.sendToChannel(p.Send, "settlement_result", result) } endType, _ := result["victory"].(map[string]interface{})["end_type"].(string) winners, _ := result["victory"].(map[string]interface{})["winners"].([]map[string]interface{}) var winnerNames []string for _, w := range winners { winnerNames = append(winnerNames, w["nickname"].(string)) } endMsg := map[string]interface{}{"end_type": endType} if endType == "all_dead" { endMsg["message"] = "全灭结局 — 没有任何人获胜" } else { msg := "" for i, n := range winnerNames { if i > 0 { msg += ", " } msg += n } endMsg["message"] = "获胜者: " + msg endMsg["winners"] = winners } for _, pid := range rm.PlayerOrder { p := rm.Players[pid] if p.IsBot || p.Send == nil { continue } h.sendToChannel(p.Send, "game_end", endMsg) } rm.State = "waiting" for _, p := range rm.Players { p.IsReady = false if p.IsBot { p.IsReady = true } } h.mu.Lock() delete(h.engines, roomID) h.mu.Unlock() h.broadcastRoomState(rm) } // ---- Audio relay ---- func (h *Hub) relayAudio(sender *Client, pcm []byte) { if sender.RoomID == "" { return } rm := h.RoomMgr.GetRoom(sender.RoomID) if rm == nil { return } idBytes := []byte(sender.ID) header := make([]byte, 2+len(idBytes)) header[0] = byte(len(idBytes) >> 8) header[1] = byte(len(idBytes)) copy(header[2:], idBytes) frame := append(header, pcm...) h.mu.RLock() defer h.mu.RUnlock() for _, pid := range rm.PlayerOrder { p := rm.Players[pid] if p.IsBot || pid == sender.ID { continue } if cl, ok := h.clients[pid]; ok { cl.mu.Lock() cl.Conn.WriteMessage(websocket.BinaryMessage, frame) cl.mu.Unlock() } } } // ---- Broadcast helpers ---- func (h *Hub) broadcastSnapshots(roomID string) { rm := h.RoomMgr.GetRoom(roomID) if rm == nil { return } h.mu.RLock() eng := h.engines[roomID] h.mu.RUnlock() if eng == nil { return } for _, pid := range rm.PlayerOrder { p := rm.Players[pid] if p.IsBot || p.Send == nil { continue } snap := eng.Snapshot(pid) data := buildMessage("state_snapshot", snap) select { case p.Send <- data: default: } } } func (h *Hub) broadcastRoomState(rm *room.Room) { players := rm.GetPlayerInfos() for _, pid := range rm.PlayerOrder { p, ok := rm.Players[pid] if !ok || p.IsBot || p.Send == nil { continue } st := map[string]interface{}{ "room_id": rm.ID, "state": rm.State, "harmony_target": rm.HarmonyTarget, "players": players, "your_player_id": pid, } select { case p.Send <- buildMessage("room_state", st): default: } } } func (h *Hub) sendTo(c *Client, t string, p map[string]interface{}) { select { case c.Send <- buildMessage(t, p): default: } } func (h *Hub) sendToChannel(ch chan []byte, t string, p map[string]interface{}) { select { case ch <- buildMessage(t, p): default: } } func buildMessage(t string, p map[string]interface{}) []byte { m := map[string]interface{}{"type": t} if p != nil { m["payload"] = p } d, _ := json.Marshal(m) return d } func generateClientID() string { const cs = "abcdefghijklmnopqrstuvwxyz0123456789" b := make([]byte, 12) for i := range b { b[i] = cs[rand.Intn(len(cs))] } return string(b) }