From 12fe92b2619ceb4d0d7344e0820bfe26a7e5f99d Mon Sep 17 00:00:00 2001 From: Grail Finder Date: Thu, 26 Jun 2025 11:36:27 +0300 Subject: [PATCH] Feat: save room even if llm failed to properly guess; fix prompt --- components/room.html | 2 +- handlers/actions.go | 1 - handlers/auth.go | 17 ++-- handlers/elements.go | 14 ++-- handlers/game.go | 4 +- llmapi/main.go | 195 ++++++++++++++++++++++--------------------- models/main.go | 18 ++++ 7 files changed, 134 insertions(+), 117 deletions(-) diff --git a/components/room.html b/components/room.html index e49d4b8..588e436 100644 --- a/components/room.html +++ b/components/room.html @@ -50,7 +50,7 @@ {{template "teamlist" .Room.RedTeam}}
-
+
Server says:
    {{range .Room.LogJournal}} diff --git a/handlers/actions.go b/handlers/actions.go index 755fa10..6fec1bf 100644 --- a/handlers/actions.go +++ b/handlers/actions.go @@ -248,7 +248,6 @@ func listRooms(allRooms bool) []*models.Room { log.Warn("failed to unmarshal room", "error", err) continue } - log.Debug("consider room for list", "room", room, "key", key) if room.IsPublic || allRooms { publicRooms = append(publicRooms, room) } diff --git a/handlers/auth.go b/handlers/auth.go index cc41031..b487a05 100644 --- a/handlers/auth.go +++ b/handlers/auth.go @@ -79,11 +79,11 @@ func HandleFrontLogin(w http.ResponseWriter, r *http.Request) { return } http.SetCookie(w, cookie) - tmpl, err := template.ParseGlob("components/*.html") - if err != nil { - abortWithError(w, err.Error()) - return - } + // tmpl, err := template.ParseGlob("components/*.html") + // if err != nil { + // abortWithError(w, err.Error()) + // return + // } // check if that user was already in db userstate, err := loadState(cleanName) if err != nil || userstate == nil { @@ -120,9 +120,10 @@ func HandleFrontLogin(w http.ResponseWriter, r *http.Request) { return } } - if err := tmpl.ExecuteTemplate(w, "base", fi); err != nil { - log.Error("failed to execute base template", "error", err) - } + // if err := tmpl.ExecuteTemplate(w, "base", fi); err != nil { + // log.Error("failed to execute base template", "error", err) + // } + http.Redirect(w, r, "/", 302) } func makeCookie(username string, remote string) (*http.Cookie, error) { diff --git a/handlers/elements.go b/handlers/elements.go index 824bda7..9d9a3e9 100644 --- a/handlers/elements.go +++ b/handlers/elements.go @@ -78,7 +78,7 @@ func HandleShowColor(w http.ResponseWriter, r *http.Request) { Actor: fi.State.Username, ActorColor: string(fi.State.Team), WordColor: string(color), - Action: "guessed", + Action: models.ActionTypeGuess, Word: word, } fi.Room.ActionHistory = append(fi.Room.ActionHistory, action) @@ -103,8 +103,8 @@ func HandleShowColor(w http.ResponseWriter, r *http.Request) { action := models.Action{ Actor: fi.State.Username, ActorColor: string(fi.State.Team), - WordColor: "black", - Action: "game over", + WordColor: models.WordColorBlack, + Action: models.ActionTypeGameOver, } fi.Room.OpenedThisTurn = 0 fi.Room.ThisTurnLimit = 0 @@ -124,8 +124,8 @@ func HandleShowColor(w http.ResponseWriter, r *http.Request) { action := models.Action{ Actor: fi.State.Username, ActorColor: string(fi.State.Team), - WordColor: "blue", - Action: "game over", + WordColor: models.WordColorBlue, + Action: models.ActionTypeGameOver, } fi.Room.OpenedThisTurn = 0 fi.Room.ThisTurnLimit = 0 @@ -139,8 +139,8 @@ func HandleShowColor(w http.ResponseWriter, r *http.Request) { action := models.Action{ Actor: fi.State.Username, ActorColor: string(fi.State.Team), - WordColor: "red", - Action: "game over", + WordColor: models.WordColorRed, + Action: models.ActionTypeGameOver, } fi.Room.OpenedThisTurn = 0 fi.Room.ThisTurnLimit = 0 diff --git a/handlers/game.go b/handlers/game.go index 2f1c261..7002638 100644 --- a/handlers/game.go +++ b/handlers/game.go @@ -153,9 +153,7 @@ func HandleStartGame(w http.ResponseWriter, r *http.Request) { Actor: fi.State.Username, ActorColor: string(fi.State.Team), WordColor: string(fi.State.Team), - Action: "game started", - // Word: clue, - // Number: num, + Action: models.ActionTypeGameStarted, } fi.Room.ActionHistory = append(fi.Room.ActionHistory, action) if err := saveFullInfo(fi); err != nil { diff --git a/llmapi/main.go b/llmapi/main.go index 637670b..480f349 100644 --- a/llmapi/main.go +++ b/llmapi/main.go @@ -120,105 +120,100 @@ func (b *Bot) StartBot() { for { select { case <-SignalChanMap[b.BotName]: - botJournalName := models.NotifyJournalPrefix + b.RoomID - b.log.Debug("got signal", "bot-team", b.Team, "bot-role", b.Role) - // get room cards and actions - room, err := getRoomByID(b.RoomID) - if err != nil { - b.log.Error("bot loop", "error", err) - continue - } - // form prompt - prompt := b.BuildPrompt(room) - b.log.Debug("got prompt", "prompt", prompt) - broker.Notifier.Notifier <- broker.NotificationEvent{ - EventName: botJournalName, - Payload: prompt, - } - // call llm - llmResp, err := b.CallLLM(prompt) - if err != nil { - broker.Notifier.Notifier <- broker.NotificationEvent{ - EventName: botJournalName, - Payload: "failed to get bot resp", - } - b.log.Error("bot loop", "error", err) - continue - } - tempMap, err := b.LLMParser.ParseBytes(llmResp) - if err != nil { - broker.Notifier.Notifier <- broker.NotificationEvent{ - EventName: botJournalName, - Payload: "failed to parse bot resp", - } - b.log.Error("bot loop", "error", err, "resp", string(llmResp)) - continue - } - eventName := models.NotifyBacklogPrefix + room.ID - eventPayload := "" - switch b.Role { - case models.UserRoleMime: - mimeResp := MimeResp{} - b.log.Info("mime resp log", "mimeResp", tempMap) - mimeResp.Clue = tempMap["clue"].(string) - mimeResp.Number = tempMap["number"].(string) - action := models.Action{ - Actor: b.BotName, - ActorColor: b.Team, - WordColor: b.Team, - Action: "gave clue", - Word: mimeResp.Clue, - Number: mimeResp.Number, - } - room.ActionHistory = append(room.ActionHistory, action) - room.MimeDone = true - eventPayload = mimeResp.Clue + mimeResp.Number - guessLimitU64, err := strconv.ParseUint(mimeResp.Number, 10, 8) + func() { + // botJournalName := models.NotifyJournalPrefix + b.RoomID + b.log.Debug("got signal", "bot-team", b.Team, "bot-role", b.Role) + // get room cards and actions + room, err := getRoomByID(b.RoomID) if err != nil { - b.log.Warn("failed to parse bot given limit", "mimeResp", mimeResp, "bot_name", b.BotName) + b.log.Error("bot loop", "error", err) + return } - room.ThisTurnLimit = uint8(guessLimitU64) - case models.UserRoleGuesser: - // // deprecated - // if err := b.checkGuesses(tempMap, room); err != nil { - // b.log.Warn("failed to check guess", "mimeResp", tempMap, "bot_name", b.BotName) - // continue - // } - guess, ok := tempMap["guess"].(string) - if !ok || guess == "" { - b.log.Warn("failed to parse guess", "mimeResp", tempMap, "bot_name", b.BotName) - } - if err := b.checkGuess(guess, room); err != nil { - b.log.Warn("failed to check guess", "mimeResp", tempMap, "bot_name", b.BotName, "guess", guess) - } - b.log.Info("mime resp log", "guesserResp", tempMap) - couldBe, err := convertToSliceOfStrings(tempMap["could_be"]) + eventName := models.NotifyBacklogPrefix + room.ID + eventPayload := "" + defer func() { // save room + if err := saveRoom(room); err != nil { + b.log.Error("failed to save room", "error", err) + return + } + broker.Notifier.Notifier <- broker.NotificationEvent{ + EventName: eventName, + Payload: eventPayload, + } + }() + // form prompt + prompt := b.BuildPrompt(room) + b.log.Debug("got prompt", "prompt", prompt) + room.LogJournal = append(room.LogJournal, b.BotName+" got prompt: "+prompt) + // call llm + llmResp, err := b.CallLLM(prompt) if err != nil { - b.log.Warn("failed to parse could_be", "bot_resp", tempMap, "bot_name", b.BotName) + room.LogJournal = append(room.LogJournal, b.BotName+" send call got error: "+err.Error()) + b.log.Error("bot loop", "error", err) + return } - room.LogJournal = append(room.LogJournal, fmt.Sprintf("%s also considered this: %v", b.BotName, couldBe)) - eventName = models.NotifyRoomUpdatePrefix + room.ID - eventPayload = "" - // TODO: needs to decide if it wants to open the next cardword or end turn - // or end turn on limit - default: - b.log.Error("unexpected role", "role", b.Role, "resp-map", tempMap) - continue - } - // save room - if err := saveRoom(room); err != nil { - b.log.Error("failed to save room", "error", err) - continue - } - if botName := room.WhichBotToMove(); botName != "" { - b.log.Debug("notifying bot", "name", botName) - SignalChanMap[botName] <- true - } - broker.Notifier.Notifier <- broker.NotificationEvent{ - EventName: eventName, - Payload: eventPayload, - } - continue + tempMap, err := b.LLMParser.ParseBytes(llmResp) + if err != nil { + room.LogJournal = append(room.LogJournal, b.BotName+" parse resp got error: "+err.Error()) + b.log.Error("bot loop", "error", err, "resp", string(llmResp)) + return + } + switch b.Role { + case models.UserRoleMime: + mimeResp := MimeResp{} + b.log.Info("mime resp log", "mimeResp", tempMap) + mimeResp.Clue = tempMap["clue"].(string) + mimeResp.Number = tempMap["number"].(string) + action := models.Action{ + Actor: b.BotName, + ActorColor: b.Team, + WordColor: b.Team, + Action: "gave clue", + Word: mimeResp.Clue, + Number: mimeResp.Number, + } + room.ActionHistory = append(room.ActionHistory, action) + room.MimeDone = true + eventPayload = mimeResp.Clue + mimeResp.Number + guessLimitU64, err := strconv.ParseUint(mimeResp.Number, 10, 8) + if err != nil { + b.log.Warn("failed to parse bot given limit", "mimeResp", mimeResp, "bot_name", b.BotName) + } + room.ThisTurnLimit = uint8(guessLimitU64) + case models.UserRoleGuesser: + // // deprecated + // if err := b.checkGuesses(tempMap, room); err != nil { + // b.log.Warn("failed to check guess", "mimeResp", tempMap, "bot_name", b.BotName) + // continue + // } + guess, ok := tempMap["guess"].(string) + if !ok || guess == "" { + b.log.Warn("failed to parse guess", "mimeResp", tempMap, "bot_name", b.BotName) + } + if err := b.checkGuess(guess, room); err != nil { + b.log.Warn("failed to check guess", "mimeResp", tempMap, "bot_name", b.BotName, "guess", guess, "error", err) + msg := fmt.Sprintf("failed to check guess", "mimeResp", tempMap, "bot_name", b.BotName, "guess", guess, "error", err) + room.LogJournal = append(room.LogJournal, msg) + } + b.log.Info("mime resp log", "guesserResp", tempMap) + couldBe, err := convertToSliceOfStrings(tempMap["could_be"]) + if err != nil { + b.log.Warn("failed to parse could_be", "bot_resp", tempMap, "bot_name", b.BotName) + } + room.LogJournal = append(room.LogJournal, fmt.Sprintf("%s also considered this: %v", b.BotName, couldBe)) + eventName = models.NotifyRoomUpdatePrefix + room.ID + eventPayload = "" + // TODO: needs to decide if it wants to open the next cardword or end turn + // or end turn on limit + default: + b.log.Error("unexpected role", "role", b.Role, "resp-map", tempMap) + return + } + if botName := room.WhichBotToMove(); botName != "" { + b.log.Debug("notifying bot", "name", botName) + SignalChanMap[botName] <- true + } + }() case <-DoneChanMap[b.BotName]: b.log.Debug("got done signal", "bot-name", b.BotName) return @@ -335,13 +330,19 @@ func saveRoom(room *models.Room) error { } func (b *Bot) BuildSimpleGuesserPrompt(room *models.Room) string { - clue := room.ActionHistory[len(room.ActionHistory)-1].Word + // find not last action, but the clue + // clue := room.ActionHistory[len(room.ActionHistory)-1].Word + clueAction, err := room.FetchLastClue() + if err != nil { + b.log.Error("failed to fetch last clue", "error", err, "room", room, "bot_name", b.BotName) + return "" + } // number := room.ActionHistory[len(room.ActionHistory)-1].Number words := make([]string, len(room.Cards)) for i, card := range room.Cards { words[i] = card.Word } - return fmt.Sprintf(GuesserSimplePrompt, clue, words) + return fmt.Sprintf(GuesserSimplePrompt, clueAction.Word, words) } func (b *Bot) BuildPrompt(room *models.Room) string { diff --git a/models/main.go b/models/main.go index 1357906..88c4b03 100644 --- a/models/main.go +++ b/models/main.go @@ -19,6 +19,15 @@ const ( WordColorUknown = "stone" // beige ) +type ActionType string + +const ( + ActionTypeClue = "gave_clue" + ActionTypeGuess = "guessed" + ActionTypeGameOver = "game_over" + ActionTypeGameStarted = "game_started" +) + func StrToWordColor(s string) WordColor { switch s { case "amber", "white": @@ -88,6 +97,15 @@ type Room struct { LogJournal []string } +func (r *Room) FetchLastClue() (*Action, error) { + for i := len(r.ActionHistory) - 1; i >= 0; i-- { + if r.ActionHistory[i].Action == string(ActionTypeClue) { + return &r.ActionHistory[i], nil + } + } + return nil, errors.New("no clue in history") +} + func (r *Room) GetPlayerByName(name string) (role UserRole, team UserTeam, found bool) { if r.RedTeam.Mime == name { return "mime", "red", true