Feat: guess limit

This commit is contained in:
Grail Finder
2025-06-14 11:37:42 +03:00
parent bddaa912de
commit fa25679624
8 changed files with 97 additions and 64 deletions

View File

@ -14,6 +14,11 @@
<p>GAME OVER; team <span class="text-{{.Room.TeamWon}}-500">{{.Room.TeamWon}}</span> won! 🧚</p> <p>GAME OVER; team <span class="text-{{.Room.TeamWon}}-500">{{.Room.TeamWon}}</span> won! 🧚</p>
{{else}} {{else}}
<p>Turn of the <span class="text-{{.Room.TeamTurn}}-500">{{.Room.TeamTurn}}</span> team</p> <p>Turn of the <span class="text-{{.Room.TeamTurn}}-500">{{.Room.TeamTurn}}</span> team</p>
{{if .Room.MimeDone}}
<p class="text-{{.Room.TeamTurn}}-500">Waiting for guessers</p>
{{else}}
<p class="text-{{.Room.TeamTurn}}-500">Waiting for mime</p>
{{end}}
{{end}} {{end}}
<p> <p>
{{if eq .State.Team ""}} {{if eq .State.Team ""}}

View File

@ -112,18 +112,19 @@ func loadState(username string) (*models.UserState, error) {
return resp, nil return resp, nil
} }
func loadBot(botName, roomID string) (*llmapi.Bot, error) { // not used
key := "botkey_" + roomID + botName // func loadBot(botName, roomID string) (*llmapi.Bot, error) {
data, err := memcache.Get(key) // key := "botkey_" + roomID + botName
if err != nil { // data, err := memcache.Get(key)
return nil, err // if err != nil {
} // return nil, err
resp := &llmapi.Bot{} // }
if err := json.Unmarshal(data, &resp); err != nil { // resp := &llmapi.Bot{}
return nil, err // if err := json.Unmarshal(data, &resp); err != nil {
} // return nil, err
return resp, nil // }
} // return resp, nil
// }
func getAllNames() []string { func getAllNames() []string {
names := []string{} names := []string{}

View File

@ -84,6 +84,14 @@ func HandleShowColor(w http.ResponseWriter, r *http.Request) {
fi.Room.ActionHistory = append(fi.Room.ActionHistory, action) fi.Room.ActionHistory = append(fi.Room.ActionHistory, action)
// if opened card is of color of opp team, change turn // if opened card is of color of opp team, change turn
oppositeColor := fi.Room.GetOppositeTeamColor() 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) { switch string(color) {
case "black": case "black":
// game over // game over
@ -96,38 +104,46 @@ func HandleShowColor(w http.ResponseWriter, r *http.Request) {
WordColor: "black", WordColor: "black",
Action: "game over", 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)
case "white", string(oppositeColor): case "white", string(oppositeColor):
// end turn // end turn
fi.Room.TeamTurn = oppositeColor fi.Room.TeamTurn = oppositeColor
fi.Room.MimeDone = false fi.Room.MimeDone = false
} fi.Room.OpenedThisTurn = 0
// check if no cards left => game over fi.Room.ThisTurnLimit = 0
if fi.Room.BlueCounter == 0 { // check if no cards left => game over
// blue won if fi.Room.BlueCounter == 0 {
fi.Room.IsRunning = false // blue won
fi.Room.IsOver = true fi.Room.IsRunning = false
fi.Room.TeamWon = "blue" fi.Room.IsOver = true
action := models.Action{ fi.Room.TeamWon = "blue"
Actor: fi.State.Username, action := models.Action{
ActorColor: string(fi.State.Team), Actor: fi.State.Username,
WordColor: "blue", ActorColor: string(fi.State.Team),
Action: "game over", 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
if fi.Room.RedCounter == 0 { fi.Room.IsRunning = false
// red won fi.Room.IsOver = true
fi.Room.IsRunning = false fi.Room.TeamWon = "red"
fi.Room.IsOver = true action := models.Action{
fi.Room.TeamWon = "red" Actor: fi.State.Username,
action := models.Action{ ActorColor: string(fi.State.Team),
Actor: fi.State.Username, WordColor: "red",
ActorColor: string(fi.State.Team), Action: "game over",
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 { if err := saveFullInfo(fi); err != nil {
abortWithError(w, err.Error()) abortWithError(w, err.Error())

View File

@ -7,6 +7,7 @@ import (
"gralias/models" "gralias/models"
"html/template" "html/template"
"net/http" "net/http"
"strconv"
) )
func HandleCreateRoom(w http.ResponseWriter, r *http.Request) { 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.IsRunning = true
fi.Room.IsOver = false fi.Room.IsOver = false
fi.Room.TeamTurn = "blue" fi.Room.TeamTurn = "blue"
fi.Room.OpenedThisTurn = 0
fi.Room.ThisTurnLimit = 0
loadCards(fi.Room) loadCards(fi.Room)
fi.Room.UpdateCounter() fi.Room.UpdateCounter()
fi.Room.TeamWon = "" fi.Room.TeamWon = ""
@ -226,6 +229,11 @@ func HandleGiveClue(w http.ResponseWriter, r *http.Request) {
abortWithError(w, err.Error()) abortWithError(w, err.Error())
return return
} }
guessLimitU64, err := strconv.ParseUint(num, 10, 8)
if err != nil {
abortWithError(w, err.Error())
return
}
// validations === // validations ===
if fi.State.Team != models.UserTeam(fi.Room.TeamTurn) { if fi.State.Team != models.UserTeam(fi.Room.TeamTurn) {
err = errors.New("not your team's move") 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.ActionHistory = append(fi.Room.ActionHistory, action)
fi.Room.MimeDone = true fi.Room.MimeDone = true
fi.Room.ThisTurnLimit = uint8(guessLimitU64) + 1
notify(models.NotifyBacklogPrefix+fi.Room.ID, clue+num) notify(models.NotifyBacklogPrefix+fi.Room.ID, clue+num)
notifyBotIfNeeded(fi) notifyBotIfNeeded(fi)
if err := saveFullInfo(fi); err != nil { if err := saveFullInfo(fi); err != nil {

View File

@ -2,6 +2,7 @@ package llmapi
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"log/slog" "log/slog"
"strings" "strings"
@ -29,7 +30,7 @@ func (p *deepSeekParser) ParseBytes(body []byte) (map[string]any, error) {
} }
if len(dsResp.Choices) == 0 { if len(dsResp.Choices) == 0 {
p.log.Error("empty choices", "dsResp", dsResp) p.log.Error("empty choices", "dsResp", dsResp)
err := fmt.Errorf("empty choices in dsResp") err := errors.New("empty choices in dsResp")
return nil, err return nil, err
} }
text := dsResp.Choices[0].Text text := dsResp.Choices[0].Text
@ -37,7 +38,7 @@ func (p *deepSeekParser) ParseBytes(body []byte) (map[string]any, error) {
ri := strings.LastIndex(text, "}") ri := strings.LastIndex(text, "}")
if li < 0 || ri < 1 { if li < 0 || ri < 1 {
p.log.Error("not a json", "msg", text) 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 return nil, err
} }
sj := text[li : ri+1] sj := text[li : ri+1]
@ -67,7 +68,7 @@ func (p *lcpRespParser) ParseBytes(body []byte) (map[string]any, error) {
} }
if len(resp.Choices) == 0 { if len(resp.Choices) == 0 {
p.log.Error("empty choices", "resp", resp) p.log.Error("empty choices", "resp", resp)
err := fmt.Errorf("empty choices in resp") err := errors.New("empty choices in resp")
return nil, err return nil, err
} }
text := resp.Choices[0].Message.Content text := resp.Choices[0].Message.Content
@ -75,7 +76,7 @@ func (p *lcpRespParser) ParseBytes(body []byte) (map[string]any, error) {
ri := strings.LastIndex(text, "}") ri := strings.LastIndex(text, "}")
if li < 0 || ri < 1 { if li < 0 || ri < 1 {
p.log.Error("not a json", "msg", text) 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 return nil, err
} }
sj := text[li : ri+1] sj := text[li : ri+1]

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"fmt"
"gralias/config" "gralias/config"
"gralias/handlers" "gralias/handlers"
"log/slog" "log/slog"
@ -19,7 +18,7 @@ func ListenToRequests(port string) error {
mux := http.NewServeMux() mux := http.NewServeMux()
server := &http.Server{ server := &http.Server{
Handler: handlers.LogRequests(handlers.GetSession(mux)), Handler: handlers.LogRequests(handlers.GetSession(mux)),
Addr: fmt.Sprintf(":%s", port), Addr: ":" + port,
ReadTimeout: time.Second * 5, // TODO: to cfg ReadTimeout: time.Second * 5, // TODO: to cfg
WriteTimeout: 0, // sse streaming WriteTimeout: 0, // sse streaming
} }

View File

@ -58,23 +58,25 @@ type Room struct {
ID string `json:"id" db:"id"` ID string `json:"id" db:"id"`
CreatedAt time.Time `json:"created_at" db:"created_at"` CreatedAt time.Time `json:"created_at" db:"created_at"`
// RoomName string `json:"room_name"` // RoomName string `json:"room_name"`
RoomPass string `json:"room_pass"` RoomPass string `json:"room_pass"`
RoomLink string RoomLink string
CreatorName string `json:"creator_name"` CreatorName string `json:"creator_name"`
PlayerList []string `json:"player_list"` PlayerList []string `json:"player_list"`
ActionHistory []Action ActionHistory []Action
TeamTurn UserTeam TeamTurn UserTeam
RedTeam Team RedTeam Team
BlueTeam Team BlueTeam Team
Cards []WordCard Cards []WordCard
WCMap map[string]WordColor ThisTurnLimit uint8 // how many cards guessers can open this turn
BotMap map[string]BotPlayer // key is bot name OpenedThisTurn uint8 // how many cards have been opened this turn
Result uint8 // 0 for unknown; 1 is win for red; 2 if for blue; WCMap map[string]WordColor
BlueCounter uint8 BotMap map[string]BotPlayer // key is bot name
RedCounter uint8 Result uint8 // 0 for unknown; 1 is win for red; 2 if for blue;
RedTurn bool // false is blue turn BlueCounter uint8
MimeDone bool RedCounter uint8
IsPublic bool RedTurn bool // false is blue turn
MimeDone bool
IsPublic bool
// GameSettings *GameSettings `json:"settings"` // GameSettings *GameSettings `json:"settings"`
IsRunning bool `json:"is_running"` IsRunning bool `json:"is_running"`
Language string `json:"language" example:"en" form:"language"` Language string `json:"language" example:"en" form:"language"`

View File

@ -6,10 +6,9 @@
- invite link; + - invite link; +
- login with invite link; + - login with invite link; +
- add html icons of whos turn it is (like an image of big ? when mime is thinking); - 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; - 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); - 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; - 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; - hide clue input for mime when it's not their turn;
- needs resend to llm btn; + - needs resend to llm btn; +
@ -26,7 +25,8 @@
### issues ### issues
- after the game started (isrunning) players should be able join guessers, but not switch team, or join as a mime; - 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; - 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); - 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; - after starting a new game (after old one) blue mime has no clue input;
- gameover to backlog; - gameover to backlog;