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::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) => {
|
||||||
|
if n >= self.current_num_passes_through_deck {
|
||||||
// no!
|
// no!
|
||||||
return
|
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,8 +385,7 @@ 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
|
||||||
@@ -370,10 +393,9 @@ impl Klondike {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => false
|
None => false,
|
||||||
}
|
|
||||||
},
|
},
|
||||||
CardPosition::TopWaste | CardPosition::Foundation(_) => false
|
CardPosition::TopWaste | CardPosition::Foundation(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,7 +426,15 @@ 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();
|
||||||
@@ -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 {
|
||||||
@@ -503,7 +544,7 @@ mod tests {
|
|||||||
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,
|
||||||
@@ -521,7 +562,7 @@ mod tests {
|
|||||||
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,
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
|
use card_stuffs::{self, CardPosition};
|
||||||
use core::panic;
|
use core::panic;
|
||||||
use std::io;
|
use std::io;
|
||||||
use card_stuffs::{self, CardPosition};
|
|
||||||
|
|
||||||
use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind};
|
use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::{Constraint, Layout, Rect, Flex},
|
layout::{Constraint, Flex, Layout, Rect},
|
||||||
style::{Style, Stylize, Color},
|
style::{Color, Style, Stylize},
|
||||||
text::Line,
|
text::Line,
|
||||||
widgets::{Block, BorderType, Borders, Paragraph, Clear, Wrap},
|
widgets::{Block, BorderType, Borders, Clear, Paragraph, Wrap},
|
||||||
DefaultTerminal, Frame,
|
DefaultTerminal, Frame,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -39,54 +39,70 @@ impl Default for App {
|
|||||||
const CARD_HEIGHT: u16 = 11;
|
const CARD_HEIGHT: u16 = 11;
|
||||||
const CARD_WIDTH: u16 = 15;
|
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([
|
let horizontal = Layout::horizontal([
|
||||||
Constraint::Length(3),
|
Constraint::Length(3),
|
||||||
Constraint::Length(3),
|
Constraint::Length(3),
|
||||||
Constraint::Length(CARD_WIDTH),
|
Constraint::Length(CARD_WIDTH),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let [w1, w2, top_waste] = horizontal.areas(area);
|
let [w1, w2, top_waste] = horizontal.areas(area);
|
||||||
|
|
||||||
// There must be a better way to do all of this
|
// There must be a better way to do all of this
|
||||||
if cards_in_waste.len() == 0 {
|
if cards_in_waste.is_empty() {
|
||||||
frame.render_widget(
|
frame.render_widget(empty_pile(highlight, selected), top_waste);
|
||||||
empty_pile(highlight),
|
|
||||||
top_waste
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if cards_in_waste.len() >= 3 {
|
if cards_in_waste.len() >= 3 {
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
partially_covered_card(&cards_in_waste[cards_in_waste.len() - 3]),
|
partially_covered_card(&cards_in_waste[cards_in_waste.len() - 3]),
|
||||||
w1
|
w1,
|
||||||
);
|
);
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
partially_covered_card(&cards_in_waste[cards_in_waste.len() - 2]),
|
partially_covered_card(&cards_in_waste[cards_in_waste.len() - 2]),
|
||||||
w2
|
w2,
|
||||||
);
|
);
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
card_widget(&cards_in_waste[cards_in_waste.len() - 1], true, highlight, false),
|
card_widget(
|
||||||
top_waste
|
&cards_in_waste[cards_in_waste.len() - 1],
|
||||||
|
true,
|
||||||
|
highlight,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
top_waste,
|
||||||
);
|
);
|
||||||
} else if cards_in_waste.len() == 2 {
|
} else if cards_in_waste.len() == 2 {
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
partially_covered_card(&cards_in_waste[cards_in_waste.len() - 2]),
|
partially_covered_card(&cards_in_waste[cards_in_waste.len() - 2]),
|
||||||
w2
|
w2,
|
||||||
);
|
);
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
card_widget(&cards_in_waste[cards_in_waste.len() - 1], true, highlight, false),
|
card_widget(
|
||||||
top_waste
|
&cards_in_waste[cards_in_waste.len() - 1],
|
||||||
|
true,
|
||||||
|
highlight,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
top_waste,
|
||||||
);
|
);
|
||||||
} else if cards_in_waste.len() == 1 {
|
} else if cards_in_waste.len() == 1 {
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
card_widget(&cards_in_waste[cards_in_waste.len() - 1], true, highlight, false),
|
card_widget(
|
||||||
top_waste
|
&cards_in_waste[cards_in_waste.len() - 1],
|
||||||
|
true,
|
||||||
|
highlight,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
top_waste,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
/// runs the application's main loop until the user quits
|
/// runs the application's main loop until the user quits
|
||||||
pub fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> {
|
pub fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> {
|
||||||
@@ -111,19 +127,19 @@ impl App {
|
|||||||
.borders(Borders::TOP)
|
.borders(Borders::TOP)
|
||||||
.border_type(BorderType::Thick)
|
.border_type(BorderType::Thick)
|
||||||
.title(Line::from("Legends of Soltar").bold()),
|
.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(
|
frame.render_widget(
|
||||||
Block::new().borders(Borders::TOP).title(status_bar_info),
|
Block::new().borders(Borders::TOP).title(status_bar_info),
|
||||||
status_bar
|
status_bar,
|
||||||
);
|
);
|
||||||
|
|
||||||
let vertical = Layout::vertical([
|
let vertical = Layout::vertical([Constraint::Length(CARD_HEIGHT), Constraint::Min(0)]);
|
||||||
Constraint::Length(CARD_HEIGHT),
|
|
||||||
Constraint::Min(0),
|
|
||||||
]);
|
|
||||||
let [dwf_area, piles_area] = vertical.areas(main_area);
|
let [dwf_area, piles_area] = vertical.areas(main_area);
|
||||||
|
|
||||||
let horizontal = Layout::horizontal([
|
let horizontal = Layout::horizontal([
|
||||||
@@ -133,23 +149,34 @@ impl App {
|
|||||||
Constraint::Length(CARD_WIDTH),
|
Constraint::Length(CARD_WIDTH),
|
||||||
Constraint::Length(CARD_WIDTH),
|
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 [deck_area, waste_area, fa, fb, fc, fd] = horizontal.areas(dwf_area);
|
||||||
let foundation_areas = [fa, fb, fc, fd];
|
let foundation_areas = [fa, fb, fc, fd];
|
||||||
|
|
||||||
frame.render_widget(
|
frame.render_widget(deck_widget(&self.cards.deck), deck_area);
|
||||||
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() {
|
for (i, fa) in foundation_areas.iter().enumerate() {
|
||||||
frame.render_widget(
|
let highlight = self.highlighted_card == CardPosition::Foundation(i);
|
||||||
empty_pile(self.highlighted_card == CardPosition::Foundation(i)),
|
let selected = {
|
||||||
*fa
|
match self.selected_card {
|
||||||
);
|
None => false,
|
||||||
|
Some(pos) => pos == CardPosition::Foundation(i),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
frame.render_widget(empty_pile(highlight, selected), *fa);
|
||||||
}
|
}
|
||||||
|
|
||||||
let horizontal = Layout::horizontal([
|
let horizontal = Layout::horizontal([
|
||||||
@@ -160,10 +187,10 @@ impl App {
|
|||||||
Constraint::Length(CARD_WIDTH),
|
Constraint::Length(CARD_WIDTH),
|
||||||
Constraint::Length(CARD_WIDTH),
|
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);
|
let pileses: [Rect; 7] = horizontal.areas(piles_area);
|
||||||
|
|
||||||
|
|
||||||
for pile in 0..card_stuffs::NUM_PILES_KLONDIKE {
|
for pile in 0..card_stuffs::NUM_PILES_KLONDIKE {
|
||||||
let mut constraints = Vec::new();
|
let mut constraints = Vec::new();
|
||||||
for card in 0..(card_stuffs::NUM_PILES_KLONDIKE + 13) {
|
for card in 0..(card_stuffs::NUM_PILES_KLONDIKE + 13) {
|
||||||
@@ -182,7 +209,8 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let vertical = Layout::vertical(constraints);
|
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() {
|
for (i, card) in card_display.iter().enumerate() {
|
||||||
match self.cards.piles[pile].get(i) {
|
match self.cards.piles[pile].get(i) {
|
||||||
@@ -190,17 +218,18 @@ impl App {
|
|||||||
Some(c) => {
|
Some(c) => {
|
||||||
let is_top_card = i == self.cards.piles[pile].len() - 1;
|
let is_top_card = i == self.cards.piles[pile].len() - 1;
|
||||||
let highlight = self.highlighted_card == CardPosition::Pile(pile, i);
|
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 {
|
let a_card = match c.visible {
|
||||||
true => card_widget(c, is_top_card, highlight, selected),
|
true => card_widget(c, is_top_card, highlight, selected),
|
||||||
false => card_widget(c, is_top_card, highlight, selected),
|
false => card_widget(c, is_top_card, highlight, selected),
|
||||||
};
|
};
|
||||||
frame.render_widget(
|
frame.render_widget(&a_card, *card)
|
||||||
&a_card,
|
|
||||||
*card
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -227,19 +256,34 @@ impl App {
|
|||||||
fn handle_key_event(&mut self, key_event: KeyEvent) {
|
fn handle_key_event(&mut self, key_event: KeyEvent) {
|
||||||
match key_event.code {
|
match key_event.code {
|
||||||
KeyCode::Char('q') => {
|
KeyCode::Char('q') => {
|
||||||
if self.show_exit { self.exit() }
|
if self.show_exit {
|
||||||
|
self.exit()
|
||||||
|
}
|
||||||
self.show_exit = true;
|
self.show_exit = true;
|
||||||
},
|
}
|
||||||
KeyCode::Char('b') => self.show_exit = false,
|
KeyCode::Char('b') => self.show_exit = false,
|
||||||
KeyCode::Char('d') => self.cards.deck_to_waste(),
|
KeyCode::Char('d') => self.cards.deck_to_waste(),
|
||||||
KeyCode::Char('w') => self.cards.waste_to_deck(),
|
KeyCode::Char('w') => self.cards.waste_to_deck(),
|
||||||
KeyCode::Char('1') => self.cards.num_cards_turned = 1,
|
KeyCode::Char('1') => self.cards.num_cards_turned = 1,
|
||||||
KeyCode::Char('3') => self.cards.num_cards_turned = 3,
|
KeyCode::Char('3') => self.cards.num_cards_turned = 3,
|
||||||
KeyCode::Char('h') => self.show_help = !self.show_help, // toggle
|
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::Char(' ') => self.select_card(),
|
||||||
KeyCode::Right => self.highlighted_card = handle_move_highlighted(&self.highlighted_card, Direction::Right, &self.cards),
|
KeyCode::Left => {
|
||||||
KeyCode::Up => self.highlighted_card = handle_move_highlighted(&self.highlighted_card, Direction::Up, &self.cards),
|
self.highlighted_card =
|
||||||
KeyCode::Down => self.highlighted_card = handle_move_highlighted(&self.highlighted_card, Direction::Down, &self.cards),
|
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) {
|
fn exit(&mut self) {
|
||||||
self.exit = true;
|
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<()> {
|
fn main() -> io::Result<()> {
|
||||||
@@ -263,17 +313,19 @@ enum Direction {
|
|||||||
Right,
|
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 {
|
match current_position {
|
||||||
CardPosition::TopWaste => {
|
CardPosition::TopWaste => match direction {
|
||||||
match direction {
|
Direction::Up | Direction::Left => CardPosition::TopWaste,
|
||||||
Direction::Up | Direction::Left => { CardPosition::TopWaste },
|
Direction::Right => CardPosition::Foundation(0),
|
||||||
Direction::Right => { CardPosition::Foundation(0) }
|
|
||||||
Direction::Down => {
|
Direction::Down => {
|
||||||
let lowest_shown_card = cards.clone().lowest_visible_card_in_pile_from_index(1, 0);
|
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) => {
|
CardPosition::Pile(p, i) => {
|
||||||
match direction {
|
match direction {
|
||||||
@@ -283,9 +335,10 @@ fn handle_move_highlighted(current_position: &CardPosition, direction: Direction
|
|||||||
} else {
|
} else {
|
||||||
CardPosition::Pile(*p, *i + 1)
|
CardPosition::Pile(*p, *i + 1)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Direction::Up => {
|
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 {
|
if *i == lowest_shown_card {
|
||||||
match *p {
|
match *p {
|
||||||
0 | 1 => CardPosition::TopWaste,
|
0 | 1 => CardPosition::TopWaste,
|
||||||
@@ -293,79 +346,84 @@ fn handle_move_highlighted(current_position: &CardPosition, direction: Direction
|
|||||||
4 => CardPosition::Foundation(1),
|
4 => CardPosition::Foundation(1),
|
||||||
5 => CardPosition::Foundation(2),
|
5 => CardPosition::Foundation(2),
|
||||||
6 => CardPosition::Foundation(3),
|
6 => CardPosition::Foundation(3),
|
||||||
_ => panic!("Should be on Pile over 6")
|
_ => panic!("Should be on Pile over 6"),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
CardPosition::Pile(*p, *i - 1)
|
CardPosition::Pile(*p, *i - 1)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Direction::Left => {
|
Direction::Left => {
|
||||||
if *p == 0 {
|
if *p == 0 {
|
||||||
CardPosition::Pile(*p, *i)
|
CardPosition::Pile(*p, *i)
|
||||||
} else {
|
} 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 {
|
if lowest_shown_card <= *i && cards.piles[*p].len() <= *i {
|
||||||
CardPosition::Pile(*p - 1, *i) // CHECK
|
CardPosition::Pile(*p - 1, *i) // CHECK
|
||||||
} else {
|
} else {
|
||||||
CardPosition::Pile(*p - 1, lowest_shown_card)
|
CardPosition::Pile(*p - 1, lowest_shown_card)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Direction::Right => {
|
Direction::Right => {
|
||||||
if *p == 6 {
|
if *p == 6 {
|
||||||
CardPosition::Pile(*p, *i)
|
CardPosition::Pile(*p, *i)
|
||||||
} else {
|
} 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 {
|
if lowest_shown_card <= *i && cards.piles[*p].len() <= *i {
|
||||||
CardPosition::Pile(*p + 1, *i) // CHECK
|
CardPosition::Pile(*p + 1, *i) // CHECK
|
||||||
} else {
|
} else {
|
||||||
CardPosition::Pile(*p + 1, lowest_shown_card)
|
CardPosition::Pile(*p + 1, lowest_shown_card)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
CardPosition::Foundation(f) => {
|
}
|
||||||
match direction {
|
CardPosition::Foundation(f) => match direction {
|
||||||
Direction::Up => { CardPosition::Foundation(*f) },
|
Direction::Up => CardPosition::Foundation(*f),
|
||||||
Direction::Left => {
|
Direction::Left => {
|
||||||
if *f == 0 { CardPosition::TopWaste }
|
if *f == 0 {
|
||||||
else { CardPosition::Foundation(f - 1) }
|
CardPosition::TopWaste
|
||||||
},
|
} else {
|
||||||
Direction::Right => {
|
CardPosition::Foundation(f - 1)
|
||||||
if *f >= 3 { CardPosition::Foundation(3) }
|
|
||||||
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 => {
|
0 => {
|
||||||
let i = cards.clone().lowest_visible_card_in_pile_from_index(3, 0);
|
let i = cards.clone().lowest_visible_card_in_pile_from_index(3, 0);
|
||||||
CardPosition::Pile(3, i)
|
CardPosition::Pile(3, i)
|
||||||
},
|
}
|
||||||
1 => {
|
1 => {
|
||||||
let i = cards.clone().lowest_visible_card_in_pile_from_index(4, 0);
|
let i = cards.clone().lowest_visible_card_in_pile_from_index(4, 0);
|
||||||
CardPosition::Pile(4, i)
|
CardPosition::Pile(4, i)
|
||||||
},
|
}
|
||||||
2 => {
|
2 => {
|
||||||
let i = cards.clone().lowest_visible_card_in_pile_from_index(5, 0);
|
let i = cards.clone().lowest_visible_card_in_pile_from_index(5, 0);
|
||||||
CardPosition::Pile(5, i)
|
CardPosition::Pile(5, i)
|
||||||
},
|
}
|
||||||
3 => {
|
3 => {
|
||||||
let i = cards.clone().lowest_visible_card_in_pile_from_index(6, 0);
|
let i = cards.clone().lowest_visible_card_in_pile_from_index(6, 0);
|
||||||
CardPosition::Pile(6, i)
|
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) {
|
fn show_help(frame: &mut Frame, area: &Rect) {
|
||||||
let block = Block::bordered().title("Help");
|
let block = Block::bordered().title("Help");
|
||||||
let text =
|
let text = "You are playing \"Legends of Soltar\" - a Klondike thingy
|
||||||
"You are playing \"Legends of Soltar\" - a Klondike thingy
|
|
||||||
Press 'q' to Quit
|
Press 'q' to Quit
|
||||||
Press '1' or '3' to change the number of cards you draw from the deck
|
Press '1' or '3' to change the number of cards you draw from the deck
|
||||||
Press 'd' to draw from your 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) {
|
fn show_exit(frame: &mut Frame, area: &Rect) {
|
||||||
let block = Block::bordered().title("Exit?");
|
let block = Block::bordered().title("Exit?");
|
||||||
let text =
|
let text = "Really want to exit Legend of Soltar?
|
||||||
"Really want to exit Legend of Soltar?
|
|
||||||
Press 'q' to Quit or 'b' to go back";
|
Press 'q' to Quit or 'b' to go back";
|
||||||
let p = Paragraph::new(text).wrap(Wrap { trim: true });
|
let p = Paragraph::new(text).wrap(Wrap { trim: true });
|
||||||
let vertical = Layout::vertical([Constraint::Max(10)]).flex(Flex::Center);
|
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);
|
frame.render_widget(p.block(block), area);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn card_widget<'a>(
|
||||||
fn card_widget<'a>(card: &'a card_stuffs::Card, top: bool, highlight: bool, select: bool) -> Paragraph<'a> {
|
card: &'a card_stuffs::Card,
|
||||||
|
top: bool,
|
||||||
|
highlight: bool,
|
||||||
|
select: bool,
|
||||||
|
) -> Paragraph<'a> {
|
||||||
if !card.visible {
|
if !card.visible {
|
||||||
return facedown_card(top);
|
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);
|
border_style = border_style.fg(Color::Green);
|
||||||
}
|
}
|
||||||
|
|
||||||
Paragraph::new(card_image)
|
Paragraph::new(card_image).style(card_style).block(
|
||||||
.style(card_style)
|
Block::new()
|
||||||
.block(Block::new()
|
|
||||||
.style(border_style)
|
.style(border_style)
|
||||||
.borders(borders)
|
.borders(borders)
|
||||||
.border_type(BorderType::Rounded))
|
.border_type(BorderType::Rounded),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn deck_widget(cards_in_deck: &Vec<card_stuffs::Card>) -> Paragraph<'static> {
|
fn deck_widget(cards_in_deck: &Vec<card_stuffs::Card>) -> Paragraph<'static> {
|
||||||
let card_image = format!(
|
let card_image = format!(
|
||||||
"#############\n\
|
"#############\n\
|
||||||
@@ -437,31 +497,31 @@ fn deck_widget(cards_in_deck: &Vec<card_stuffs::Card>) -> Paragraph<'static> {
|
|||||||
#### {:02} #####\n\
|
#### {:02} #####\n\
|
||||||
#############\n\
|
#############\n\
|
||||||
#############\n\
|
#############\n\
|
||||||
#############", cards_in_deck.len()
|
#############",
|
||||||
|
cards_in_deck.len()
|
||||||
);
|
);
|
||||||
|
|
||||||
Paragraph::new(card_image)
|
Paragraph::new(card_image).block(
|
||||||
.block(Block::new()
|
Block::new()
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.border_type(BorderType::Rounded))
|
.border_type(BorderType::Rounded),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn partially_covered_card(card: &card_stuffs::Card) -> Paragraph {
|
fn partially_covered_card(card: &card_stuffs::Card) -> Paragraph {
|
||||||
let card_image = format!(
|
let card_image = format!("{value}{suit}", value = card.value, suit = card.suit);
|
||||||
"{value}{suit}", value=card.value, suit=card.suit
|
|
||||||
);
|
|
||||||
let card_style = match card.suit.colour() {
|
let card_style = match card.suit.colour() {
|
||||||
card_stuffs::Colour::Black => Style::new().black().bg(Color::White),
|
card_stuffs::Colour::Black => Style::new().black().bg(Color::White),
|
||||||
card_stuffs::Colour::Red => Style::new().red().bg(Color::White),
|
card_stuffs::Colour::Red => Style::new().red().bg(Color::White),
|
||||||
};
|
};
|
||||||
let borders = Borders::TOP | Borders::LEFT | Borders::BOTTOM;
|
let borders = Borders::TOP | Borders::LEFT | Borders::BOTTOM;
|
||||||
let border_style = Style::new().white().on_black();
|
let border_style = Style::new().white().on_black();
|
||||||
Paragraph::new(card_image)
|
Paragraph::new(card_image).style(card_style).block(
|
||||||
.style(card_style)
|
Block::new()
|
||||||
.block(Block::new()
|
|
||||||
.style(border_style)
|
.style(border_style)
|
||||||
.borders(borders)
|
.borders(borders)
|
||||||
.border_type(BorderType::Rounded))
|
.border_type(BorderType::Rounded),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn facedown_card(top: bool) -> Paragraph<'static> {
|
fn facedown_card(top: bool) -> Paragraph<'static> {
|
||||||
@@ -482,13 +542,14 @@ fn facedown_card(top: bool) -> Paragraph<'static> {
|
|||||||
borders |= Borders::BOTTOM;
|
borders |= Borders::BOTTOM;
|
||||||
}
|
}
|
||||||
|
|
||||||
Paragraph::new(hidden_card)
|
Paragraph::new(hidden_card).block(
|
||||||
.block(Block::new()
|
Block::new()
|
||||||
.borders(borders)
|
.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/
|
// made using https://www.asciiart.eu/
|
||||||
let hidden_card = format!(
|
let hidden_card = format!(
|
||||||
"
|
"
|
||||||
@@ -504,16 +565,18 @@ fn empty_pile(highlight: bool) -> Paragraph<'static> {
|
|||||||
let mut border_style = Style::new();
|
let mut border_style = Style::new();
|
||||||
if highlight {
|
if highlight {
|
||||||
border_style = border_style.fg(Color::Blue);
|
border_style = border_style.fg(Color::Blue);
|
||||||
|
} else if selected {
|
||||||
|
border_style = border_style.fg(Color::Green);
|
||||||
}
|
}
|
||||||
|
|
||||||
Paragraph::new(hidden_card)
|
Paragraph::new(hidden_card).block(
|
||||||
.block(Block::new()
|
Block::new()
|
||||||
.style(border_style)
|
.style(border_style)
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.border_type(BorderType::Rounded))
|
.border_type(BorderType::Rounded),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn card_paragraph(c: &card_stuffs::Card) -> String {
|
fn card_paragraph(c: &card_stuffs::Card) -> String {
|
||||||
match c.value {
|
match c.value {
|
||||||
card_stuffs::Value::Ace => {
|
card_stuffs::Value::Ace => {
|
||||||
@@ -537,8 +600,11 @@ XX
|
|||||||
{suit}{suit}{suit}{suit}{suit}{suit}{suit}
|
{suit}{suit}{suit}{suit}{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 => {
|
card_stuffs::Value::Two => {
|
||||||
/*
|
/*
|
||||||
XX
|
XX
|
||||||
@@ -560,8 +626,11 @@ XX
|
|||||||
{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::Three => {
|
card_stuffs::Value::Three => {
|
||||||
/*
|
/*
|
||||||
XX X
|
XX X
|
||||||
@@ -583,8 +652,11 @@ XX X
|
|||||||
{suit}
|
{suit}
|
||||||
{suit}
|
{suit}
|
||||||
{suit}{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 => {
|
card_stuffs::Value::Four => {
|
||||||
format!(
|
format!(
|
||||||
"{value}{suit}
|
"{value}{suit}
|
||||||
@@ -595,8 +667,11 @@ XX X
|
|||||||
|
|
||||||
{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::Five => {
|
card_stuffs::Value::Five => {
|
||||||
format!(
|
format!(
|
||||||
"{value}{suit}
|
"{value}{suit}
|
||||||
@@ -607,8 +682,11 @@ XX X
|
|||||||
|
|
||||||
{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::Six => {
|
card_stuffs::Value::Six => {
|
||||||
/*
|
/*
|
||||||
XX
|
XX
|
||||||
@@ -630,8 +708,11 @@ 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::Seven => {
|
card_stuffs::Value::Seven => {
|
||||||
/*
|
/*
|
||||||
XX
|
XX
|
||||||
@@ -653,8 +734,11 @@ XX
|
|||||||
{suit}{suit} {suit}{suit}
|
{suit}{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::Eight => {
|
card_stuffs::Value::Eight => {
|
||||||
/*
|
/*
|
||||||
XX
|
XX
|
||||||
@@ -676,8 +760,11 @@ XX
|
|||||||
{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::Nine => {
|
card_stuffs::Value::Nine => {
|
||||||
/*
|
/*
|
||||||
XX
|
XX
|
||||||
@@ -699,8 +786,11 @@ 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::Ten => {
|
card_stuffs::Value::Ten => {
|
||||||
/*
|
/*
|
||||||
XX
|
XX
|
||||||
@@ -722,8 +812,11 @@ XX
|
|||||||
{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::Jack => {
|
card_stuffs::Value::Jack => {
|
||||||
/*
|
/*
|
||||||
XX
|
XX
|
||||||
@@ -745,8 +838,11 @@ 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::Queen => {
|
card_stuffs::Value::Queen => {
|
||||||
/*
|
/*
|
||||||
XX
|
XX
|
||||||
@@ -768,8 +864,11 @@ XX
|
|||||||
{suit} {suit}
|
{suit} {suit}
|
||||||
{suit}{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::King => {
|
card_stuffs::Value::King => {
|
||||||
/*
|
/*
|
||||||
XX
|
XX
|
||||||
@@ -791,7 +890,10 @@ XX
|
|||||||
{suit} {suit}{suit}
|
{suit} {suit}{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
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"] }
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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