package handlers import ( "context" "errors" "fmt" "gralias/broker" "gralias/llmapi" "gralias/models" "gralias/wordloader" ) 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 := repo.RoomCreate(ctx, room); err != nil { return nil, err } return room, nil } func saveFullInfo(ctx context.Context, fi *models.FullInfo) error { // INFO: no transactions; so case is possible where first object is updated but the second is not if err := repo.PlayerUpdate(ctx, fi.State); err != nil { return err } log.Debug("saved user state", "state", fi.State) // save or update // fi.Room.Cards // fi.Room.WCMap if err := repo.RoomUpdate(ctx, fi.Room); err != nil { return err } return nil } func notifyBotIfNeeded(room *models.Room) { if botName := 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 getAllNames() []string { // names := []string{} // // will not scale // session := &models.Session{} // // filter by key size only sessions // for _, name := 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) state, err := getPlayerByCtx(ctx) if err != nil { return nil, err } resp.State = state if state.RoomID == nil || *state.RoomID == "" { log.Debug("returning state without room", "username", state.Username) return resp, nil } // room, err := getRoomByID(state.RoomID) room, err := repo.RoomGetExtended(ctx, *state.RoomID) // room, err := repo.RoomGetByID(ctx, *state.RoomID) if err != nil { // room was deleted; remove it from player; log.Warn("failed to find room despite knowing room_id;", "room_id", state.RoomID) state.Team = models.UserTeamNone state.Role = models.UserRoleNone if err := repo.PlayerExitRoom(ctx, state.Username); err != nil { log.Warn("failed to exit room", "room_id", state.RoomID, "username", state.Username) return resp, err } return nil, err } resp.Room = room return resp, nil } func getPlayerByCtx(ctx context.Context) (*models.Player, error) { username, ok := ctx.Value(models.CtxUsernameKey).(string) if !ok { log.Debug("no username in ctx") return &models.Player{}, errors.New("no username in ctx") } return repo.PlayerGetByName(ctx, username) } // // DEPRECATED // 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) fi.Room.RemovePlayer(fi.State.Username) // 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(ctx, 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 // } // if room.IsPublic || allRooms { // publicRooms = append(publicRooms, room) // } // } // } // return publicRooms // } // get bots func listBots() []models.Player { bots, err := repo.PlayerList(context.Background(), true) if err != nil { log.Error("failed to fetch bots from db", "error", err) } return bots } // get players func notify(event, msg string) { Notifier.Notifier <- broker.NotificationEvent{ EventName: event, Payload: msg, } } func loadCards(room *models.Room) { // store it somewhere wordMap := map[string]string{ "en": "assets/words/en_nouns.txt", "ru": "assets/words/ru_nouns.txt", } wl := wordloader.InitDefaultLoader(wordMap[room.Settings.Language]) 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 _, bot := range bots { if err := recoverBot(bot); err != nil { log.Warn("failed to recover bot", "botName", bot.Username, "error", err) } } } func recoverBot(bm models.Player) error { // check if room still exists if bm.RoomID == nil { return errors.New("bot has no room id") } if _, err := repo.RoomGetByID(context.Background(), *bm.RoomID); err != nil { return fmt.Errorf("no such room: %s; err: %w", *bm.RoomID, err) } log.Debug("recovering bot", "bot", bm) _, err := llmapi.NewBot(string(bm.Role), string(bm.Team), bm.Username, *bm.RoomID, 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 := repo.RoomGetByID(context.Background(), 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) // } // us := &models.Player{ // Username: pm["Username"], // RoomID: pm["RoomID"], // Team: team, // Role: role, // } // return saveState(pm["Username"], us) // } // validateMove checks if it is players turn func validateMove(fi *models.FullInfo, ur models.UserRole) error { if fi.State.Role != ur { err := fmt.Errorf("need to be %s to make that action", ur) return err } // whos move it is? if fi.State.Team != models.UserTeam(fi.Room.TeamTurn) { err := errors.New("not your team's move") return err } switch ur { case models.UserRoleGuesser: if !fi.Room.MimeDone { err := errors.New("wait for the mime to give a clue") return err } case models.UserRoleMime: if fi.Room.MimeDone { err := errors.New("clue was already given") return err } default: return fmt.Errorf("uknown user role: %s", ur) } return nil }