It begins!

This commit is contained in:
Hamcha 2019-05-31 01:16:15 +02:00
parent 95fa916611
commit cd6e6d686e
Signed by: hamcha
GPG Key ID: A40413D21021EAEE
8 changed files with 336 additions and 2 deletions

View File

@ -1,3 +1,3 @@
# mlp-draft
# draft
Libraries and servers for MLP:CCG drafting
Libraries and servers for drafting, with focus on MLP:CCG drafting

5
cube.go Normal file
View File

@ -0,0 +1,5 @@
package draft
type Cube struct {
Cards []Card
}

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module git.fromouter.space/mcg/draft
go 1.12

137
mlp/booster.go Normal file
View File

@ -0,0 +1,137 @@
package mlp
import (
"math/rand"
"git.fromouter.space/mcg/draft"
)
/*
(This data was taken from the MLP:CCG wikia at mlpccg.fandom.com and confirmed
by people at the MLP:CCG Discord)
Distribution rates for packs is usually 8 commons, 3 uncommons and 1 rare.
No Fixed or Promo cards can be found in packs.
UR distribution depends on set:
- PR has 1/13 chance of UR replacing a common
- CN->AD has 1/11 chance of UR replacing a common
- EO->FF has 1/3 chance of SR/UR replacing a common
SR are twice as common as UR, so that's one more thing to keep in mind.
Lastly, RR can replace another common in the ratio of ~1/2 every 6 boxes, depending
on set. Specifically, this is the RR ratio for each set:
- EO->HM: 1/108
- MT->FF: 1/216
*/
// BoxSchema returns the pack schema from a booster box for a specific set
func (set *Set) BoxSchema() draft.PackSchema {
// Return blank schemas for invalid sets
if set.ID == SetRockNRave || set.ID == SetCelestialSolstice {
return draft.PackSchema{}
}
var rr []draft.AlternateProvider
var srur []draft.AlternateProvider
// Check for RR chances
switch set.ID {
case SetEquestrialOdysseys,
SetHighMagic:
rr = []draft.AlternateProvider{
{
Probability: 1.0 / 108.0,
Provider: set.ProviderByRarity(RarityRoyalRare),
},
}
case SetMarksInTime,
SetDefendersOfEquestria,
SetSeaquestriaBeyond,
SetFriendsForever:
rr = []draft.AlternateProvider{
{
Probability: 1.0 / 216.0,
Provider: set.ProviderByRarity(RarityRoyalRare),
},
}
}
// Check for SR/UR chances
switch set.ID {
case SetPremiere:
srur = []draft.AlternateProvider{
{
Probability: 1.0 / 13.0,
Provider: set.ProviderByRarity(RarityUltraRare),
},
}
case SetCanterlotNights,
SetCrystalGames,
SetAbsoluteDiscord:
srur = []draft.AlternateProvider{
{
Probability: 1.0 / 11.0,
Provider: set.ProviderByRarity(RarityUltraRare),
},
}
default:
srur = []draft.AlternateProvider{
{
Probability: (1.0 / 9.0) * 2.0,
Provider: set.ProviderByRarity(RaritySuperRare),
}, {
Probability: 1.0 / 9.0,
Provider: set.ProviderByRarity(RarityUltraRare),
},
}
}
return draft.PackSchema{
Slots: []draft.PackSlot{
// Fixed common slots
{Amount: 6, Provider: set.ProviderByRarity(RarityCommon)},
// Common slot that can be replaced by RR
{Amount: 1, Provider: set.ProviderByRarity(RarityCommon), Alternate: rr},
// Common slot that can be replaced by SR/UR
{Amount: 1, Provider: set.ProviderByRarity(RarityCommon), Alternate: srur},
// Fixed rare and uncommon slots
{Amount: 1, Provider: set.ProviderByRarity(RarityRare)},
{Amount: 3, Provider: set.ProviderByRarity(RarityUncommon)},
},
}
}
// ProviderByRarity returns a provider for a given rarity level from a given set
func (set *Set) ProviderByRarity(rarity Rarity) draft.CardProvider {
var collection []draft.Card
// RR flow is super easy, just pick one from our hardcoded list
if rarity == RarityRoyalRare {
rr, ok := royalRares[set.ID]
// If asking for RR from a set that doesn't have one, exit early
if !ok {
return nil
}
collection = rr
} else {
//TODO Filter cards by rarity
for _, card := range set.Cards {
if card.Rarity == rarity {
collection = append(collection, draft.Card{ID: card.ID})
}
}
}
return func(n int) []draft.Card {
out := make([]draft.Card, n)
for n := range out {
// Pick a RR at random
idx := rand.Intn(len(collection))
out[n] = collection[idx]
}
return out
}
}

33
mlp/mlp.go Normal file
View File

