1#![warn(
4 missing_docs,
5 clippy::all,
6 clippy::pedantic,
7 clippy::missing_docs_in_private_items
8)]
9
10mod game;
11mod cards;
12
13use crate::game::Game;
14
15use std::io::{self, stdout};
16
17use crossterm::{
18 cursor, terminal, ExecutableCommand
19};
20
21const MIN_TERMINAL_WIDTH: u16 = 60;
23const MIN_TERMINAL_HEIGHT: u16 = 24;
25
26fn run() -> Result<(), io::Error> {
32 terminal::enable_raw_mode()?;
34 let mut stdout = stdout();
35 stdout.execute(terminal::EnterAlternateScreen)?;
36 stdout.execute(cursor::Hide)?;
37 stdout.execute(terminal::Clear(terminal::ClearType::All))?;
38
39 let mut rng = rand::thread_rng();
41 let mut game = Game::new(&mut rng);
42 game.print(&mut stdout)?;
43
44 loop {
46 let event = crossterm::event::read()?;
47 match event {
48 crossterm::event::Event::Key(key_event) => {
49 use crossterm::event::{KeyModifiers as MOD, KeyCode::{Char, Left, Right, Enter}, KeyEventKind::{Press, Repeat}};
50 if key_event.kind == Press || key_event.kind == Repeat {
51 match (key_event.code, key_event.modifiers) {
52 (Left | Char('a'), MOD::NONE) => {
53 if !game.is_won() {game.move_cursor_left();}
54 },
55 (Right | Char('d') , MOD::NONE) => {
56 if !game.is_won() {game.move_cursor_right();}
57 },
58 (Char(' ') | Enter, MOD::NONE) => {
59 if !game.is_won() {game.handle_card_press();}
60 },
61 (Char('z'), MOD::NONE) => {
62 game.perform_undo();
63 },
64 (Char('h'), MOD::NONE) => {
65 game.toggle_high_contrast();
66 },
67 (Char('f'), MOD::NONE) => {
68 game.quick_stack_to_foundations();
69 },
70 (Char('n'), MOD::CONTROL) => {
71 game = Game::new(&mut rng);
72 },
73 (Char('q'), MOD::CONTROL) => {
74 break
75 },
76 _ => {
77
78 }
79 }
80 }
81 },
82 crossterm::event::Event::Resize(_term_width, _term_height) => {
83 }
85 _ => {}
86 }
87 game.print(&mut stdout)?;
88 }
89 Ok(())
90}
91
92fn cleanup() {
95 let mut stdout = stdout();
96 let _ = stdout.execute(cursor::Show);
98 let _ = terminal::disable_raw_mode();
99 let _ = stdout.execute(terminal::Clear(terminal::ClearType::All));
100 let _ = stdout.execute(terminal::LeaveAlternateScreen);
101 println!();
102}
103
104fn main() -> Result<(), Box<dyn std::error::Error>> {
110 let (term_width, term_height) = terminal::size()?;
112 if term_width < MIN_TERMINAL_WIDTH || term_height < MIN_TERMINAL_HEIGHT {
113 println!("Your terminal window is too small for FreeCell! It's gotta be at least {MIN_TERMINAL_WIDTH} chars wide and {MIN_TERMINAL_HEIGHT} chars tall.");
114 return Err("terminal too small".into());
115 }
116 run()?;
117 cleanup();
118 Ok(())
119}