rsnaker/lib.rs
1#![forbid(unsafe_code)]
2#![deny(clippy::all)]
3#![deny(clippy::pedantic)]
4// Documentation for all Clippy lints: https://github.com/rust-lang/rust-clippy/
5//! # Snake Game using Ratatui
6//!
7//! This module implements a terminal-based snake game using the Ratatui crate for rendering.
8//!
9//! ## Features
10//! - **Terminal UI**: Uses Ratatui for rendering a grid-based game.
11//! - **Game Logic**: Manages snake movement, collisions, and scoring.
12//! - **Multithreading**: Uses multiple threads for input handling, rendering at 60 FPS, and game logic execution.
13//! - **Emoji-based graphics**: Supports rendering the snake using emojis instead of ASCII.
14//! - **Configurable parameters**: With `clap` for command-line arguments.
15//!
16//! ## TODO
17//! - [ ] Add a save score (local db) with a pseudo got from cmdline
18//! - [ ] Add some performance log with tracing for example
19//! - [ ] Fix too much life display outside of screen
20//!
21//!
22//! ## References
23//! - Clippy lints: <https://github.com/rust-lang/rust-clippy/>
24//! - Ratatui tutorial: <https://ratatui.rs/tutorials/hello-world/>
25//! - Example: <https://ratatui.rs/examples/widgets/canvas/>
26//!
27//! ## Architecture
28//! - Uses `RwLock` for synchronization.
29//! - Spawns separate threads for input handling, rendering (60Hz), and game logic execution.
30//!
31//! ## Documentation generation
32//! - `cargo doc --document-private-items --no-deps --open`
33//!
34//! ## Tests
35//! - As usual run them with `cargo test` the project is set up with a lib containing all the code, and a main.rs just calling it
36//! - As this is a widespread pattern providing full compliance with the Rust test ecosystem, allowing doc comment to be automatically tested, for example.
37
38pub mod controls;
39pub mod game_logic;
40pub mod graphics;
41
42use crate::game_logic::playing_thread_manager::Game;
43use crate::graphics::sprites::snake_body::SnakeBody;
44use clap::Parser;
45use game_logic::game_options::GameOptions;
46use graphics::sprites::map::Map as Carte;
47use ratatui::text::Span;
48use std::cmp::max;
49
50/// # Panics
51/// If bad characters (invalid size) are provided for snake body or head
52pub fn start_snake() {
53 // get command line options and parsed them to check for errors
54 let mut args = GameOptions::parse();
55 args.validate_and_adapt();
56 //load or save as wished, for the easiest use hard-coded path,as people using it will not like cli
57 if args.save {
58 args.save_to_toml(game_logic::game_options::SAVE_FILE)
59 .expect("Fail to save Snake configuration file");
60 }
61 if args.load {
62 args = GameOptions::load_from_toml(game_logic::game_options::SAVE_FILE)
63 .expect("Fail to load Snake configuration file");
64 }
65 // If everything is OK, inits terminal for rendering
66 let mut terminal = ratatui::init();
67 //ratatui using UnicodeWidthStr crates as dep
68 // get the correct case size for display
69 let case_size = u16::try_from(max(
70 Span::raw(&args.body_symbol).width(),
71 Span::raw(&args.head_symbol).width(),
72 ))
73 .expect("Bad symbol size, use a real character");
74 //except if gamer want to quit from the menu screen, we continue
75 // set up parameters from option parsing
76 let map: Carte = Carte::new(case_size, terminal.get_frame().area());
77
78 let body_symbol = args.body_symbol.clone();
79 let head_symbol = args.head_symbol.clone();
80 let serpent: SnakeBody = SnakeBody::new(
81 &body_symbol,
82 &head_symbol,
83 args.snake_length,
84 GameOptions::initial_position(),
85 case_size,
86 );
87 // init our own Game engine
88 let mut jeu = Game::new(args, serpent, map, terminal);
89 // Display greeting screen
90 jeu.menu();
91
92 //in all cases, restore
93 ratatui::restore();
94}