Compare commits

...

2 Commits

Author SHA1 Message Date
906aaa1e59 Using ureq to download files
I might just throw out the TempFile thing - should actually save them somewhere. As
I think it would likely be useful for testing too
2025-08-13 20:22:49 +01:00
6b4105ecd9 Selection and Highlighting appears okay
Still need to appropriately test with piles of visible cards > 1
2025-06-28 00:44:03 +01:00
5 changed files with 616 additions and 365 deletions

View File

@@ -1,16 +1,16 @@
use thiserror::Error; use rand::rng;
use rand::seq::SliceRandom;
use std::fmt;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use strum_macros::EnumIter; use strum_macros::EnumIter;
use std::fmt; use thiserror::Error;
use rand::seq::SliceRandom;
use rand::rng;
#[derive(PartialEq, Debug, EnumIter, Copy, Clone)] #[derive(PartialEq, Debug, EnumIter, Copy, Clone)]
pub enum Suit { pub enum Suit {
Heart, Heart,
Diamond, Diamond,
Club, Club,
Spade Spade,
} }
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
@@ -53,7 +53,7 @@ pub enum Value {
Ten, Ten,
Jack, Jack,
Queen, Queen,
King King,
} }
impl Value { impl Value {
@@ -97,7 +97,6 @@ impl fmt::Display for Value {
} }
} }
#[derive(PartialEq, Debug, Copy, Clone)] #[derive(PartialEq, Debug, Copy, Clone)]
pub struct Card { pub struct Card {
pub suit: Suit, pub suit: Suit,
@@ -105,7 +104,6 @@ pub struct Card {
pub visible: bool, pub visible: bool,
} }
impl fmt::Display for Card { impl fmt::Display for Card {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", self.value, self.suit) write!(f, "{}{}", self.value, self.suit)
@@ -140,8 +138,13 @@ impl Card {
} }
// Needs to be adjacent // Needs to be adjacent
if self.value == Value::King || self.value.indexed_values() + 1 != top.value.indexed_values() { if self.value == Value::King
return Err(StackingError::NotAdjacent(self.to_string(), top.to_string())); || self.value.indexed_values() + 1 != top.value.indexed_values()
{
return Err(StackingError::NotAdjacent(
self.to_string(),
top.to_string(),
));
} }
Ok(()) Ok(())
@@ -153,9 +156,12 @@ impl Card {
if self.value == Value::Ace { if self.value == Value::Ace {
return Ok(()); return Ok(());
} else { } else {
return Err(StackingError::NotAdjacent(self.to_string(), "an empty foundation".to_string())); return Err(StackingError::NotAdjacent(
self.to_string(),
"an empty foundation".to_string(),
));
} }
}, }
Some(c) => { Some(c) => {
if self.suit != c.suit { if self.suit != c.suit {
return Err(StackingError::WrongSuit); return Err(StackingError::WrongSuit);
@@ -172,7 +178,7 @@ impl Card {
#[derive(Debug)] #[derive(Debug)]
pub struct Deck { pub struct Deck {
pub cards: Vec<Card> pub cards: Vec<Card>,
} }
impl Default for Deck { impl Default for Deck {
@@ -180,18 +186,14 @@ impl Default for Deck {
let mut array = Vec::new(); let mut array = Vec::new();
for suit in Suit::iter() { for suit in Suit::iter() {
for value in Value::iter() { for value in Value::iter() {
array.push( array.push(Card {
Card { suit,
suit, value,
value, ..Default::default()
..Default::default() });
}
);
} }
} }
Deck { Deck { cards: array }
cards: array,
}
} }
} }
@@ -222,7 +224,7 @@ pub struct CardAndPosition {
pub enum CardPosition { pub enum CardPosition {
TopWaste, // I don't think we'd ever interact with anything other than the top of the Waste TopWaste, // I don't think we'd ever interact with anything other than the top of the Waste
Pile(usize, usize), // (PileNumber, Index) Pile(usize, usize), // (PileNumber, Index)
Foundation(usize) Foundation(usize),
} }
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
@@ -245,7 +247,7 @@ pub struct Klondike {
} }
impl Klondike { impl Klondike {
pub fn deck_to_waste(self: &mut Self) { pub fn deck_to_waste(&mut self) {
for _ in 0..self.num_cards_turned { for _ in 0..self.num_cards_turned {
let card = self.deck.pop(); let card = self.deck.pop();
match card { match card {
@@ -255,13 +257,15 @@ impl Klondike {
} }
} }
// TODO return some sort of error // TODO return some sort of error
pub fn waste_to_deck(self: &mut Self) { pub fn waste_to_deck(&mut self) {
match self.max_num_passes_through_deck { match self.max_num_passes_through_deck {
NumPassesThroughDeck::Unlimited => (), NumPassesThroughDeck::Unlimited => (),
NumPassesThroughDeck::Limited(n) => if n >= self.current_num_passes_through_deck { NumPassesThroughDeck::Limited(n) => {
// no! if n >= self.current_num_passes_through_deck {
return // no!
}, return;
}
}
} }
if self.deck.len() != 0 { if self.deck.len() != 0 {
// no! // no!
@@ -275,7 +279,7 @@ impl Klondike {
pub fn move_card(self, source_card: &CardAndPosition, dest_card: &CardAndPosition) -> bool { pub fn move_card(self, source_card: &CardAndPosition, dest_card: &CardAndPosition) -> bool {
// TODO raise errors properly // TODO raise errors properly
assert!(source_card.card.is_some()); assert!(source_card.card.is_some());
assert_eq!(source_card.card.unwrap().visible, true); assert!(source_card.card.unwrap().visible);
dest_card.pos.is_valid_dest(); dest_card.pos.is_valid_dest();
// Maybe TODO - check the .cards is the actual card in that position // Maybe TODO - check the .cards is the actual card in that position
@@ -283,19 +287,30 @@ impl Klondike {
match dest_card.pos { match dest_card.pos {
CardPosition::Pile(_, _) => self.move_card_to_pile(&source_card, &dest_card), CardPosition::Pile(_, _) => self.move_card_to_pile(&source_card, &dest_card),
CardPosition::Foundation(_f) => self.move_card_to_foundation(source_card, dest_card), CardPosition::Foundation(_f) => self.move_card_to_foundation(source_card, dest_card),
CardPosition::TopWaste => unreachable!() CardPosition::TopWaste => unreachable!(),
} }
} }
pub fn move_card_to_foundation(mut self, source_card: &CardAndPosition, dest_card: &CardAndPosition) -> bool { pub fn move_card_to_foundation(
mut self,
source_card: &CardAndPosition,
dest_card: &CardAndPosition,
) -> bool {
// TODO check the cards referenced in source and dest are the ones that are actually there // TODO check the cards referenced in source and dest are the ones that are actually there
// TODO - ditto this for the "move to pile" function // TODO - ditto this for the "move to pile" function
// TODO actually learn Rust properly so I can figure out why I need to clone the whole struct to check a value // TODO actually learn Rust properly so I can figure out why I need to clone the whole struct to check a value
if source_card.pos != CardPosition::TopWaste && !self.clone().is_card_top_of_pile(&source_card.pos) { if source_card.pos != CardPosition::TopWaste
&& !self.clone().is_card_top_of_pile(&source_card.pos)
{
// TODO as above - make all these proper errors // TODO as above - make all these proper errors
return false; return false;
} }
if source_card.card.unwrap().can_be_placed_on_foundation(&dest_card.card).is_err() { if source_card
.card
.unwrap()
.can_be_placed_on_foundation(&dest_card.card)
.is_err()
{
return false; return false;
} }
@@ -305,27 +320,36 @@ impl Klondike {
let card = self.waste.pop().unwrap(); let card = self.waste.pop().unwrap();
self.foundation[foundation_index].push(card); self.foundation[foundation_index].push(card);
return true; return true;
}, }
CardPosition::Pile(pile_index, _) => { CardPosition::Pile(pile_index, _) => {
let card = self.piles[pile_index].pop().unwrap(); let card = self.piles[pile_index].pop().unwrap();
self.foundation[foundation_index].push(card); self.foundation[foundation_index].push(card);
return true; return true;
}, }
CardPosition::Foundation(_) => { CardPosition::Foundation(_) => {
unreachable!() unreachable!()
}, }
} }
} }
unreachable!(); unreachable!();
} }
pub fn move_card_to_pile(mut self, source_card: &CardAndPosition, dest_card: &CardAndPosition) -> bool { pub fn move_card_to_pile(
if source_card.card.unwrap().can_be_placed_on_pile(&dest_card.card.unwrap()).is_err() { mut self,
source_card: &CardAndPosition,
dest_card: &CardAndPosition,
) -> bool {
if source_card
.card
.unwrap()
.can_be_placed_on_pile(&dest_card.card.unwrap())
.is_err()
{
return false; return false;
} }
let (dpile_index, dcard_index) = match dest_card.pos { let (dpile_index, dcard_index) = match dest_card.pos {
CardPosition::Pile(p, i) => (p, i), CardPosition::Pile(p, i) => (p, i),
CardPosition::TopWaste | CardPosition::Foundation(_) => return false CardPosition::TopWaste | CardPosition::Foundation(_) => return false,
}; };
if dcard_index != self.piles[dpile_index].len() - 1 { if dcard_index != self.piles[dpile_index].len() - 1 {
// Can't move to anything other than top of pile // Can't move to anything other than top of pile
@@ -337,7 +361,7 @@ impl Klondike {
CardPosition::TopWaste => { CardPosition::TopWaste => {
let card = self.waste.pop().unwrap(); let card = self.waste.pop().unwrap();
self.piles[dpile_index].push(card); self.piles[dpile_index].push(card);
}, }
CardPosition::Pile(spile_index, scard_index) => { CardPosition::Pile(spile_index, scard_index) => {
let num_cards_to_take = self.piles[spile_index].len() - scard_index; // -1 maybe? let num_cards_to_take = self.piles[spile_index].len() - scard_index; // -1 maybe?
let mut cards: Vec<Card> = Vec::new(); let mut cards: Vec<Card> = Vec::new();
@@ -349,11 +373,11 @@ impl Klondike {
} }
// TODO Properly learn rust and why I can't use drain & extend methods // TODO Properly learn rust and why I can't use drain & extend methods
// https://doc.rust-lang.org/std/vec/struct.Vec.html#method.drain // https://doc.rust-lang.org/std/vec/struct.Vec.html#method.drain
}, }
CardPosition::Foundation(s_index) => { CardPosition::Foundation(s_index) => {
let card = self.foundation[s_index].pop().unwrap(); let card = self.foundation[s_index].pop().unwrap();
self.piles[dpile_index].push(card); self.piles[dpile_index].push(card);
}, }
} }
true true
} }
@@ -361,19 +385,17 @@ impl Klondike {
fn is_card_top_of_pile(self, pos: &CardPosition) -> bool { fn is_card_top_of_pile(self, pos: &CardPosition) -> bool {
// TODO consider, at which point the Pos::Pile() ranges etc are correct // TODO consider, at which point the Pos::Pile() ranges etc are correct
match pos { match pos {
CardPosition::Pile(pile_index, card_index) => { CardPosition::Pile(pile_index, card_index) => match self.piles.get(*pile_index) {
match self.piles.get(*pile_index) { Some(pile_index) => {
Some(pile_index) => { if *card_index == (pile_index.len() - 1) {
if *card_index == (pile_index.len() - 1) { true
true } else {
} else { false
false
}
} }
None => false
} }
None => false,
}, },
CardPosition::TopWaste | CardPosition::Foundation(_) => false CardPosition::TopWaste | CardPosition::Foundation(_) => false,
} }
} }
@@ -383,7 +405,7 @@ impl Klondike {
} }
for (i, card) in self.piles[pile].iter().skip(index).enumerate() { for (i, card) in self.piles[pile].iter().skip(index).enumerate() {
if card.visible { if card.visible {
return i; return i;
} }
} }
panic!("Pile with only facedown cards - this is wrong") panic!("Pile with only facedown cards - this is wrong")
@@ -404,9 +426,17 @@ impl Default for Klondike {
fn default() -> Self { fn default() -> Self {
let mut deck = Deck::default(); let mut deck = Deck::default();
deck.shuffle(Seed::Unseeded); deck.shuffle(Seed::Unseeded);
let mut piles: [Vec<Card>; NUM_PILES_KLONDIKE] = [Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new()]; let mut piles: [Vec<Card>; NUM_PILES_KLONDIKE] = [
Vec::new(),
Vec::new(),
Vec::new(),
Vec::new(),
Vec::new(),
Vec::new(),
Vec::new(),
];
for pile in 0..NUM_PILES_KLONDIKE { for pile in 0..NUM_PILES_KLONDIKE {
for num in 0..pile+1 { for num in 0..pile + 1 {
let mut c = deck.cards.pop().unwrap(); let mut c = deck.cards.pop().unwrap();
if num == pile { if num == pile {
c.visible = true; c.visible = true;
@@ -445,19 +475,28 @@ mod tests {
value: Value::Six, value: Value::Six,
..Default::default() ..Default::default()
}; };
assert_eq!(testing_card.can_be_placed_on_pile(&bad_same_suit), Err(StackingError::SameColour)); assert_eq!(
testing_card.can_be_placed_on_pile(&bad_same_suit),
Err(StackingError::SameColour)
);
let bad_same_colour = Card { let bad_same_colour = Card {
suit: Suit::Diamond, suit: Suit::Diamond,
value: Value::Six, value: Value::Six,
..Default::default() ..Default::default()
}; };
assert_eq!(testing_card.can_be_placed_on_pile(&bad_same_colour), Err(StackingError::SameColour)); assert_eq!(
testing_card.can_be_placed_on_pile(&bad_same_colour),
Err(StackingError::SameColour)
);
let should_stack_card = Card { let should_stack_card = Card {
suit: Suit::Club, suit: Suit::Club,
value: Value::Six, value: Value::Six,
..Default::default() ..Default::default()
}; };
assert_eq!(testing_card.can_be_placed_on_pile(&should_stack_card), Ok(())); assert_eq!(
testing_card.can_be_placed_on_pile(&should_stack_card),
Ok(())
);
let value_too_high = Card { let value_too_high = Card {
suit: Suit::Club, suit: Suit::Club,
value: Value::Seven, value: Value::Seven,
@@ -467,7 +506,9 @@ mod tests {
if let Err(e) = not_adj_error { if let Err(e) = not_adj_error {
match e { match e {
StackingError::NotAdjacent(_, _) => assert!(true), StackingError::NotAdjacent(_, _) => assert!(true),
StackingError::SameColour => assert!(false, "Colour is different - incorrect error"), StackingError::SameColour => {
assert!(false, "Colour is different - incorrect error")
}
StackingError::WrongSuit => unreachable!(), StackingError::WrongSuit => unreachable!(),
} }
} else { } else {
@@ -498,12 +539,12 @@ mod tests {
let ace = Card { let ace = Card {
suit: Suit::Spade, suit: Suit::Spade,
value: Value::Ace, value: Value::Ace,
.. Default::default() ..Default::default()
}; };
klon.piles[0].push(ace.clone()); klon.piles[0].push(ace.clone());
let source_card = CardAndPosition { let source_card = CardAndPosition {
card: Some(ace.clone()), card: Some(ace.clone()),
pos: CardPosition::Pile(0, 1) pos: CardPosition::Pile(0, 1),
}; };
let dest_card = CardAndPosition { let dest_card = CardAndPosition {
card: None, card: None,
@@ -516,12 +557,12 @@ mod tests {
let two = Card { let two = Card {
suit: Suit::Spade, suit: Suit::Spade,
value: Value::Two, value: Value::Two,
.. Default::default() ..Default::default()
}; };
klon.piles[0].push(two.clone()); klon.piles[0].push(two.clone());
let source_card = CardAndPosition { let source_card = CardAndPosition {
card: Some(two.clone()), card: Some(two.clone()),
pos: CardPosition::Pile(0, 1) pos: CardPosition::Pile(0, 1),
}; };
let dest_card = CardAndPosition { let dest_card = CardAndPosition {
card: None, card: None,
@@ -534,7 +575,7 @@ mod tests {
let ace = Card { let ace = Card {
suit: Suit::Spade, suit: Suit::Spade,
value: Value::Ace, value: Value::Ace,
.. Default::default() ..Default::default()
}; };
klon.waste.push(ace.clone()); klon.waste.push(ace.clone());
let source_card = CardAndPosition { let source_card = CardAndPosition {
@@ -552,7 +593,7 @@ mod tests {
let two = Card { let two = Card {
suit: Suit::Spade, suit: Suit::Spade,
value: Value::Two, value: Value::Two,
.. Default::default() ..Default::default()
}; };
klon.waste.push(two.clone()); klon.waste.push(two.clone());
let source_card = CardAndPosition { let source_card = CardAndPosition {
@@ -570,12 +611,12 @@ mod tests {
let ace = Card { let ace = Card {
suit: Suit::Spade, suit: Suit::Spade,
value: Value::Ace, value: Value::Ace,
.. Default::default() ..Default::default()
}; };
let two = Card { let two = Card {
suit: Suit::Spade, suit: Suit::Spade,
value: Value::Two, value: Value::Two,
.. Default::default() ..Default::default()
}; };
klon.waste.push(two.clone()); klon.waste.push(two.clone());
klon.piles[0].push(ace.clone()); klon.piles[0].push(ace.clone());
@@ -589,8 +630,6 @@ mod tests {
}; };
assert!(klon.move_card_to_foundation(&source_card, &dest_card)); assert!(klon.move_card_to_foundation(&source_card, &dest_card));
// TODO the following cases: // TODO the following cases:
// - moving a card from pile to foundation when something is already there // - moving a card from pile to foundation when something is already there
// - moving Ace from waste to top of pile // - moving Ace from waste to top of pile
@@ -604,12 +643,12 @@ mod tests {
let ace = Card { let ace = Card {
suit: Suit::Heart, suit: Suit::Heart,
value: Value::Ace, value: Value::Ace,
.. Default::default() ..Default::default()
}; };
let two = Card { let two = Card {
suit: Suit::Spade, suit: Suit::Spade,
value: Value::Two, value: Value::Two,
.. Default::default() ..Default::default()
}; };
klon.waste.push(two.clone()); klon.waste.push(two.clone());
klon.piles[0].push(ace.clone()); klon.piles[0].push(ace.clone());
@@ -631,12 +670,12 @@ mod tests {
let ace_heart = Card { let ace_heart = Card {
suit: Suit::Heart, suit: Suit::Heart,
value: Value::Ace, value: Value::Ace,
.. Default::default() ..Default::default()
}; };
let ace_spade = Card { let ace_spade = Card {
suit: Suit::Spade, suit: Suit::Spade,
value: Value::Ace, value: Value::Ace,
.. Default::default() ..Default::default()
}; };
klon.waste.push(ace_heart.clone()); klon.waste.push(ace_heart.clone());
klon.piles[0].push(ace_spade.clone()); klon.piles[0].push(ace_spade.clone());
@@ -655,7 +694,7 @@ mod tests {
fn get_a_whole_deck() { fn get_a_whole_deck() {
let d = Deck::default(); let d = Deck::default();
assert_eq!(d.cards.len(), 52); // Probably should test whether all cards are in... eh assert_eq!(d.cards.len(), 52); // Probably should test whether all cards are in... eh
//println!("{:#?}", d); // A "manual" review looks alright //println!("{:#?}", d); // A "manual" review looks alright
} }
#[test] #[test]
@@ -664,12 +703,12 @@ mod tests {
let ace = Card { let ace = Card {
suit: Suit::Heart, suit: Suit::Heart,
value: Value::Ace, value: Value::Ace,
.. Default::default() ..Default::default()
}; };
let two = Card { let two = Card {
suit: Suit::Spade, suit: Suit::Spade,
value: Value::Two, value: Value::Two,
.. Default::default() ..Default::default()
}; };
klon.piles[0].push(two.clone()); klon.piles[0].push(two.clone());
klon.piles[1].push(ace.clone()); klon.piles[1].push(ace.clone());
@@ -683,18 +722,18 @@ mod tests {
}; };
assert!(klon.move_card_to_pile(&source_card, &dest_card)); assert!(klon.move_card_to_pile(&source_card, &dest_card));
} }
#[test] #[test]
fn move_pile_card_to_bad_pile() { fn move_pile_card_to_bad_pile() {
let mut klon = Klondike::default(); let mut klon = Klondike::default();
let ace = Card { let ace = Card {
suit: Suit::Heart, suit: Suit::Heart,
value: Value::Ace, value: Value::Ace,
.. Default::default() ..Default::default()
}; };
let two = Card { let two = Card {
suit: Suit::Diamond, suit: Suit::Diamond,
value: Value::Two, value: Value::Two,
.. Default::default() ..Default::default()
}; };
klon.piles[0].push(two.clone()); klon.piles[0].push(two.clone());
klon.piles[1].push(ace.clone()); klon.piles[1].push(ace.clone());

File diff suppressed because it is too large Load Diff

View File

@@ -7,6 +7,10 @@ edition = "2021"
[dependencies] [dependencies]
chrono = { version = "0.4.39", features = ["serde"] } chrono = { version = "0.4.39", features = ["serde"] }
clap = { version = "4.5.42", features = ["derive"] }
closestmatch = "0.1.2"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.138" serde_json = "1.0.138"
tempfile = "3.20.0"
ureq = { version = "3.0.12", features = ["json"] }
uuid = { version = "1.12.1", features = ["v4", "serde"] } uuid = { version = "1.12.1", features = ["v4", "serde"] }

View File

@@ -1,8 +1,83 @@
use chrono::NaiveDate; use chrono::NaiveDate;
use serde::Deserialize; use serde::Deserialize;
use serde_json::Value; use serde_json::Value;
use std::fs;
use std::io::{Read, Seek, SeekFrom, Write};
use tempfile::NamedTempFile;
use ureq;
use uuid::Uuid; use uuid::Uuid;
#[allow(dead_code)]
#[derive(Deserialize, Debug)]
struct ScryfallBulkData {
pub id: Uuid,
pub uri: String,
#[serde(rename = "type")]
pub stype: String,
pub name: String,
pub description: String,
pub download_uri: String,
pub updated_at: String,
pub content_type: String,
pub content_encoding: String,
}
#[allow(dead_code)]
#[derive(Deserialize, Debug)]
struct ScryfallBulk {
pub object: String,
pub has_more: bool,
pub data: Vec<ScryfallBulkData>,
}
#[derive(Deserialize, PartialEq, Debug)]
pub enum ScryfallBulkType {
#[serde(rename = "oracle_cards")]
OracleCards,
#[serde(rename = "unique_artwork")]
UniqueArtwork,
#[serde(rename = "default_cards")]
DefaultCards,
#[serde(rename = "all_cards")]
AllCards,
#[serde(rename = "rulings")]
Rulings,
}
const SCRYFALL_BULK_API: &str = "https://api.scryfall.com/bulk-data";
pub fn download_latest(
_stype: ScryfallBulkType,
mut dest_file: &NamedTempFile,
) -> Result<(), Box<dyn std::error::Error>> {
let bulk_body: ScryfallBulk = ureq::get(SCRYFALL_BULK_API)
.header("User-Agent", "Arthur's Card Finger Testing v0.1")
.header("Accept", "application/json")
.call()?
.body_mut()
.read_json::<ScryfallBulk>()?;
let mut download_uri = String::new();
for scryfall_bulk in bulk_body.data {
// TODO: Actually implement getting different types
if scryfall_bulk.stype == "oracle_cards" {
download_uri = scryfall_bulk.download_uri;
}
}
assert!(!download_uri.is_empty());
let cards_response = ureq::get(download_uri)
.header("User-Agent", "Arthur's Card Finger Testing v0.1")
.header("Accept", "application/json")
.call()?
.body_mut()
.with_config()
.limit(700 * 1024 * 1024)
.read_to_string()?;
write!(dest_file, "{}", cards_response)?;
Ok(())
}
// Info from here: // Info from here:
// https://scryfall.com/docs/api/cards // https://scryfall.com/docs/api/cards
#[allow(dead_code)] #[allow(dead_code)]
@@ -110,7 +185,6 @@ struct ScryfallCard {
pub watermark: Option<String>, pub watermark: Option<String>,
pub preview: Option<Preview>, pub preview: Option<Preview>,
// These aren't in the Scryfall docs, but some cards do have 'em // These aren't in the Scryfall docs, but some cards do have 'em
pub foil: Option<bool>, pub foil: Option<bool>,
pub nonfoil: Option<bool>, pub nonfoil: Option<bool>,
@@ -785,4 +859,14 @@ mod tests {
let ac = fs::read_to_string(f).unwrap(); let ac = fs::read_to_string(f).unwrap();
let _ac: Vec<ScryfallCard> = serde_json::from_str(&ac).unwrap(); let _ac: Vec<ScryfallCard> = serde_json::from_str(&ac).unwrap();
} }
#[test]
#[ignore]
fn get_scryfall_bulk_page() {
let mut file = NamedTempFile::new().unwrap();
let _ = download_latest(ScryfallBulkType::OracleCards, &file);
let file_size = file.seek(SeekFrom::End(0)).unwrap();
assert!(file_size > 4092);
println!("File size: {}", file_size);
}
} }

View File

@@ -0,0 +1,22 @@
use clap::Parser;
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
#[arg(short, long)]
update: bool,
remainder: Vec<String>,
}
fn main() {
let mut args = Args::parse();
if args.update {
unimplemented!("Haven't implemented update yet");
}
let card_name = args.remainder;
if card_name.is_empty() {
panic!("You need to put some card text to search");
}
let search_string = card_name.join(" ");
dbg!(search_string);
}