2023-10-12 13:54:19 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"math/rand"
|
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
identifierURL = "https://mtgjson.com/api/v5/AllIdentifiers.json"
|
|
|
|
pricesURL = "https://mtgjson.com/api/v5/AllPricesToday.json"
|
|
|
|
)
|
|
|
|
|
|
|
|
type CardRecord struct {
|
|
|
|
card Card `json:"card"`
|
|
|
|
price CardPriceEntry `json:"price"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
identifierFile := flag.String("i", "AllIdentifiers.json", "identifier file (AllIdentifiers.json), if not found it will be downloaded")
|
|
|
|
pricesFile := flag.String("p", "AllPricesToday.json", "prices file (AllPricesToday.json), if not found it will be downloaded")
|
|
|
|
priceFilter := flag.Float64("price", 0.04, "Maximum card price allowed (in whole units of any currency)")
|
|
|
|
typeFilter := flag.String("remove-types", "token,emblem,sticker,card,hero,plane,scheme", "comma-separated list of card types to remove")
|
|
|
|
cardsPerDraft := flag.Int("packSize", 100, "How many cards to put in each \"dollar store\" pack")
|
|
|
|
packCount := flag.Int("packCount", 2, "How many \"dollar store\" pack to generate")
|
|
|
|
landLimit := flag.Int("landLimit", 5, "Limit how many lands to put in each pack (0 to disable)")
|
|
|
|
flag.Parse()
|
|
|
|
|
|
|
|
priceChan := make(chan []CardPriceEntry)
|
|
|
|
cardChan := make(chan map[string]Card)
|
|
|
|
|
|
|
|
log.Println(" == Loading data... ==")
|
|
|
|
go loadAsync(loadCardInfos(*identifierFile), cardChan)
|
|
|
|
go loadAsync(loadPrices(*pricesFile, *priceFilter), priceChan)
|
|
|
|
|
|
|
|
cards := <-cardChan
|
|
|
|
prices := <-priceChan
|
|
|
|
|
|
|
|
// Filter cards
|
|
|
|
forbiddenTypes := make(map[string]struct{})
|
|
|
|
for _, t := range strings.Split(*typeFilter, ",") {
|
|
|
|
forbiddenTypes[t] = struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
var filtered []CardRecord
|
|
|
|
for _, price := range prices {
|
|
|
|
entry := cards[price.UUID]
|
|
|
|
|
|
|
|
// Filter card types we don't want
|
|
|
|
typeOk := true
|
|
|
|
for _, t := range entry.Types {
|
|
|
|
if _, ok := forbiddenTypes[strings.ToLower(t)]; ok {
|
|
|
|
typeOk = false
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !typeOk {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
filtered = append(filtered, CardRecord{
|
|
|
|
card: entry,
|
|
|
|
price: price,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
types := make(map[string]int)
|
|
|
|
for _, record := range filtered {
|
|
|
|
log.Printf("Price for %s: %.2f %s at %s\n", record.card.Name, record.price.Price, record.price.Currency, record.price.Seller)
|
|
|
|
for _, t := range record.card.Types {
|
|
|
|
types[strings.ToUpper(t)[0:1]+t[1:]]++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Println(" == Stats ==")
|
|
|
|
log.Printf("Found %d cards\n", len(filtered))
|
|
|
|
for t, count := range types {
|
|
|
|
log.Printf(" - %s: %d\n", t, count)
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Println(" == Generating packs... ==")
|
|
|
|
for i := 0; i < *packCount; i++ {
|
|
|
|
pack := generatePack(filtered, *cardsPerDraft, *landLimit)
|
2023-10-13 10:07:16 +00:00
|
|
|
checkErr(os.WriteFile(fmt.Sprintf("pack-%d.txt", i), []byte(serializePack(pack)), 0644))
|
2023-10-12 13:54:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
log.Println(" == Done ==")
|
|
|
|
}
|
|
|
|
|
|
|
|
func isType(card Card, askingType string) bool {
|
|
|
|
for _, t := range card.Types {
|
|
|
|
if strings.ToLower(t) == strings.ToLower(askingType) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func serializePack(pack []CardRecord) (out string) {
|
|
|
|
for _, card := range pack {
|
|
|
|
out += "1 " + card.card.Name + "\n"
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func generatePack(filtered []CardRecord, cardsPerDraft, landLimit int) []CardRecord {
|
|
|
|
// Pick random cards
|
|
|
|
randCards := make([]CardRecord, 0)
|
|
|
|
landCount := 0
|
|
|
|
for len(randCards) < cardsPerDraft {
|
|
|
|
pick := filtered[rand.Intn(len(filtered))]
|
|
|
|
if isType(pick.card, "land") {
|
|
|
|
if landLimit > 0 && landCount >= landLimit {
|
|
|
|
continue
|
|
|
|
} else {
|
|
|
|
landCount++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
randCards = append(randCards, pick)
|
|
|
|
}
|
|
|
|
return randCards
|
|
|
|
}
|
|
|
|
|
|
|
|
func checkErr(err error) {
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Fatal error: %s\n", err.Error())
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func loadAsync[T any](fn func() (T, error), chn chan T) {
|
|
|
|
out, err := fn()
|
|
|
|
checkErr(err)
|
|
|
|
chn <- out
|
|
|
|
}
|