109 lines
2.9 KiB
Go
109 lines
2.9 KiB
Go
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]
|
|
}
|
|
}
|