Files
gralias/models/main.go
2025-07-02 11:18:28 +03:00

441 lines
9.9 KiB
Go

package models
import (
"errors"
"fmt"
"gralias/utils"
"time"
"github.com/rs/xid"
)
type (
UserTeam string
UserRole string
)
const (
// 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:"-"`
CreatedAtUnix int64 `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"`
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 {
Username string
Active bool
}
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:"-"`
WCMap map[string]WordColor `db:"-"`
BotMap map[string]BotPlayer `db:"-"`
Mark CardMark `db:"-"`
LogJournal []string `db:"-"`
Settings GameSettings `db:"-"`
}
func (r *Room) ClearMarks() {
for i, _ := range r.Cards {
r.Cards[i].Mark = []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)
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) {
for i, card := range r.Cards {
if card.Word == word {
r.Cards[i].Revealed = true
}
}
}
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"`
MimeView bool `json:"mime_view" db:"mime_view"` // user who sees that card is mime
Mark []CardMark `json:"marks" db:"-"`
}
// table: settings
type GameSettings struct {
Language string `json:"language" example:"en" form:"language" db:"language"`
RoomPass string `json:"room_pass" db:"room_pass"`
TurnSecondsLeft uint32 `db:"-"`
RoundTime uint32 `json:"round_time" db:"round_time"`
}
// =====
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()
return &Room{
ID: roomID,
CreatedAt: time.Now(),
CreatorName: creator,
Language: rr.Language,
RoundTime: rr.RoundTime,
RoomPass: rr.RoomPass,
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.PlayerList = utils.RemoveFromSlice(f.State.Username, f.Room.PlayerList)
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.ExitRoom()
resp := f.Room
f.Room = nil
return resp
}