Files
gralias/handlers/actions.go

369 lines
9.2 KiB
Go

package handlers
import (
"context"
"encoding/json"
"errors"
"fmt"
"gralias/broker"
"gralias/llmapi"
"gralias/models"
"gralias/utils"
"gralias/wordloader"
"strings"
)
func createRoom(ctx context.Context, req *models.RoomReq) (*models.Room, error) {
creator, ok := ctx.Value(models.CtxUsernameKey).(string)
if !ok {
err := errors.New("failed to extract user from ctx")
return nil, err
}
room := req.CreateRoom(creator)
room.RoomLink = cfg.BaseURL + "/room-join?id=" + room.ID
if err := saveRoom(room); err != nil {
return nil, err
}
return room, nil
}
func saveRoom(room *models.Room) error {
key := models.CacheRoomPrefix + room.ID
data, err := json.Marshal(room)
if err != nil {
return err
}
memcache.Set(key, data)
return nil
}
func getRoomByID(roomID string) (*models.Room, error) {
roomBytes, err := memcache.Get(models.CacheRoomPrefix + roomID)
if err != nil {
return nil, err
}
resp := &models.Room{}
if err := json.Unmarshal(roomBytes, &resp); err != nil {
return nil, err
}
return resp, nil
}
func removeRoom(roomID string) {
key := models.CacheRoomPrefix + roomID
memcache.RemoveKey(key)
}
// context
func getStateByCtx(ctx context.Context) (*models.UserState, error) {
username, ok := ctx.Value(models.CtxUsernameKey).(string)
if !ok {
log.Debug("no username in ctx")
return &models.UserState{}, errors.New("no username in ctx")
}
us, err := loadState(username)
if err != nil {
return &models.UserState{}, err
}
return us, nil
}
func saveFullInfo(fi *models.FullInfo) error {
// INFO: no transactions; so case is possible where first object is updated but the second is not
if err := saveState(fi.State.Username, fi.State); err != nil {
return err
}
log.Debug("saved user state", "state", fi.State)
if err := saveRoom(fi.Room); err != nil {
return err
}
return nil
}
func notifyBotIfNeeded(fi *models.FullInfo) {
if botName := fi.Room.WhichBotToMove(); botName != "" {
log.Debug("got botname", "name", botName, "channel_len", len(llmapi.SignalChanMap[botName]))
llmapi.SignalChanMap[botName] <- true
log.Debug("after sending signal", "name", botName)
}
}
// cache
func saveState(username string, state *models.UserState) error {
key := models.CacheStatePrefix + username
data, err := json.Marshal(state)
if err != nil {
return err
}
memcache.Set(key, data)
return nil
}
func loadState(username string) (*models.UserState, error) {
key := models.CacheStatePrefix + username
data, err := memcache.Get(key)
if err != nil {
return nil, err
}
resp := &models.UserState{}
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{}
// will not scale
wholeMemStore := memcache.GetAll()
session := &models.Session{}
// filter by key size only sessions
for k, v := range wholeMemStore {
// xid is 20 in len
if len(k) != 20 {
continue
}
if err := json.Unmarshal(v, &session); err != nil {
log.Error("failed to unmarshal", "error", err)
continue
}
names = append(names, session.Username)
}
return names
}
// can room exists without state? I think no
func getFullInfoByCtx(ctx context.Context) (*models.FullInfo, error) {
resp := &models.FullInfo{}
state, err := getStateByCtx(ctx)
if err != nil {
return nil, err
}
resp.State = state
if state.RoomID == "" {
return resp, nil
}
room, err := getRoomByID(state.RoomID)
if err != nil {
log.Warn("failed to find room despite knowing room_id;",
"room_id", state.RoomID)
return nil, err
}
resp.Room = room
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 = ""
}
}
func joinTeam(ctx context.Context, role, team string) (*models.FullInfo, error) {
// get username
fi, _ := getFullInfoByCtx(ctx)
// leave gueesers if present
leaveRole(fi)
// get room
if role == "mime" {
if team == "blue" {
if fi.Room.BlueTeam.Mime != "" {
// error: alredy taken
err := errors.New("Mime role already taken!")
return fi, err
}
fi.Room.BlueTeam.Mime = fi.State.Username
fi.Room.BlueTeam.Color = "blue"
fi.State.Team = "blue"
fi.State.Role = "mime"
} else if team == "red" {
if fi.Room.RedTeam.Mime != "" {
// error: alredy taken
err := errors.New("Mime role already taken!")
return fi, err
}
fi.Room.RedTeam.Mime = fi.State.Username
fi.Room.RedTeam.Color = "red"
fi.State.Team = "red"
fi.State.Role = "mime"
} else {
err := errors.New("uknown team:" + team)
return nil, err
}
} else if role == "guesser" {
if team == "blue" {
fi.Room.BlueTeam.Guessers = append(fi.Room.BlueTeam.Guessers, fi.State.Username)
fi.Room.BlueTeam.Color = "blue"
fi.State.Team = "blue"
fi.State.Role = "guesser"
} else if team == "red" {
fi.Room.RedTeam.Guessers = append(fi.Room.RedTeam.Guessers, fi.State.Username)
fi.Room.RedTeam.Color = "red"
fi.State.Team = "red"
fi.State.Role = "guesser"
} else {
err := errors.New("uknown team:" + team)
return nil, err
}
} else {
err := errors.New("uknown role:" + role)
return nil, err
}
if err := saveFullInfo(fi); err != nil {
return nil, err
}
return fi, nil
}
// get all rooms
func listRooms(allRooms bool) []*models.Room {
cacheMap := memcache.GetAll()
publicRooms := []*models.Room{}
// no way to know if room is public until unmarshal -_-;
for key, value := range cacheMap {
if strings.HasPrefix(key, models.CacheRoomPrefix) {
room := &models.Room{}
if err := json.Unmarshal(value, &room); err != nil {
log.Warn("failed to unmarshal room", "error", err)
continue
}
log.Debug("consider room for list", "room", room, "key", key)
if room.IsPublic || allRooms {
publicRooms = append(publicRooms, room)
}
}
}
return publicRooms
}
// get bots
func listBots() map[string]map[string]string {
cacheMap := memcache.GetAll()
resp := make(map[string]map[string]string)
// no way to know if room is public until unmarshal -_-;
for key, value := range cacheMap {
if strings.HasPrefix(key, models.CacheBotPredix) {
botMap := make(map[string]string)
if err := json.Unmarshal(value, &botMap); err != nil {
log.Warn("failed to unmarshal bot", "error", err)
continue
}
resp[botMap["bot_name"]] = botMap
}
}
return resp
}
// get players
func listPlayers() map[string]map[string]string {
cacheMap := memcache.GetAll()
resp := make(map[string]map[string]string)
// no way to know if room is public until unmarshal -_-;
for key, value := range cacheMap {
if strings.HasPrefix(key, models.CacheStatePrefix) {
playerMap := make(map[string]string)
if err := json.Unmarshal(value, &playerMap); err != nil {
log.Warn("failed to unmarshal player", "error", err)
continue
}
resp[playerMap["Username"]] = playerMap
}
}
return resp
}
func notify(event, msg string) {
Notifier.Notifier <- broker.NotificationEvent{
EventName: event,
Payload: msg,
}
}
func loadCards(room *models.Room) {
wl := wordloader.InitDefaultLoader("assets/words/en_nouns.txt")
cards, err := wl.Load()
if err != nil {
// no logger
fmt.Println("failed to load cards", "error", err)
}
room.Cards = cards
room.WCMap = make(map[string]models.WordColor)
for _, card := range room.Cards {
room.WCMap[card.Word] = card.Color
}
}
func recoverBots() {
bots := listBots()
for botName, botMap := range bots {
if err := recoverBot(botMap); err != nil {
log.Warn("failed to recover bot", "botName", botName, "error", err)
}
}
}
func recoverBot(bm map[string]string) error {
// check if room still exists
if _, err := getRoomByID(bm["room_id"]); err != nil {
return fmt.Errorf("no such room: %s; err: %w", bm["room_id"], err)
}
log.Debug("recovering bot", "bot", bm)
_, err := llmapi.NewBot(bm["role"], bm["team"], bm["bot_name"], bm["room_id"], cfg, true)
if err != nil {
return err
}
return nil
}
func recoverPlayers() {
players := listPlayers()
for playerName, playerMap := range players {
if err := recoverPlayer(playerMap); err != nil {
log.Warn("failed to recover player", "playerName", playerName, "error", err)
}
}
}
func recoverPlayer(pm map[string]string) error {
// check if room still exists
room, err := getRoomByID(pm["RoomID"])
if err != nil {
return fmt.Errorf("no such room: %s; err: %w", pm["RoomID"], err)
}
log.Debug("recovering player", "player", pm)
role, team, ok := room.GetPlayerByName(pm["Username"])
if !ok {
return fmt.Errorf("failed to find player %s in the room %v", pm["Username"], room)
}
// pm["Role"] = string(role)
// pm["Team"] = string(team)
us := &models.UserState{
Username: pm["Username"],
RoomID: pm["RoomID"],
Team: team,
Role: role,
}
return saveState(pm["Username"], us)
}