package handlers import ( "database/sql" "encoding/json" "fmt" "math/rand" "net/http" "strings" "github.com/go-chi/chi/v5" "mmgame/internal/agents" "mmgame/internal/database" "mmgame/internal/models" "mmgame/internal/scripts" "mmgame/internal/util" ) func CreateGame(w http.ResponseWriter, r *http.Request) { var req struct { ScriptID string `json:"script_id"` HumanRoleID string `json:"human_role_id"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, 422, "请求格式错误") return } script := scripts.GetByID(req.ScriptID) if script == nil { writeError(w, 404, "剧本不存在") return } gameID := newID() phase := "" if len(script.Phases) > 0 { phase = script.Phases[0].Name } configJSON, _ := json.Marshal(map[string]interface{}{"script": script}) _, err := database.DB.Exec( `INSERT INTO game_sessions (id, script_id, status, phase, phase_index, config, created_at) VALUES (?, ?, ?, ?, 0, ?, ?)`, gameID, req.ScriptID, models.GameStatusWaiting, phase, string(configJSON), util.NowStr()) if err != nil { writeError(w, 500, "服务器错误") return } for _, role := range script.Roles { isHuman := role.ID == req.HumanRoleID if _, err := database.DB.Exec( `INSERT INTO session_players (id, session_id, role_id, role_name, is_human, is_ready, avatar_url, status, joined_at) VALUES (?, ?, ?, ?, ?, 0, '', ?, ?)`, newID(), gameID, role.ID, role.Name, isHuman, models.PlayerStatusActive, util.NowStr()); err != nil { writeError(w, 500, "服务器错误") return } } if len(script.Phases) > 0 { for _, clueID := range script.Phases[0].Clues { if _, err := database.DB.Exec( `INSERT INTO clues_state (id, session_id, clue_id, status, created_at) VALUES (?, ?, ?, 'released', ?)`, newID(), gameID, clueID, util.NowStr()); err != nil { writeError(w, 500, "服务器错误") return } } } writeJSON(w, 200, map[string]string{"game_id": gameID}) } func JoinGame(w http.ResponseWriter, r *http.Request) { gameID := chi.URLParam(r, "game_id") var req struct { RoleID string `json:"role_id"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, 422, "请求格式错误") return } var exists int err := database.DB.QueryRow(`SELECT 1 FROM game_sessions WHERE id = ?`, gameID).Scan(&exists) if err != nil { writeError(w, 404, "游戏不存在") return } playerID := newID() if _, err := database.DB.Exec( `INSERT INTO session_players (id, session_id, role_id, is_human, is_ready, status, joined_at) VALUES (?, ?, ?, 1, 0, ?, ?)`, playerID, gameID, req.RoleID, models.PlayerStatusActive, util.NowStr()); err != nil { writeError(w, 500, "服务器错误") return } var scriptID string _ = database.DB.QueryRow(`SELECT script_id FROM game_sessions WHERE id = ?`, gameID).Scan(&scriptID) script := scripts.GetByID(scriptID) if script != nil { for _, role := range script.Roles { if role.ID == req.RoleID { writeJSON(w, 200, map[string]interface{}{ "player_id": playerID, "role_id": req.RoleID, "role_name": role.Name, "public_profile": role.PublicProfile, "secret_profile": role.SecretProfile, "secret": role.Secret, "goal": role.Goal, }) return } } } writeJSON(w, 200, map[string]interface{}{"player_id": playerID, "role_id": req.RoleID}) } func StartGame(w http.ResponseWriter, r *http.Request) { gameID := chi.URLParam(r, "game_id") game, err := getGame(gameID) if err != nil { writeError(w, 404, "游戏不存在") return } _, err = database.DB.Exec( `UPDATE game_sessions SET status = ?, started_at = ? WHERE id = ?`, models.GameStatusPlaying, util.NowStr(), gameID) if err != nil { writeError(w, 500, "服务器错误") return } script := scriptFromConfig(game.Config) background := "" title := "" if script != nil { background = script.Background title = script.Title } content := fmt.Sprintf("欢迎来到【%s】。\n%s\n\n请各位玩家阅读角色信息,做好准备后,在公屏中发送「准备好了」开始游戏。", title, background) msgID := newID() now := util.NowStr() if _, err := database.DB.Exec( `INSERT INTO chat_messages (id, session_id, sender_role_id, sender_name, message_type, content, phase_index, created_at) VALUES (?, ?, 'dm', 'DM', ?, ?, ?, ?)`, msgID, gameID, models.MessageTypeSystem, content, game.PhaseIndex, now); err != nil { writeError(w, 500, "服务器错误") return } broadcast(gameID, map[string]interface{}{ "type": "game_started", "data": map[string]interface{}{ "phase": game.Phase, "phase_index": game.PhaseIndex, "message": map[string]interface{}{ "id": msgID, "sender_name": "DM", "sender_role_id": "dm", "message_type": models.MessageTypeSystem, "content": content, "created_at": now, }, }, }) writeJSON(w, 200, map[string]interface{}{"status": "started", "phase": game.Phase}) } type npcResponse struct { msg *models.ChatMessage broadcast map[string]interface{} } func generateNPCResponses(gameID string, senderRoleID, content, msgType string, targetRoleID *string, script *scripts.Script, phaseName string, phaseIndex int) []npcResponse { var responses []npcResponse if script == nil { return responses } var npcRoles []*scripts.Role for i := range script.Roles { if script.Roles[i].ID != senderRoleID { npcRoles = append(npcRoles, &script.Roles[i]) } } rand.Shuffle(len(npcRoles), func(i, j int) { npcRoles[i], npcRoles[j] = npcRoles[j], npcRoles[i] }) if len(npcRoles) == 0 { return responses } recentMsgs := queryMessages(gameID) recentList := []map[string]string{} if len(recentMsgs) > 10 { recentMsgs = recentMsgs[len(recentMsgs)-10:] } for _, m := range recentMsgs { recentList = append(recentList, map[string]string{"sender_name": m.SenderName, "content": m.Content}) } knownClues := []string{} for _, c := range queryClues(gameID) { if c.Status == "released" && script != nil { for _, sc := range script.Clues { if sc.ID == c.ClueID { knownClues = append(knownClues, sc.Description) break } } } } var phaseGoal string if phaseIndex < len(script.Phases) { phaseGoal = script.Phases[phaseIndex].Description } allRoles := []*scripts.Role{} for i := range script.Roles { allRoles = append(allRoles, &script.Roles[i]) } count := 2 if len(npcRoles) < count { count = len(npcRoles) } for _, npcRole := range npcRoles[:count] { agent := agents.NewNPCAgent(npcRole, script.Background, "") isPrivate := msgType == "private" && targetRoleID != nil && *targetRoleID == npcRole.ID response := agent.Respond(phaseName, recentList, content, isPrivate, knownClues, phaseGoal, allRoles) reply, _ := response["content"].(string) action, _ := response["action"].(string) if reply == "" || action == "remain_silent" { continue } var npcTarget *string if isPrivate { npcTarget = &senderRoleID } npcMsgType := models.MessageTypePublic if isPrivate { npcMsgType = models.MessageTypePrivate } now := util.NowStr() npcMsg := &models.ChatMessage{ ID: newID(), SessionID: gameID, SenderRoleID: npcRole.ID, SenderName: npcRole.Name, MessageType: npcMsgType, Content: reply, TargetRoleID: npcTarget, PhaseIndex: phaseIndex, CreatedAt: util.ParseTime(now), } responses = append(responses, npcResponse{ msg: npcMsg, broadcast: map[string]interface{}{ "type": "chat_message", "data": map[string]interface{}{ "id": npcMsg.ID, "sender_role_id": npcRole.ID, "sender_name": npcRole.Name, "message_type": npcMsgType, "content": reply, "target_role_id": npcTarget, "phase_index": phaseIndex, "created_at": now, }, }, }) } return responses } func SendChat(w http.ResponseWriter, r *http.Request) { gameID := chi.URLParam(r, "game_id") var req struct { Type string `json:"type"` Content string `json:"content"` TargetRoleID *string `json:"target_role_id"` SenderRoleID string `json:"sender_role_id"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, 422, "请求格式错误") return } game, err := getGame(gameID) if err != nil { writeError(w, 404, "游戏不存在") return } senderName := req.SenderRoleID script := scriptFromConfig(game.Config) if script != nil { for _, role := range script.Roles { if role.ID == req.SenderRoleID { senderName = role.Name break } } } msgType := req.Type if msgType == "" { msgType = models.MessageTypePublic } msgID := newID() now := util.NowStr() if _, err := database.DB.Exec( `INSERT INTO chat_messages (id, session_id, sender_role_id, sender_name, message_type, content, target_role_id, phase_index, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, msgID, gameID, req.SenderRoleID, senderName, msgType, req.Content, req.TargetRoleID, game.PhaseIndex, now); err != nil { writeError(w, 500, "服务器错误") return } npcResponses := generateNPCResponses(gameID, req.SenderRoleID, req.Content, msgType, req.TargetRoleID, script, game.Phase, game.PhaseIndex) for _, n := range npcResponses { if n.msg != nil { if _, err := database.DB.Exec( `INSERT INTO chat_messages (id, session_id, sender_role_id, sender_name, message_type, content, target_role_id, phase_index, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, n.msg.ID, n.msg.SessionID, n.msg.SenderRoleID, n.msg.SenderName, n.msg.MessageType, n.msg.Content, n.msg.TargetRoleID, n.msg.PhaseIndex, util.TimeStr(n.msg.CreatedAt)); err != nil { continue } } } msgData := map[string]interface{}{ "type": "chat_message", "data": map[string]interface{}{ "id": msgID, "sender_role_id": req.SenderRoleID, "sender_name": senderName, "message_type": msgType, "content": req.Content, "target_role_id": req.TargetRoleID, "phase_index": game.PhaseIndex, "created_at": now, }, } if msgType == "private" && req.TargetRoleID != nil { broadcastToRole(gameID, req.SenderRoleID, msgData) broadcastToRole(gameID, *req.TargetRoleID, msgData) } else { broadcast(gameID, msgData) } for _, n := range npcResponses { b := n.broadcast data, _ := b["data"].(map[string]interface{}) if data != nil { if data["message_type"] == "private" { target, _ := data["target_role_id"].(string) if target != "" { broadcastToRole(gameID, req.SenderRoleID, b) broadcastToRole(gameID, target, b) continue } } } broadcast(gameID, b) } npcList := []map[string]interface{}{} for _, n := range npcResponses { npcList = append(npcList, n.broadcast) } writeJSON(w, 200, map[string]interface{}{ "status": "ok", "message_id": msgID, "npc_responses": npcList, }) } func AdvancePhase(w http.ResponseWriter, r *http.Request) { gameID := chi.URLParam(r, "game_id") game, err := getGame(gameID) if err != nil { writeError(w, 404, "游戏不存在") return } script := scriptFromConfig(game.Config) var phases []scripts.Phase if script != nil { phases = script.Phases } nextIdx := game.PhaseIndex + 1 if nextIdx >= len(phases) { writeJSON(w, 200, map[string]string{"status": "already_at_end"}) return } _, err = database.DB.Exec( `UPDATE game_sessions SET phase_index = ?, phase = ? WHERE id = ?`, nextIdx, phases[nextIdx].Name, gameID) if err != nil { writeError(w, 500, "服务器错误") return } phase := &phases[nextIdx] content := fmt.Sprintf("【%s】\n%s\n\n%s", phase.Name, phase.Description, phase.PublicInfo) msgID := newID() now := util.NowStr() if _, err := database.DB.Exec( `INSERT INTO chat_messages (id, session_id, sender_role_id, sender_name, message_type, content, phase_index, created_at) VALUES (?, ?, 'dm', 'DM', ?, ?, ?, ?)`, msgID, gameID, models.MessageTypeSystem, content, nextIdx, now); err != nil { writeError(w, 500, "服务器错误") return } cluesToRelease := phase.Clues for _, clueID := range cluesToRelease { var existingID string err := database.DB.QueryRow( `SELECT id FROM clues_state WHERE session_id = ? AND clue_id = ?`, gameID, clueID).Scan(&existingID) if err == sql.ErrNoRows { if _, err := database.DB.Exec( `INSERT INTO clues_state (id, session_id, clue_id, status, created_at) VALUES (?, ?, ?, 'released', ?)`, newID(), gameID, clueID, util.NowStr()); err != nil { continue } } } broadcast(gameID, map[string]interface{}{ "type": "phase_change", "data": map[string]interface{}{ "phase": phase.Name, "phase_index": nextIdx, "message": map[string]interface{}{ "id": msgID, "sender_name": "DM", "sender_role_id": "dm", "message_type": models.MessageTypeSystem, "content": content, "created_at": now, }, "new_clues": cluesToRelease, }, }) writeJSON(w, 200, map[string]interface{}{ "status": "ok", "phase": phase.Name, "phase_index": nextIdx, }) } func RevealClue(w http.ResponseWriter, r *http.Request) { gameID := chi.URLParam(r, "game_id") clueID := chi.URLParam(r, "clue_id") var stateID string err := database.DB.QueryRow( `SELECT id FROM clues_state WHERE session_id = ? AND clue_id = ?`, gameID, clueID).Scan(&stateID) if err == sql.ErrNoRows { writeError(w, 404, "线索不存在或未发放") return } if err != nil { writeError(w, 500, "服务器错误") return } if _, err := database.DB.Exec(`UPDATE clues_state SET status = 'revealed' WHERE id = ?`, stateID); err != nil { writeError(w, 500, "服务器错误") return } game, _ := getGame(gameID) script := scriptFromConfig(game.Config) var clueInfo interface{} if script != nil { for _, c := range script.Clues { if c.ID == clueID { clueInfo = c break } } } broadcast(gameID, map[string]interface{}{ "type": "clue_revealed", "data": map[string]interface{}{"clue_id": clueID, "clue": clueInfo}, }) writeJSON(w, 200, map[string]interface{}{"status": "revealed", "clue": clueInfo}) } func SubmitVote(w http.ResponseWriter, r *http.Request) { gameID := chi.URLParam(r, "game_id") var req struct { TargetRoleID string `json:"target_role_id"` Reason string `json:"reason"` VoterRoleID string `json:"voter_role_id"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, 422, "请求格式错误") return } var voterID, voterName string err := database.DB.QueryRow( `SELECT id, role_name FROM session_players WHERE session_id = ? AND role_id = ?`, gameID, req.VoterRoleID).Scan(&voterID, &voterName) if err != nil { writeError(w, 404, "投票者不存在") return } var targetID string err = database.DB.QueryRow( `SELECT id FROM session_players WHERE session_id = ? AND role_id = ?`, gameID, req.TargetRoleID).Scan(&targetID) if err != nil { writeError(w, 404, "投票目标不存在") return } if _, err := database.DB.Exec( `INSERT INTO votes (id, session_id, round, voter_id, target_id, reason, created_at) VALUES (?, ?, 1, ?, ?, ?, ?)`, newID(), gameID, voterID, targetID, req.Reason, util.NowStr()); err != nil { writeError(w, 500, "服务器错误") return } broadcast(gameID, map[string]interface{}{ "type": "vote_cast", "data": map[string]interface{}{ "voter_role_id": req.VoterRoleID, "target_role_id": req.TargetRoleID, "voter_name": voterName, }, }) writeJSON(w, 200, map[string]string{"status": "ok"}) } func EndVote(w http.ResponseWriter, r *http.Request) { gameID := chi.URLParam(r, "game_id") game, err := getGame(gameID) if err != nil { writeError(w, 404, "游戏不存在") return } script := scriptFromConfig(game.Config) var allClues map[string]scripts.Clue var roleByID map[string]*scripts.Role if script != nil { allClues = map[string]scripts.Clue{} for _, c := range script.Clues { allClues[c.ID] = c } roleByID = map[string]*scripts.Role{} for i := range script.Roles { roleByID[script.Roles[i].ID] = &script.Roles[i] } } existingVotes := queryVotes(gameID) existingVoterIDs := map[string]bool{} for _, v := range existingVotes { existingVoterIDs[v.VoterID] = true } playersList := queryPlayers(gameID) playersByID := map[string]*models.SessionPlayer{} for _, p := range playersList { playersByID[p.ID] = p } releasedClueIDs := map[string]bool{} for _, c := range queryClues(gameID) { if c.Status == "released" { releasedClueIDs[c.ClueID] = true } } knownClues := []string{} if allClues != nil { for cid := range releasedClueIDs { if c, ok := allClues[cid]; ok { knownClues = append(knownClues, c.Description) } } } var npcPlayers []*models.SessionPlayer for _, p := range playersList { if !p.IsHuman && !existingVoterIDs[p.ID] { npcPlayers = append(npcPlayers, p) } } if script != nil { for _, npc := range npcPlayers { var targets []*models.SessionPlayer for _, p := range playersList { if p.ID != npc.ID { targets = append(targets, p) } } if len(targets) == 0 { continue } var targetID string if npcRole, ok := roleByID[npc.RoleID]; ok { agent := agents.NewNPCAgent(npcRole, script.Background, "") voteReason := agent.Respond("投票阶段", []map[string]string{}, "请投票选出你最怀疑的人,并说明理由", false, knownClues, "", nil) voteContent, _ := voteReason["content"].(string) targetID = targets[rand.Intn(len(targets))].ID for _, t := range targets { if t.RoleName != "" && strings.Contains(voteContent, t.RoleName) { targetID = t.ID break } } } else { targetID = targets[rand.Intn(len(targets))].ID } if _, err := database.DB.Exec( `INSERT INTO votes (id, session_id, round, voter_id, target_id, reason, created_at) VALUES (?, ?, 1, ?, ?, '', ?)`, newID(), gameID, npc.ID, targetID, util.NowStr()); err != nil { continue } targetPlayer := playersByID[targetID] if targetPlayer != nil { broadcast(gameID, map[string]interface{}{ "type": "vote_cast", "data": map[string]interface{}{ "voter_role_id": npc.RoleID, "voter_name": npc.RoleName, "target_role_id": targetPlayer.RoleID, "target_name": targetPlayer.RoleName, }, }) } } } votes := queryVotes(gameID) tally := map[string][]string{} for _, v := range votes { targetRole := "unknown" if p, ok := playersByID[v.TargetID]; ok { targetRole = p.RoleID } voterRole := "unknown" if p, ok := playersByID[v.VoterID]; ok { voterRole = p.RoleID } tally[targetRole] = append(tally[targetRole], voterRole) } maxVotes := 0 accusedRoleID := "" for roleID, voters := range tally { if len(voters) > maxVotes { maxVotes = len(voters) accusedRoleID = roleID } } accusedName := accusedRoleID if script != nil { for _, role := range script.Roles { if role.ID == accusedRoleID { accusedName = role.Name break } } } truth := "" if script != nil { truth = script.Truth } if _, err := database.DB.Exec( `UPDATE game_sessions SET status = ?, completed_at = ? WHERE id = ?`, models.GameStatusCompleted, util.NowStr(), gameID); err != nil { writeError(w, 500, "服务器错误") return } content := fmt.Sprintf("投票结束!最多票指向:%s(%d票)\n\n真相:%s", accusedName, maxVotes, truth) msgID := newID() now := util.NowStr() if _, err := database.DB.Exec( `INSERT INTO chat_messages (id, session_id, sender_role_id, sender_name, message_type, content, phase_index, created_at) VALUES (?, ?, 'dm', 'DM', ?, ?, ?, ?)`, msgID, gameID, models.MessageTypeSystem, content, game.PhaseIndex, now); err != nil { writeError(w, 500, "服务器错误") return } broadcast(gameID, map[string]interface{}{ "type": "vote_result", "data": map[string]interface{}{ "tally": tally, "accused_role_id": accusedRoleID, "accused_name": accusedName, "truth": truth, "game_id": gameID, "message": map[string]interface{}{ "id": msgID, "sender_name": "DM", "sender_role_id": "dm", "message_type": models.MessageTypeSystem, "content": content, "created_at": now, }, }, }) removeGameConns(gameID) writeJSON(w, 200, map[string]interface{}{ "status": "completed", "accused": accusedName, "truth": truth, }) } func GetGameState(w http.ResponseWriter, r *http.Request) { gameID := chi.URLParam(r, "game_id") game, err := getGame(gameID) if err != nil { writeError(w, 404, "游戏不存在") return } players := queryPlayers(gameID) clues := queryClues(gameID) msgs := queryMessages(gameID) script := scriptFromConfig(game.Config) playersOut := []map[string]interface{}{} for _, p := range players { playersOut = append(playersOut, map[string]interface{}{ "id": p.ID, "role_id": p.RoleID, "role_name": p.RoleName, "is_human": p.IsHuman, "status": p.Status, }) } cluesOut := []map[string]interface{}{} for _, c := range clues { cluesOut = append(cluesOut, map[string]interface{}{"clue_id": c.ClueID, "status": c.Status}) } msgsOut := []map[string]interface{}{} for _, m := range msgs { msgsOut = append(msgsOut, messageDict(m)) } var scriptOut interface{} if script != nil { scriptOut = script } else { scriptOut = map[string]interface{}{} } writeJSON(w, 200, map[string]interface{}{ "id": game.ID, "script_id": game.ScriptID, "status": game.Status, "phase": game.Phase, "phase_index": game.PhaseIndex, "script": scriptOut, "players": playersOut, "clues": cluesOut, "messages": msgsOut, }) } func GetReplay(w http.ResponseWriter, r *http.Request) { gameID := chi.URLParam(r, "game_id") game, err := getGame(gameID) if err != nil { writeError(w, 404, "游戏不存在") return } msgs := queryMessages(gameID) votes := queryVotes(gameID) players := map[string]*models.SessionPlayer{} for _, p := range queryPlayers(gameID) { players[p.ID] = p } script := scriptFromConfig(game.Config) scriptTitle := game.ScriptID truth := "" var rolesOut []map[string]interface{} if script != nil { scriptTitle = script.Title truth = script.Truth for _, role := range script.Roles { rolesOut = append(rolesOut, map[string]interface{}{ "id": role.ID, "name": role.Name, "publicProfile": role.PublicProfile, "secretProfile": role.SecretProfile, "goal": role.Goal, }) } } msgsOut := []map[string]interface{}{} for _, m := range msgs { msgsOut = append(msgsOut, map[string]interface{}{ "id": m.ID, "sender_name": m.SenderName, "content": m.Content, "type": m.MessageType, "time": util.TimeStr(m.CreatedAt), }) } voteDetails := []map[string]interface{}{} for _, v := range votes { voter := players[v.VoterID] target := players[v.TargetID] voterName := "unknown" if voter != nil { voterName = voter.RoleName } targetName := "unknown" if target != nil { targetName = target.RoleName } reason := "" if v.Reason != nil { reason = *v.Reason } voteDetails = append(voteDetails, map[string]interface{}{ "voter_name": voterName, "target_name": targetName, "reason": reason, }) } writeJSON(w, 200, map[string]interface{}{ "game_id": gameID, "script_title": scriptTitle, "truth": truth, "roles": rolesOut, "messages": msgsOut, "votes": voteDetails, }) }