package game import ( "fmt" "strings" ) // Card packs are the selectable sets of pets and food a game is played with. // Turtle, Golden, and Unicorn all ship with full six-tier card data. // PackInfo describes one selectable pack. Playable gates whether a lobby may // choose it and start a game with it. type PackInfo struct { ID string `json:"id"` Name string `json:"name"` Emoji string `json:"emoji"` Playable bool `json:"playable"` } // DefaultPack is the pack a freshly created game starts on. const DefaultPack = "turtle" // Packs is the ordered catalog of packs shown in the lobby. var Packs = []PackInfo{ {ID: "turtle", Name: "Turtle Pack", Emoji: "🐢", Playable: true}, {ID: "golden", Name: "Golden Pack", Emoji: "🥇", Playable: true}, {ID: "unicorn", Name: "Unicorn Pack", Emoji: "🦄", Playable: true}, } // packByID looks up a pack, returning false if the id is unknown. func packByID(id string) (PackInfo, bool) { for _, p := range Packs { if p.ID == id { return p, true } } return PackInfo{}, false } // PacksNeeded is how many packs a game must combine to seat n players: the // rulebook asks for at least 2 packs at 4 players and 3 at 6, i.e. one per // pair. More packs than the minimum are always allowed — a deeper shop just // means fewer repeated pets. func PacksNeeded(players int) int { if players < MinPlayers { return 1 } return players / 2 } // sortPacks puts a pack selection into catalog order, so the same choice // always reads the same way in views and logs. func sortPacks(ids []string) []string { out := make([]string, 0, len(ids)) for _, p := range Packs { for _, id := range ids { if id == p.ID { out = append(out, p.ID) break } } } return out } // validatePacks checks a pack selection: non-empty, known, playable, and free // of duplicates. It returns the selection in catalog order. func validatePacks(ids []string) ([]string, error) { if len(ids) == 0 { return nil, fmt.Errorf("%w: pick at least one pack", ErrInvalidAction) } seen := map[string]bool{} for _, id := range ids { pack, ok := packByID(id) if !ok { return nil, fmt.Errorf("%w: unknown pack %q", ErrInvalidAction, id) } if !pack.Playable { return nil, fmt.Errorf("%w: the %s isn't available yet", ErrInvalidAction, pack.Name) } if seen[id] { return nil, fmt.Errorf("%w: %s is selected twice", ErrInvalidAction, pack.Name) } seen[id] = true } return sortPacks(ids), nil } // PackNames renders a pack selection as a readable list ("Turtle Pack and // Golden Pack") for log lines. func PackNames(ids []string) string { names := make([]string, 0, len(ids)) for _, id := range ids { if p, ok := packByID(id); ok { names = append(names, p.Name) } } switch len(names) { case 0: return "no packs" case 1: return names[0] case 2: return names[0] + " and " + names[1] default: return strings.Join(names[:len(names)-1], ", ") + ", and " + names[len(names)-1] } }