Files
MMGame/backend/internal/handlers/games.go
T
gmh01 7614e79d12 refactor: rewrite backend in Go, replacing Python FastAPI
Port the FastAPI backend to Go 1.26, preserving all API functionality:
- auth (register/login/guest/logout/profile/history) with JWT + bcrypt
- scripts (list/detail) backed by embedded sample_data.json
- games (create/join/start/chat/phase/clue/vote/state/replay) + WebSocket
- rule-based NPC agent plus DeepSeek/Ollama AI backends
- SQLite persistence via modernc.org/sqlite (pure Go, no cgo)

Frontend and documents are unchanged.
2026-08-03 11:39:53 +08:00

867 lines
23 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
})
}