115 lines
3.5 KiB
Rust
115 lines
3.5 KiB
Rust
use anyhow::Result;
|
|
use clap::Parser;
|
|
use rand::{seq::SliceRandom, Rng};
|
|
|
|
mod cards;
|
|
mod files;
|
|
mod price;
|
|
|
|
const IDENTIFIER_URL: &str = "https://mtgjson.com/api/v5/AllIdentifiers.json";
|
|
const PRICES_URL: &str = "https://mtgjson.com/api/v5/AllPricesToday.json";
|
|
|
|
#[derive(Parser, Debug)]
|
|
#[command(author, version, about, long_about = None)]
|
|
struct Args {
|
|
/// Identifier file (AllIdentifiers.json), if not found it will be downloaded
|
|
#[arg(short = 'i', long, default_value = "AllIdentifiers.json")]
|
|
identifier_file: String,
|
|
|
|
/// Prices file (AllPricesToday.json), if not found it will be downloaded
|
|
#[arg(short = 'p', long, default_value = "AllPricesToday.json")]
|
|
prices_file: String,
|
|
|
|
/// Maximum card price allowed (in whole units of any currency)
|
|
#[arg(long = "price", default_value_t = 0.04f64)]
|
|
price_filter: f64,
|
|
|
|
/// Comma-separated list of card types to remove
|
|
#[arg(long, default_value = "token,emblem,sticker,card,hero,plane,scheme")]
|
|
remove_types: String,
|
|
|
|
/// How many cards to put in each "dollar store" pack
|
|
#[arg(short = 'c', long = "pack-size", default_value_t = 100)]
|
|
cards_per_draft: usize,
|
|
|
|
/// How many "dollar store" pack to generate
|
|
#[arg(short = 'n', long, default_value_t = 2)]
|
|
pack_count: usize,
|
|
|
|
/// Limit how many lands to put in each pack (0 to disable)
|
|
#[arg(short = 'l', long, default_value_t = 5)]
|
|
land_limit: usize,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<()> {
|
|
let args = Args::parse();
|
|
|
|
let prices_file = files::open_or_download(PRICES_URL, &args.prices_file).await?;
|
|
let cards_file = files::open_or_download(IDENTIFIER_URL, &args.identifier_file).await?;
|
|
|
|
let prices = price::get_cards_in_budget(prices_file, args.price_filter)?;
|
|
let card_db = cards::CardDB::load(cards_file)?;
|
|
|
|
let remove_types: Vec<_> = args
|
|
.remove_types
|
|
.split(',')
|
|
.map(|s| s.trim())
|
|
.filter(|s| !s.is_empty())
|
|
.collect();
|
|
|
|
let cards: Vec<_> = prices
|
|
.iter()
|
|
// Map UUIDs to cards
|
|
.filter_map(|uuid| card_db.get_by_uuid(uuid))
|
|
// Remove types we don't care about
|
|
.filter(|card| !remove_types.iter().any(|t| card.is_type(t)))
|
|
.collect();
|
|
|
|
// Print stats
|
|
println!("Found {} cards", cards.len());
|
|
|
|
// Generate packs of cards
|
|
for index in 0..args.pack_count {
|
|
let decklist = generate_pack(&cards, args.cards_per_draft, args.land_limit);
|
|
// Save decklist to file
|
|
files::save_decklist(&format!("decklist-{}.txt", index), decklist).await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_pack(
|
|
card_list: &Vec<&cards::Card>,
|
|
cards_per_draft: usize,
|
|
land_limit: usize,
|
|
) -> Vec<String> {
|
|
let mut rng = rand::thread_rng();
|
|
|
|
// Generate a bunch of random cards
|
|
let mut picked: Vec<_> = (0..cards_per_draft)
|
|
.map(|_| card_list[rng.gen_range(0..card_list.len())])
|
|
.collect();
|
|
|
|
// Check for too many lands
|
|
if land_limit > 0 {
|
|
let non_lands: Vec<_> = card_list.iter().filter(|c| !c.is_type("land")).collect();
|
|
let mut num_lands = picked.iter().filter(|c| c.is_type("land")).count();
|
|
|
|
picked = picked
|
|
.into_iter()
|
|
.map(|c| {
|
|
if c.is_type("land") && num_lands > land_limit {
|
|
num_lands -= 1;
|
|
non_lands.choose(&mut rng).unwrap()
|
|
} else {
|
|
c
|
|
}
|
|
})
|
|
.collect();
|
|
}
|
|
|
|
// Return the picked card names
|
|
picked.iter().map(|c| c.name.clone()).collect()
|
|
}
|