From 72053281e250a3a2e06deb3784405856eea88703 Mon Sep 17 00:00:00 2001 From: Grail Finder Date: Fri, 27 Jun 2025 13:22:00 +0300 Subject: [PATCH] Chore: refactor; mark words [WIP] --- components/cardword.html | 14 ++-- handlers/actions.go | 53 ++++++++++---- handlers/elements.go | 144 +++++++++++++++++++++++++++++++++++---- llmapi/main.go | 2 + main.go | 1 + models/main.go | 25 ++++++- todos.md | 2 + 7 files changed, 208 insertions(+), 33 deletions(-) diff --git a/components/cardword.html b/components/cardword.html index 2c55499..9f855a0 100644 --- a/components/cardword.html +++ b/components/cardword.html @@ -10,10 +10,16 @@ {{end}} {{else}} -
- {{.Word}} +
+
+ {{.Word}} +
+
+   +
{{end}} {{end}} diff --git a/handlers/actions.go b/handlers/actions.go index 781da4f..30e439f 100644 --- a/handlers/actions.go +++ b/handlers/actions.go @@ -8,7 +8,6 @@ import ( "gralias/broker" "gralias/llmapi" "gralias/models" - "gralias/utils" "gralias/wordloader" "strings" ) @@ -173,22 +172,22 @@ func getFullInfoByCtx(ctx context.Context) (*models.FullInfo, error) { return resp, nil } -func leaveRole(fi *models.FullInfo) { - fi.Room.RedTeam.Guessers = utils.RemoveFromSlice(fi.State.Username, fi.Room.RedTeam.Guessers) - fi.Room.BlueTeam.Guessers = utils.RemoveFromSlice(fi.State.Username, fi.Room.BlueTeam.Guessers) - if fi.Room.RedTeam.Mime == fi.State.Username { - fi.Room.RedTeam.Mime = "" - } - if fi.Room.BlueTeam.Mime == fi.State.Username { - fi.Room.BlueTeam.Mime = "" - } -} +// // DEPRECATED +// func leaveRole(fi *models.FullInfo) { +// fi.Room.RedTeam.Guessers = utils.RemoveFromSlice(fi.State.Username, fi.Room.RedTeam.Guessers) +// fi.Room.BlueTeam.Guessers = utils.RemoveFromSlice(fi.State.Username, fi.Room.BlueTeam.Guessers) +// if fi.Room.RedTeam.Mime == fi.State.Username { +// fi.Room.RedTeam.Mime = "" +// } +// if fi.Room.BlueTeam.Mime == fi.State.Username { +// fi.Room.BlueTeam.Mime = "" +// } +// } func joinTeam(ctx context.Context, role, team string) (*models.FullInfo, error) { // get username fi, _ := getFullInfoByCtx(ctx) - // leave gueesers if present - leaveRole(fi) + fi.Room.RemovePlayer(fi.State.Username) // get room if role == "mime" { if team == "blue" { @@ -367,3 +366,31 @@ func recoverPlayer(pm map[string]string) error { } return saveState(pm["Username"], us) } + +// validateMove checks if it is players turn +func validateMove(fi *models.FullInfo, ur models.UserRole) error { + if fi.State.Role != ur { + err := fmt.Errorf("need to be %s to make that action", ur) + return err + } + // whos move it is? + if fi.State.Team != models.UserTeam(fi.Room.TeamTurn) { + err := errors.New("not your team's move") + return err + } + switch ur { + case models.UserRoleGuesser: + if !fi.Room.MimeDone { + err := errors.New("wait for the mime to give a clue") + return err + } + case models.UserRoleMime: + if fi.Room.MimeDone { + err := errors.New("clue was already given") + return err + } + default: + return fmt.Errorf("uknown user role: %s", ur) + } + return nil +} diff --git a/handlers/elements.go b/handlers/elements.go index 656451f..7afaa3d 100644 --- a/handlers/elements.go +++ b/handlers/elements.go @@ -1,12 +1,12 @@ package handlers import ( - "errors" "fmt" "gralias/llmapi" "gralias/models" "html/template" "net/http" + "strings" ) func HandleShowCreateForm(w http.ResponseWriter, r *http.Request) { @@ -46,21 +46,10 @@ func HandleShowColor(w http.ResponseWriter, r *http.Request) { abortWithError(w, err.Error()) return } - if fi.State.Role != "guesser" { - err = errors.New("need to be guesser to open the card") + if err := validateMove(fi, models.UserRoleGuesser); err != nil { abortWithError(w, err.Error()) return } - // whos move it is? - if fi.State.Team != models.UserTeam(fi.Room.TeamTurn) { - err = errors.New("not your team's move") - abortWithError(w, err.Error()) - return - } - if !fi.Room.MimeDone { - abortWithError(w, "wait for the clue") - return - } color, exists := fi.Room.WCMap[word] log.Debug("got show-color request", "word", word, "color", color) if !exists { @@ -159,6 +148,135 @@ func HandleShowColor(w http.ResponseWriter, r *http.Request) { } } +func HandleMarkCard(w http.ResponseWriter, r *http.Request) { + word := r.URL.Query().Get("word") + ctx := r.Context() + tmpl, err := template.ParseGlob("components/*.html") + if err != nil { + abortWithError(w, err.Error()) + return + } + fi, err := getFullInfoByCtx(ctx) + if err != nil { + abortWithError(w, err.Error()) + return + } + if err := validateMove(fi, models.UserRoleGuesser); err != nil { + abortWithError(w, err.Error()) + return + } + color, exists := fi.Room.WCMap[word] + log.Debug("got show-color request", "word", word, "color", color) + if !exists { + abortWithError(w, "word is not found") + return + } + // check if card already was revealed + for i, card := range fi.Room.Cards { + if !strings.EqualFold(card.Word, word) { + continue + } + if card.Revealed { + abortWithError(w, "cannot mark already revealed") + return + } + fi.Room.Cards[i].Mark = append(card.Mark, models.CardMark{ + Username: fi.State.Username, + Active: true, + }) + } + cardword := models.WordCard{ + Word: word, + Color: color, + Revealed: false, + } + fi.Room.RevealSpecificWord(word) + fi.Room.UpdateCounter() + action := models.Action{ + Actor: fi.State.Username, + ActorColor: string(fi.State.Team), + WordColor: string(color), + Action: models.ActionTypeGuess, + Word: word, + } + fi.Room.ActionHistory = append(fi.Room.ActionHistory, action) + // if opened card is of color of opp team, change turn + oppositeColor := fi.Room.GetOppositeTeamColor() + fi.Room.OpenedThisTurn++ + if fi.Room.ThisTurnLimit > 0 { + if fi.Room.ThisTurnLimit >= fi.Room.OpenedThisTurn { + // end turn + fi.Room.TeamTurn = oppositeColor + fi.Room.MimeDone = false + fi.Room.OpenedThisTurn = 0 + fi.Room.ThisTurnLimit = 0 + } + } + switch string(color) { + case "black": + // game over + fi.Room.IsRunning = false + fi.Room.IsOver = true + fi.Room.TeamWon = oppositeColor + action := models.Action{ + Actor: fi.State.Username, + ActorColor: string(fi.State.Team), + WordColor: models.WordColorBlack, + Action: models.ActionTypeGameOver, + } + fi.Room.OpenedThisTurn = 0 + fi.Room.ThisTurnLimit = 0 + fi.Room.ActionHistory = append(fi.Room.ActionHistory, action) + case "white", string(oppositeColor): + // end turn + fi.Room.TeamTurn = oppositeColor + fi.Room.MimeDone = false + fi.Room.OpenedThisTurn = 0 + fi.Room.ThisTurnLimit = 0 + // check if no cards left => game over + if fi.Room.BlueCounter == 0 { + // blue won + fi.Room.IsRunning = false + fi.Room.IsOver = true + fi.Room.TeamWon = "blue" + action := models.Action{ + Actor: fi.State.Username, + ActorColor: string(fi.State.Team), + WordColor: models.WordColorBlue, + Action: models.ActionTypeGameOver, + } + fi.Room.OpenedThisTurn = 0 + fi.Room.ThisTurnLimit = 0 + fi.Room.ActionHistory = append(fi.Room.ActionHistory, action) + } + if fi.Room.RedCounter == 0 { + // red won + fi.Room.IsRunning = false + fi.Room.IsOver = true + fi.Room.TeamWon = "red" + action := models.Action{ + Actor: fi.State.Username, + ActorColor: string(fi.State.Team), + WordColor: models.WordColorRed, + Action: models.ActionTypeGameOver, + } + fi.Room.OpenedThisTurn = 0 + fi.Room.ThisTurnLimit = 0 + fi.Room.ActionHistory = append(fi.Room.ActionHistory, action) + } + } + if err := saveFullInfo(fi); err != nil { + abortWithError(w, err.Error()) + return + } + // get mime bot for opp team and notify it + notifyBotIfNeeded(fi) + notify(models.NotifyRoomUpdatePrefix+fi.Room.ID, "") + if err := tmpl.ExecuteTemplate(w, "cardword", cardword); err != nil { + log.Error("failed to execute cardword template", "error", err) + } +} + func HandleActionHistory(w http.ResponseWriter, r *http.Request) { fi, err := getFullInfoByCtx(r.Context()) if err != nil { diff --git a/llmapi/main.go b/llmapi/main.go index 082c96b..ae4da2e 100644 --- a/llmapi/main.go +++ b/llmapi/main.go @@ -232,6 +232,8 @@ func RemoveBot(botName string, room *models.Room) error { delete(room.BotMap, botName) delete(DoneChanMap, botName) delete(SignalChanMap, botName) + // remove role from room + room.RemovePlayer(botName) return saveRoom(room) } diff --git a/main.go b/main.go index 69154f7..d7aad1c 100644 --- a/main.go +++ b/main.go @@ -48,6 +48,7 @@ func ListenToRequests(port string) *http.Server { mux.HandleFunc("POST /check/name", handlers.HandleNameCheck) mux.HandleFunc("GET /add-bot", handlers.HandleAddBot) mux.HandleFunc("GET /remove-bot", handlers.HandleRemoveBot) + mux.HandleFunc("GET /mark-card", handlers.HandleMarkCard) // special mux.HandleFunc("GET /renotify-bot", handlers.HandleRenotifyBot) // sse diff --git a/models/main.go b/models/main.go index deb626e..7b60eab 100644 --- a/models/main.go +++ b/models/main.go @@ -63,6 +63,11 @@ type BotPlayer struct { Team UserTeam } +type CardMark struct { + Username string + Active bool +} + type Room struct { ID string `json:"id" db:"id"` CreatedAt time.Time `json:"created_at" db:"created_at"` // limit? @@ -89,11 +94,24 @@ type Room struct { RoundTime int32 `json:"round_time"` IsOver bool TeamWon UserTeam // blue | red + // + Mark CardMark // card is marked // needed for debug LogJournal []string LastActionTS time.Time } +func (r *Room) RemovePlayer(username string) { + r.RedTeam.Guessers = utils.RemoveFromSlice(username, r.RedTeam.Guessers) + r.BlueTeam.Guessers = utils.RemoveFromSlice(username, r.BlueTeam.Guessers) + if r.RedTeam.Mime == username { + r.RedTeam.Mime = "" + } + if r.BlueTeam.Mime == username { + r.BlueTeam.Mime = "" + } +} + // FindBotByTeamRole returns bot name if found; otherwise empty string func (r *Room) FindBotByTeamRole(team, role string) string { for bn, b := range r.BotMap { @@ -249,9 +267,10 @@ func (r *Room) RevealSpecificWord(word string) { } type WordCard struct { - Word string `json:"word"` - Color WordColor `json:"color"` - Revealed bool `json:"revealed"` + Word string `json:"word"` + Color WordColor `json:"color"` + Revealed bool `json:"revealed"` + Mark []CardMark `json:"marks"` } type GameSettings struct { diff --git a/todos.md b/todos.md index 69b514b..af32b5a 100644 --- a/todos.md +++ b/todos.md @@ -45,3 +45,5 @@ - guesser bot no request after game restart; - if mime joins another role, he stays as mime (before game start); - guesser llm makes up words, likely the prompt should be more clear; +- remove bot does not remove for player roles in the room; + +- remove join as mime button if there is a mime already on that team;