483 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			483 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package models
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"gralias/utils"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/rs/xid"
 | |
| )
 | |
| 
 | |
| type (
 | |
| 	UserTeam string
 | |
| 	UserRole string
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	// Context keys
 | |
| 	TxContextKey = "tx"
 | |
| 	// UserTeam
 | |
| 	UserTeamBlue = "blue"
 | |
| 	UserTeamRed  = "red"
 | |
| 	UserTeamNone = ""
 | |
| 	//UserRole
 | |
| 	UserRoleMime    = "mime"
 | |
| 	UserRoleGuesser = "guesser"
 | |
| 	UserRoleNone    = ""
 | |
| )
 | |
| 
 | |
| func StrToUserTeam(s string) UserTeam {
 | |
| 	switch s {
 | |
| 	case "blue":
 | |
| 		return UserTeamBlue
 | |
| 	case "red":
 | |
| 		return UserTeamRed
 | |
| 	default:
 | |
| 		return UserTeamNone
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func StrToUserRole(s string) UserRole {
 | |
| 	switch s {
 | |
| 	case "mime":
 | |
| 		return UserRoleMime
 | |
| 	case "guesser":
 | |
| 		return UserRoleGuesser
 | |
| 	default:
 | |
| 		return UserRoleNone
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type WordColor string
 | |
| 
 | |
| const (
 | |
| 	WordColorWhite  = "amber"
 | |
| 	WordColorBlue   = "blue"
 | |
| 	WordColorRed    = "red"
 | |
| 	WordColorBlack  = "black"
 | |
| 	WordColorUknown = "stone" // beige
 | |
| )
 | |
| 
 | |
| type ActionType string
 | |
| 
 | |
| const (
 | |
| 	ActionTypeClue        = "gave_clue"
 | |
| 	ActionTypeGuess       = "guessed"
 | |
| 	ActionTypeGameOver    = "game_over"
 | |
| 	ActionTypeGameStarted = "game_started"
 | |
| )
 | |
| 
 | |
| func StrToWordColor(s string) WordColor {
 | |
| 	switch s {
 | |
| 	case "amber", "white":
 | |
| 		return WordColorWhite
 | |
| 	case "blue":
 | |
| 		return WordColorBlue
 | |
| 	case "red":
 | |
| 		return WordColorRed
 | |
| 	case "black":
 | |
| 		return WordColorBlack
 | |
| 	default:
 | |
| 		return WordColorUknown
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type Team struct {
 | |
| 	Guessers []string
 | |
| 	Mime     string
 | |
| 	Color    string
 | |
| }
 | |
| 
 | |
| type Action struct {
 | |
| 	ID         uint32    `json:"id" db:"id"`
 | |
| 	RoomID     string    `json:"room_id" db:"room_id"`
 | |
| 	Actor      string    `json:"actor" db:"actor"`
 | |
| 	ActorColor string    `json:"actor_color" db:"actor_color"`
 | |
| 	Action     string    `json:"action_type" db:"action_type"`
 | |
| 	Word       string    `json:"word" db:"word"`
 | |
| 	WordColor  string    `json:"word_color" db:"word_color"`
 | |
| 	Number     string    `json:"number_associated" db:"number_associated"`
 | |
| 	CreatedAt  time.Time `json:"created_at" db:"created_at"`
 | |
| }
 | |
| 
 | |
| type Player struct {
 | |
| 	ID       uint32   `json:"id" db:"id"`
 | |
| 	RoomID   *string  `json:"room_id" db:"room_id"`
 | |
| 	Username string   `json:"username" db:"username"`
 | |
| 	Password string   `json:"-" db:"password"`
 | |
| 	Team     UserTeam `json:"team" db:"team"`
 | |
| 	Role     UserRole `json:"role" db:"role"`
 | |
| 	IsBot    bool     `json:"is_bot" db:"is_bot"`
 | |
| }
 | |
| 
 | |
| func InitPlayer(username string) *Player {
 | |
| 	return &Player{
 | |
| 		// last id + 1?
 | |
| 		Username: username,
 | |
| 		Team:     UserTeamNone,
 | |
| 		Role:     UserRoleNone,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type BotPlayer struct {
 | |
| 	Role UserRole // gueeser | mime
 | |
| 	Team UserTeam
 | |
| }
 | |
| 
 | |
| type CardMark struct {
 | |
| 	CardID   uint32 `db:"card_id"`
 | |
| 	Username string `db:"username"`
 | |
| }
 | |
| 
 | |
| type Journal struct {
 | |
| 	ID        uint32    `db:"id"`
 | |
| 	Username  string    `db:"username"`
 | |
| 	RoomID    string    `db:"room_id"`
 | |
| 	Entry     string    `db:"entry"`
 | |
| 	CreatedAt time.Time `db:"created_at"`
 | |
| }
 | |
| 
 | |
| type PlayerStats struct {
 | |
| 	ID                  uint32  `db:"id"`
 | |
| 	Username            string  `db:"username"`
 | |
| 	GamesPlayed         int     `db:"games_played"`
 | |
| 	GamesWon            int     `db:"games_won"`
 | |
| 	GamesLost           int     `db:"games_lost"`
 | |
| 	OpenedOppositeWords int     `db:"opened_opposite_words"`
 | |
| 	OpenedWhiteWords    int     `db:"opened_white_words"`
 | |
| 	OpenedBlackWords    int     `db:"opened_black_words"`
 | |
| 	MimeWinrate         float64 `db:"mime_winrate"`
 | |
| 	GuesserWinrate      float64 `db:"guesser_winrate"`
 | |
| 	PlayedAsMime        int     `db:"played_as_mime"`
 | |
| 	PlayedAsGuesser     int     `db:"played_as_guesser"`
 | |
| }
 | |
| 
 | |
| type Room struct {
 | |
| 	ID             string    `json:"id" db:"id"`
 | |
| 	CreatedAt      time.Time `json:"created_at" db:"created_at"`
 | |
| 	CreatorName    string    `json:"creator_name" db:"creator_name"`
 | |
| 	TeamTurn       UserTeam  `db:"team_turn"`
 | |
| 	ThisTurnLimit  uint8     `db:"this_turn_limit"`
 | |
| 	OpenedThisTurn uint8     `db:"opened_this_turn"`
 | |
| 	BlueCounter    uint8     `db:"blue_counter"`
 | |
| 	RedCounter     uint8     `db:"red_counter"`
 | |
| 	RedTurn        bool      `db:"red_turn"`
 | |
| 	MimeDone       bool      `db:"mime_done"`
 | |
| 	IsRunning      bool      `json:"is_running" db:"is_running"`
 | |
| 	IsOver         bool      `db:"is_over"`
 | |
| 	TeamWon        UserTeam  `db:"team_won"`
 | |
| 	RoomLink       string    `db:"room_link"`
 | |
| 	// fields not in db
 | |
| 	ActionHistory []Action             `db:"-"`
 | |
| 	RedTeam       Team                 `db:"-"`
 | |
| 	BlueTeam      Team                 `db:"-"`
 | |
| 	Cards         []WordCard           `db:"-"`
 | |
| 	BotMap        map[string]BotPlayer `db:"-"`
 | |
| 	LogJournal    []Journal            `db:"-"`
 | |
| 	Settings      GameSettings         `db:"-"`
 | |
| }
 | |
| 
 | |
| func (r *Room) FindColor(word string) (WordColor, bool) {
 | |
| 	for _, card := range r.Cards {
 | |
| 		if strings.EqualFold(card.Word, word) {
 | |
| 			return card.Color, true
 | |
| 		}
 | |
| 	}
 | |
| 	return "", false
 | |
| }
 | |
| 
 | |
| func (r *Room) ClearMarks() {
 | |
| 	for i := range r.Cards {
 | |
| 		r.Cards[i].Marks = []CardMark{}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (r *Room) RemovePlayer(username string) {
 | |
| 	r.RedTeam.Guessers = utils.RemoveFromSlice(username, r.RedTeam.Guessers)
 | |
| 	r.BlueTeam.Guessers = utils.RemoveFromSlice(username, r.BlueTeam.Guessers)
 | |
| 	if r.RedTeam.Mime == username {
 | |
| 		r.RedTeam.Mime = ""
 | |
| 	}
 | |
| 	if r.BlueTeam.Mime == username {
 | |
| 		r.BlueTeam.Mime = ""
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // FindBotByTeamRole returns bot name if found; otherwise empty string
 | |
| func (r *Room) FindBotByTeamRole(team, role string) string {
 | |
| 	for bn, b := range r.BotMap {
 | |
| 		if b.Role == StrToUserRole(role) && b.Team == StrToUserTeam(team) {
 | |
| 			return bn
 | |
| 		}
 | |
| 	}
 | |
| 	return ""
 | |
| }
 | |
| 
 | |
| func (r *Room) FetchLastClue() (*Action, error) {
 | |
| 	for i := len(r.ActionHistory) - 1; i >= 0; i-- {
 | |
| 		if r.ActionHistory[i].Action == string(ActionTypeClue) {
 | |
| 			return &r.ActionHistory[i], nil
 | |
| 		}
 | |
| 	}
 | |
| 	return nil, errors.New("no clue in history")
 | |
| }
 | |
| 
 | |
| func (r *Room) FetchLastClueWord() string {
 | |
| 	for i := len(r.ActionHistory) - 1; i >= 0; i-- {
 | |
| 		if r.ActionHistory[i].Action == string(ActionTypeClue) {
 | |
| 			return r.ActionHistory[i].Word
 | |
| 		}
 | |
| 	}
 | |
| 	return ""
 | |
| }
 | |
| 
 | |
| func (r *Room) GetPlayerByName(name string) (role UserRole, team UserTeam, found bool) {
 | |
| 	if r.RedTeam.Mime == name {
 | |
| 		return "mime", "red", true
 | |
| 	}
 | |
| 	if r.BlueTeam.Mime == name {
 | |
| 		return "mime", "blue", true
 | |
| 	}
 | |
| 	for _, guesser := range r.RedTeam.Guessers {
 | |
| 		if guesser == name {
 | |
| 			return "guesser", "red", true
 | |
| 		}
 | |
| 	}
 | |
| 	for _, guesser := range r.BlueTeam.Guessers {
 | |
| 		if guesser == name {
 | |
| 			return "guesser", "blue", true
 | |
| 		}
 | |
| 	}
 | |
| 	return "", "", false
 | |
| }
 | |
| 
 | |
| func (r *Room) GetPlayerInfoByName(name string) *BotPlayer {
 | |
| 	bp := &BotPlayer{}
 | |
| 	if r.RedTeam.Mime == name {
 | |
| 		bp.Role = UserRoleMime
 | |
| 		bp.Team = UserTeamRed
 | |
| 	}
 | |
| 	if r.BlueTeam.Mime == name {
 | |
| 		bp.Role = UserRoleMime
 | |
| 		bp.Team = UserTeamBlue
 | |
| 	}
 | |
| 	for _, guesser := range r.RedTeam.Guessers {
 | |
| 		if guesser == name {
 | |
| 			bp.Role = UserRoleGuesser
 | |
| 			bp.Team = UserTeamRed
 | |
| 		}
 | |
| 	}
 | |
| 	for _, guesser := range r.BlueTeam.Guessers {
 | |
| 		if guesser == name {
 | |
| 			bp.Role = UserRoleGuesser
 | |
| 			bp.Team = UserTeamBlue
 | |
| 		}
 | |
| 	}
 | |
| 	return bp
 | |
| }
 | |
| 
 | |
| func (r *Room) CanStart() error {
 | |
| 	if r.IsRunning {
 | |
| 		return errors.New("cannot start; game is already running")
 | |
| 	}
 | |
| 	if r.RedTeam.Mime == "" {
 | |
| 		return errors.New("cannot start; red team has no mime")
 | |
| 	}
 | |
| 	if r.BlueTeam.Mime == "" {
 | |
| 		return errors.New("cannot start; blue team has no mime")
 | |
| 	}
 | |
| 	if len(r.RedTeam.Guessers) == 0 {
 | |
| 		return errors.New("cannot start; red team has no guessers")
 | |
| 	}
 | |
| 	if len(r.BlueTeam.Guessers) == 0 {
 | |
| 		return errors.New("cannot start; blue team has no guessers")
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func getGuesser(m map[string]BotPlayer, team UserTeam) string {
 | |
| 	for k, v := range m {
 | |
| 		if v.Team == team && v.Role == UserRoleGuesser {
 | |
| 			return k
 | |
| 		}
 | |
| 	}
 | |
| 	return ""
 | |
| }
 | |
| 
 | |
| // WhichBotToMove returns bot name that have to move or empty string
 | |
| func (r *Room) WhichBotToMove() string {
 | |
| 	fmt.Println("looking for bot to move", "team-turn:", r.TeamTurn,
 | |
| 		"mime-done:", r.MimeDone, "bot-map:", r.BotMap, "is_running:", r.IsRunning,
 | |
| 		"blueMime:", r.BlueTeam.Mime, "redMime:", r.RedTeam.Mime, "card-limit:", r.ThisTurnLimit, "opened:", r.OpenedThisTurn)
 | |
| 	if !r.IsRunning {
 | |
| 		return ""
 | |
| 	}
 | |
| 	switch r.TeamTurn {
 | |
| 	case UserTeamBlue:
 | |
| 		if !r.MimeDone {
 | |
| 			_, ok := r.BotMap[r.BlueTeam.Mime]
 | |
| 			if ok {
 | |
| 				return r.BlueTeam.Mime
 | |
| 			}
 | |
| 		} else {
 | |
| 			return getGuesser(r.BotMap, UserTeamBlue)
 | |
| 		}
 | |
| 	case UserTeamRed:
 | |
| 		if !r.MimeDone {
 | |
| 			_, ok := r.BotMap[r.RedTeam.Mime]
 | |
| 			if ok {
 | |
| 				return r.RedTeam.Mime
 | |
| 			}
 | |
| 		} else {
 | |
| 			return getGuesser(r.BotMap, UserTeamRed)
 | |
| 		}
 | |
| 	default:
 | |
| 		// how did we got here?
 | |
| 		return ""
 | |
| 	}
 | |
| 	return ""
 | |
| }
 | |
| 
 | |
| func (r *Room) GetOppositeTeamColor() UserTeam {
 | |
| 	switch r.TeamTurn {
 | |
| 	case UserTeamRed:
 | |
| 		return UserTeamBlue
 | |
| 	case UserTeamBlue:
 | |
| 		return UserTeamRed
 | |
| 	}
 | |
| 	return UserTeamNone
 | |
| }
 | |
| 
 | |
| func (r *Room) UpdateCounter() {
 | |
| 	redCounter := uint8(0)
 | |
| 	blueCounter := uint8(0)
 | |
| 	for _, card := range r.Cards {
 | |
| 		if card.Revealed {
 | |
| 			continue
 | |
| 		}
 | |
| 		switch card.Color {
 | |
| 		case WordColorRed:
 | |
| 			redCounter++
 | |
| 		case WordColorBlue:
 | |
| 			blueCounter++
 | |
| 		}
 | |
| 	}
 | |
| 	r.RedCounter = redCounter
 | |
| 	r.BlueCounter = blueCounter
 | |
| }
 | |
| 
 | |
| func (r *Room) ChangeTurn() {
 | |
| 	switch r.TeamTurn {
 | |
| 	case "blue":
 | |
| 		r.TeamTurn = "red"
 | |
| 	case "red":
 | |
| 		r.TeamTurn = "blue"
 | |
| 	default:
 | |
| 		r.TeamTurn = "blue"
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (r *Room) RevealAllCards() {
 | |
| 	for i := range r.Cards {
 | |
| 		r.Cards[i].Revealed = true
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (r *Room) MimeView() {
 | |
| 	for i := range r.Cards {
 | |
| 		r.Cards[i].Mime = true
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (r *Room) GuesserView() {
 | |
| 	for i := range r.Cards {
 | |
| 		r.Cards[i].Mime = false
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (r *Room) RevealSpecificWord(word string) uint32 {
 | |
| 	for i, card := range r.Cards {
 | |
| 		if card.Word == word {
 | |
| 			r.Cards[i].Revealed = true
 | |
| 			return card.ID
 | |
| 		}
 | |
| 	}
 | |
| 	return 0
 | |
| }
 | |
| 
 | |
| type WordCard struct {
 | |
| 	ID       uint32     `json:"id" db:"id"`
 | |
| 	RoomID   string     `json:"room_id" db:"room_id"`
 | |
| 	Word     string     `json:"word" db:"word"`
 | |
| 	Color    WordColor  `json:"color" db:"color"`
 | |
| 	Revealed bool       `json:"revealed" db:"revealed"`
 | |
| 	Mime     bool       `json:"mime" db:"mime_view"` // user who sees that card is mime
 | |
| 	Marks    []CardMark `json:"marks" db:"-"`
 | |
| }
 | |
| 
 | |
| // table: settings
 | |
| type GameSettings struct {
 | |
| 	ID       uint32 `json:"id" db:"id"`
 | |
| 	RoomID   string `db:"room_id"`
 | |
| 	Language string `json:"language" example:"en" form:"language" db:"language"`
 | |
| 	RoomPass string `json:"room_pass" db:"room_pass"`
 | |
| 
 | |
| 	RoundTime uint32    `json:"round_time" db:"turn_time"`
 | |
| 	CreatedAt time.Time `db:"created_at"`
 | |
| }
 | |
| 
 | |
| // =====
 | |
| 
 | |
| type RoomReq struct {
 | |
| 	// is not user or not unique
 | |
| 	RoomPass string `json:"room_pass" form:"room_pass"`
 | |
| 	// GameSettings
 | |
| 	Language  string `json:"language" form:"language"`
 | |
| 	RoundTime uint32
 | |
| }
 | |
| 
 | |
| func (rr *RoomReq) CreateRoom(creator string) *Room {
 | |
| 	roomID := xid.New().String()
 | |
| 	settings := GameSettings{
 | |
| 		Language:  rr.Language,
 | |
| 		RoundTime: rr.RoundTime,
 | |
| 		RoomPass:  rr.RoomPass,
 | |
| 	}
 | |
| 	return &Room{
 | |
| 		ID:          roomID,
 | |
| 		CreatedAt:   time.Now(),
 | |
| 		CreatorName: creator,
 | |
| 		Settings:    settings,
 | |
| 		BotMap:      make(map[string]BotPlayer),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // ====
 | |
| 
 | |
| type FullInfo struct {
 | |
| 	// State     *UserState
 | |
| 	State     *Player
 | |
| 	Room      *Room
 | |
| 	List      []*Room
 | |
| 	LinkLogin string // room_id
 | |
| }
 | |
| 
 | |
| func (f *FullInfo) ExitRoom() *Room {
 | |
| 	f.Room.RedTeam.Guessers = utils.RemoveFromSlice(f.State.Username, f.Room.RedTeam.Guessers)
 | |
| 	f.Room.BlueTeam.Guessers = utils.RemoveFromSlice(f.State.Username, f.Room.BlueTeam.Guessers)
 | |
| 	if f.Room.RedTeam.Mime == f.State.Username {
 | |
| 		f.Room.RedTeam.Mime = ""
 | |
| 	}
 | |
| 	if f.Room.BlueTeam.Mime == f.State.Username {
 | |
| 		f.Room.BlueTeam.Mime = ""
 | |
| 	}
 | |
| 	f.State.RoomID = nil
 | |
| 	resp := f.Room
 | |
| 	f.Room = nil
 | |
| 	return resp
 | |
| }
 | |
| 
 | |
| // =======
 | 
