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.
This commit is contained in:
gmh01
2026-08-03 11:39:53 +08:00
parent c176c04aab
commit 7614e79d12
43 changed files with 2498 additions and 1462 deletions
+866
View File
@@ -0,0 +1,866 @@
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,
})
}