diff --git a/components/room.html b/components/room.html index a079694..df752a9 100644 --- a/components/room.html +++ b/components/room.html @@ -14,6 +14,11 @@

GAME OVER; team {{.Room.TeamWon}} won! 🧚

{{else}}

Turn of the {{.Room.TeamTurn}} team

+ {{if .Room.MimeDone}} +

Waiting for guessers

+ {{else}} +

Waiting for mime

+ {{end}} {{end}}

{{if eq .State.Team ""}} diff --git a/handlers/actions.go b/handlers/actions.go index 6de4866..9019c12 100644 --- a/handlers/actions.go +++ b/handlers/actions.go @@ -112,18 +112,19 @@ func loadState(username string) (*models.UserState, error) { return resp, nil } -func loadBot(botName, roomID string) (*llmapi.Bot, error) { - key := "botkey_" + roomID + botName - data, err := memcache.Get(key) - if err != nil { - return nil, err - } - resp := &llmapi.Bot{} - if err := json.Unmarshal(data, &resp); err != nil { - return nil, err - } - return resp, nil -} +// not used +// func loadBot(botName, roomID string) (*llmapi.Bot, error) { +// key := "botkey_" + roomID + botName +// data, err := memcache.Get(key) +// if err != nil { +// return nil, err +// } +// resp := &llmapi.Bot{} +// if err := json.Unmarshal(data, &resp); err != nil { +// return nil, err +// } +// return resp, nil +// } func getAllNames() []string { names := []string{} diff --git a/handlers/elements.go b/handlers/elements.go index b7a2281..f65947d 100644 --- a/handlers/elements.go +++ b/handlers/elements.go @@ -84,6 +84,14 @@ func HandleShowColor(w http.ResponseWriter, r *http.Request) { 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 >= 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 @@ -96,38 +104,46 @@ func HandleShowColor(w http.ResponseWriter, r *http.Request) { WordColor: "black", Action: "game over", } + 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 - } - // 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: "blue", - Action: "game over", + 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: "blue", + Action: "game over", + } + fi.Room.OpenedThisTurn = 0 + fi.Room.ThisTurnLimit = 0 + fi.Room.ActionHistory = append(fi.Room.ActionHistory, action) } - 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: "red", - Action: "game over", + 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: "red", + Action: "game over", + } + fi.Room.OpenedThisTurn = 0 + fi.Room.ThisTurnLimit = 0 + fi.Room.ActionHistory = append(fi.Room.ActionHistory, action) } - fi.Room.ActionHistory = append(fi.Room.ActionHistory, action) } if err := saveFullInfo(fi); err != nil { abortWithError(w, err.Error()) diff --git a/handlers/game.go b/handlers/game.go index 4da845c..86e9691 100644 --- a/handlers/game.go +++ b/handlers/game.go @@ -7,6 +7,7 @@ import ( "gralias/models" "html/template" "net/http" + "strconv" ) func HandleCreateRoom(w http.ResponseWriter, r *http.Request) { @@ -143,6 +144,8 @@ func HandleStartGame(w http.ResponseWriter, r *http.Request) { fi.Room.IsRunning = true fi.Room.IsOver = false fi.Room.TeamTurn = "blue" + fi.Room.OpenedThisTurn = 0 + fi.Room.ThisTurnLimit = 0 loadCards(fi.Room) fi.Room.UpdateCounter() fi.Room.TeamWon = "" @@ -226,6 +229,11 @@ func HandleGiveClue(w http.ResponseWriter, r *http.Request) { abortWithError(w, err.Error()) return } + guessLimitU64, err := strconv.ParseUint(num, 10, 8) + if err != nil { + abortWithError(w, err.Error()) + return + } // validations === if fi.State.Team != models.UserTeam(fi.Room.TeamTurn) { err = errors.New("not your team's move") @@ -253,6 +261,7 @@ func HandleGiveClue(w http.ResponseWriter, r *http.Request) { } fi.Room.ActionHistory = append(fi.Room.ActionHistory, action) fi.Room.MimeDone = true + fi.Room.ThisTurnLimit = uint8(guessLimitU64) + 1 notify(models.NotifyBacklogPrefix+fi.Room.ID, clue+num) notifyBotIfNeeded(fi) if err := saveFullInfo(fi); err != nil { diff --git a/llmapi/parser.go b/llmapi/parser.go index 72dbe52..24a8253 100644 --- a/llmapi/parser.go +++ b/llmapi/parser.go @@ -2,6 +2,7 @@ package llmapi import ( "encoding/json" + "errors" "fmt" "log/slog" "strings" @@ -29,7 +30,7 @@ func (p *deepSeekParser) ParseBytes(body []byte) (map[string]any, error) { } if len(dsResp.Choices) == 0 { p.log.Error("empty choices", "dsResp", dsResp) - err := fmt.Errorf("empty choices in dsResp") + err := errors.New("empty choices in dsResp") return nil, err } text := dsResp.Choices[0].Text @@ -37,7 +38,7 @@ func (p *deepSeekParser) ParseBytes(body []byte) (map[string]any, error) { ri := strings.LastIndex(text, "}") if li < 0 || ri < 1 { p.log.Error("not a json", "msg", text) - err := fmt.Errorf("fn: ParseBytes, not a json") + err := fmt.Errorf("fn: ParseBytes, not a json; data: %s", text) return nil, err } sj := text[li : ri+1] @@ -67,7 +68,7 @@ func (p *lcpRespParser) ParseBytes(body []byte) (map[string]any, error) { } if len(resp.Choices) == 0 { p.log.Error("empty choices", "resp", resp) - err := fmt.Errorf("empty choices in resp") + err := errors.New("empty choices in resp") return nil, err } text := resp.Choices[0].Message.Content @@ -75,7 +76,7 @@ func (p *lcpRespParser) ParseBytes(body []byte) (map[string]any, error) { ri := strings.LastIndex(text, "}") if li < 0 || ri < 1 { p.log.Error("not a json", "msg", text) - err := fmt.Errorf("fn: ParseBytes, not a json") + err := fmt.Errorf("fn: ParseBytes, not a json; data: %s", text) return nil, err } sj := text[li : ri+1] diff --git a/main.go b/main.go index bc818c0..40da06c 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,6 @@ package main import ( - "fmt" "gralias/config" "gralias/handlers" "log/slog" @@ -19,7 +18,7 @@ func ListenToRequests(port string) error { mux := http.NewServeMux() server := &http.Server{ Handler: handlers.LogRequests(handlers.GetSession(mux)), - Addr: fmt.Sprintf(":%s", port), + Addr: ":" + port, ReadTimeout: time.Second * 5, // TODO: to cfg WriteTimeout: 0, // sse streaming } diff --git a/models/main.go b/models/main.go index 0b11243..c74583c 100644 --- a/models/main.go +++ b/models/main.go @@ -58,23 +58,25 @@ type Room struct { ID string `json:"id" db:"id"` CreatedAt time.Time `json:"created_at" db:"created_at"` // RoomName string `json:"room_name"` - RoomPass string `json:"room_pass"` - RoomLink string - CreatorName string `json:"creator_name"` - PlayerList []string `json:"player_list"` - ActionHistory []Action - TeamTurn UserTeam - RedTeam Team - BlueTeam Team - Cards []WordCard - WCMap map[string]WordColor - BotMap map[string]BotPlayer // key is bot name - Result uint8 // 0 for unknown; 1 is win for red; 2 if for blue; - BlueCounter uint8 - RedCounter uint8 - RedTurn bool // false is blue turn - MimeDone bool - IsPublic bool + RoomPass string `json:"room_pass"` + RoomLink string + CreatorName string `json:"creator_name"` + PlayerList []string `json:"player_list"` + ActionHistory []Action + TeamTurn UserTeam + RedTeam Team + BlueTeam Team + Cards []WordCard + ThisTurnLimit uint8 // how many cards guessers can open this turn + OpenedThisTurn uint8 // how many cards have been opened this turn + WCMap map[string]WordColor + BotMap map[string]BotPlayer // key is bot name + Result uint8 // 0 for unknown; 1 is win for red; 2 if for blue; + BlueCounter uint8 + RedCounter uint8 + RedTurn bool // false is blue turn + MimeDone bool + IsPublic bool // GameSettings *GameSettings `json:"settings"` IsRunning bool `json:"is_running"` Language string `json:"language" example:"en" form:"language"` diff --git a/todos.md b/todos.md index 7f0359d..1fa1a4e 100644 --- a/todos.md +++ b/todos.md @@ -6,10 +6,9 @@ - invite link; + - login with invite link; + - add html icons of whos turn it is (like an image of big ? when mime is thinking); -- there three places for bot to check if its its move: start-game; end-turn, after mime gave clue; -- remove bot button (if game is not running, or bot already added); +- there three places for bot to check if its its move: start-game; end-turn, after mime gave clue; + +- remove bot button (if game is not running, or bot already added); + - show in backlog (and with that in prompt to llm) how many cards are left to open, also additional comment: if guess was right; -- if bot already added; remove add bot button; - hide clue input for mime when it's not their turn; - needs resend to llm btn; + @@ -26,7 +25,8 @@ ### issues - after the game started (isrunning) players should be able join guessers, but not switch team, or join as a mime; - cleanup backlog after new game is started; -- guessers should not be able to open more cards, than mime gave them +1; +- guessers should not be able to open more cards, than mime gave them +1 (auto end turn); + +- 0 should mean without limit; - sse hangs / fails connection which causes to wait for cards to open a few seconds (on local machine); - after starting a new game (after old one) blue mime has no clue input; - gameover to backlog;