@ -0,0 +1,33 @@
package mlp
// Rarity denotes a card's rarity
type Rarity string
// All card rarities
const (
RarityCommon Rarity = "C"
RarityUncommon Rarity = "U"
RarityRare Rarity = "R"
RaritySuperRare Rarity = "SR"
RarityUltraRare Rarity = "UR"
RarityRoyalRare Rarity = "RR"
)
// SetID denotes a card's set
type SetID string
// All sets
const (
SetPremiere SetID = "PR"
SetCanterlotNights SetID = "CN"
SetRockNRave SetID = "RR"
SetCelestialSolstice SetID = "CS"
SetCrystalGames SetID = "CG"
SetAbsoluteDiscord SetID = "AD"
SetEquestrialOdysseys SetID = "EO"
SetHighMagic SetID = "HM"
SetMarksInTime SetID = "MT"
SetDefendersOfEquestria SetID = "DE"
SetSeaquestriaBeyond SetID = "SB"
SetFriendsForever SetID = "FF"
)

30
mlp/royalrares.go Normal file
View File

@ -0,0 +1,30 @@
package mlp
import "git.fromouter.space/mcg/draft"
// Royal rares for each set
var royalRares = map[SetID][]draft.Card{
SetEquestrialOdysseys: {
draft.Card{ID: "eo207"}, // Discord, Wrathful
draft.Card{ID: "eo208"}, // Pinkie Pie, Remix Master
},
SetHighMagic: {
draft.Card{ID: "hm149"}, // Trixie, Highest Level Unicorn
draft.Card{ID: "hm147"}, // Fluttershy, Saddle Rager
draft.Card{ID: "hm145"}, // Rarity, Radiance
},
SetMarksInTime: {
draft.Card{ID: "mt139"}, // Rainbow Dash, One Winged Warrior
draft.Card{ID: "mt141"}, // Princess Twilight Sparkle, Time Patrol
},
SetDefendersOfEquestria: {
draft.Card{ID: "de135"}, // Applejack, Captain of the Seven Seas
},
SetSeaquestriaBeyond: {
draft.Card{ID: "sb135"}, // Tempest Shadow, Stormcaller
},
SetFriendsForever: {
draft.Card{ID: "ff136"}, // Mistmane, Pillar of Beauty
},
}

63
mlp/set.go Normal file
View File

@ -0,0 +1,63 @@
package mlp
import (
"encoding/json"
"io/ioutil"
"net/http"
"strings"
)
// Set is a set/expansion of MLP:CCG
type Set struct {
ID SetID
Name string
Cards []Card
}
// Card is a single MLP:CCG card in a set
type Card struct {
ID string
Name string
Subname string
Element []string
Keywords []string
Traits []string
Requirement PowerRequirement `json:",omitempty"`
Cost *int `json:",omitempty"`
Power *int `json:",omitempty"`
Type string
Text string
Rarity Rarity
ProblemBonus *int `json:",omitempty"`
ProblemOpponentPower int `json:",omitempty"`
ProblemRequirement PowerRequirement `json:",omitempty"`
}
// PowerRequirement denotes one or more power requirements, colored or not
type PowerRequirement map[string]int
// LoadSet loads a set with a specified ID from JSON
func LoadSet(id SetID, setdata []byte) (*Set, error) {
var set Set
err := json.Unmarshal(setdata, &set)
set.ID = id
return &set, err
}
// LoadSetHTTP loads a set using MCG's remote server
func LoadSetHTTP(id SetID) (*Set, error) {
// Get SetID as string and make it lowercase
setid := strings.ToLower(string(id))
resp, err := http.Get("https://mcg.zyg.ovh/setdata/" + setid + ".json")
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return LoadSet(id, data)
}

63
pack.go Normal file
View File

@ -0,0 +1,63 @@
package draft // import "git.fromouter.space/mcg/draft"
import (
"math/rand"
)
// Pack is a collection of cards from a booster pack
type Pack []Card
// Card is a single card
type Card struct {
ID string
}
// CardProvider is a function that returns as many cards of a certain types as needed
type CardProvider func(int) []Card
// PackSchema is all that's needed to generate a certain type of pack
type PackSchema struct {
Slots []PackSlot
}
// PackSlot is part of how packs are made, one or more providers provide
// cards for X cards of the whole pack
type PackSlot struct {
Amount int
Provider CardProvider
Alternate []AlternateProvider
}
// AlternateProvider are Card providers that can replace one or more slots
// with special cards (foils, ultra rares)
type AlternateProvider struct {
Probability float32
Provider CardProvider
}
// MakePack makes a booster pack from a given schema
func MakePack(schema PackSchema) Pack {
pack := make(Pack, 0)
for _, slot := range schema.Slots {
// Default provider
provider := slot.Provider
// Check for random alternates
if slot.Alternate != nil {
var currentProb float32
var chosenProb = rand.Float32()
for _, alt := range slot.Alternate {
currentProb += alt.Probability
if chosenProb > currentProb {
provider = alt.Provider
break
}
}
}
// Extract cards from provider and add them to the pack
cards := provider(slot.Amount)
pack = append(pack, cards...)
}
return pack
}