- {{if and (eq .State.Username .Room.CreatorName) (.Room.IsRunning)}}
+ {{if and (eq .State.Username .Room.CreatorName) (.Room.BotFailed)}}
{{end}}
diff --git a/handlers/elements.go b/handlers/elements.go
index b6d2ca1..8867332 100644
--- a/handlers/elements.go
+++ b/handlers/elements.go
@@ -42,8 +42,9 @@ func HandleShowColor(w http.ResponseWriter, r *http.Request) {
return
}
fi, err := getFullInfoByCtx(ctx)
- if err != nil {
- abortWithError(w, err.Error())
+ if err != nil || fi == nil {
+ log.Error("failed to fetch fi", "error", err)
+ http.Redirect(w, r, "/", 302)
return
}
if err := validateMove(fi, models.UserRoleGuesser); err != nil {
@@ -206,8 +207,9 @@ func HandleMarkCard(w http.ResponseWriter, r *http.Request) {
return
}
fi, err := getFullInfoByCtx(ctx)
- if err != nil {
- abortWithError(w, err.Error())
+ if err != nil || fi == nil {
+ log.Error("failed to fetch fi", "error", err)
+ http.Redirect(w, r, "/", 302)
return
}
if err := validateMove(fi, models.UserRoleGuesser); err != nil {
@@ -274,8 +276,9 @@ func HandleMarkCard(w http.ResponseWriter, r *http.Request) {
func HandleActionHistory(w http.ResponseWriter, r *http.Request) {
fi, err := getFullInfoByCtx(r.Context())
- if err != nil {
- abortWithError(w, err.Error())
+ if err != nil || fi == nil {
+ log.Error("failed to fetch fi", "error", err)
+ http.Redirect(w, r, "/", 302)
return
}
tmpl, err := template.ParseGlob("components/*.html")
@@ -293,8 +296,9 @@ func HandleAddBot(w http.ResponseWriter, r *http.Request) {
team := r.URL.Query().Get("team")
role := r.URL.Query().Get("role")
fi, err := getFullInfoByCtx(r.Context())
- if err != nil {
- abortWithError(w, err.Error())
+ if err != nil || fi == nil {
+ log.Error("failed to fetch fi", "error", err)
+ http.Redirect(w, r, "/", 302)
return
}
var botname string
@@ -319,8 +323,9 @@ func HandleRemoveBot(w http.ResponseWriter, r *http.Request) {
botName := r.URL.Query().Get("bot")
log.Debug("got remove-bot request", "bot_name", botName)
fi, err := getFullInfoByCtx(r.Context())
- if err != nil {
- abortWithError(w, err.Error())
+ if err != nil || fi == nil {
+ log.Error("failed to fetch fi", "error", err)
+ http.Redirect(w, r, "/", 302)
return
}
if err := llmapi.RemoveBot(botName, fi.Room); err != nil {
diff --git a/handlers/game.go b/handlers/game.go
index 11a286c..a34e657 100644
--- a/handlers/game.go
+++ b/handlers/game.go
@@ -72,8 +72,9 @@ func HandleJoinTeam(w http.ResponseWriter, r *http.Request) {
}
// get username
fi, err := getFullInfoByCtx(r.Context())
- if err != nil {
- abortWithError(w, err.Error())
+ if err != nil || fi == nil {
+ log.Error("failed to fetch fi", "error", err)
+ http.Redirect(w, r, "/", 302)
return
}
if fi.Room == nil {
@@ -111,8 +112,9 @@ func HandleJoinTeam(w http.ResponseWriter, r *http.Request) {
func HandleEndTurn(w http.ResponseWriter, r *http.Request) {
// get username
fi, err := getFullInfoByCtx(r.Context())
- if err != nil {
- abortWithError(w, err.Error())
+ if err != nil || fi == nil {
+ log.Error("failed to fetch fi", "error", err)
+ http.Redirect(w, r, "/", 302)
return
}
// check if one who pressed it is from the team who has the turn
@@ -143,8 +145,9 @@ func HandleEndTurn(w http.ResponseWriter, r *http.Request) {
func HandleStartGame(w http.ResponseWriter, r *http.Request) {
fi, err := getFullInfoByCtx(r.Context())
- if err != nil {
- abortWithError(w, err.Error())
+ if err != nil || fi == nil {
+ log.Error("failed to fetch fi", "error", err)
+ http.Redirect(w, r, "/", 302)
return
}
// check if enough players
@@ -293,8 +296,9 @@ func HandleGiveClue(w http.ResponseWriter, r *http.Request) {
clue := r.PostFormValue("clue")
num := r.PostFormValue("number")
fi, err := getFullInfoByCtx(r.Context())
- if err != nil {
- abortWithError(w, err.Error())
+ if err != nil || fi == nil {
+ log.Error("failed to fetch fi", "error", err)
+ http.Redirect(w, r, "/", 302)
return
}
guessLimitU64, err := strconv.ParseUint(num, 10, 8)
@@ -360,8 +364,9 @@ func HandleGiveClue(w http.ResponseWriter, r *http.Request) {
func HandleRenotifyBot(w http.ResponseWriter, r *http.Request) {
fi, err := getFullInfoByCtx(r.Context())
- if err != nil {
- abortWithError(w, err.Error())
+ if err != nil || fi == nil {
+ log.Error("failed to fetch fi", "error", err)
+ http.Redirect(w, r, "/", 302)
return
}
notifyBotIfNeeded(fi.Room)
diff --git a/handlers/handlers.go b/handlers/handlers.go
index 20b9398..f7b56c0 100644
--- a/handlers/handlers.go
+++ b/handlers/handlers.go
@@ -75,8 +75,9 @@ func HandleExit(w http.ResponseWriter, r *http.Request) {
return
}
fi, err := getFullInfoByCtx(r.Context())
- if err != nil {
- abortWithError(w, err.Error())
+ if err != nil || fi == nil {
+ log.Error("failed to fetch fi", "error", err)
+ http.Redirect(w, r, "/", 302)
return
}
if fi.Room.IsRunning {
diff --git a/llmapi/main.go b/llmapi/main.go
index 8d18853..0742069 100644
--- a/llmapi/main.go
+++ b/llmapi/main.go
@@ -25,10 +25,10 @@ var (
DoneChanMap = make(map[string]chan bool)
mapMutex = &sync.RWMutex{}
// got prompt: control character (\\u0000-\\u001F) found while parsing a string at line 4 column 0
- // MimePrompt = `we are playing alias;\nyou are a mime (player who gives a clue of one noun word and number of cards you expect them to open) of the %s team (people who would guess by your clue want open the %s cards);\nplease return your clue, number of cards to open and what words you mean them to find using that clue in json like:\n{\n\"clue\": \"one-word-noun\",\n\"number\": \"number-from-0-to-9\",\n\"words_I_mean_my_team_to_open\": [\"this\", \"that\", ...]\n}\nthe team who openes all their cards first wins.\nplease return json only.\nunopen Blue cards left: %d;\nunopen Red cards left: %d;\nhere is the game info in json:\n%s`
- // GuesserPrompt = `we are playing alias;\nyou are to guess words of the %s team (you want open %s cards) by given clue and a number of meant guesses;\nplease return your guesses and words that could be meant by the clue, but you do not wish to open yet, in json like:\n{\n\"guesses\": [\"word1\", \"word2\", ...],\n\"could_be\": [\"this\", \"that\", ...]\n}\nthe team who openes all their cards first wins.\nplease return json only.\nunopen Blue cards left: %d;\nunopen Red cards left: %d;\nhere is the cards (and other info), you need to choose revealed==false words:\n%s`
- GuesserSimplePrompt = `we are playing game of alias;\n you were given a clue: \"%s\";\nplease return your guess and words that could be meant by the clue, but you do not wish to open yet, in json like:\n{\n\"guess\": \"most_relevant_word_to_the_clue\",\n\"could_be\": [\"this\", \"that\", ...]\n}\nhere is the words that you can choose from:\n%v`
- MimeSimplePrompt = `we are playing alias;\nyou are to give one word clue and a number of words you mean your team to open; your team words: %v;\nhere are the words of opposite team you want to avoid: %v;\nand here is a black word that is critical not to pick: %s;\nplease return your clue, number of cards to open and what words you mean them to find using that clue in json like:\n{\n\"clue\": \"one-word-noun\",\n\"number\": \"number-from-0-to-9-as-string\",\n\"words_I_mean_my_team_to_open\": [\"this\", \"that\", ...]\n}\nplease return json only.\nunopen Blue cards left: %d;\nunopen Red cards left: %d;`
+ GuesserSimplePrompt = `we are playing game of alias;\n you were given a clue: \"%s\";\nplease return your guess and words that could be meant by the clue, but you do not wish to open yet, in json like:\n{\n\"guess\": \"most_relevant_word_to_the_clue\",\n\"could_be\": [\"this\", \"that\", ...]\n}\nhere is the words that you can choose from:\n%v`
+ MimeSimplePrompt = `we are playing alias;\nyou are to give one word clue and a number of words you mean your team to open; your team words: %v;\nhere are the words of opposite team you want to avoid: %v;\nand here is a black word that is critical not to pick: %s;\nplease return your clue, number of cards to open and what words you mean them to find using that clue in json like:\n{\n\"clue\": \"one-word-noun\",\n\"number\": \"number-from-0-to-9-as-string\",\n\"words_I_mean_my_team_to_open\": [\"this\", \"that\", ...]\n}\nplease return json only.`
+ GuesserSimplePromptRU = `мы играем в alias;\n тебе дана подсказка (clue): \"%s\";\nпожалуйста, верни свою догадку (guess), а также слова, что тоже подходят к подсказке, но ты меньше в них уверен, в формате json; пример:\n{\n\"guess\": \"отгадка\",\n\"could_be\": [\"слово1\", \"слово2\", ...]\n}\nвот список слов из которых нужно выбрать:\n%v`
+ MimeSimplePromptRU = `мы играем в alias;\nтебе нужно дать подсказку одним словом и число слов, что ты подразумевал этой подсказкой; слова твоей комманды: %v;\nслова противоположной комманды, что ты хочешь избежать: %v;\nи вот ЧЕРНОЕ СЛОВО, открыв которое твоя комманда проиграет игру: %s;\nпожалуйста, верни подсказку (одним словом) и количество слов, что ты подразумеваешь в формате json; пример:\n{\n\"clue\": \"подсказка\",\n\"number\": \"число-от-0-до-9-as-string\",\n\"words_I_mean_my_team_to_open\": [\"слово1\", \"слово2\", ...]\n}\nпожалуйста верни только json.`
)
func convertToSliceOfStrings(value any) ([]string, error) {
@@ -188,6 +188,11 @@ func (b *Bot) BotMove() {
b.log.Error("bot loop", "error", err)
return
}
+ if room.BotFailed {
+ if err := repo.RoomUnSetBotFailed(context.Background(), room.ID); err != nil {
+ b.log.Error("failed to unset bot failed bool", "error", err)
+ }
+ }
// eventName := models.NotifyBacklogPrefix + room.ID
eventName := models.NotifyRoomUpdatePrefix + room.ID
eventPayload := ""
@@ -231,6 +236,9 @@ func (b *Bot) BotMove() {
b.log.Warn("failed to write to journal", "entry", lj)
}
b.log.Error("bot loop", "error", err)
+ if err := repo.RoomSetBotFailed(context.Background(), room.ID); err != nil {
+ b.log.Error("failed to set bot failed bool", "error", err)
+ }
return
}
tempMap, err := b.LLMParser.ParseBytes(llmResp)
@@ -545,6 +553,9 @@ func (b *Bot) BuildSimpleGuesserPrompt(room *models.Room) string {
}
words[i] = card.Word
}
+ if strings.EqualFold(room.Settings.Language, "ru") {
+ return fmt.Sprintf(MimeSimplePromptRU, clueAction.Word, words)
+ }
return fmt.Sprintf(GuesserSimplePrompt, clueAction.Word, words)
}
@@ -573,34 +584,20 @@ func (b *Bot) BuildSimpleMimePrompt(room *models.Room) string {
theirwords = append(theirwords, card.Word)
}
}
- return fmt.Sprintf(MimeSimplePrompt, ourwords, theirwords, blackWord, room.BlueCounter, room.RedCounter)
+ if strings.EqualFold(room.Settings.Language, "ru") {
+ return fmt.Sprintf(MimeSimplePromptRU, ourwords, theirwords, blackWord)
+ }
+ return fmt.Sprintf(MimeSimplePrompt, ourwords, theirwords, blackWord)
}
func (b *Bot) BuildPrompt(room *models.Room) string {
if b.Role == "" {
return ""
}
- // toText := make(map[string]any)
- // toText["backlog"] = room.ActionHistory
- // // mime sees all colors;
- // // guesser sees only revealed ones
- // if b.Role == models.UserRoleMime {
- // toText["cards"] = room.Cards
- // }
- // data, err := json.Marshal(toText)
- // if err != nil {
- // b.log.Error("failed to marshal", "error", err)
- // return ""
- // }
- // Escape the JSON string for inclusion in another JSON field
- // escapedData := strings.ReplaceAll(string(data), `"`, `\\"`)
if b.Role == models.UserRoleMime {
- // return fmt.Sprintf(MimeSimplePrompt, b.Team, b.Team, room.BlueCounter, room.RedCounter, escapedData)
- // return fmt.Sprintf(MimePrompt, b.Team, b.Team, room.BlueCounter, room.RedCounter, escapedData)
return b.BuildSimpleMimePrompt(room)
}
if b.Role == models.UserRoleGuesser {
- // return fmt.Sprintf(GuesserPrompt, b.Team, b.Team, room.BlueCounter, room.RedCounter, escapedData)
return b.BuildSimpleGuesserPrompt(room)
}
return ""
@@ -666,16 +663,5 @@ func (b *Bot) CallLLM(prompt string) ([]byte, error) {
b.log.Debug("llm resp", "body", string(body), "url", b.cfg.LLMConfig.URL, "attempt", attempt)
return body, nil
}
- entry := fmt.Sprintf("bot '%s' exceeded attempts to call llm;", b.BotName)
- lj := models.Journal{
- Entry: entry,
- Username: b.BotName,
- RoomID: b.RoomID,
- }
- if err := repo.JournalCreate(context.Background(), &lj); err != nil {
- b.log.Warn("failed to write to journal", "entry", lj)
- }
- // notify room
- // This line should not be reached because each error path returns in the loop.
return nil, errors.New("unknown error in retry loop")
}
diff --git a/migrations/001_initial_schema.up.sql b/migrations/001_initial_schema.up.sql
index bae52bd..d5c696a 100644
--- a/migrations/001_initial_schema.up.sql
+++ b/migrations/001_initial_schema.up.sql
@@ -13,6 +13,7 @@ CREATE TABLE rooms (
mime_done BOOLEAN NOT NULL DEFAULT FALSE,
is_running BOOLEAN NOT NULL DEFAULT FALSE,
is_over BOOLEAN NOT NULL DEFAULT FALSE,
+ bot_failed BOOLEAN NOT NULL DEFAULT FALSE,
team_won TEXT NOT NULL DEFAULT '',
room_link TEXT NOT NULL DEFAULT ''
);
diff --git a/models/main.go b/models/main.go
index 8bab780..e53d395 100644
--- a/models/main.go
+++ b/models/main.go
@@ -178,6 +178,8 @@ type Room struct {
BotMap map[string]BotPlayer `db:"-"`
LogJournal []Journal `db:"-"`
Settings GameSettings `db:"-"`
+ //
+ BotFailed bool `db:"bot_failed"`
}
func (r *Room) FindColor(word string) (WordColor, bool) {
diff --git a/repos/rooms.go b/repos/rooms.go
index 87b901a..343f984 100644
--- a/repos/rooms.go
+++ b/repos/rooms.go
@@ -15,6 +15,20 @@ type RoomsRepo interface {
RoomCreate(ctx context.Context, room *models.Room) error
RoomDeleteByID(ctx context.Context, id string) error
RoomUpdate(ctx context.Context, room *models.Room) error
+ RoomSetBotFailed(ctx context.Context, roomID string) error
+ RoomUnSetBotFailed(ctx context.Context, roomID string) error
+}
+
+func (p *RepoProvider) RoomSetBotFailed(ctx context.Context, roomID string) error {
+ db := getDB(ctx, p.DB)
+ _, err := db.ExecContext(ctx, "UPDATE rooms SET bot_failed = true WHERE id = ?", roomID)
+ return err
+}
+
+func (p *RepoProvider) RoomUnSetBotFailed(ctx context.Context, roomID string) error {
+ db := getDB(ctx, p.DB)
+ _, err := db.ExecContext(ctx, "UPDATE rooms SET bot_failed = false WHERE id = ?", roomID)
+ return err
}
func (p *RepoProvider) RoomList(ctx context.Context) ([]*models.Room, error) {
diff --git a/todos.md b/todos.md
index 3f1fb02..e725224 100644
--- a/todos.md
+++ b/todos.md
@@ -21,8 +21,8 @@
- redo card .revealed use: it should mean that card is revealed for everybody, while mime should be able to see cards as is; +
- better styles and fluff;
- common auth system between sites;
-- signup vs login;
-- passwords (to room and to login);
+- signup vs login; +
+- passwords (to room and to login); +
===
- show in backlog (and with that in prompt to llm) how many cards are left to open, also additional comment: if guess was right;
- gameover to backlog;
@@ -33,7 +33,7 @@
- possibly turn markings into parts of names of users (first three letters?); +
- at game creation list languages and support them at backend; +
- sql ping goroutine with reconnect on fail; +
-- player stats: played games, lost, won, rating elo, opened opposite words, opened white words, opened black words.
+- player stats: played games, lost, won, rating elo, opened opposite words, opened white words, opened black words. +
- at the end of the game, all colors should be revealed;
- tracing;
@@ -91,5 +91,5 @@
- mime sees the clue input out of turn; (eh)
- there is a problem of two timers, they both could switch turn, but it is not easy to stop them from llmapi or handlers. +
- journal still does not work; +
-- lose/win game; then exit room (while being the creator), then press to stats -> cannot find session in db, although cookie in place and session in db;
-- exit endpoints delets player from db;
+- lose/win game; then exit room (while being the creator), then press to stats -> cannot find session in db, although cookie in place and session in db; +
+- exit endpoints delets player from db; +