Compare commits

...

2 Commits

Author SHA1 Message Date
Grail Finder
b64c3a4eab Enha: mutexes for global maps 2025-07-11 12:21:51 +03:00
Grail Finder
d41ed9d822 Chore: debug logs 2025-07-11 11:58:57 +03:00
5 changed files with 45 additions and 13 deletions

View File

@@ -59,7 +59,7 @@ func getFullInfoByCtx(ctx context.Context) (*models.FullInfo, error) {
}
resp.State = state
if state.RoomID == nil || *state.RoomID == "" {
log.Debug("returning state without room", "username", state.Username)
// log.Debug("returning state without room", "username", state.Username)
return resp, nil
}
// room, err := getRoomByID(state.RoomID)

View File

@@ -293,7 +293,6 @@ func HandleAddBot(w http.ResponseWriter, r *http.Request) {
// get team; // get role; make up a name
team := r.URL.Query().Get("team")
role := r.URL.Query().Get("role")
log.Debug("got add-bot request", "team", team, "role", role)
fi, err := getFullInfoByCtx(r.Context())
if err != nil {
abortWithError(w, err.Error())
@@ -307,6 +306,7 @@ func HandleAddBot(w http.ResponseWriter, r *http.Request) {
} else {
botname = fmt.Sprintf("bot_%d", maxID+1) // what if many rooms?
}
log.Debug("got add-bot request", "team", team, "role", role, "max_id", maxID, "botname", botname, "error", err)
_, err = llmapi.NewBot(role, team, botname, fi.Room.ID, cfg, false)
if err != nil {
abortWithError(w, err.Error())

View File

@@ -62,7 +62,7 @@ func GetSession(next http.Handler) http.Handler {
}
userSession, err := repo.SessionByToken(r.Context(), sessionToken)
if err != nil {
msg := "auth failed; session does not exists"
msg := "auth failed; session does not exist"
log.Debug(msg, "error", err, "key", sessionToken)
next.ServeHTTP(w, r)
return

View File

@@ -14,6 +14,7 @@ import (
"os"
"strconv"
"strings"
"sync"
"time"
)
@@ -22,9 +23,10 @@ var (
repo = repos.RP
SignalChanMap = make(map[string]chan bool)
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`
// 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;`
)
@@ -202,7 +204,11 @@ func (b *Bot) BotMove() {
}
if botName := room.WhichBotToMove(); botName != "" {
b.log.Debug("notifying bot", "name", botName)
SignalChanMap[botName] <- true
mapMutex.RLock()
if sigChan, ok := SignalChanMap[botName]; ok {
sigChan <- true
}
mapMutex.RUnlock()
b.log.Debug("after sending the signal", "name", botName)
}
broker.Notifier.Notifier <- broker.NotificationEvent{
@@ -327,11 +333,21 @@ func (b *Bot) BotMove() {
// StartBot
func (b *Bot) StartBot() {
mapMutex.Lock()
signalChan, sOk := SignalChanMap[b.BotName]
doneChan, dOk := DoneChanMap[b.BotName]
mapMutex.Unlock()
if !sOk || !dOk {
b.log.Error("bot channels not found in map", "bot-name", b.BotName)
return
}
for {
select {
case <-SignalChanMap[b.BotName]:
case <-signalChan:
b.BotMove()
case <-DoneChanMap[b.BotName]:
case <-doneChan:
b.log.Debug("got done signal", "bot-name", b.BotName)
return
}
@@ -339,14 +355,21 @@ func (b *Bot) StartBot() {
}
func RemoveBot(botName string, room *models.Room) error {
mapMutex.Lock()
// channels
DoneChanMap[botName] <- true
close(DoneChanMap[botName])
close(SignalChanMap[botName])
if doneChan, ok := DoneChanMap[botName]; ok {
doneChan <- true
close(doneChan)
}
if signalChan, ok := SignalChanMap[botName]; ok {
close(signalChan)
}
// maps
delete(room.BotMap, botName)
delete(DoneChanMap, botName)
delete(SignalChanMap, botName)
mapMutex.Unlock()
delete(room.BotMap, botName)
// remove role from room
room.RemovePlayer(botName)
slog.Debug("removing bot player", "name", botName, "room_id", room.ID, "room", room)
@@ -358,6 +381,7 @@ func RemoveBot(botName string, room *models.Room) error {
}
func RemoveBotNoRoom(botName string) error {
mapMutex.Lock()
// channels
dc, ok := DoneChanMap[botName]
if ok {
@@ -371,6 +395,7 @@ func RemoveBotNoRoom(botName string) error {
// maps
delete(DoneChanMap, botName)
delete(SignalChanMap, botName)
mapMutex.Unlock()
// remove role from room
return repo.PlayerDelete(context.Background(), botName)
}
@@ -447,9 +472,13 @@ func NewBot(role, team, name, roomID string, cfg *config.Config, recovery bool)
return nil, err
}
}
bot.log.Debug("before adding to ch map", "name", bot.BotName)
// buffered channel to send to it in the same goroutine
mapMutex.Lock()
SignalChanMap[bot.BotName] = make(chan bool, 1)
DoneChanMap[bot.BotName] = make(chan bool, 1)
mapMutex.Unlock()
bot.log.Debug("after adding to ch map", "name", bot.BotName)
go bot.StartBot() // run bot routine
return bot, nil
}
@@ -542,7 +571,7 @@ func (b *Bot) BuildPrompt(room *models.Room) string {
// return ""
// }
// Escape the JSON string for inclusion in another JSON field
// escapedData := strings.ReplaceAll(string(data), `"`, `\"`)
// 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)
@@ -618,3 +647,4 @@ func (b *Bot) CallLLM(prompt string) ([]byte, error) {
// This line should not be reached because each error path returns in the loop.
return nil, errors.New("unknown error in retry loop")
}

View File

@@ -89,3 +89,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;
- player got deleted from db;