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