diff --git a/mlp/block.go b/mlp/block.go new file mode 100644 index 0000000..578b002 --- /dev/null +++ b/mlp/block.go @@ -0,0 +1,33 @@ +package mlp + +import "git.fromouter.space/mcg/draft" + +// BlockPacks returns a pack provider for a block draft +func BlockPacks(block BlockID) (draft.PackProvider, error) { + var setids []SetID + switch block { + case BlockPremiere: + setids = []SetID{SetPremiere, SetCanterlotNights, SetCrystalGames, SetAbsoluteDiscord} + case BlockOdyssey: + setids = []SetID{SetEquestrialOdysseys, SetEquestrialOdysseys, SetHighMagic, SetMarksInTime} + case BlockDefenders: + setids = []SetID{SetFriendsForever, SetDefendersOfEquestria, SetSeaquestriaBeyond, SetFriendsForever} + } + + sets := make([]*Set, len(setids)) + for i, setid := range setids { + set, err := LoadSetMemory(setid) + if err != nil { + return nil, err + } + sets[i] = set + } + + return func() []draft.Pack { + packs := make([]draft.Pack, len(sets)) + for i, set := range sets { + packs[i] = draft.MakePack(set) + } + return packs + }, nil +} diff --git a/mlp/block_test.go b/mlp/block_test.go new file mode 100644 index 0000000..a74adc4 --- /dev/null +++ b/mlp/block_test.go @@ -0,0 +1,64 @@ +package mlp_test + +import ( + "testing" + + "git.fromouter.space/mcg/draft/mlp" +) + +// Tests a block packprovider +func TestBlockProvider(t *testing.T) { + // Clean all loaded sets + mlp.CleanSetCache() + + // Load all needed sets + sets := []mlp.SetID{ + mlp.SetPremiere, + mlp.SetCanterlotNights, + mlp.SetCrystalGames, + mlp.SetAbsoluteDiscord, + mlp.SetEquestrialOdysseys, + mlp.SetHighMagic, + mlp.SetMarksInTime, + mlp.SetDefendersOfEquestria, + mlp.SetSeaquestriaBeyond, + mlp.SetFriendsForever, + } + for _, setid := range sets { + _, err := mlp.LoadSetHTTP(setid) + if err != nil { + t.Fatalf("Could not load needed set: %s", err.Error()) + } + } + + PR, err := mlp.BlockPacks(mlp.BlockPremiere) + if err != nil { + t.Fatalf("Could not load sets for premiere block draft: %s", err.Error()) + } + EO, err := mlp.BlockPacks(mlp.BlockOdyssey) + if err != nil { + t.Fatalf("Could not load sets for odyssey block draft: %s", err.Error()) + } + DE, err := mlp.BlockPacks(mlp.BlockDefenders) + if err != nil { + t.Fatalf("Could not load sets for defenders block draft: %s", err.Error()) + } + + // Generate packs for each block + _ = PR() + _ = EO() + _ = DE() +} + +// Tests trying to make a packprovider of a block that's not loaded +func TestBlockError(t *testing.T) { + // Clean all loaded sets + mlp.CleanSetCache() + _, err := mlp.BlockPacks(mlp.BlockPremiere) + if err == nil { + t.Fatal("Could somehow generate a packprovider even though the set shouldn't be loaded") + } + if err != mlp.ErrSetNotLoaded { + t.Fatalf("Got an error but it's not the right one: %s", err.Error()) + } +} diff --git a/mlp/booster_test.go b/mlp/booster_test.go index 6e44a8d..c5efc0c 100644 --- a/mlp/booster_test.go +++ b/mlp/booster_test.go @@ -14,18 +14,18 @@ func TestAlternates(t *testing.T) { // Load Premiere/CN as they have their own UR ratios prSet, err := mlp.LoadSetHTTP(mlp.SetPremiere) if err != nil { - t.Errorf("Could not fetch set data: %s", err.Error()) + t.Fatalf("Could not fetch set data: %s", err.Error()) } cnSet, err := mlp.LoadSetHTTP(mlp.SetCanterlotNights) if err != nil { - t.Errorf("Could not fetch set data: %s", err.Error()) + t.Fatalf("Could not fetch set data: %s", err.Error()) } // Load set with Royal rares eoSet, err := mlp.LoadSetHTTP(mlp.SetEquestrialOdysseys) if err != nil { - t.Errorf("Could not fetch set data: %s", err.Error()) + t.Fatalf("Could not fetch set data: %s", err.Error()) } // Find all Premiere URs @@ -62,7 +62,7 @@ func TestAlternates(t *testing.T) { } if !prurfound { - t.Errorf("No PR UR found after 1000 packs") + t.Fatalf("No PR UR found after 1000 packs") } // Get some CN packs and search for URs @@ -83,7 +83,7 @@ func TestAlternates(t *testing.T) { } if !cnurfound { - t.Errorf("No CN UR found after 1000 packs") + t.Fatalf("No CN UR found after 1000 packs") } eorrfound := false @@ -98,20 +98,20 @@ func TestAlternates(t *testing.T) { } if !eorrfound { - t.Errorf("No EO RR found after 100k packs") + t.Fatalf("No EO RR found after 100k packs") } } // TestPackFixedSets tries to get packs a set that isn't a true set // This should result in empty packs func TestPackFixedSets(t *testing.T) { - set, err := mlp.LoadSet(mlp.SetRockNRave, []byte("{}")) + set, err := mlp.LoadSetBytes(mlp.SetRockNRave, []byte("{}")) if err != nil { - t.Errorf("Could not load set: %s", err.Error()) + t.Fatalf("Could not load set: %s", err.Error()) } pack := draft.MakePack(set) if len(pack) != 0 { - t.Errorf("Expected an empty pack but got %d cards", len(pack)) + t.Fatalf("Expected an empty pack but got %d cards", len(pack)) } } diff --git a/mlp/i8pcube_test.go b/mlp/i8pcube_test.go index b0ab895..19fc153 100644 --- a/mlp/i8pcube_test.go +++ b/mlp/i8pcube_test.go @@ -28,13 +28,13 @@ func TestDraftI8PCube(t *testing.T) { pack3 := draft.MakePack(cube.Problems) if len(pack1) != 12 { - t.Errorf("Expected 12 cards in pack 1 but got %d", len(pack1)) + t.Fatalf("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)) + t.Fatalf("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)) + t.Fatalf("Expected 2 cards in pack 3 but got %d", len(pack3)) } t.Logf("Cards in pack1: %s\n", pack1) @@ -60,7 +60,7 @@ func TestDryCube(t *testing.T) { cube := mlp.MakeI8PCube(pool, mlp.DefaultI8PSchema()) pack := draft.MakePack(cube.Main) if len(pack) != 11 { - t.Errorf("Expected 11 cards in pack but got %d", len(pack)) + t.Fatalf("Expected 11 cards in pack but got %d", len(pack)) } } diff --git a/mlp/mlp.go b/mlp/mlp.go index 4eb9c77..8c535ea 100644 --- a/mlp/mlp.go +++ b/mlp/mlp.go @@ -31,3 +31,13 @@ const ( SetSeaquestriaBeyond SetID = "SB" SetFriendsForever SetID = "FF" ) + +// BlockID denotes a certain block +type BlockID string + +// All blocks +const ( + BlockPremiere BlockID = "PR" // Premiere block - PR/CN/CG/AD + BlockOdyssey BlockID = "EO" // Odyssey block - EO/HM/MT + BlockDefenders BlockID = "DE" // Defenders block - DE/SB/FF +) diff --git a/mlp/set.go b/mlp/set.go index e1400b4..414ccab 100644 --- a/mlp/set.go +++ b/mlp/set.go @@ -2,6 +2,7 @@ package mlp import ( "encoding/json" + "errors" "fmt" "io/ioutil" "net/http" @@ -46,16 +47,65 @@ func (c Card) ToDraftCard() draft.Card { // 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 loadedSets = make(map[SetID]*Set) + +// Errors +var ( + ErrSetNotLoaded = errors.New("set not loaded") +) + +// CleanSetCache removes all loaded sets from memory +func CleanSetCache() { + loadedSets = make(map[SetID]*Set) +} + +// LoadSetData loads sets from data +func LoadSetData(sets map[SetID][]byte) error { + for setid, setdata := range sets { + _, err := LoadSetBytes(setid, setdata) + if err != nil { + return err + } + } + return nil +} + +// LoadSetMemory loads a set from memory (must have been previously loaded with LoadSetData or other functions) +func LoadSetMemory(id SetID) (*Set, error) { + // Check if set is already loaded + if set, ok := loadedSets[id]; ok { + return set, nil + } + // Not loaded, return error + return nil, ErrSetNotLoaded +} + +// LoadSetBytes loads a set with a specified ID from JSON +func LoadSetBytes(id SetID, setdata []byte) (*Set, error) { + // Check if set is already loaded + if set, ok := loadedSets[id]; ok { + return set, nil + } + var set Set err := json.Unmarshal(setdata, &set) set.ID = id + + // If set loaded fine, cache it + if err == nil { + loadedSets[set.ID] = &set + } + return &set, err } // LoadSetHTTP loads a set using MCG's remote server func LoadSetHTTP(id SetID) (*Set, error) { + // Check if set is already loaded + if set, ok := loadedSets[id]; ok { + return set, nil + } + // Get SetID as string and make it lowercase setid := strings.ToLower(string(id)) @@ -72,5 +122,5 @@ func LoadSetHTTP(id SetID) (*Set, error) { return nil, err } - return LoadSet(id, data) + return LoadSetBytes(id, data) } diff --git a/mlp/set_test.go b/mlp/set_test.go index 12dd6aa..723d105 100644 --- a/mlp/set_test.go +++ b/mlp/set_test.go @@ -10,9 +10,12 @@ import ( // TestSet retrieves a set online and generates a couple packs with it // This test *requires* an internet connection! func TestSet(t *testing.T) { + // Clean all loaded sets + mlp.CleanSetCache() + deSet, err := mlp.LoadSetHTTP(mlp.SetDefendersOfEquestria) if err != nil { - t.Errorf("Could not fetch set data: %s", err.Error()) + t.Fatalf("Could not fetch set data: %s", err.Error()) } pack1 := draft.MakePack(deSet) @@ -20,10 +23,10 @@ func TestSet(t *testing.T) { // Make sure both packs have the exact number of cards if len(pack1) != 12 { - t.Errorf("Expected 12 cards in pack 1 but got %d", len(pack1)) + t.Fatalf("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)) + t.Fatalf("Expected 12 cards in pack 2 but got %d", len(pack2)) } t.Logf("Cards in pack 1: %s\n", pack1) @@ -35,6 +38,123 @@ func TestSet(t *testing.T) { func TestWrongSet(t *testing.T) { _, err := mlp.LoadSetHTTP("nopenope") if err == nil { - t.Errorf("Expected an error but didn't get one!") + t.Fatalf("Expected an error but didn't get one!") + } +} + +// TestAllLoads tests all the LoadSet* functions +// This test *requires* an internet connection! +func TestAllLoads(t *testing.T) { + // Clean all loaded sets + mlp.CleanSetCache() + + // Test LoadSetHTTP + _, err := mlp.LoadSetHTTP(mlp.SetAbsoluteDiscord) + if err != nil { + t.Fatalf("[LoadSetHTTP] Could not fetch set data from the internet: %s", err.Error()) + } + + // Load set from bytes + _, err = mlp.LoadSetBytes(mlp.SetCelestialSolstice, []byte("{}")) + if err != nil { + t.Fatalf("[LoadSetBytes] Could not load set: %s", err.Error()) + } + + // Load set to memory + err = mlp.LoadSetData(map[mlp.SetID][]byte{ + mlp.SetRockNRave: []byte("{}"), + }) + if err != nil { + t.Fatalf("[LoadSetData] Could not load set: %s", err.Error()) + } + + // Load set from memory + _, err = mlp.LoadSetMemory(mlp.SetRockNRave) + if err != nil { + t.Fatalf("[LoadSetMemory] Could not load set: %s", err.Error()) + } +} + +// TestNotLoadedErr tests that LoadSetMemory fails if set is not cached +func TestNotLoadedErr(t *testing.T) { + // Clean all sets from memory first + mlp.CleanSetCache() + + // Load set from memory (should fail) + _, err := mlp.LoadSetMemory(mlp.SetFriendsForever) + if err == nil { + t.Fatal("[LoadSetMemory] Set loaded but shouldn't") + } else if err != mlp.ErrSetNotLoaded { + t.Fatalf("[LoadSetMemory] Set not loaded but error is not the right one: %s", err.Error()) + } + + // Actually load set + err = mlp.LoadSetData(map[mlp.SetID][]byte{ + mlp.SetFriendsForever: []byte("{}"), + }) + if err != nil { + t.Fatalf("[LoadSetData] Could not load set: %s", err.Error()) + } + + // Load set from memory (should succeed) + _, err = mlp.LoadSetMemory(mlp.SetFriendsForever) + if err != nil { + t.Fatalf("[LoadSetMemory] Could not load set: %s", err.Error()) + } +} + +// TestLoadCache tests caching on all Load functions that support it +func TestLoadCache(t *testing.T) { + // Clean all sets from memory first + mlp.CleanSetCache() + + // Load dummy set + err := mlp.LoadSetData(map[mlp.SetID][]byte{ + mlp.SetFriendsForever: []byte("{}"), + }) + if err != nil { + t.Fatalf("[LoadSetData] Could not load set: %s", err.Error()) + } + + // + // Try all set loading functions to trigger the cache + // + + _, err = mlp.LoadSetMemory(mlp.SetFriendsForever) + if err != nil { + t.Fatalf("[LoadSetMemory] Could not load set: %s", err.Error()) + } + + _, err = mlp.LoadSetBytes(mlp.SetFriendsForever, []byte("THIS SHOULD BE IGNORED")) + if err != nil { + t.Fatalf("[LoadSetBytes] Could not load set: %s", err.Error()) + } + + loadedset, err := mlp.LoadSetHTTP(mlp.SetFriendsForever) + if err != nil { + t.Fatalf("[LoadSetHTTP] Could not load set: %s", err.Error()) + } + + // Check that loaded set via HTTP is the dummy cache and not the real thing + if len(loadedset.Cards) != 0 { + t.Fatalf("[LoadSetHTTP] Set not loaded from cache") + } +} + +// TestMalformedJSONLoad tests that LoadSetData/LoadSetBytes return an error when given malformed JSON +func TestMalformedJSONLoad(t *testing.T) { + // Clean all sets from memory first + mlp.CleanSetCache() + + _, err := mlp.LoadSetBytes(mlp.SetFriendsForever, []byte("THIS SHOULD FAIL")) + if err == nil { + t.Fatalf("LoadSetBytes with invalid data succeeded but shouldn't have") + } + + err = mlp.LoadSetData(map[mlp.SetID][]byte{ + mlp.SetFriendsForever: []byte("THIS SHOULD FAIL"), + }) + if err == nil { + t.Fatalf("LoadSetData with invalid data succeeded but shouldn't have") } } diff --git a/pod_test.go b/pod_test.go index 17e7205..771305b 100644 --- a/pod_test.go +++ b/pod_test.go @@ -18,7 +18,7 @@ func TestCreatePod(t *testing.T) { pod := draft.MakePod(PlayersPerPod, testProvider) if len(pod.Players) != PlayersPerPod { - t.Fatalf("Expected %d players in pod but got %d\n", PlayersPerPod, len(pod.Players)) + t.Fatalf("Expected %d players in pod but got %d", PlayersPerPod, len(pod.Players)) } for i, player := range pod.Players { @@ -27,7 +27,7 @@ func TestCreatePod(t *testing.T) { t.Logf(" - Pack #%d: %s", packi, pack) } if len(player.Packs) != PacksPerPlayer { - t.Fatalf("Player #%d has %d packs but should have %d\n", i, PacksPerPlayer, len(player.Packs)) + t.Fatalf("Player #%d has %d packs but should have %d", i, PacksPerPlayer, len(player.Packs)) } } } @@ -51,7 +51,7 @@ func TestPick(t *testing.T) { // Open new packs! err := pod.OpenPacks() if err != nil { - t.Fatalf("Got an error while opening packs #%d: %s\n", packnum, err.Error()) + t.Fatalf("Got an error while opening packs #%d: %s", packnum, err.Error()) } for picknum := 0; picknum < PACKSIZE; picknum++ { @@ -59,7 +59,7 @@ func TestPick(t *testing.T) { // Pick first card for each player err := player.Pick(player.CurrentPack[0]) if err != nil { - t.Fatalf("Tried picking first card in pack but couldn't: %s\n", err.Error()) + t.Fatalf("Tried picking first card in pack but couldn't: %s", err.Error()) } } @@ -69,16 +69,15 @@ func TestPick(t *testing.T) { // Pass packs around err := pod.NextPacks() if err != nil { - t.Fatalf("Got an error while passing packs: %s\n", err.Error()) + t.Fatalf("Got an error while passing packs: %s", err.Error()) } case <-pod.ReadyNextPack: break default: - t.Fatal("ReadyNextPick/ReadyNextPack channel should trigger but hasn't\n") + t.Fatal("ReadyNextPick/ReadyNextPack channel should trigger but hasn't") } } } - } func TestPodErrors(t *testing.T) { @@ -95,17 +94,17 @@ func TestPodErrors(t *testing.T) { // Pick a card that doesn't exist err := pod.Players[0].Pick(draft.Card{ID: "nana"}) if err == nil { - t.Fatal("Tried picking inexistant card but it succeeded\n") + t.Fatal("Tried picking inexistant card but it succeeded") } else if err != draft.ErrNotInPack { - t.Fatalf("Got error for wrong pick but not the right one: %s\n", err.Error()) + t.Fatalf("Got error for wrong pick but not the right one: %s", err.Error()) } // Try getting packs from nearby players when no one is passing them err = pod.NextPacks() if err == nil { - t.Fatal("Tried getting inexistant packs from nearby players but it succeeded\n") + t.Fatal("Tried getting inexistant packs from nearby players but it succeeded") } else if err != draft.ErrNoPendingPack { - t.Fatalf("Got error for non existant pack but not the right one: %s\n", err.Error()) + t.Fatalf("Got error for non existant pack but not the right one: %s", err.Error()) } // Try opening more packs than each player has @@ -113,8 +112,8 @@ func TestPodErrors(t *testing.T) { err = pod.OpenPacks() } if err == nil { - t.Fatal("Tried opening too many packs but it succeeded\n") + t.Fatal("Tried opening too many packs but it succeeded") } else if err != draft.ErrNoPacksLeft { - t.Fatalf("Got error for too many packs but not the right one: %s\n", err.Error()) + t.Fatalf("Got error for too many packs but not the right one: %s", err.Error()) } } diff --git a/set_test.go b/set_test.go index fe2f099..3a5afb1 100644 --- a/set_test.go +++ b/set_test.go @@ -31,16 +31,16 @@ func TestSetRepeatable(t *testing.T) { pack := draft.MakePack(testSet) if len(pack) < PACKSIZE { - t.Errorf("Pack expected to contain %d cards, contains %d", PACKSIZE, len(pack)) + t.Fatalf("Pack expected to contain %d cards, contains %d", PACKSIZE, len(pack)) } // Check that all cards have something in it for i, card := range pack { if card.ID == "" { - t.Errorf("Pack contains \"empty\" card") + t.Fatalf("Pack contains \"empty\" card") } if card.ID != "a" && card.ID != "b" && card.ID != "c" { - t.Errorf("Pack contains unexpected card %s at index %d, not contained in pool", card.ID, i) + t.Fatalf("Pack contains unexpected card %s at index %d, not contained in pool", card.ID, i) } } } @@ -82,7 +82,7 @@ func TestAlternateProviders(t *testing.T) { // After 500k packs, I'm expecting distribution to be within 2% if distribution < 0.29 || distribution > 0.31 { - t.Errorf("Distribution is sketchy after 500k packs: %f%%", distribution*100) + t.Fatalf("Distribution is sketchy after 500k packs: %f%%", distribution*100) } } @@ -93,14 +93,14 @@ func TestCubeOverflow(t *testing.T) { // Pack 2 can only contain 4 cards, as there are not enough cards to fill it if len(pack2) >= PACKSIZE { - t.Errorf("Pack 2 expected to contain only 4 cards, has %d", len(pack2)) + t.Fatalf("Pack 2 expected to contain only 4 cards, has %d", len(pack2)) } // Check for duplicates allcards := append(pack1, pack2...) uniq := sliceUniq(allcards) if len(allcards) != len(uniq) { - t.Errorf("Duplicate cards found across packs") + t.Fatalf("Duplicate cards found across packs") } } @@ -110,7 +110,7 @@ func TestPackString(t *testing.T) { expected := "a b c" if p.String() != expected { - t.Errorf("Expected \"%s\" but got \"%s\"", expected, p) + t.Fatalf("Expected \"%s\" but got \"%s\"", expected, p) } }