package handler import ( "Embalming_Girl_Server/db" "Embalming_Girl_Server/game" "Embalming_Girl_Server/room" "encoding/json" "fmt" "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 UserID int64 Username string Nickname string Token string Version 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) OnlineCount() int { h.mu.RLock() defer h.mu.RUnlock() return len(h.clients) } func (h *Hub) OnlineClientsList() []map[string]interface{} { h.mu.RLock() defer h.mu.RUnlock() var list []map[string]interface{} for _, c := range h.clients { list = append(list, map[string]interface{}{ "id": c.ID, "username": c.Username, "nickname": c.Nickname, "room_id": c.RoomID, "version": c.Version, }) } return list } 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.mu.RLock() _, hasGame := h.engines[c.RoomID] h.mu.RUnlock() if hasGame && c.Username != "" { h.RoomMgr.DisconnectPlayer(c.RoomID, c.ID) h.broadcastToRoom(c.RoomID, "player_disconnected", map[string]interface{}{ "nickname": c.Nickname, "message": c.Nickname + " 断线了", }) log.Printf("Player %s disconnected, slot preserved, 60s takeover timer started", c.Username) go func(roomID, playerID, nick string) { time.Sleep(60 * time.Second) if !h.RoomMgr.IsPlayerDisconnected(roomID, playerID) { return } h.RoomMgr.SetPlayerBotControlled(roomID, playerID, true) h.mu.RLock() eng := h.engines[roomID] h.mu.RUnlock() if eng != nil { eng.SetBotControlled(playerID, true) } h.broadcastToRoom(roomID, "player_bot_takeover", map[string]interface{}{ "nickname": nick, "message": nick + " 已被人机接管", }) log.Printf("Player %s taken over by bot in room %s", nick, roomID) if eng != nil && eng.Phase == "playing" { cp := eng.CurrentPlayer() if cp != nil && cp.ID == playerID { h.processBotTurns(roomID) } } }(c.RoomID, c.ID, c.Nickname) } else { h.RoomMgr.LeaveRoom(c.RoomID, c.ID) rm := h.RoomMgr.GetRoom(c.RoomID) if rm != nil { h.broadcastRoomState(rm) } else { h.mu.Lock() delete(h.engines, c.RoomID) h.mu.Unlock() } } } close(c.Send) log.Printf("Disconnected: %s (%s)", c.ID, c.Username) } func (h *Hub) handleMessage(c *Client, msg map[string]interface{}) { t, _ := msg["type"].(string) p, _ := msg["payload"].(map[string]interface{}) switch t { case "register": h.handleRegister(c, p) case "login": h.handleLogin(c, p) case "update_profile": h.handleUpdateProfile(c, p) case "get_profile": h.handleGetProfile(c, p) case "create_room": h.handleCreateRoom(c, p) case "join_room": h.handleJoinRoom(c, p) case "rejoin_room": h.handleRejoin(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 "set_chaos": h.handleSetChaos(c, p) case "create_bot_room": h.handleCreateBotRoom(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) handleRegister(c *Client, p map[string]interface{}) { username, _ := p["username"].(string) password, _ := p["password"].(string) nickname, _ := p["nickname"].(string) user, err := db.Register(username, password, nickname) if err != nil { h.sendTo(c, "register_result", map[string]interface{}{"success": false, "message": err.Error()}) return } h.sendTo(c, "register_result", map[string]interface{}{ "success": true, "message": "注册成功", "username": user.Username, "nickname": user.Nickname, }) } func (h *Hub) handleLogin(c *Client, p map[string]interface{}) { username, _ := p["username"].(string) password, _ := p["password"].(string) token, _ := p["token"].(string) var user *db.User var err error if token != "" { user, err = db.ValidateToken(token) if err == nil { token = token } } else { token, user, err = db.Login(username, password) } if err != nil { h.sendTo(c, "login_result", map[string]interface{}{"success": false, "message": err.Error()}) return } c.UserID = user.ID c.Username = user.Username c.Nickname = user.Nickname c.Token = token if v, ok := p["version"].(string); ok { c.Version = v } log.Printf("User logged in: %s (%s)", user.Username, c.ID) result := map[string]interface{}{ "success": true, "token": token, "username": user.Username, "nickname": user.Nickname, "avatar": user.Avatar, "bio": user.Bio, } if roomID, playerID, found := h.RoomMgr.FindRoomByUsername(user.Username); found { result["active_room"] = roomID result["active_player_id"] = playerID } h.sendTo(c, "login_result", result) } func (h *Hub) handleUpdateProfile(c *Client, p map[string]interface{}) { if c.UserID == 0 { h.sendTo(c, "error", map[string]interface{}{"message": "未登录"}) return } nick, _ := p["nickname"].(string) bio, _ := p["bio"].(string) avatar, _ := p["avatar"].(string) if nick == "" { nick = c.Nickname } db.UpdateProfile(c.UserID, nick, bio, avatar) c.Nickname = nick h.sendTo(c, "profile_updated", map[string]interface{}{"success": true, "nickname": nick, "bio": bio, "avatar": avatar}) } func (h *Hub) handleGetProfile(c *Client, p map[string]interface{}) { username, _ := p["username"].(string) user, err := db.GetProfile(username) if err != nil { h.sendTo(c, "profile_info", map[string]interface{}{"success": false}) return } h.sendTo(c, "profile_info", map[string]interface{}{ "success": true, "username": user.Username, "nickname": user.Nickname, "bio": user.Bio, "avatar": user.Avatar, }) } func (h *Hub) handleRejoin(c *Client, p map[string]interface{}) { roomID, _ := p["room_id"].(string) oldPID, _ := p["player_id"].(string) if roomID == "" || oldPID == "" { h.sendTo(c, "error", map[string]interface{}{"message": "缺少参数"}) return } if !h.RoomMgr.ReconnectPlayer(roomID, oldPID, c.ID, c.Send) { h.sendTo(c, "error", map[string]interface{}{"message": "无法重连到对局"}) return } c.RoomID = roomID log.Printf("Player %s rejoined room %s", c.Username, roomID) h.mu.RLock() eng := h.engines[roomID] h.mu.RUnlock() if eng != nil { eng.UpdatePlayerID(oldPID, c.ID) eng.SetBotControlled(c.ID, false) h.sendToChannel(c.Send, "game_start", map[string]interface{}{ "your_player_id": c.ID, "harmony_target": eng.HarmonyTarget, "player_count": len(eng.Players), "rejoin": true, }) snap := eng.Snapshot(c.ID) h.sendToChannel(c.Send, "state_snapshot", snap) } rm := h.RoomMgr.GetRoom(roomID) if rm != nil { h.broadcastRoomState(rm) } h.sendTo(c, "rejoin_result", map[string]interface{}{"success": true, "room_id": roomID}) } 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 rm.Players[c.ID].Username = c.Username 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 rm.Players[c.ID].Username = c.Username 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) handleSetChaos(c *Client, p map[string]interface{}) { if c.RoomID == "" { return } rm := h.RoomMgr.GetRoom(c.RoomID) if rm == nil { return } enabled, _ := p["enabled"].(bool) rm.ChaosMode = enabled h.broadcastRoomState(rm) } func (h *Hub) handleCreateBotRoom(c *Client, p map[string]interface{}) { count := 4 if v, ok := p["player_count"].(float64); ok { count = int(v) } if count < 3 || count > 6 { h.sendTo(c, "error", map[string]interface{}{"message": "人数必须在3-6之间"}) return } rid := fmt.Sprintf("BOT_%s", generateClientID()[:6]) h.RoomMgr.CreateTestRoom(rid, count) rm, ok := h.RoomMgr.JoinRoom(rid, c.ID, c.Nickname) if !ok { h.sendTo(c, "error", map[string]interface{}{"message": "创建失败"}) return } c.RoomID = rm.ID rm.Players[c.ID].Send = c.Send rm.Players[c.ID].Username = c.Username h.sendTo(c, "room_joined", map[string]interface{}{"room_id": rm.ID}) h.broadcastRoomState(rm) log.Printf("Bot room %s created by %s (%d players)", rid, c.Username, count) } 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.ChaosMode = rm.ChaosMode 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) h.flushGameLogs(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 && !cp.BotControlled) { break } time.Sleep(300 * time.Millisecond) action, target := eng.BotAutoPlay(cp.ID) if action == "" { break } eng.ConfirmAction(cp.ID, action, target) h.broadcastSnapshots(roomID) h.flushGameLogs(roomID) if eng.IsEffectResolving() { h.processBotEffectResponses(roomID) if eng.IsEffectResolving() { break } h.broadcastSnapshots(roomID) } if eng.Phase == "all_exited" { h.handleAllExited(roomID) return } } h.startTurnTimer(roomID) } 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) } func (h *Hub) startTurnTimer(roomID string) { h.mu.RLock() eng := h.engines[roomID] h.mu.RUnlock() if eng == nil || eng.Phase != "playing" { return } cp := eng.CurrentPlayer() if cp == nil || cp.IsBot || cp.BotControlled { return } turnID := eng.TurnID go func() { time.Sleep(30 * time.Second) h.mu.RLock() eng2 := h.engines[roomID] h.mu.RUnlock() if eng2 == nil || eng2.Phase != "playing" || eng2.TurnID != turnID { return } cp2 := eng2.CurrentPlayer() if cp2 == nil { return } eng2.ActionLogs = append(eng2.ActionLogs, cp2.Nickname+" 超时,自动出牌") action, target := eng2.BotAutoPlay(cp2.ID) if action == "" { return } eng2.ConfirmAction(cp2.ID, action, target) h.broadcastSnapshots(roomID) h.flushGameLogs(roomID) if eng2.Phase == "all_exited" { h.handleAllExited(roomID) return } h.processBotTurns(roomID) }() } // ---- Broadcast helpers ---- func (h *Hub) broadcastToRoom(roomID, msgType string, data map[string]interface{}) { rm := h.RoomMgr.GetRoom(roomID) if rm == nil { return } for _, pid := range rm.PlayerOrder { p := rm.Players[pid] if p.IsBot || p.Send == nil { continue } h.sendToChannel(p.Send, msgType, data) } } func (h *Hub) flushGameLogs(roomID string) { h.mu.RLock() eng := h.engines[roomID] h.mu.RUnlock() if eng == nil { return } rm := h.RoomMgr.GetRoom(roomID) if rm == nil { return } for _, msg := range eng.FlushLogs() { for _, pid := range rm.PlayerOrder { p := rm.Players[pid] if p.IsBot || p.Send == nil { continue } h.sendToChannel(p.Send, "game_log", map[string]interface{}{"text": msg}) } } } 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) }