it works!

This commit is contained in:
Hamcha 2023-10-12 15:54:19 +02:00
commit 6568ce1b69
Signed by: hamcha
GPG key ID: 1669C533B8CF6D89
7 changed files with 348 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*.exe
*.json

46
cards.go Normal file
View file

@ -0,0 +1,46 @@
package main
import "os"
type Card struct {
AsciiName string `json:"asciiName"`
Name string `json:"name"`
Identifiers struct {
CardKingdomId string `json:"cardKingdomId"`
CardsphereId string `json:"cardsphereId"`
MCMId string `json:"mcmId"`
MCMMetaId string `json:"mcmMetaId"`
MTGOId string `json:"mtgoId"`
MultiverseId string `json:"multiverseId"`
ScryfallId string `json:"scryfallId"`
TCGPlayerProductId string `json:"tcgplayerProductId"`
} `json:"identifiers"`
Rarity string `json:"rarity"`
SetCode string `json:"setCode"`
CardType string `json:"type"`
Types []string `json:"types"`
UUID string `json:"uuid"`
}
type CardDB struct {
Data map[string]Card `json:"data"`
}
func loadCardInfos(identifierFile string) func() (map[string]Card, error) {
return func() (map[string]Card, error) {
// Check if identifiers file exists
_, err := os.Stat(identifierFile)
if os.IsNotExist(err) {
// Download identifiers file
err = downloadFile(identifierURL, identifierFile)
}
if err != nil {
return nil, err
}
// Read identifiers file
var cardDB CardDB
err = readJSONFile(identifierFile, &cardDB)
return cardDB.Data, err
}
}

44
files.go Normal file
View file

@ -0,0 +1,44 @@
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
jsoniter "github.com/json-iterator/go"
)
func readJSONFile(filename string, dst any) error {
log.Printf("Reading %s\n", filename)
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
return jsoniter.ConfigFastest.NewDecoder(file).Decode(dst)
}
func downloadFile(url, filename string) error {
log.Printf("%s not found, downloading from %s \n", filename, url)
response, err := http.Get(url)
if err != nil {
return err
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return fmt.Errorf("request failed with status code: %d", response.StatusCode)
}
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, response.Body)
return err
}

9
go.mod Normal file
View file

@ -0,0 +1,9 @@
module git.fromouter.space/Hamcha/blah
go 1.20
require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
)

12
go.sum Normal file
View file

@ -0,0 +1,12 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=

138
main.go Normal file
View file

@ -0,0 +1,138 @@
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)
checkErr(os.WriteFile(fmt.Sprintf("pack-%d.json", i), []byte(serializePack(pack)), 0644))
}
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
}

97
price.go Normal file
View file

@ -0,0 +1,97 @@
package main
import (
"os"
)
type PriceRecord struct {
MTGO map[string]PriceList `json:"mtgo,omitempty"`
Paper map[string]PriceList `json:"paper,omitempty"`
}
type PricePoints struct {
Normal map[string]float64 `json:"normal,omitempty"`
Foil map[string]float64 `json:"foil,omitempty"`
}
type PriceList struct {
Retail *PricePoints `json:"retail,omitempty"`
Currency string `json:"currency"`
Buylist *PricePoints `json:"buylist,omitempty"`
}
type PriceDB struct {
Data map[string]PriceRecord `json:"data"`
}
type CardPriceEntry struct {
UUID string `json:"uuid"`
Price float64 `json:"price"`
Currency string `json:"currency"`
Seller string `json:"seller"`
}
func loadPrices(pricesFile string, priceFilter float64) func() ([]CardPriceEntry, error) {
return func() ([]CardPriceEntry, error) {
// Check if prices file exists
_, err := os.Stat(pricesFile)
if os.IsNotExist(err) {
// Download prices file
err = downloadFile(pricesURL, pricesFile)
}
if err != nil {
return nil, err
}
// Read prices file
var priceDB PriceDB
err = readJSONFile(pricesFile, &priceDB)
if err != nil {
return nil, err
}
// Filter cards we care about
entries := make([]CardPriceEntry, 0)
for uuid, priceCatalog := range priceDB.Data {
// Get all paper retail prices
lowestPrice, currency, seller := getLowestPrice(priceCatalog.Paper)
if priceFilter < lowestPrice {
continue
}
entries = append(entries, CardPriceEntry{
UUID: uuid,
Price: lowestPrice,
Currency: currency,
Seller: seller,
})
}
return entries, nil
}
}
func getLowestPrice(sellers map[string]PriceList) (lowestPrice float64, currency string, seller string) {
lowestPrice = 9999
for cardSeller, priceList := range sellers {
if priceList.Retail == nil {
continue
}
for _, price := range priceList.Retail.Normal {
if price < lowestPrice {
lowestPrice = price
currency = priceList.Currency
seller = cardSeller
}
}
for _, price := range priceList.Retail.Foil {
if price < lowestPrice {
lowestPrice = price
currency = priceList.Currency
seller = cardSeller
}
}
}
return
}