397 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			397 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package handlers
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"encoding/json"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"gralias/broker"
 | |
| 	"gralias/llmapi"
 | |
| 	"gralias/models"
 | |
| 	"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)
 | |
| 	// do I need last action here? since room save is kind of an action on itself
 | |
| 	// time.Now().Add(time.Hour).Sub(room.LastActionTS)
 | |
| 	anHour := int64(216000) // 60 * 60 * 60
 | |
| 	memcache.Expire(key, anHour)
 | |
| 	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
 | |
| }
 | |
| 
 | |
| // // 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(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() 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)
 | |
| 	}
 | |
| 	us := &models.UserState{
 | |
| 		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
 | |
| }
 | 
