Compare commits
No commits in common. "65a4a2e6bd69c7fe7ce37a69ba48c95fe3d276bb" and "38d36b6306f896bbb46f34f7d68f48fb781e0b3d" have entirely different histories.
65a4a2e6bd
...
38d36b6306
6 changed files with 31 additions and 275 deletions
147
mlp/i8pcube.go
147
mlp/i8pcube.go
|
@ -1,18 +1,11 @@
|
||||||
package mlp
|
package mlp
|
||||||
|
|
||||||
import (
|
import "git.fromouter.space/mcg/draft"
|
||||||
"math/rand"
|
|
||||||
|
|
||||||
"git.fromouter.space/mcg/draft"
|
|
||||||
)
|
|
||||||
|
|
||||||
// I8PCube is a cube that uses I8Pages' pack schema
|
// I8PCube is a cube that uses I8Pages' pack schema
|
||||||
// I8Pages' cube uses different kinds of packs, so a single schema is not possible.
|
// 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
|
// Therefore, I8PCube itself is not a valid set, but contains two valid sets for
|
||||||
// both types of packs (main deck / problems)
|
// both types of packs (main deck / problems)
|
||||||
//
|
|
||||||
// Infos on I8Pages' cube:
|
|
||||||
// https://docs.google.com/spreadsheets/d/1Ufz4QLwLCZ1vLemAhE6cuAmu2lq_VAN7RhLO9p1opNQ
|
|
||||||
type I8PCube struct {
|
type I8PCube struct {
|
||||||
Main *I8PSet
|
Main *I8PSet
|
||||||
Problems *I8PSet
|
Problems *I8PSet
|
||||||
|
@ -20,140 +13,22 @@ type I8PCube struct {
|
||||||
|
|
||||||
// I8PSet is one of the sets of packs contained in a I8PCube
|
// I8PSet is one of the sets of packs contained in a I8PCube
|
||||||
type I8PSet struct {
|
type I8PSet struct {
|
||||||
Cards I8PPool
|
Cards map[string][]Card
|
||||||
Schema draft.PackSchema
|
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
|
// PackSchema returns the pack schema for building packs from a I8PCube
|
||||||
func (s *I8PSet) PackSchema() draft.PackSchema {
|
func (s *I8PSet) PackSchema() draft.PackSchema {
|
||||||
return s.Schema
|
return s.Schema
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *I8PSet) shuffle(typ I8PType) {
|
// MakeI8PCube takes an organized set of cards and sorts them into a draftable I8PCube
|
||||||
rand.Shuffle(len(s.Cards[typ]), func(i, j int) {
|
func MakeI8PCube(cards map[string][]Card) *I8PCube {
|
||||||
s.Cards[typ][i], s.Cards[typ][j] = s.Cards[typ][j], s.Cards[typ][i]
|
//TODO Separate problems from main deck
|
||||||
})
|
//TODO Make schemas
|
||||||
|
return &I8PCube{
|
||||||
|
Main: &I8PSet{
|
||||||
|
Cards: cards,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -5,8 +5,6 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.fromouter.space/mcg/draft"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Set is a set/expansion of MLP:CCG
|
// Set is a set/expansion of MLP:CCG
|
||||||
|
@ -35,13 +33,6 @@ type Card struct {
|
||||||
ProblemRequirement PowerRequirement `json:",omitempty"`
|
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
|
// PowerRequirement denotes one or more power requirements, colored or not
|
||||||
type PowerRequirement map[string]int
|
type PowerRequirement map[string]int
|
||||||
|
|
||||||
|
|
12
mlp/utils.go
12
mlp/utils.go
|
@ -1,12 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
14
pack.go
14
pack.go
|
@ -1,9 +1,6 @@
|
||||||
package draft // import "git.fromouter.space/mcg/draft"
|
package draft // import "git.fromouter.space/mcg/draft"
|
||||||
|
|
||||||
import (
|
import "math/rand"
|
||||||
"math/rand"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Pack is a collection of cards from a booster pack
|
// Pack is a collection of cards from a booster pack
|
||||||
type Pack []Card
|
type Pack []Card
|
||||||
|
@ -69,12 +66,3 @@ func MakePackWithSchema(schema PackSchema) Pack {
|
||||||
}
|
}
|
||||||
return pack
|
return pack
|
||||||
}
|
}
|
||||||
|
|
||||||
// String encodes a pack to a list of space-separated IDs
|
|
||||||
func (p Pack) String() (str string) {
|
|
||||||
for _, card := range p {
|
|
||||||
str += " " + card.ID
|
|
||||||
}
|
|
||||||
str = strings.TrimSpace(str)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
50
set_test.go
50
set_test.go
|
@ -1,23 +1,21 @@
|
||||||
package draft_test
|
package draft
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.fromouter.space/mcg/draft"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestSetRepeatable makes sure that a set can generate more cards than it contains
|
// TestSetRepeatable makes sure that a set can generate more cards than it contains
|
||||||
func TestSetRepeatable(t *testing.T) {
|
func TestSetRepeatable(t *testing.T) {
|
||||||
const PACKSIZE = 5
|
const PACKSIZE = 5
|
||||||
s := &draft.GenericSet{
|
s := &GenericSet{
|
||||||
Cards: []draft.Card{{ID: "a"}, {ID: "b"}, {ID: "c"}},
|
Cards: []Card{{ID: "a"}, {ID: "b"}, {ID: "c"}},
|
||||||
PackSize: PACKSIZE,
|
PackSize: PACKSIZE,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a pack
|
// Create a pack
|
||||||
pack := draft.MakePack(s)
|
pack := MakePack(s)
|
||||||
|
|
||||||
if len(pack) < PACKSIZE {
|
if len(pack) < PACKSIZE {
|
||||||
t.Errorf("Pack expected to contain %d cards, contains %d", PACKSIZE, len(pack))
|
t.Errorf("Pack expected to contain %d cards, contains %d", PACKSIZE, len(pack))
|
||||||
|
@ -36,17 +34,17 @@ func TestSetRepeatable(t *testing.T) {
|
||||||
|
|
||||||
// TestAlternateProviders tests alternate providers
|
// TestAlternateProviders tests alternate providers
|
||||||
func TestAlternateProviders(t *testing.T) {
|
func TestAlternateProviders(t *testing.T) {
|
||||||
customProvider := func(n int) []draft.Card {
|
customProvider := func(n int) []Card {
|
||||||
out := make([]draft.Card, n)
|
out := make([]Card, n)
|
||||||
for n := range out {
|
for n := range out {
|
||||||
out[n] = draft.Card{ID: "x"}
|
out[n] = Card{ID: "x"}
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
s := &draft.GenericSet{
|
s := &GenericSet{
|
||||||
Cards: []draft.Card{{ID: "a"}, {ID: "b"}, {ID: "c"}},
|
Cards: []Card{{ID: "a"}, {ID: "b"}, {ID: "c"}},
|
||||||
PackSize: 1,
|
PackSize: 1,
|
||||||
Alternates: []draft.AlternateProvider{
|
Alternates: []AlternateProvider{
|
||||||
{
|
{
|
||||||
Probability: 0.3,
|
Probability: 0.3,
|
||||||
Provider: customProvider,
|
Provider: customProvider,
|
||||||
|
@ -58,7 +56,7 @@ func TestAlternateProviders(t *testing.T) {
|
||||||
|
|
||||||
nonalternates, alternates := 0, 0
|
nonalternates, alternates := 0, 0
|
||||||
for i := 0; i < 500000; i++ {
|
for i := 0; i < 500000; i++ {
|
||||||
pack := draft.MakePack(s)
|
pack := MakePack(s)
|
||||||
if pack[0].ID == "x" {
|
if pack[0].ID == "x" {
|
||||||
alternates++
|
alternates++
|
||||||
} else {
|
} else {
|
||||||
|
@ -78,16 +76,16 @@ func TestAlternateProviders(t *testing.T) {
|
||||||
// TestCubeOverflow makes sure cubes stop providing cards as they are exhausted instead of going out of bound
|
// TestCubeOverflow makes sure cubes stop providing cards as they are exhausted instead of going out of bound
|
||||||
func TestCubeOverflow(t *testing.T) {
|
func TestCubeOverflow(t *testing.T) {
|
||||||
const PACKSIZE = 5
|
const PACKSIZE = 5
|
||||||
c := &draft.GenericCube{
|
c := &GenericCube{
|
||||||
Cards: []draft.Card{
|
Cards: []Card{
|
||||||
{ID: "a"}, {ID: "b"}, {ID: "c"}, {ID: "d"}, {ID: "e"},
|
{ID: "a"}, {ID: "b"}, {ID: "c"}, {ID: "d"}, {ID: "e"},
|
||||||
{ID: "f"}, {ID: "g"}, {ID: "h"}, {ID: "i"},
|
{ID: "f"}, {ID: "g"}, {ID: "h"}, {ID: "i"},
|
||||||
},
|
},
|
||||||
PackSize: PACKSIZE,
|
PackSize: PACKSIZE,
|
||||||
}
|
}
|
||||||
|
|
||||||
pack1 := draft.MakePack(c)
|
pack1 := MakePack(c)
|
||||||
pack2 := draft.MakePack(c)
|
pack2 := MakePack(c)
|
||||||
|
|
||||||
// Pack 2 can only contain 4 cards, as there are not enough cards to fill it
|
// Pack 2 can only contain 4 cards, as there are not enough cards to fill it
|
||||||
if len(pack2) >= PACKSIZE {
|
if len(pack2) >= PACKSIZE {
|
||||||
|
@ -102,21 +100,11 @@ func TestCubeOverflow(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestPackString makes sure packs are serialized correctly
|
|
||||||
func TestPackString(t *testing.T) {
|
|
||||||
p := draft.Pack{{ID: "a"}, {ID: "b"}, {ID: "c"}}
|
|
||||||
expected := "a b c"
|
|
||||||
|
|
||||||
if p.String() != expected {
|
|
||||||
t.Errorf("Expected \"%s\" but got \"%s\"", expected, p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExampleGenericSet is an example usage of the Set APIs to make packs
|
// ExampleGenericSet is an example usage of the Set APIs to make packs
|
||||||
func ExampleGenericSet() {
|
func ExampleGenericSet() {
|
||||||
// Create a set with some items
|
// Create a set with some items
|
||||||
s := &draft.GenericSet{
|
s := &GenericSet{
|
||||||
Cards: []draft.Card{
|
Cards: []Card{
|
||||||
{ID: "a"},
|
{ID: "a"},
|
||||||
{ID: "b"},
|
{ID: "b"},
|
||||||
{ID: "c"},
|
{ID: "c"},
|
||||||
|
@ -125,7 +113,7 @@ func ExampleGenericSet() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a pack
|
// Create a pack
|
||||||
pack := draft.MakePack(s)
|
pack := MakePack(s)
|
||||||
|
|
||||||
// Print cards in pack
|
// Print cards in pack
|
||||||
for i, card := range pack {
|
for i, card := range pack {
|
||||||
|
@ -134,7 +122,7 @@ func ExampleGenericSet() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://gist.github.com/alioygur/16c66b4249cb42715091fe010eec7e33
|
// https://gist.github.com/alioygur/16c66b4249cb42715091fe010eec7e33
|
||||||
func sliceUniq(s []draft.Card) []draft.Card {
|
func sliceUniq(s []Card) []Card {
|
||||||
for i := 0; i < len(s); i++ {
|
for i := 0; i < len(s); i++ {
|
||||||
for i2 := i + 1; i2 < len(s); i2++ {
|
for i2 := i + 1; i2 < len(s); i2++ {
|
||||||
if s[i].ID == s[i2].ID {
|
if s[i].ID == s[i2].ID {
|
||||||
|
|
Loading…
Reference in a new issue