From 65a4a2e6bd69c7fe7ce37a69ba48c95fe3d276bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=AEittaG=20ordnasselA?= Date: Fri, 31 May 2019 14:21:50 +0200 Subject: [PATCH] Add basic implementation of I8PCube --- mlp/i8pcube.go | 147 ++++++++++++++++++++++++++++++++++++++++---- mlp/i8pcube_test.go | 74 ++++++++++++++++++++++ mlp/set.go | 9 +++ mlp/utils.go | 12 ++++ 4 files changed, 231 insertions(+), 11 deletions(-) create mode 100644 mlp/i8pcube_test.go create mode 100644 mlp/utils.go diff --git a/mlp/i8pcube.go b/mlp/i8pcube.go index 1d835d4..3ecb7d5 100644 --- a/mlp/i8pcube.go +++ b/mlp/i8pcube.go @@ -1,11 +1,18 @@ package mlp -import "git.fromouter.space/mcg/draft" +import ( + "math/rand" + + "git.fromouter.space/mcg/draft" +) // I8PCube is a cube that uses I8Pages' pack schema // I8Pages' cube uses different kinds of packs, so a single schema is not possible. // Therefore, I8PCube itself is not a valid set, but contains two valid sets for // both types of packs (main deck / problems) +// +// Infos on I8Pages' cube: +// https://docs.google.com/spreadsheets/d/1Ufz4QLwLCZ1vLemAhE6cuAmu2lq_VAN7RhLO9p1opNQ type I8PCube struct { Main *I8PSet Problems *I8PSet @@ -13,22 +20,140 @@ type I8PCube struct { // I8PSet is one of the sets of packs contained in a I8PCube type I8PSet struct { - Cards map[string][]Card + Cards I8PPool Schema draft.PackSchema } +// I8PType is a category of cards to be seeded into packs +type I8PType string + +// All types used for seeding packs +const ( + I8PTypeBlue I8PType = "blue" + I8PTypeOrange I8PType = "orange" + I8PTypePink I8PType = "pink" + I8PTypePurple I8PType = "purple" + I8PTypeWhite I8PType = "white" + I8PTypeYellow I8PType = "yellow" + I8PTypeNone I8PType = "none" + I8PTypeMulti I8PType = "multi" + I8PTypeEntry I8PType = "entry" + I8PTypeProblem I8PType = "problem" +) + +// I8PPool is a pool of card divided into categories +type I8PPool map[I8PType][]Card + +// MakeI8PCube takes an organized set of cards and sorts them into a draftable I8PCube +func MakeI8PCube(cards I8PPool) *I8PCube { + return &I8PCube{ + Main: makeMainSet(cards), + Problems: makeProblemSet(cards), + } +} + +func makeMainSet(cards I8PPool) (set *I8PSet) { + set = &I8PSet{ + Cards: I8PPool{ + I8PTypeBlue: cards[I8PTypeBlue], + I8PTypeOrange: cards[I8PTypeOrange], + I8PTypePink: cards[I8PTypePink], + I8PTypePurple: cards[I8PTypePurple], + I8PTypeWhite: cards[I8PTypeWhite], + I8PTypeYellow: cards[I8PTypeYellow], + I8PTypeNone: cards[I8PTypeNone], + I8PTypeMulti: cards[I8PTypeMulti], + I8PTypeEntry: cards[I8PTypeEntry], + }, + } + //TODO Make schema more flexible + set.Schema = draft.PackSchema{ + Slots: []draft.PackSlot{ + {Amount: 1, Provider: set.ProviderByType(I8PTypeBlue)}, + {Amount: 1, Provider: set.ProviderByType(I8PTypeOrange)}, + {Amount: 1, Provider: set.ProviderByType(I8PTypePink)}, + {Amount: 1, Provider: set.ProviderByType(I8PTypePurple)}, + {Amount: 1, Provider: set.ProviderByType(I8PTypeWhite)}, + {Amount: 1, Provider: set.ProviderByType(I8PTypeYellow)}, + {Amount: 1, Provider: set.ProviderByType(I8PTypeNone)}, + {Amount: 2, Provider: set.ProviderByType(I8PTypeMulti)}, + {Amount: 2, Provider: set.ProviderByType(I8PTypeEntry)}, + {Amount: 1, Provider: set.ProviderOther()}, + }, + } + return +} + +func makeProblemSet(cards I8PPool) (set *I8PSet) { + set = &I8PSet{ + Cards: I8PPool{ + I8PTypeProblem: cards[I8PTypeProblem], + }, + } + set.Schema = draft.PackSchema{ + Slots: []draft.PackSlot{ + { + Amount: 12, + Provider: set.ProviderByType(I8PTypeProblem), + }, + }, + } + return +} + +// ProviderByType provides cards from a specified category +func (s *I8PSet) ProviderByType(typ I8PType) draft.CardProvider { + return func(n int) (out []draft.Card) { + s.shuffle(typ) + if len(s.Cards[typ]) < n { + n = len(s.Cards[typ]) + } + out, s.Cards[typ] = toDraft(s.Cards[typ][:n]), s.Cards[typ][n:] + return + } +} + +// ProviderOther picks a random card for any available set (except Problems) +func (s *I8PSet) ProviderOther() draft.CardProvider { + choices := []I8PType{ + I8PTypeBlue, + I8PTypeOrange, + I8PTypePink, + I8PTypePurple, + I8PTypeWhite, + I8PTypeYellow, + I8PTypeNone, + I8PTypeMulti, + I8PTypeEntry, + } + return func(n int) (out []draft.Card) { + // Filter types that have enough cards for what we need + // n is almost always 1, so this should not introduce bias + available := []I8PType{} + for _, typ := range choices { + if len(s.Cards[typ]) >= n { + available = append(available, typ) + } + } + + // Check if there are no pools we can pick stuff from, and exit early + // THIS IS NOT NORMAL, AND YOU SHOULD FIX YOUR CUBE! + if len(available) < 1 { + return + } + + typ := available[rand.Intn(len(available))] + return s.ProviderByType(typ)(n) + } +} + // PackSchema returns the pack schema for building packs from a I8PCube func (s *I8PSet) PackSchema() draft.PackSchema { return s.Schema } -// MakeI8PCube takes an organized set of cards and sorts them into a draftable I8PCube -func MakeI8PCube(cards map[string][]Card) *I8PCube { - //TODO Separate problems from main deck - //TODO Make schemas - return &I8PCube{ - Main: &I8PSet{ - Cards: cards, - }, - } +func (s *I8PSet) shuffle(typ I8PType) { + rand.Shuffle(len(s.Cards[typ]), func(i, j int) { + s.Cards[typ][i], s.Cards[typ][j] = s.Cards[typ][j], s.Cards[typ][i] + }) } diff --git a/mlp/i8pcube_test.go b/mlp/i8pcube_test.go new file mode 100644 index 0000000..6b19d22 --- /dev/null +++ b/mlp/i8pcube_test.go @@ -0,0 +1,74 @@ +package mlp_test + +import ( + "fmt" + "testing" + + "git.fromouter.space/mcg/draft" + "git.fromouter.space/mcg/draft/mlp" +) + +// TestDraftI8PCube sets up a I8PCube and drafts 3 packs from it +func TestDraftI8PCube(t *testing.T) { + pool := mlp.I8PPool{ + mlp.I8PTypeBlue: mockCards("b1", "b2", "b3"), + mlp.I8PTypeOrange: mockCards("o1", "o2", "o3"), + mlp.I8PTypePink: mockCards("p1", "p2", "p3"), + mlp.I8PTypePurple: mockCards("u1", "u2", "u3"), + mlp.I8PTypeWhite: mockCards("w1", "w2", "w3"), + mlp.I8PTypeYellow: mockCards("y1", "y2", "y3"), + mlp.I8PTypeNone: mockCards("n1", "n2", "n3"), + mlp.I8PTypeMulti: mockCards("m1", "m2", "m3", "m4"), + mlp.I8PTypeEntry: mockCards("e1", "e2", "e3", "e4"), + mlp.I8PTypeProblem: mockCards("P1", "P2"), + } + cube := mlp.MakeI8PCube(pool) + + pack1 := draft.MakePack(cube.Main) + pack2 := draft.MakePack(cube.Main) + pack3 := draft.MakePack(cube.Problems) + + if len(pack1) != 12 { + t.Errorf("Expected 12 cards in pack 1 but got %d", len(pack1)) + } + if len(pack2) != 12 { + t.Errorf("Expected 12 cards in pack 2 but got %d", len(pack2)) + } + if len(pack3) != 2 { + t.Errorf("Expected 2 cards in pack 3 but got %d", len(pack3)) + } + + fmt.Printf("Cards in pack1: %s\n", pack1) + fmt.Printf("Cards in pack2: %s\n", pack2) + fmt.Printf("Cards in pack3: %s\n", pack3) +} + +// TestDryCube tries drying up the cube to make the "OtherProvider" not able to pick a card +// Expected behavior is to have a 11 card pack generate and not crash. +// This should **never** happen! If it does, please go fix your cube! +func TestDryCube(t *testing.T) { + pool := mlp.I8PPool{ + mlp.I8PTypeBlue: mockCards("b1"), + mlp.I8PTypeOrange: mockCards("o1"), + mlp.I8PTypePink: mockCards("p1"), + mlp.I8PTypePurple: mockCards("u1"), + mlp.I8PTypeWhite: mockCards("w1"), + mlp.I8PTypeYellow: mockCards("y1"), + mlp.I8PTypeNone: mockCards("n1"), + mlp.I8PTypeMulti: mockCards("m1", "m4"), + mlp.I8PTypeEntry: mockCards("e1", "e4"), + } + cube := mlp.MakeI8PCube(pool) + pack := draft.MakePack(cube.Main) + if len(pack) != 11 { + t.Errorf("Expected 11 cards in pack but got %d", len(pack)) + } +} + +func mockCards(ids ...string) []mlp.Card { + out := make([]mlp.Card, len(ids)) + for i, id := range ids { + out[i] = mlp.Card{ID: id} + } + return out +} diff --git a/mlp/set.go b/mlp/set.go index 2483488..d196f8e 100644 --- a/mlp/set.go +++ b/mlp/set.go @@ -5,6 +5,8 @@ import ( "io/ioutil" "net/http" "strings" + + "git.fromouter.space/mcg/draft" ) // Set is a set/expansion of MLP:CCG @@ -33,6 +35,13 @@ type Card struct { ProblemRequirement PowerRequirement `json:",omitempty"` } +// ToDraftCard converts cards to draft.Card +func (c Card) ToDraftCard() draft.Card { + return draft.Card{ + ID: c.ID, + } +} + // PowerRequirement denotes one or more power requirements, colored or not type PowerRequirement map[string]int diff --git a/mlp/utils.go b/mlp/utils.go new file mode 100644 index 0000000..e16e54d --- /dev/null +++ b/mlp/utils.go @@ -0,0 +1,12 @@ +package mlp + +import "git.fromouter.space/mcg/draft" + +// Converts multiple mlp.Card to draft.Card +func toDraft(cards []Card) []draft.Card { + out := make([]draft.Card, len(cards)) + for i, card := range cards { + out[i] = card.ToDraftCard() + } + return out +}