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}}
+
{{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;