Files
gralias/models/main.go
2025-06-27 12:05:55 +03:00

311 lines
6.8 KiB
Go

package models
import (
"errors"
"fmt"
"gralias/utils"
"time"
"github.com/rs/xid"
)
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 {
Actor string
ActorColor string
Action string // clue | guess
Word string
WordColor string
Number string // for clue
}
type BotPlayer struct {
Role UserRole // gueeser | mime
Team UserTeam
}
type Room struct {
ID string `json:"id" db:"id"`
CreatedAt time.Time `json:"created_at" db:"created_at"` // limit?
RoomPass string `json:"room_pass"`
RoomLink string
CreatorName string `json:"creator_name"`
ActionHistory []Action
TeamTurn UserTeam
RedTeam Team
BlueTeam Team
Cards []WordCard
ThisTurnLimit uint8 // how many cards guessers can open this turn
OpenedThisTurn uint8 // how many cards have been opened this turn
WCMap map[string]WordColor
BotMap map[string]BotPlayer // key is bot name
Result uint8 // 0 for unknown; 1 is win for red; 2 if for blue;
BlueCounter uint8
RedCounter uint8
RedTurn bool // false is blue turn
MimeDone bool
IsPublic bool
IsRunning bool `json:"is_running"`
Language string `json:"language" example:"en" form:"language"`
RoundTime int32 `json:"round_time"`
IsOver bool
TeamWon UserTeam // blue | red
// needed for debug
LogJournal []string
LastActionTS time.Time
}
// 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) 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) 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 "red":
return UserTeamBlue
case "blue":
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) RevealSpecificWord(word string) {
for i, card := range r.Cards {
if card.Word == word {
r.Cards[i].Revealed = true
}
}
}
type WordCard struct {
Word string `json:"word"`
Color WordColor `json:"color"`
Revealed bool `json:"revealed"`
}
type GameSettings struct {
IsRunning bool `json:"is_running"`
Language string `json:"language" example:"en" form:"language"`
RoundTime int32 `json:"round_time"`
ProgressPct uint32 `json:"progress_pct"`
IsOver bool
}
// =====
type RoomReq struct {
// is not user or not unique
RoomPass string `json:"room_pass" form:"room_pass"`
RoomName string `json:"room_name" form:"room_name"`
// GameSettings
}
func (rr *RoomReq) CreateRoom(creator string) *Room {
roomID := xid.New().String()
return &Room{
// RoomName: ,
RoomPass: rr.RoomPass,
ID: roomID,
CreatedAt: time.Now(),
// PlayerList: []string{creator},
CreatorName: creator,
BotMap: make(map[string]BotPlayer),
}
}
// ====
type FullInfo struct {
State *UserState
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
}