|
|
|
|
@@ -1,28 +1,180 @@
|
|
|
|
|
use std::io;
|
|
|
|
|
use card_stuffs;
|
|
|
|
|
use card_stuffs::{self};
|
|
|
|
|
|
|
|
|
|
use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind};
|
|
|
|
|
use ratatui::{
|
|
|
|
|
buffer::Buffer,
|
|
|
|
|
layout::{Constraint, Layout, Rect},
|
|
|
|
|
style::Stylize,
|
|
|
|
|
symbols::border,
|
|
|
|
|
text::{Line, Text},
|
|
|
|
|
widgets::{block::title, Block, BorderType, Borders, Paragraph, Widget, Padding},
|
|
|
|
|
layout::{Constraint, Layout, Rect, Flex},
|
|
|
|
|
style::{Style, Stylize, Color},
|
|
|
|
|
text::Line,
|
|
|
|
|
widgets::{Block, BorderType, Borders, ListItem, List, Widget, Paragraph},
|
|
|
|
|
DefaultTerminal, Frame,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Default)]
|
|
|
|
|
pub struct App {
|
|
|
|
|
cards: card_stuffs::Klondike,
|
|
|
|
|
// There aren't pretty... not sure what else I can do about that though...
|
|
|
|
|
highlighted_card: Option<card_stuffs::Card>,
|
|
|
|
|
// I should think about making this a Vec so I can highlight a whole stack which is about to move
|
|
|
|
|
selected_card: Option<card_stuffs::Card>,
|
|
|
|
|
exit: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const CARD_HEIGHT: u16 = 12;
|
|
|
|
|
const CARD_WIDTH: u16 = 10;
|
|
|
|
|
const CARD_HEIGHT: u16 = 11;
|
|
|
|
|
const CARD_WIDTH: u16 = 15;
|
|
|
|
|
|
|
|
|
|
fn waste_widget(cards_in_waste: &Vec<card_stuffs::Card>) -> List {
|
|
|
|
|
let mut cards_to_display = Vec::new();
|
|
|
|
|
if cards_in_waste.len() >= 3 {
|
|
|
|
|
cards_to_display.push(cards_in_waste.windows(3).last());
|
|
|
|
|
} else if cards_in_waste.len() == 2 {
|
|
|
|
|
cards_to_display.push(cards_in_waste.windows(2).last());
|
|
|
|
|
} else if cards_in_waste.len() == 1 {
|
|
|
|
|
cards_to_display.push(cards_in_waste.windows(1).last());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Eeeek - we're no longer a basic "List"... I think maybe I need
|
|
|
|
|
// a new widget I've make myself... I probably should do that for all cards
|
|
|
|
|
// to be toootaly honest
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// This (and the rest) probably shouldn't be ListItems
|
|
|
|
|
let hidden_card = [
|
|
|
|
|
format!("#############"),
|
|
|
|
|
format!("#############"),
|
|
|
|
|
format!("#############"),
|
|
|
|
|
format!("#####TODO####"),
|
|
|
|
|
format!("#############"),
|
|
|
|
|
format!("#############"),
|
|
|
|
|
format!("#############"),
|
|
|
|
|
format!("#############"),
|
|
|
|
|
format!("#############"),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
let card_image: Vec<ListItem> = hidden_card.iter().map(|m| {
|
|
|
|
|
ListItem::new(m.to_string())
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
List::new(card_image)
|
|
|
|
|
.block(Block::new()
|
|
|
|
|
.borders(Borders::ALL)
|
|
|
|
|
.border_type(BorderType::Rounded))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn deck_widget(cards_in_deck: &Vec<card_stuffs::Card>) -> Paragraph<'static> {
|
|
|
|
|
let card_image = format!(
|
|
|
|
|
"#############\n\
|
|
|
|
|
#############\n\
|
|
|
|
|
### Cards ###\n\
|
|
|
|
|
### Left ###\n\
|
|
|
|
|
#############\n\
|
|
|
|
|
#### {:02} #####\n\
|
|
|
|
|
#############\n\
|
|
|
|
|
#############\n\
|
|
|
|
|
#############", cards_in_deck.len()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
Paragraph::new(card_image)
|
|
|
|
|
.block(Block::new()
|
|
|
|
|
.borders(Borders::ALL)
|
|
|
|
|
.border_type(BorderType::Rounded))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn card_widget<'a>(card: &'a card_stuffs::Card, top: bool, highlight: bool, select: bool) -> Paragraph<'a> {
|
|
|
|
|
|
|
|
|
|
let card_image = format!(
|
|
|
|
|
"{value}{suit}
|
|
|
|
|
|
|
|
|
|
{suit} {suit}
|
|
|
|
|
|
|
|
|
|
{suit}
|
|
|
|
|
|
|
|
|
|
{suit} {suit}
|
|
|
|
|
|
|
|
|
|
{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 mut borders = Borders::TOP | Borders::LEFT | Borders::RIGHT;
|
|
|
|
|
if top {
|
|
|
|
|
borders |= Borders::BOTTOM;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut border_style = Style::new().white().on_black();
|
|
|
|
|
if highlight {
|
|
|
|
|
border_style = border_style.fg(Color::Blue);
|
|
|
|
|
} else if select {
|
|
|
|
|
border_style = border_style.fg(Color::Green);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Paragraph::new(card_image)
|
|
|
|
|
.style(card_style)
|
|
|
|
|
.block(Block::new()
|
|
|
|
|
.style(border_style)
|
|
|
|
|
.borders(borders)
|
|
|
|
|
.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_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()
|
|
|
|
|
.style(border_style)
|
|
|
|
|
.borders(borders)
|
|
|
|
|
.border_type(BorderType::Rounded))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct CardWidget {
|
|
|
|
|
card: card_stuffs::Card,
|
|
|
|
|
// put display stuff in here?
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Widget for CardWidget {
|
|
|
|
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
|
|
|
|
let mut border_style = Style::default();
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn facedown_card(top: bool) -> Paragraph<'static> {
|
|
|
|
|
let hidden_card = format!(
|
|
|
|
|
"#############\n\
|
|
|
|
|
#############\n\
|
|
|
|
|
#############\n\
|
|
|
|
|
#############\n\
|
|
|
|
|
#############\n\
|
|
|
|
|
#############\n\
|
|
|
|
|
#############\n\
|
|
|
|
|
#############\n\
|
|
|
|
|
#############"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let mut borders = Borders::TOP | Borders::LEFT | Borders::RIGHT;
|
|
|
|
|
if top {
|
|
|
|
|
borders |= Borders::BOTTOM;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Paragraph::new(hidden_card)
|
|
|
|
|
.block(Block::new()
|
|
|
|
|
.borders(borders)
|
|
|
|
|
.border_type(BorderType::Rounded))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl App {
|
|
|
|
|
|
|
|
|
|
/// runs the application's main loop until the user quits
|
|
|
|
|
pub fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> {
|
|
|
|
|
while !self.exit {
|
|
|
|
|
@@ -38,7 +190,7 @@ impl App {
|
|
|
|
|
Constraint::Min(0),
|
|
|
|
|
Constraint::Length(1),
|
|
|
|
|
]);
|
|
|
|
|
let [title_bar, main_area, status_bar] = vertical.areas(frame.area());
|
|
|
|
|
let [title_bar, main_area, _status_bar] = vertical.areas(frame.area());
|
|
|
|
|
|
|
|
|
|
frame.render_widget(
|
|
|
|
|
Block::new()
|
|
|
|
|
@@ -49,41 +201,105 @@ impl App {
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let vertical = Layout::vertical([
|
|
|
|
|
Constraint::Length(CARD_HEIGHT + 2), // for padding
|
|
|
|
|
Constraint::Length(CARD_HEIGHT),
|
|
|
|
|
Constraint::Min(0)
|
|
|
|
|
]);
|
|
|
|
|
let [dwf_area, piles_area] = vertical.areas(main_area);
|
|
|
|
|
|
|
|
|
|
frame.render_widget(
|
|
|
|
|
Block::new().borders(Borders::ALL).title("dwf_area"),
|
|
|
|
|
dwf_area
|
|
|
|
|
);
|
|
|
|
|
frame.render_widget(
|
|
|
|
|
Block::new().borders(Borders::ALL).title("piles_area"),
|
|
|
|
|
piles_area
|
|
|
|
|
);
|
|
|
|
|
let horizontal = Layout::horizontal([
|
|
|
|
|
Constraint::Length(CARD_WIDTH),
|
|
|
|
|
Constraint::Length(2),
|
|
|
|
|
Constraint::Length(3 + 3 + CARD_WIDTH), // for 2 cards shown underneath
|
|
|
|
|
Constraint::Length(CARD_WIDTH),
|
|
|
|
|
Constraint::Length(2),
|
|
|
|
|
Constraint::Length(CARD_WIDTH),
|
|
|
|
|
Constraint::Length(2),
|
|
|
|
|
Constraint::Length(CARD_WIDTH),
|
|
|
|
|
Constraint::Length(2),
|
|
|
|
|
Constraint::Length(CARD_WIDTH),
|
|
|
|
|
Constraint::Length(2),
|
|
|
|
|
]).flex(Flex::SpaceAround);
|
|
|
|
|
|
|
|
|
|
let [deck_area, waste_area, fa, fb, fc, fd] = horizontal.areas(dwf_area);
|
|
|
|
|
let foundation_areas = [fa, fb, fc, fd];
|
|
|
|
|
|
|
|
|
|
let horizontal = Layout::horizontal([
|
|
|
|
|
Constraint::Length(3),
|
|
|
|
|
Constraint::Length(3),
|
|
|
|
|
Constraint::Length(CARD_WIDTH),
|
|
|
|
|
Constraint::Length(2),
|
|
|
|
|
Constraint::Length(CARD_WIDTH),
|
|
|
|
|
Constraint::Length(2),
|
|
|
|
|
]);
|
|
|
|
|
let piles_and_spacers: [Rect; 14] = horizontal.areas(piles_area);
|
|
|
|
|
for pile in 0..card_stuffs::NUM_PILES_KLONDIKE {
|
|
|
|
|
let [w1, w2, waste_area] = horizontal.areas(waste_area);
|
|
|
|
|
|
|
|
|
|
frame.render_widget(
|
|
|
|
|
deck_widget(&self.cards.deck),
|
|
|
|
|
deck_area
|
|
|
|
|
);
|
|
|
|
|
frame.render_widget(
|
|
|
|
|
partially_covered_card(&self.cards.piles[0][0]),
|
|
|
|
|
w1
|
|
|
|
|
);
|
|
|
|
|
frame.render_widget(
|
|
|
|
|
partially_covered_card(&self.cards.piles[1][0]),
|
|
|
|
|
w2
|
|
|
|
|
);
|
|
|
|
|
frame.render_widget(
|
|
|
|
|
waste_widget(&self.cards.waste),
|
|
|
|
|
waste_area
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
for fa in foundation_areas {
|
|
|
|
|
frame.render_widget(
|
|
|
|
|
Block::new().borders(Borders::ALL).title(format!("pile {}", pile)),
|
|
|
|
|
piles_and_spacers[pile*2]
|
|
|
|
|
)
|
|
|
|
|
facedown_card(true),
|
|
|
|
|
fa
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let horizontal = Layout::horizontal([
|
|
|
|
|
Constraint::Length(CARD_WIDTH),
|
|
|
|
|
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);
|
|
|
|
|
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) {
|
|
|
|
|
match self.cards.piles[pile].get(card) {
|
|
|
|
|
Some(_) => {
|
|
|
|
|
if card == self.cards.piles[pile].len() - 1 {
|
|
|
|
|
constraints.push(Constraint::Length(CARD_HEIGHT));
|
|
|
|
|
} else {
|
|
|
|
|
constraints.push(Constraint::Length(2));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None => {
|
|
|
|
|
constraints.push(Constraint::Length(0));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let vertical = Layout::vertical(constraints);
|
|
|
|
|
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) {
|
|
|
|
|
None => break,
|
|
|
|
|
Some(c) => {
|
|
|
|
|
let is_top_card = i == self.cards.piles[pile].len() - 1;
|
|
|
|
|
let highlight = self.highlighted_card.is_some() && *c == self.highlighted_card.unwrap();
|
|
|
|
|
let selected = self.selected_card.is_some() && *c == self.selected_card.unwrap();
|
|
|
|
|
let a_card = match c.visible {
|
|
|
|
|
true => card_widget(c, is_top_card, highlight, selected),
|
|
|
|
|
//false => turned_over_card(is_top_card),
|
|
|
|
|
false => card_widget(c, is_top_card, highlight, selected),
|
|
|
|
|
};
|
|
|
|
|
frame.render_widget(
|
|
|
|
|
&a_card,
|
|
|
|
|
*card
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|