262 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			262 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 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 fi.State == nil {
 | |
| 		return errors.New("player is nil")
 | |
| 	}
 | |
| 	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)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // can room exists without state? I think no
 | |
| func getFullInfoByCtx(ctx context.Context) (*models.FullInfo, error) {
 | |
| 	resp := &models.FullInfo{}
 | |
| 	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, "error", err)
 | |
| 		state.Team = models.UserTeamNone
 | |
| 		state.Role = models.UserRoleNone
 | |
| 		if err := repo.PlayerExitRoom(ctx, state.Username); err != nil {
 | |
| 			log.Warn("failed to exit room", "error", err,
 | |
| 				"room_id", state.RoomID, "username", state.Username)
 | |
| 			return resp, err
 | |
| 		}
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	// get card_marks
 | |
| 	if room.IsRunning && room.MimeDone {
 | |
| 		if err := fillCardMarks(ctx, room); err != nil {
 | |
| 			log.Warn("failed to fill card marks", "error", err,
 | |
| 				"room_id", state.RoomID, "username", state.Username)
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 	resp.Room = room
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func fillCardMarks(ctx context.Context, room *models.Room) error {
 | |
| 	marks, err := repo.CardMarksByRoomID(ctx, room.ID)
 | |
| 	if err != nil {
 | |
| 		log.Warn("failed to fetch card marks by room_id", "room_id", room.ID, "error", err)
 | |
| 		return err
 | |
| 	}
 | |
| 	for i, card := range room.Cards {
 | |
| 		for _, mark := range marks {
 | |
| 			if mark.CardID == card.ID {
 | |
| 				room.Cards[i].Marks = append(room.Cards[i].Marks, mark)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return 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)
 | |
| }
 | |
| 
 | |
| 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 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) {
 | |
| 	// remove old cards
 | |
| 	room.Cards = []models.WordCard{}
 | |
| 	// try to delete old cards from db (in case players play another round)
 | |
| 	// nolint: errcheck
 | |
| 	repo.WordCardsDeleteByRoomID(context.Background(), room.ID)
 | |
| 	// 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
 | |
| }
 | |
| 
 | |
| 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
 | |
| }
 | |
| 
 | |
| // 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
 | |
| }
 | 
