It begins!
This commit is contained in:
parent
95fa916611
commit
cd6e6d686e
8 changed files with 336 additions and 2 deletions
|
@ -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
5
cube.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package draft
|
||||||
|
|
||||||
|
type Cube struct {
|
||||||
|
Cards []Card
|
||||||
|
}
|
3
go.mod
Normal file
3
go.mod
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module git.fromouter.space/mcg/draft
|
||||||
|
|
||||||
|
go 1.12
|
137
mlp/booster.go
Normal file
137
mlp/booster.go
Normal 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
33
mlp/mlp.go
Normal 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
30
mlp/royalrares.go
Normal 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
63
mlp/set.go
Normal 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
63
pack.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in a new issue