package game import ( "io" "log" "net/http" "path" "strconv" "strings" "golang.org/x/exp/slices" "gopkg.in/yaml.v3" ) type CardType int const ( UndefinedCardType CardType = iota UnitType SpellType ArtifactType EquipmentType PotionType IntentionType ) func (ct CardType) String() string { switch ct { case UndefinedCardType: return "undefined" case UnitType: return "unit" case SpellType: return "spell" case ArtifactType: return "artifact" case EquipmentType: return "equipment" case PotionType: return "potion" case IntentionType: return "intention" } log.Panicf("Switch not exhausting %d", ct) return "" } var CardTypes = struct { Unit CardType Spell CardType Artifact CardType Equipment CardType Potion CardType Intention CardType }{ Unit: UnitType, Spell: SpellType, Artifact: ArtifactType, Equipment: EquipmentType, Potion: PotionType, Intention: IntentionType, } var CardTypeNames = map[string]CardType{ "unit": UnitType, "spell": SpellType, "artifact": ArtifactType, "equipment": EquipmentType, "potion": PotionType, "intention": IntentionType, } const ( CARD_DATA_URL string = "data/cards" NOT_BUYABLE int = -1 ) type Card struct { Name string Set SetIdentifier Type CardType BuyCost int Values map[string]interface{} } var cards = map[string]map[string][]byte{} func retrieveCardDefinition(cardPath string) []byte { url := PROTOCOL + path.Join(BASE_URL, CARD_DATA_URL, cardPath+".yml") log.Printf("loading %s\n", url) resp, err := http.Get(url) if err != nil { log.Fatal(err) } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } return data } func (card *Card) parseDefinition(definition []byte) { data := make(map[interface{}]interface{}) err := yaml.Unmarshal(definition, &data) if err != nil { log.Fatalf("parsing %s failed with: %v\n", definition, err) } name, found := data["name"] if !found { log.Fatalf("no name in card yaml %s\n", definition) } delete(data, "name") switch nameYml := name.(type) { case string: card.Name = nameYml case map[string]interface{}: cardName := nameYml["en"] card.Name = cardName.(string) default: log.Fatalf("Unexpected type %T of yml card field 'Name'\n", name) } t, found := data["type"] if !found { log.Fatalf("no type in card yaml %s\n", definition) } delete(data, "type") card.Type = CardTypeNames[t.(string)] bc, found := data["buy"] if found { card.BuyCost = bc.(int) } delete(data, "buy") for k, v := range data { card.Values[k.(string)] = v } } func (card *Card) getCanonicalValues(key string) []string { var values []string untypedValues, found := card.Values[key] if !found { return []string{} } switch typedValues := untypedValues.(type) { case map[string]interface{}: enValues := typedValues["en"] switch typedEnValues := enValues.(type) { case string: values = []string{typedEnValues} case []string: values = typedEnValues case []interface{}: values = []string{} for _, v := range typedEnValues { values = append(values, v.(string)) } } case string: values = []string{typedValues} case int: values = []string{strconv.Itoa(typedValues)} case []string: values = typedValues } for i, value := range values { values[i] = strings.ToLower(value) } return values } func (card *Card) getEffects() []string { return card.getCanonicalValues("effect") } func (card *Card) hasEffect(effect string) bool { return slices.Contains(card.getEffects(), effect) } func (card *Card) hasMovementEffect(effect string) bool { if slices.Contains(card.getEffects(), effect) { return true } movementDesc := card.getCanonicalValues("movement")[0] return strings.Contains(movementDesc, effect) } func (card *Card) getAI() string { aiDesc := card.getCanonicalValues("ai") if len(aiDesc) == 0 { return "" } return strings.Split(aiDesc[0], " ")[0] } func NewCard(cardPath string) *Card { cardName := path.Base(cardPath) set := path.Dir(cardPath) cardDefinition, found := cards[set][cardName] if !found { cardDefinition = retrieveCardDefinition(cardPath) if cards[set] == nil { cards[set] = map[string][]byte{} } cards[set][cardName] = cardDefinition } c := Card{"", SetIdentifierNames[set], UndefinedCardType, -1, map[string]interface{}{}} c.parseDefinition(cardDefinition) return &c } func (c *Card) Path() string { fileName := strings.ReplaceAll(strings.ToLower(c.Name), " ", "_") return path.Join(c.Set.String(), fileName) } func (c *Card) IsPermanent() bool { return c.Type != UndefinedCardType && c.Type != SpellType && c.Type != IntentionType } func (c *Card) GetPlayCosts() int { switch c.Type { case UnitType: return c.Values["upkeep"].(int) default: return c.Values["play"].(int) } }