diff --git a/card_stuffs/src/lib.rs b/card_stuffs/src/lib.rs index 1db28f2..d6a6d4f 100644 --- a/card_stuffs/src/lib.rs +++ b/card_stuffs/src/lib.rs @@ -1,16 +1,16 @@ -use thiserror::Error; +use rand::rng; +use rand::seq::SliceRandom; +use std::fmt; use strum::IntoEnumIterator; use strum_macros::EnumIter; -use std::fmt; -use rand::seq::SliceRandom; -use rand::rng; +use thiserror::Error; #[derive(PartialEq, Debug, EnumIter, Copy, Clone)] pub enum Suit { Heart, Diamond, Club, - Spade + Spade, } #[derive(PartialEq, Debug)] @@ -53,7 +53,7 @@ pub enum Value { Ten, Jack, Queen, - King + King, } impl Value { @@ -97,7 +97,6 @@ impl fmt::Display for Value { } } - #[derive(PartialEq, Debug, Copy, Clone)] pub struct Card { pub suit: Suit, @@ -105,7 +104,6 @@ pub struct Card { pub visible: bool, } - impl fmt::Display for Card { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}{}", self.value, self.suit) @@ -140,8 +138,13 @@ impl Card { } // Needs to be adjacent - if self.value == Value::King || self.value.indexed_values() + 1 != top.value.indexed_values() { - return Err(StackingError::NotAdjacent(self.to_string(), top.to_string())); + if self.value == Value::King + || self.value.indexed_values() + 1 != top.value.indexed_values() + { + return Err(StackingError::NotAdjacent( + self.to_string(), + top.to_string(), + )); } Ok(()) @@ -153,9 +156,12 @@ impl Card { if self.value == Value::Ace { return Ok(()); } 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) => { if self.suit != c.suit { return Err(StackingError::WrongSuit); @@ -172,7 +178,7 @@ impl Card { #[derive(Debug)] pub struct Deck { - pub cards: Vec + pub cards: Vec, } impl Default for Deck { @@ -180,18 +186,14 @@ impl Default for Deck { let mut array = Vec::new(); for suit in Suit::iter() { for value in Value::iter() { - array.push( - Card { - suit, - value, - ..Default::default() - } - ); + array.push(Card { + suit, + value, + ..Default::default() + }); } } - Deck { - cards: array, - } + Deck { cards: array } } } @@ -222,7 +224,7 @@ pub struct CardAndPosition { pub enum CardPosition { TopWaste, // I don't think we'd ever interact with anything other than the top of the Waste Pile(usize, usize), // (PileNumber, Index) - Foundation(usize) + Foundation(usize), } #[derive(Debug, Copy, Clone)] @@ -245,7 +247,7 @@ pub struct 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 { let card = self.deck.pop(); match card { @@ -255,13 +257,15 @@ impl Klondike { } } // 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 { NumPassesThroughDeck::Unlimited => (), - NumPassesThroughDeck::Limited(n) => if n >= self.current_num_passes_through_deck { - // no! - return - }, + NumPassesThroughDeck::Limited(n) => { + if n >= self.current_num_passes_through_deck { + // no! + return; + } + } } if self.deck.len() != 0 { // no! @@ -275,7 +279,7 @@ impl Klondike { pub fn move_card(self, source_card: &CardAndPosition, dest_card: &CardAndPosition) -> bool { // TODO raise errors properly 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(); // Maybe TODO - check the .cards is the actual card in that position @@ -283,19 +287,30 @@ impl Klondike { match dest_card.pos { CardPosition::Pile(_, _) => self.move_card_to_pile(&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 - 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 - 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 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; } @@ -305,27 +320,36 @@ impl Klondike { let card = self.waste.pop().unwrap(); self.foundation[foundation_index].push(card); return true; - }, + } CardPosition::Pile(pile_index, _) => { let card = self.piles[pile_index].pop().unwrap(); self.foundation[foundation_index].push(card); return true; - }, + } CardPosition::Foundation(_) => { unreachable!() - }, + } } } unreachable!(); } - pub fn move_card_to_pile(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() { + pub fn move_card_to_pile( + 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; } let (dpile_index, dcard_index) = match dest_card.pos { 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 { // Can't move to anything other than top of pile @@ -337,7 +361,7 @@ impl Klondike { CardPosition::TopWaste => { let card = self.waste.pop().unwrap(); self.piles[dpile_index].push(card); - }, + } CardPosition::Pile(spile_index, scard_index) => { let num_cards_to_take = self.piles[spile_index].len() - scard_index; // -1 maybe? let mut cards: Vec = Vec::new(); @@ -349,11 +373,11 @@ impl Klondike { } // 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 - }, + } CardPosition::Foundation(s_index) => { let card = self.foundation[s_index].pop().unwrap(); self.piles[dpile_index].push(card); - }, + } } true } @@ -361,19 +385,17 @@ impl Klondike { fn is_card_top_of_pile(self, pos: &CardPosition) -> bool { // TODO consider, at which point the Pos::Pile() ranges etc are correct match pos { - CardPosition::Pile(pile_index, card_index) => { - match self.piles.get(*pile_index) { - Some(pile_index) => { - if *card_index == (pile_index.len() - 1) { - true - } else { - false - } + CardPosition::Pile(pile_index, card_index) => match self.piles.get(*pile_index) { + Some(pile_index) => { + if *card_index == (pile_index.len() - 1) { + true + } else { + 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() { if card.visible { - return i; + return i; } } panic!("Pile with only facedown cards - this is wrong") @@ -404,9 +426,17 @@ impl Default for Klondike { fn default() -> Self { let mut deck = Deck::default(); deck.shuffle(Seed::Unseeded); - let mut piles: [Vec; NUM_PILES_KLONDIKE] = [Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new()]; + let mut piles: [Vec; 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 num in 0..pile+1 { + for num in 0..pile + 1 { let mut c = deck.cards.pop().unwrap(); if num == pile { c.visible = true; @@ -445,19 +475,28 @@ mod tests { value: Value::Six, ..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 { suit: Suit::Diamond, value: Value::Six, ..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 { suit: Suit::Club, value: Value::Six, ..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 { suit: Suit::Club, value: Value::Seven, @@ -467,7 +506,9 @@ mod tests { if let Err(e) = not_adj_error { match e { 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!(), } } else { @@ -498,12 +539,12 @@ mod tests { let ace = Card { suit: Suit::Spade, value: Value::Ace, - .. Default::default() + ..Default::default() }; klon.piles[0].push(ace.clone()); let source_card = CardAndPosition { card: Some(ace.clone()), - pos: CardPosition::Pile(0, 1) + pos: CardPosition::Pile(0, 1), }; let dest_card = CardAndPosition { card: None, @@ -516,12 +557,12 @@ mod tests { let two = Card { suit: Suit::Spade, value: Value::Two, - .. Default::default() + ..Default::default() }; klon.piles[0].push(two.clone()); let source_card = CardAndPosition { card: Some(two.clone()), - pos: CardPosition::Pile(0, 1) + pos: CardPosition::Pile(0, 1), }; let dest_card = CardAndPosition { card: None, @@ -534,7 +575,7 @@ mod tests { let ace = Card { suit: Suit::Spade, value: Value::Ace, - .. Default::default() + ..Default::default() }; klon.waste.push(ace.clone()); let source_card = CardAndPosition { @@ -552,7 +593,7 @@ mod tests { let two = Card { suit: Suit::Spade, value: Value::Two, - .. Default::default() + ..Default::default() }; klon.waste.push(two.clone()); let source_card = CardAndPosition { @@ -570,12 +611,12 @@ mod tests { let ace = Card { suit: Suit::Spade, value: Value::Ace, - .. Default::default() + ..Default::default() }; let two = Card { suit: Suit::Spade, value: Value::Two, - .. Default::default() + ..Default::default() }; klon.waste.push(two.clone()); klon.piles[0].push(ace.clone()); @@ -589,8 +630,6 @@ mod tests { }; assert!(klon.move_card_to_foundation(&source_card, &dest_card)); - - // TODO the following cases: // - moving a card from pile to foundation when something is already there // - moving Ace from waste to top of pile @@ -604,12 +643,12 @@ mod tests { let ace = Card { suit: Suit::Heart, value: Value::Ace, - .. Default::default() + ..Default::default() }; let two = Card { suit: Suit::Spade, value: Value::Two, - .. Default::default() + ..Default::default() }; klon.waste.push(two.clone()); klon.piles[0].push(ace.clone()); @@ -631,12 +670,12 @@ mod tests { let ace_heart = Card { suit: Suit::Heart, value: Value::Ace, - .. Default::default() + ..Default::default() }; let ace_spade = Card { suit: Suit::Spade, value: Value::Ace, - .. Default::default() + ..Default::default() }; klon.waste.push(ace_heart.clone()); klon.piles[0].push(ace_spade.clone()); @@ -655,7 +694,7 @@ mod tests { fn get_a_whole_deck() { let d = Deck::default(); 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] @@ -664,12 +703,12 @@ mod tests { let ace = Card { suit: Suit::Heart, value: Value::Ace, - .. Default::default() + ..Default::default() }; let two = Card { suit: Suit::Spade, value: Value::Two, - .. Default::default() + ..Default::default() }; klon.piles[0].push(two.clone()); klon.piles[1].push(ace.clone()); @@ -683,18 +722,18 @@ mod tests { }; assert!(klon.move_card_to_pile(&source_card, &dest_card)); } - #[test] + #[test] fn move_pile_card_to_bad_pile() { let mut klon = Klondike::default(); let ace = Card { suit: Suit::Heart, value: Value::Ace, - .. Default::default() + ..Default::default() }; let two = Card { suit: Suit::Diamond, value: Value::Two, - .. Default::default() + ..Default::default() }; klon.piles[0].push(two.clone()); klon.piles[1].push(ace.clone()); diff --git a/card_stuffs/src/main.rs b/card_stuffs/src/main.rs index 7934d86..2a38488 100644 --- a/card_stuffs/src/main.rs +++ b/card_stuffs/src/main.rs @@ -1,13 +1,13 @@ +use card_stuffs::{self, CardPosition}; use core::panic; use std::io; -use card_stuffs::{self, CardPosition}; use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}; use ratatui::{ - layout::{Constraint, Layout, Rect, Flex}, - style::{Style, Stylize, Color}, + layout::{Constraint, Flex, Layout, Rect}, + style::{Color, Style, Stylize}, text::Line, - widgets::{Block, BorderType, Borders, Paragraph, Clear, Wrap}, + widgets::{Block, BorderType, Borders, Clear, Paragraph, Wrap}, DefaultTerminal, Frame, }; @@ -39,54 +39,70 @@ impl Default for App { const CARD_HEIGHT: u16 = 11; const CARD_WIDTH: u16 = 15; -fn draw_waste(cards_in_waste: &Vec, area: Rect, frame: &mut Frame, highlight: bool, selected: bool) { +fn draw_waste( + cards_in_waste: &Vec, + area: Rect, + frame: &mut Frame, + highlight: bool, + selected: bool, +) { let horizontal = Layout::horizontal([ Constraint::Length(3), Constraint::Length(3), Constraint::Length(CARD_WIDTH), ]); + let [w1, w2, top_waste] = horizontal.areas(area); // There must be a better way to do all of this - if cards_in_waste.len() == 0 { - frame.render_widget( - empty_pile(highlight, selected), - top_waste - ); + if cards_in_waste.is_empty() { + frame.render_widget(empty_pile(highlight, selected), top_waste); } if cards_in_waste.len() >= 3 { frame.render_widget( partially_covered_card(&cards_in_waste[cards_in_waste.len() - 3]), - w1 + w1, ); frame.render_widget( partially_covered_card(&cards_in_waste[cards_in_waste.len() - 2]), - w2 + w2, ); frame.render_widget( - card_widget(&cards_in_waste[cards_in_waste.len() - 1], true, highlight, false), - top_waste + card_widget( + &cards_in_waste[cards_in_waste.len() - 1], + true, + highlight, + false, + ), + top_waste, ); } else if cards_in_waste.len() == 2 { frame.render_widget( partially_covered_card(&cards_in_waste[cards_in_waste.len() - 2]), - w2 + w2, ); frame.render_widget( - card_widget(&cards_in_waste[cards_in_waste.len() - 1], true, highlight, false), - top_waste + card_widget( + &cards_in_waste[cards_in_waste.len() - 1], + true, + highlight, + false, + ), + top_waste, ); } else if cards_in_waste.len() == 1 { frame.render_widget( - card_widget(&cards_in_waste[cards_in_waste.len() - 1], true, highlight, false), - top_waste + card_widget( + &cards_in_waste[cards_in_waste.len() - 1], + true, + highlight, + false, + ), + top_waste, ); } - } - - impl App { /// runs the application's main loop until the user quits pub fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> { @@ -111,19 +127,19 @@ impl App { .borders(Borders::TOP) .border_type(BorderType::Thick) .title(Line::from("Legends of Soltar").bold()), - title_bar + title_bar, ); - let status_bar_info = format!("Cards Per-Draw: {} ---- Times Through Deck: {} ---- Press 'h' for Help ", self.cards.num_cards_turned, self.cards.current_num_passes_through_deck); + let status_bar_info = format!( + "Cards Per-Draw: {} ---- Times Through Deck: {} ---- Press 'h' for Help ", + self.cards.num_cards_turned, self.cards.current_num_passes_through_deck + ); frame.render_widget( Block::new().borders(Borders::TOP).title(status_bar_info), - status_bar + status_bar, ); - let vertical = Layout::vertical([ - Constraint::Length(CARD_HEIGHT), - Constraint::Min(0), - ]); + let vertical = Layout::vertical([Constraint::Length(CARD_HEIGHT), Constraint::Min(0)]); let [dwf_area, piles_area] = vertical.areas(main_area); let horizontal = Layout::horizontal([ @@ -133,22 +149,20 @@ impl App { Constraint::Length(CARD_WIDTH), Constraint::Length(CARD_WIDTH), Constraint::Length(CARD_WIDTH), - ]).flex(Flex::SpaceAround); + ]) + .flex(Flex::SpaceAround); let [deck_area, waste_area, fa, fb, fc, fd] = horizontal.areas(dwf_area); let foundation_areas = [fa, fb, fc, fd]; - frame.render_widget( - deck_widget(&self.cards.deck), - deck_area - ); + frame.render_widget(deck_widget(&self.cards.deck), deck_area); { let highlight = self.highlighted_card == CardPosition::TopWaste; let selected = { match self.selected_card { None => false, - Some(pos) => pos == CardPosition::TopWaste + Some(pos) => pos == CardPosition::TopWaste, } }; draw_waste(&self.cards.waste, waste_area, frame, highlight, selected); @@ -159,13 +173,10 @@ impl App { let selected = { match self.selected_card { None => false, - Some(pos) => pos == CardPosition::Foundation(i) + Some(pos) => pos == CardPosition::Foundation(i), } }; - frame.render_widget( - empty_pile(highlight, selected), - *fa - ); + frame.render_widget(empty_pile(highlight, selected), *fa); } let horizontal = Layout::horizontal([ @@ -176,10 +187,10 @@ impl App { Constraint::Length(CARD_WIDTH), Constraint::Length(CARD_WIDTH), Constraint::Length(CARD_WIDTH), - ]).flex(Flex::SpaceAround); + ]) + .flex(Flex::SpaceAround); let pileses: [Rect; 7] = horizontal.areas(piles_area); - for pile in 0..card_stuffs::NUM_PILES_KLONDIKE { let mut constraints = Vec::new(); for card in 0..(card_stuffs::NUM_PILES_KLONDIKE + 13) { @@ -198,7 +209,8 @@ impl App { } let vertical = Layout::vertical(constraints); - let card_display: [Rect; card_stuffs::NUM_PILES_KLONDIKE + 13] = vertical.areas(pileses[pile]); + let card_display: [Rect; card_stuffs::NUM_PILES_KLONDIKE + 13] = + vertical.areas(pileses[pile]); for (i, card) in card_display.iter().enumerate() { match self.cards.piles[pile].get(i) { @@ -209,19 +221,15 @@ impl App { let selected = { match self.selected_card { None => false, - Some(pos) => pos == CardPosition::Pile(pile, i) + Some(pos) => pos == CardPosition::Pile(pile, i), } }; let a_card = match c.visible { true => card_widget(c, is_top_card, highlight, selected), false => card_widget(c, is_top_card, highlight, selected), }; - frame.render_widget( - &a_card, - *card - ) + frame.render_widget(&a_card, *card) } - } } } @@ -248,9 +256,11 @@ impl App { fn handle_key_event(&mut self, key_event: KeyEvent) { match key_event.code { KeyCode::Char('q') => { - if self.show_exit { self.exit() } + if self.show_exit { + self.exit() + } self.show_exit = true; - }, + } KeyCode::Char('b') => self.show_exit = false, KeyCode::Char('d') => self.cards.deck_to_waste(), KeyCode::Char('w') => self.cards.waste_to_deck(), @@ -258,10 +268,22 @@ impl App { KeyCode::Char('3') => self.cards.num_cards_turned = 3, KeyCode::Char('h') => self.show_help = !self.show_help, // toggle KeyCode::Char(' ') => self.select_card(), - KeyCode::Left => self.highlighted_card = handle_move_highlighted(&self.highlighted_card, Direction::Left, &self.cards), - KeyCode::Right => self.highlighted_card = handle_move_highlighted(&self.highlighted_card, Direction::Right, &self.cards), - KeyCode::Up => self.highlighted_card = handle_move_highlighted(&self.highlighted_card, Direction::Up, &self.cards), - KeyCode::Down => self.highlighted_card = handle_move_highlighted(&self.highlighted_card, Direction::Down, &self.cards), + KeyCode::Left => { + self.highlighted_card = + handle_move_highlighted(&self.highlighted_card, Direction::Left, &self.cards) + } + KeyCode::Right => { + self.highlighted_card = + handle_move_highlighted(&self.highlighted_card, Direction::Right, &self.cards) + } + KeyCode::Up => { + self.highlighted_card = + handle_move_highlighted(&self.highlighted_card, Direction::Up, &self.cards) + } + KeyCode::Down => { + self.highlighted_card = + handle_move_highlighted(&self.highlighted_card, Direction::Down, &self.cards) + } _ => {} } } @@ -273,7 +295,7 @@ impl App { fn select_card(&mut self) { self.selected_card = Some(self.highlighted_card); // FIXME - actually moving the selected card to next position - // FIXME - don't allow selection of empty pile/foundation + // FIXME - don't allow selection of empty pile/foundation } } @@ -291,16 +313,18 @@ enum Direction { Right, } -fn handle_move_highlighted(current_position: &CardPosition, direction: Direction, cards: &card_stuffs::Klondike) -> CardPosition { +fn handle_move_highlighted( + current_position: &CardPosition, + direction: Direction, + cards: &card_stuffs::Klondike, +) -> CardPosition { match current_position { - CardPosition::TopWaste => { - match direction { - Direction::Up | Direction::Left => { CardPosition::TopWaste }, - Direction::Right => { CardPosition::Foundation(0) } - Direction::Down => { - let lowest_shown_card = cards.clone().lowest_visible_card_in_pile_from_index(1, 0); - CardPosition:: Pile(1, lowest_shown_card) - } + CardPosition::TopWaste => match direction { + Direction::Up | Direction::Left => CardPosition::TopWaste, + Direction::Right => CardPosition::Foundation(0), + Direction::Down => { + let lowest_shown_card = cards.clone().lowest_visible_card_in_pile_from_index(1, 0); + CardPosition::Pile(1, lowest_shown_card) } }, CardPosition::Pile(p, i) => { @@ -311,9 +335,10 @@ fn handle_move_highlighted(current_position: &CardPosition, direction: Direction } else { CardPosition::Pile(*p, *i + 1) } - }, + } Direction::Up => { - let lowest_shown_card = cards.clone().lowest_visible_card_in_pile_from_index(*p, 0); + let lowest_shown_card = + cards.clone().lowest_visible_card_in_pile_from_index(*p, 0); if *i == lowest_shown_card { match *p { 0 | 1 => CardPosition::TopWaste, @@ -321,79 +346,84 @@ fn handle_move_highlighted(current_position: &CardPosition, direction: Direction 4 => CardPosition::Foundation(1), 5 => CardPosition::Foundation(2), 6 => CardPosition::Foundation(3), - _ => panic!("Should be on Pile over 6") + _ => panic!("Should be on Pile over 6"), } } else { CardPosition::Pile(*p, *i - 1) } - }, + } Direction::Left => { if *p == 0 { CardPosition::Pile(*p, *i) } else { - let lowest_shown_card = cards.clone().lowest_visible_card_in_pile_from_index(*p - 1, 0); + let lowest_shown_card = cards + .clone() + .lowest_visible_card_in_pile_from_index(*p - 1, 0); if lowest_shown_card <= *i && cards.piles[*p].len() <= *i { CardPosition::Pile(*p - 1, *i) // CHECK } else { CardPosition::Pile(*p - 1, lowest_shown_card) } } - }, + } Direction::Right => { if *p == 6 { CardPosition::Pile(*p, *i) } else { - let lowest_shown_card = cards.clone().lowest_visible_card_in_pile_from_index(*p + 1, 0); + let lowest_shown_card = cards + .clone() + .lowest_visible_card_in_pile_from_index(*p + 1, 0); if lowest_shown_card <= *i && cards.piles[*p].len() <= *i { CardPosition::Pile(*p + 1, *i) // CHECK } else { CardPosition::Pile(*p + 1, lowest_shown_card) } } - }, - } - }, - CardPosition::Foundation(f) => { - match direction { - Direction::Up => { CardPosition::Foundation(*f) }, - Direction::Left => { - if *f == 0 { CardPosition::TopWaste } - else { CardPosition::Foundation(f - 1) } - }, - Direction::Right => { - if *f >= 3 { CardPosition::Foundation(3) } - else { CardPosition::Foundation(*f + 1)} - } - Direction::Down => { - match f { - 0 => { - let i = cards.clone().lowest_visible_card_in_pile_from_index(3, 0); - CardPosition::Pile(3, i) - }, - 1 => { - let i = cards.clone().lowest_visible_card_in_pile_from_index(4, 0); - CardPosition::Pile(4, i) - }, - 2 => { - let i = cards.clone().lowest_visible_card_in_pile_from_index(5, 0); - CardPosition::Pile(5, i) - }, - 3 => { - let i = cards.clone().lowest_visible_card_in_pile_from_index(6, 0); - CardPosition::Pile(6, i) - }, - _ => panic!("Can't be on a foundation this high") - } } } } + CardPosition::Foundation(f) => match direction { + Direction::Up => CardPosition::Foundation(*f), + Direction::Left => { + if *f == 0 { + CardPosition::TopWaste + } else { + CardPosition::Foundation(f - 1) + } + } + Direction::Right => { + if *f >= 3 { + CardPosition::Foundation(3) + } else { + CardPosition::Foundation(*f + 1) + } + } + Direction::Down => match f { + 0 => { + let i = cards.clone().lowest_visible_card_in_pile_from_index(3, 0); + CardPosition::Pile(3, i) + } + 1 => { + let i = cards.clone().lowest_visible_card_in_pile_from_index(4, 0); + CardPosition::Pile(4, i) + } + 2 => { + let i = cards.clone().lowest_visible_card_in_pile_from_index(5, 0); + CardPosition::Pile(5, i) + } + 3 => { + let i = cards.clone().lowest_visible_card_in_pile_from_index(6, 0); + CardPosition::Pile(6, i) + } + _ => panic!("Can't be on a foundation this high"), + }, + }, } } fn show_help(frame: &mut Frame, area: &Rect) { let block = Block::bordered().title("Help"); - let text = -"You are playing \"Legends of Soltar\" - a Klondike thingy + let text = "You are playing \"Legends of Soltar\" - a Klondike thingy Press 'q' to Quit Press '1' or '3' to change the number of cards you draw from the deck Press 'd' to draw from your deck @@ -409,8 +439,7 @@ Press 'w' to put the waste pile back into the deck (you can only do this when th fn show_exit(frame: &mut Frame, area: &Rect) { let block = Block::bordered().title("Exit?"); - let text = -"Really want to exit Legend of Soltar? + let text = "Really want to exit Legend of Soltar? Press 'q' to Quit or 'b' to go back"; let p = Paragraph::new(text).wrap(Wrap { trim: true }); let vertical = Layout::vertical([Constraint::Max(10)]).flex(Flex::Center); @@ -421,8 +450,12 @@ Press 'q' to Quit or 'b' to go back"; frame.render_widget(p.block(block), area); } - -fn card_widget<'a>(card: &'a card_stuffs::Card, top: bool, highlight: bool, select: bool) -> Paragraph<'a> { +fn card_widget<'a>( + card: &'a card_stuffs::Card, + top: bool, + highlight: bool, + select: bool, +) -> Paragraph<'a> { if !card.visible { return facedown_card(top); } @@ -446,18 +479,17 @@ fn card_widget<'a>(card: &'a card_stuffs::Card, top: bool, highlight: bool, sele border_style = border_style.fg(Color::Green); } - Paragraph::new(card_image) - .style(card_style) - .block(Block::new() + Paragraph::new(card_image).style(card_style).block( + Block::new() .style(border_style) .borders(borders) - .border_type(BorderType::Rounded)) + .border_type(BorderType::Rounded), + ) } - fn deck_widget(cards_in_deck: &Vec) -> Paragraph<'static> { let card_image = format!( - "#############\n\ + "#############\n\ #############\n\ ### Cards ###\n\ ### Left ###\n\ @@ -465,31 +497,31 @@ fn deck_widget(cards_in_deck: &Vec) -> Paragraph<'static> { #### {:02} #####\n\ #############\n\ #############\n\ - #############", cards_in_deck.len() + #############", + cards_in_deck.len() ); - Paragraph::new(card_image) - .block(Block::new() + Paragraph::new(card_image).block( + Block::new() .borders(Borders::ALL) - .border_type(BorderType::Rounded)) + .border_type(BorderType::Rounded), + ) } fn partially_covered_card(card: &card_stuffs::Card) -> Paragraph { - let card_image = format!( - "{value}{suit}", value=card.value, suit=card.suit - ); + let card_image = format!("{value}{suit}", value = card.value, suit = card.suit); let card_style = match card.suit.colour() { card_stuffs::Colour::Black => Style::new().black().bg(Color::White), card_stuffs::Colour::Red => Style::new().red().bg(Color::White), }; let borders = Borders::TOP | Borders::LEFT | Borders::BOTTOM; let border_style = Style::new().white().on_black(); - Paragraph::new(card_image) - .style(card_style) - .block(Block::new() + Paragraph::new(card_image).style(card_style).block( + Block::new() .style(border_style) .borders(borders) - .border_type(BorderType::Rounded)) + .border_type(BorderType::Rounded), + ) } fn facedown_card(top: bool) -> Paragraph<'static> { @@ -510,16 +542,17 @@ fn facedown_card(top: bool) -> Paragraph<'static> { borders |= Borders::BOTTOM; } - Paragraph::new(hidden_card) - .block(Block::new() - .borders(borders) - .border_type(BorderType::Rounded)) + Paragraph::new(hidden_card).block( + Block::new() + .borders(borders) + .border_type(BorderType::Rounded), + ) } fn empty_pile(highlight: bool, selected: bool) -> Paragraph<'static> { // made using https://www.asciiart.eu/ let hidden_card = format!( -" + " XX XX XX XX @@ -536,30 +569,30 @@ fn empty_pile(highlight: bool, selected: bool) -> Paragraph<'static> { border_style = border_style.fg(Color::Green); } - Paragraph::new(hidden_card) - .block(Block::new() + Paragraph::new(hidden_card).block( + Block::new() .style(border_style) .borders(Borders::ALL) - .border_type(BorderType::Rounded)) + .border_type(BorderType::Rounded), + ) } - fn card_paragraph(c: &card_stuffs::Card) -> String { match c.value { card_stuffs::Value::Ace => { -/* -XX - X - X X - X X - X X - XXXXXXX - X X - X X - XX - */ + /* + XX + X + X X + X X + X X + XXXXXXX + X X + X X + XX + */ format!( -"{value}{suit} + "{value}{suit} {suit} {suit} {suit} {suit} {suit} @@ -567,22 +600,25 @@ XX {suit}{suit}{suit}{suit}{suit}{suit}{suit} {suit} {suit} {suit} {suit} - {value}{suit}", value=c.value, suit=c.suit) - }, + {value}{suit}", + value = c.value, + suit = c.suit + ) + } card_stuffs::Value::Two => { -/* -XX - X - XXX - X + /* + XX + X + XXX + X - X - XXX - X - XX - */ + X + XXX + X + XX + */ format!( -"{value}{suit} + "{value}{suit} {suit} {suit}{suit}{suit} {suit} @@ -590,22 +626,25 @@ XX {suit} {suit}{suit}{suit} {suit} - {value}{suit}", value=c.value, suit=c.suit) - }, + {value}{suit}", + value = c.value, + suit = c.suit + ) + } card_stuffs::Value::Three => { -/* -XX X - XXX - X - X - XXX - X - X - XXX - X XX - */ + /* + XX X + XXX + X + X + XXX + X + X + XXX + X XX + */ format!( -"{value}{suit} {suit} + "{value}{suit} {suit} {suit}{suit}{suit} {suit} {suit} @@ -613,11 +652,14 @@ XX X {suit} {suit} {suit}{suit}{suit} - {suit} {value}{suit}", value=c.value, suit=c.suit) - }, + {suit} {value}{suit}", + value = c.value, + suit = c.suit + ) + } card_stuffs::Value::Four => { format!( -"{value}{suit} + "{value}{suit} {suit}{suit} {suit}{suit} @@ -625,11 +667,14 @@ XX X {suit}{suit} {suit}{suit} - {value}{suit}", value=c.value, suit=c.suit) - }, + {value}{suit}", + value = c.value, + suit = c.suit + ) + } card_stuffs::Value::Five => { format!( -"{value}{suit} + "{value}{suit} {suit}{suit} {suit}{suit} @@ -637,22 +682,25 @@ XX X {suit}{suit} {suit}{suit} - {value}{suit}", value=c.value, suit=c.suit) - }, + {value}{suit}", + value = c.value, + suit = c.suit + ) + } card_stuffs::Value::Six => { -/* -XX + /* + XX - XX XX + XX XX - XX XX + XX XX - XX XX + XX XX - XX -*/ + XX + */ format!( -"{value}{suit} + "{value}{suit} {suit}{suit} {suit}{suit} @@ -660,22 +708,25 @@ XX {suit}{suit} {suit}{suit} - {value}{suit}", value=c.value, suit=c.suit) - }, + {value}{suit}", + value = c.value, + suit = c.suit + ) + } card_stuffs::Value::Seven => { -/* -XX - XX XX + /* + XX + XX XX - XX + XX - XX XX + XX XX - XX XX - XX -*/ + XX XX + XX + */ format!( -"{value}{suit} + "{value}{suit} {suit}{suit} {suit}{suit} {suit}{suit} @@ -683,22 +734,25 @@ XX {suit}{suit} {suit}{suit} {suit}{suit} {suit}{suit} - {value}{suit}", value=c.value, suit=c.suit) - }, + {value}{suit}", + value = c.value, + suit = c.suit + ) + } card_stuffs::Value::Eight => { -/* -XX + /* + XX - X X - X - X X - X - X X + X X + X + X X + X + X X - XX -*/ + XX + */ format!( -"{value}{suit} + "{value}{suit} {suit} {suit} {suit} @@ -706,22 +760,25 @@ XX {suit} {suit} {suit} - {value}{suit}", value=c.value, suit=c.suit) - }, - card_stuffs::Value::Nine=> { -/* -XX - X X + {value}{suit}", + value = c.value, + suit = c.suit + ) + } + card_stuffs::Value::Nine => { + /* + XX + X X - X X - X - X X + X X + X + X X - X X - XX -*/ + X X + XX + */ format!( -"{value}{suit} + "{value}{suit} {suit} {suit} {suit} {suit} @@ -729,22 +786,25 @@ XX {suit} {suit} {suit} {suit} - {value}{suit}", value=c.value, suit=c.suit) - }, + {value}{suit}", + value = c.value, + suit = c.suit + ) + } card_stuffs::Value::Ten => { -/* -XX - X X - X - X X + /* + XX + X X + X + X X - X X - X - X X - XX -*/ + X X + X + X X + XX + */ format!( -"{value}{suit} + "{value}{suit} {suit} {suit} {suit} {suit} {suit} @@ -752,22 +812,25 @@ XX {suit} {suit} {suit} {suit} {suit} - {value}{suit}", value=c.value, suit=c.suit) - }, + {value}{suit}", + value = c.value, + suit = c.suit + ) + } card_stuffs::Value::Jack => { -/* -XX + /* + XX - XXXXX - X - X - X X - XX + XXXXX + X + X + X X + XX - XX -*/ + XX + */ format!( -"{value}{suit} + "{value}{suit} {suit}{suit}{suit}{suit}{suit} {suit} @@ -775,22 +838,25 @@ XX {suit} {suit} {suit}{suit} - {value}{suit}", value=c.value, suit=c.suit) - }, + {value}{suit}", + value = c.value, + suit = c.suit + ) + } card_stuffs::Value::Queen => { -/* -XX - XXXX - X X - X X - X X - X XX - XXXXX - X - XX -*/ + /* + XX + XXXX + X X + X X + X X + X XX + XXXXX + X + XX + */ format!( -"{value}{suit} + "{value}{suit} {suit}{suit}{suit}{suit} {suit} {suit} {suit} {suit} @@ -798,22 +864,25 @@ XX {suit} {suit} {suit}{suit}{suit}{suit}{suit} {suit} - {value}{suit}", value=c.value, suit=c.suit) - }, + {value}{suit}", + value = c.value, + suit = c.suit + ) + } card_stuffs::Value::King => { -/* -XX - X X - X XX - X XX - XX - X XX - X XX - X X - XX -*/ + /* + XX + X X + X XX + X XX + XX + X XX + X XX + X X + XX + */ format!( -"{value}{suit} + "{value}{suit} {suit} {suit} {suit} {suit}{suit} {suit} {suit}{suit} @@ -821,7 +890,10 @@ XX {suit} {suit}{suit} {suit} {suit}{suit} {suit} {suit} - {value}{suit}", value=c.value, suit=c.suit) - }, + {value}{suit}", + value = c.value, + suit = c.suit + ) + } } } diff --git a/scryfall_deser/Cargo.toml b/scryfall_deser/Cargo.toml index 644410b..96c1f76 100644 --- a/scryfall_deser/Cargo.toml +++ b/scryfall_deser/Cargo.toml @@ -7,6 +7,10 @@ edition = "2021" [dependencies] 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_json = "1.0.138" +tempfile = "3.20.0" +ureq = { version = "3.0.12", features = ["json"] } uuid = { version = "1.12.1", features = ["v4", "serde"] } diff --git a/scryfall_deser/src/lib.rs b/scryfall_deser/src/lib.rs index 53b03b3..c15a303 100644 --- a/scryfall_deser/src/lib.rs +++ b/scryfall_deser/src/lib.rs @@ -1,8 +1,83 @@ use chrono::NaiveDate; use serde::Deserialize; use serde_json::Value; +use std::fs; +use std::io::{Read, Seek, SeekFrom, Write}; +use tempfile::NamedTempFile; +use ureq; 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, +} + +#[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> { + 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::()?; + + 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: // https://scryfall.com/docs/api/cards #[allow(dead_code)] @@ -110,7 +185,6 @@ struct ScryfallCard { pub watermark: Option, pub preview: Option, - // These aren't in the Scryfall docs, but some cards do have 'em pub foil: Option, pub nonfoil: Option, @@ -785,4 +859,14 @@ mod tests { let ac = fs::read_to_string(f).unwrap(); let _ac: Vec = 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); + } } diff --git a/scryfall_deser/src/main.rs b/scryfall_deser/src/main.rs new file mode 100644 index 0000000..bd2babc --- /dev/null +++ b/scryfall_deser/src/main.rs @@ -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, +} + +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); +}