Compare commits
2 Commits
9f1eac75a0
...
906aaa1e59
| Author | SHA1 | Date | |
|---|---|---|---|
| 906aaa1e59 | |||
| 6b4105ecd9 |
@@ -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<Card>
|
||||
pub cards: Vec<Card>,
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
NumPassesThroughDeck::Limited(n) => {
|
||||
if n >= self.current_num_passes_through_deck {
|
||||
// no!
|
||||
return
|
||||
},
|
||||
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<Card> = 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,8 +385,7 @@ 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) {
|
||||
CardPosition::Pile(pile_index, card_index) => match self.piles.get(*pile_index) {
|
||||
Some(pile_index) => {
|
||||
if *card_index == (pile_index.len() - 1) {
|
||||
true
|
||||
@@ -370,10 +393,9 @@ impl Klondike {
|
||||
false
|
||||
}
|
||||
}
|
||||
None => false
|
||||
}
|
||||
None => false,
|
||||
},
|
||||
CardPosition::TopWaste | CardPosition::Foundation(_) => false
|
||||
CardPosition::TopWaste | CardPosition::Foundation(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,9 +426,17 @@ impl Default for Klondike {
|
||||
fn default() -> Self {
|
||||
let mut deck = Deck::default();
|
||||
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 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());
|
||||
@@ -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());
|
||||
@@ -689,12 +728,12 @@ mod tests {
|
||||
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());
|
||||
|
||||
@@ -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<card_stuffs::Card>, area: Rect, frame: &mut Frame, highlight: bool) {
|
||||
fn draw_waste(
|
||||
cards_in_waste: &Vec<card_stuffs::Card>,
|
||||
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),
|
||||
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,23 +149,34 @@ 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);
|
||||
|
||||
draw_waste(&self.cards.waste, waste_area, frame, self.highlighted_card == CardPosition::TopWaste);
|
||||
{
|
||||
let highlight = self.highlighted_card == CardPosition::TopWaste;
|
||||
let selected = {
|
||||
match self.selected_card {
|
||||
None => false,
|
||||
Some(pos) => pos == CardPosition::TopWaste,
|
||||
}
|
||||
};
|
||||
draw_waste(&self.cards.waste, waste_area, frame, highlight, selected);
|
||||
}
|
||||
|
||||
for (i, fa) in foundation_areas.iter().enumerate() {
|
||||
frame.render_widget(
|
||||
empty_pile(self.highlighted_card == CardPosition::Foundation(i)),
|
||||
*fa
|
||||
);
|
||||
let highlight = self.highlighted_card == CardPosition::Foundation(i);
|
||||
let selected = {
|
||||
match self.selected_card {
|
||||
None => false,
|
||||
Some(pos) => pos == CardPosition::Foundation(i),
|
||||
}
|
||||
};
|
||||
frame.render_widget(empty_pile(highlight, selected), *fa);
|
||||
}
|
||||
|
||||
let horizontal = Layout::horizontal([
|
||||
@@ -160,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) {
|
||||
@@ -182,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) {
|
||||
@@ -190,17 +218,18 @@ impl App {
|
||||
Some(c) => {
|
||||
let is_top_card = i == self.cards.piles[pile].len() - 1;
|
||||
let highlight = self.highlighted_card == CardPosition::Pile(pile, i);
|
||||
let selected = true; // FIXME
|
||||
let selected = {
|
||||
match self.selected_card {
|
||||
None => false,
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -227,19 +256,34 @@ 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(),
|
||||
KeyCode::Char('1') => self.cards.num_cards_turned = 1,
|
||||
KeyCode::Char('3') => self.cards.num_cards_turned = 3,
|
||||
KeyCode::Char('h') => self.show_help = !self.show_help, // toggle
|
||||
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::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)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -247,6 +291,12 @@ impl App {
|
||||
fn exit(&mut self) {
|
||||
self.exit = true;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
@@ -263,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) }
|
||||
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(1, lowest_shown_card)
|
||||
}
|
||||
},
|
||||
CardPosition::Pile(p, i) => {
|
||||
@@ -283,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,
|
||||
@@ -293,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) },
|
||||
}
|
||||
}
|
||||
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)}
|
||||
if *f == 0 {
|
||||
CardPosition::TopWaste
|
||||
} else {
|
||||
CardPosition::Foundation(f - 1)
|
||||
}
|
||||
Direction::Down => {
|
||||
match f {
|
||||
}
|
||||
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"),
|
||||
},
|
||||
},
|
||||
_ => 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
|
||||
@@ -381,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);
|
||||
@@ -393,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);
|
||||
}
|
||||
@@ -418,15 +479,14 @@ 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<card_stuffs::Card>) -> Paragraph<'static> {
|
||||
let card_image = format!(
|
||||
"#############\n\
|
||||
@@ -437,31 +497,31 @@ fn deck_widget(cards_in_deck: &Vec<card_stuffs::Card>) -> 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> {
|
||||
@@ -482,16 +542,17 @@ fn facedown_card(top: bool) -> Paragraph<'static> {
|
||||
borders |= Borders::BOTTOM;
|
||||
}
|
||||
|
||||
Paragraph::new(hidden_card)
|
||||
.block(Block::new()
|
||||
Paragraph::new(hidden_card).block(
|
||||
Block::new()
|
||||
.borders(borders)
|
||||
.border_type(BorderType::Rounded))
|
||||
.border_type(BorderType::Rounded),
|
||||
)
|
||||
}
|
||||
|
||||
fn empty_pile(highlight: bool) -> Paragraph<'static> {
|
||||
fn empty_pile(highlight: bool, selected: bool) -> Paragraph<'static> {
|
||||
// made using https://www.asciiart.eu/
|
||||
let hidden_card = format!(
|
||||
"
|
||||
"
|
||||
XX XX
|
||||
XX XX
|
||||
|
||||
@@ -504,21 +565,23 @@ fn empty_pile(highlight: bool) -> Paragraph<'static> {
|
||||
let mut border_style = Style::new();
|
||||
if highlight {
|
||||
border_style = border_style.fg(Color::Blue);
|
||||
} else if selected {
|
||||
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
|
||||
/*
|
||||
XX
|
||||
X
|
||||
X X
|
||||
X X
|
||||
@@ -529,7 +592,7 @@ XX
|
||||
XX
|
||||
*/
|
||||
format!(
|
||||
"{value}{suit}
|
||||
"{value}{suit}
|
||||
{suit}
|
||||
{suit} {suit}
|
||||
{suit} {suit}
|
||||
@@ -537,11 +600,14 @@ 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
|
||||
/*
|
||||
XX
|
||||
X
|
||||
XXX
|
||||
X
|
||||
@@ -552,7 +618,7 @@ XX
|
||||
XX
|
||||
*/
|
||||
format!(
|
||||
"{value}{suit}
|
||||
"{value}{suit}
|
||||
{suit}
|
||||
{suit}{suit}{suit}
|
||||
{suit}
|
||||
@@ -560,11 +626,14 @@ 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
|
||||
/*
|
||||
XX X
|
||||
XXX
|
||||
X
|
||||
X
|
||||
@@ -575,7 +644,7 @@ XX X
|
||||
X XX
|
||||
*/
|
||||
format!(
|
||||
"{value}{suit} {suit}
|
||||
"{value}{suit} {suit}
|
||||
{suit}{suit}{suit}
|
||||
{suit}
|
||||
{suit}
|
||||
@@ -583,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}
|
||||
|
||||
@@ -595,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}
|
||||
|
||||
@@ -607,11 +682,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::Six => {
|
||||
/*
|
||||
XX
|
||||
/*
|
||||
XX
|
||||
|
||||
XX XX
|
||||
|
||||
@@ -620,9 +698,9 @@ XX
|
||||
XX XX
|
||||
|
||||
XX
|
||||
*/
|
||||
*/
|
||||
format!(
|
||||
"{value}{suit}
|
||||
"{value}{suit}
|
||||
|
||||
{suit}{suit} {suit}{suit}
|
||||
|
||||
@@ -630,11 +708,14 @@ 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
|
||||
@@ -643,9 +724,9 @@ XX
|
||||
|
||||
XX XX
|
||||
XX
|
||||
*/
|
||||
*/
|
||||
format!(
|
||||
"{value}{suit}
|
||||
"{value}{suit}
|
||||
{suit}{suit} {suit}{suit}
|
||||
|
||||
{suit}{suit}
|
||||
@@ -653,11 +734,14 @@ 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
|
||||
@@ -666,9 +750,9 @@ XX
|
||||
X X
|
||||
|
||||
XX
|
||||
*/
|
||||
*/
|
||||
format!(
|
||||
"{value}{suit}
|
||||
"{value}{suit}
|
||||
|
||||
{suit} {suit}
|
||||
{suit}
|
||||
@@ -676,11 +760,14 @@ XX
|
||||
{suit}
|
||||
{suit} {suit}
|
||||
|
||||
{value}{suit}", value=c.value, suit=c.suit)
|
||||
},
|
||||
card_stuffs::Value::Nine=> {
|
||||
/*
|
||||
XX
|
||||
{value}{suit}",
|
||||
value = c.value,
|
||||
suit = c.suit
|
||||
)
|
||||
}
|
||||
card_stuffs::Value::Nine => {
|
||||
/*
|
||||
XX
|
||||
X X
|
||||
|
||||
X X
|
||||
@@ -689,9 +776,9 @@ XX
|
||||
|
||||
X X
|
||||
XX
|
||||
*/
|
||||
*/
|
||||
format!(
|
||||
"{value}{suit}
|
||||
"{value}{suit}
|
||||
{suit} {suit}
|
||||
|
||||
{suit} {suit}
|
||||
@@ -699,11 +786,14 @@ 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
|
||||
/*
|
||||
XX
|
||||
X X
|
||||
X
|
||||
X X
|
||||
@@ -712,9 +802,9 @@ XX
|
||||
X
|
||||
X X
|
||||
XX
|
||||
*/
|
||||
*/
|
||||
format!(
|
||||
"{value}{suit}
|
||||
"{value}{suit}
|
||||
{suit} {suit}
|
||||
{suit}
|
||||
{suit} {suit}
|
||||
@@ -722,11 +812,14 @@ 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
|
||||
@@ -735,9 +828,9 @@ XX
|
||||
XX
|
||||
|
||||
XX
|
||||
*/
|
||||
*/
|
||||
format!(
|
||||
"{value}{suit}
|
||||
"{value}{suit}
|
||||
|
||||
{suit}{suit}{suit}{suit}{suit}
|
||||
{suit}
|
||||
@@ -745,11 +838,14 @@ 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
|
||||
/*
|
||||
XX
|
||||
XXXX
|
||||
X X
|
||||
X X
|
||||
@@ -758,9 +854,9 @@ XX
|
||||
XXXXX
|
||||
X
|
||||
XX
|
||||
*/
|
||||
*/
|
||||
format!(
|
||||
"{value}{suit}
|
||||
"{value}{suit}
|
||||
{suit}{suit}{suit}{suit}
|
||||
{suit} {suit}
|
||||
{suit} {suit}
|
||||
@@ -768,11 +864,14 @@ 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
|
||||
/*
|
||||
XX
|
||||
X X
|
||||
X XX
|
||||
X XX
|
||||
@@ -781,9 +880,9 @@ XX
|
||||
X XX
|
||||
X X
|
||||
XX
|
||||
*/
|
||||
*/
|
||||
format!(
|
||||
"{value}{suit}
|
||||
"{value}{suit}
|
||||
{suit} {suit}
|
||||
{suit} {suit}{suit}
|
||||
{suit} {suit}{suit}
|
||||
@@ -791,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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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<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:
|
||||
// https://scryfall.com/docs/api/cards
|
||||
#[allow(dead_code)]
|
||||
@@ -110,7 +185,6 @@ struct ScryfallCard {
|
||||
pub watermark: Option<String>,
|
||||
pub preview: Option<Preview>,
|
||||
|
||||
|
||||
// These aren't in the Scryfall docs, but some cards do have 'em
|
||||
pub foil: Option<bool>,
|
||||
pub nonfoil: Option<bool>,
|
||||
@@ -785,4 +859,14 @@ mod tests {
|
||||
let ac = fs::read_to_string(f).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);
|
||||
}
|
||||
}
|
||||
|
||||
22
scryfall_deser/src/main.rs
Normal file
22
scryfall_deser/src/main.rs
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user