Return back to the "Add C51 Train" Page
/* main.rs */
mod d51;
mod parsing;
use d51::*;
use parsing::*;
fn main() {
let (speed, fly, kind, oops) = parse_opts(); // Get the program options from the command-line, if any.
draw_d51(speed, fly, oops);
} // end of main()
/* d51.rs */
use ncurses::*;
use std::{thread, time::Duration};
fn my_mvaddstr(row: i32, mut col: i32, frame_line: &str, screen_width: i32, screen_height: i32) {
/* This function recieves one line of an ASCII-art image, and
trims away any of that line that would otherwise be displayed
off-screen; then it displays the remaining portion of the
line.
*/
let mut line = String::from(frame_line); // An &str is immutable by nature; we need a mutable string.
// Trim from left side as train moves off left edge
if col < 0 { // If we've moved left of the left-edge,
for _ in 0..col.abs() { // for each column beyond edge,
line.remove(0); // trim the first char off the string, repeatedly.
} // "line" should now have front end trimmed off.
col = 0;
}
// Trim from right side if it extends off right edge
if col + (line.len() as i32) > screen_width {
let amt_to_trim = (col + (line.len() as i32)) - screen_width;
for _ in 0..amt_to_trim {
line.pop();
}
}
if (row >= 0) && (row < screen_height) {
// We're within screen bounds, so it's okay to display this line.
mvaddstr(row, col, &line);
}
} // end of my_mvaddstr()
pub fn draw_d51(delay:u64, fly:bool, oops:bool) {
// Start ncurses, initializing "stdscr()".
initscr();
// Don't echo keypresses to screen.
noecho();
// Turn off the display of the cursor.
curs_set(CURSOR_VISIBILITY::CURSOR_INVISIBLE);
// Get the screen dimensions.
let mut screen_height = 0;
let mut screen_width = 0;
getmaxyx(stdscr(), &mut screen_height, &mut screen_width); // Dims are now in screen_height/width.
let d51: Vec<Vec<String>> = get_d51();
let height = d51[0].len() as i32; // How tall is the D51?
let mut row = screen_height - height; // Put the wheels at the bottom of the screen.
let count_of_inner_vecs: usize = d51.len(); // How many animation cel frames in this image?
let length: i32 = d51[0][0].len() as i32; // How long (width in screen columns/chars) is the train?
let ms = Duration::from_millis(delay); // This is the "delay" converted to a format for the "sleep()" function below.
let mut current_inner_vec: usize = count_of_inner_vecs - 1; // Count down from highest-numbered frame; then repeat cycle.
for col in ((-length)..screen_width).rev() {
// Count down from right-edge until nose of train drags all of train off left-edge.
let current_frame = &d51[current_inner_vec]; // Of the various inner_vecs, get the one we're drawing into its own variable.
let mut line = row; // Keep "row" unaltered unless we're flying; use "line" within loop.
for each_line in current_frame {
my_mvaddstr(line, col, &each_line, screen_width, screen_height);
line = line + 1;
}
refresh(); // Necessary to "bring to the forefront" the drawing we just did "in the back staging area".
/* Pause so our eyes can see the frame. */
thread::sleep(ms);
if current_inner_vec > 0 {
current_inner_vec = current_inner_vec - 1; // Prepare to display next frame.
} else {
// we've gotten down to last frame, so ...
current_inner_vec = count_of_inner_vecs - 1; // ... start over.
if fly {
// And if we're flying ...
row -= 1; // ... raise the next drawing one row up, and ...
line = row + height; // ... erase last (bottom) line drawn, using enough repeated spaces for train's length.
my_mvaddstr(
line,
col,
&" ".repeat(length as usize),
screen_width,
screen_height,
);
// No need to refresh(); next frame will do it for us.
}
}
}
/* Once finished, terminate ncurses. */
endwin();
} // end of draw_d51()
fn get_d51() -> Vec<Vec<String>> {
let d51: Vec<Vec<String>> = vec![
vec![ /* Frame-set #0 */ // The next ten lines form a "Vector of Strings".
" ==== ________ ___________ ".to_string(), // Each of these ten lines is a String.
" _D _| |_______/ \\__I_I_____===__|_________| ".to_string(), // Think of each character in a String as a single Pringle's Potato Chip.
" |(_)--- | H\\________/ | | =|___ ___| ".to_string(), // Each of these lines is about 48 "Pringle's chips" long.
" / | | H | | | | ||_| |_|| ".to_string(), // Now imagine those 48 chips in a Pringle's can. The can is the "String".
" | | | H |__--------------------| [___] | ".to_string(), // Now imagine ten Pringle's cans in a cardboard carton. The carton is the "Vector".
" | ________|___H__/__|_____/[][]~\\_______| | ".to_string(),
" |/ | |-----------I_____I [][] [] D |=======|__ ".to_string(),
"__/ =| o |=-~~\\ /~~\\ /~~\\ /~~\\ ____Y___________|__ ".to_string(),
" |/-=|___|= || || || |_____/~\\___/ ".to_string(),
" \\_/ \\O=====O=====O=====O_/ \\_/ ".to_string()
],
vec![ /* Frame-set #1 */ // Now imagine six of those cartons in a shipping container.
" ==== ________ ___________ ".to_string(), // This second vector is the second carton in that shipping container.
" _D _| |_______/ \\__I_I_____===__|_________| ".to_string(),
" |(_)--- | H\\________/ | | =|___ ___| ".to_string(),
" / | | H | | | | ||_| |_|| ".to_string(),
" | | | H |__--------------------| [___] | ".to_string(),
" | ________|___H__/__|_____/[][]~\\_______| | ".to_string(),
" |/ | |-----------I_____I [][] [] D |=======|__ ".to_string(),
"__/ =| o |=-~~\\ /~~\\ /~~\\ /~~\\ ____Y___________|__ ".to_string(),
" |/-=|___|=O=====O=====O=====O |_____/~\\___/ ".to_string(),
" \\_/ \\__/ \\__/ \\__/ \\__/ \\_/ ".to_string()
],
vec![ /* Frame-set #2 */
" ==== ________ ___________ ".to_string(), // The shipping container holding all six cartons is the "Vector of Vector of Strings".
" _D _| |_______/ \\__I_I_____===__|_________| ".to_string(), // This shipping container is labeled "d51" by the "let d51" line above.
" |(_)--- | H\\________/ | | =|___ ___| ".to_string(),
" / | | H | | | | ||_| |_|| ".to_string(), // The "pub fn get_d51()" line above "returns" (that's the "->" symbol) this vector of
" | | | H |__--------------------| [___] | ".to_string(), // vector of Strings, but unlabeled; this function only uses the label "d51" internally.
" | ________|___H__/__|_____/[][]~\\_______| | ".to_string(), // The portion of the program that calls this function puts its own label on the shipping
" |/ | |-----------I_____I [][] [] D |=======|__ ".to_string(), // container it gets back from this function. It is free to use the same label, or a
"__/ =| o |=-O=====O=====O=====O \\ ____Y___________|__ ".to_string(), // different one.
" |/-=|___|= || || || |_____/~\\___/ ".to_string(),
" \\_/ \\__/ \\__/ \\__/ \\__/ \\_/ ".to_string()
],
vec![ /* Frame-set #3 */ // This is the fourth ('cause we started counting with zero) vector of Strings in the
" ==== ________ ___________ ".to_string(), // shipping container. It can be accessed as "d51[3]", as in, "Put the d51[3] carton
" _D _| |_______/ \\__I_I_____===__|_________| ".to_string(), // of Pringle's on this shelf, and put the other five cartons, d51[0], d51[1], d51[2],
" |(_)--- | H\\________/ | | =|___ ___| ".to_string(), // d51[4], and d51[5] in the storage closet."
" / | | H | | | | ||_| |_|| ".to_string(),
" | | | H |__--------------------| [___] | ".to_string(), // This fifth line of the fourth carton can be accessed as"d51[3][4]" (4 because we
" | ________|___H__/__|_____/[][]~\\_______| | ".to_string(), // start counting with zero).
" |/ | |-----------I_____I [][] [] D |=======|__ ".to_string(),
"__/ =| o |=-~O=====O=====O=====O\\ ____Y___________|__ ".to_string(),
" |/-=|___|= || || || |_____/~\\___/ ".to_string(),
" \\_/ \\__/ \\__/ \\__/ \\__/ \\_/ ".to_string()
],
vec![ /* Frame-set #4 */
" ==== ________ ___________ ".to_string(),
" _D _| |_______/ \\__I_I_____===__|_________| ".to_string(),
" |(_)--- | H\\________/ | | =|___ ___| ".to_string(),
" / | | H | | | | ||_| |_|| ".to_string(),
" | | | H |__--------------------| [___] | ".to_string(),
" | ________|___H__/__|_____/[][]~\\_______| | ".to_string(),
" |/ | |-----------I_____I [][] [] D |=======|__ ".to_string(),
"__/ =| o |=-~~\\ /~~\\ /~~\\ /~~\\ ____Y___________|__ ".to_string(),
" |/-=|___|= O=====O=====O=====O|_____/~\\___/ ".to_string(),
" \\_/ \\__/ \\__/ \\__/ \\__/ \\_/ ".to_string()
],
vec![ /* Frame-set #5 */
" ==== ________ ___________ ".to_string(),
" _D _| |_______/ \\__I_I_____===__|_________| ".to_string(),
" |(_)--- | H\\________/ | | =|___ ___| ".to_string(),
" / | | H | | | | ||_| |_|| ".to_string(),
" | | | H |__--------------------| [___] | ".to_string(),
" | ________|___H__/__|_____/[][]~\\_______| | ".to_string(),
" |/ | |-----------I_____I [][] [] D |=======|__ ".to_string(),
"__/ =| o |=-~~\\ /~~\\ /~~\\ /~~\\ ____Y___________|__ ".to_string(),
" |/-=|___|= || || || |_____/~\\___/ ".to_string(),
" \\_/ \\_O=====O=====O=====O/ \\_/ ".to_string()
],
];
return d51;
} // end of get_d51()
/* parsing.rs */
// Use c[ommand] l[ine] a[rgument] p[arser] to get command-line arguments.
use clap::Parser;
// This tells clap to derive info it needs from the struct we build below, from the "#[" lines..
#[derive(Parser)]
/* Define the argument inputs we might expect; put them in a struct named "Args". */
struct Args {
/// Move the train '[[z]ip]py', '[f]ast', '[[m]ed]ium', '[s]low' or '[c]rawl'?
#[arg(short, long, default_value_t = String::from("med"))]
// The above tells clap that the next line is an argument, that can be entered in as "s" or "speed".
speed: String,
/// Fly?
#[arg(short, long, default_value_t = false)]
fly: bool,
/// What Kind of object? D51 | C51 | Little | Jack | Boat | Plane
#[arg(short, long, default_value_t = String::from("D51"))]
kind: String,
/// Oops?
#[arg(short, long, default_value_t = false)]
oops: bool,
} // end of Args definitions
pub fn parse_opts() -> (u64, bool, String, bool) {
// Get command-line options using Clap.
let switches: Args = Args::parse();
// Set "speed".
let speed:u64 = match (&switches.speed.to_uppercase()).as_str() {
// The variable "switches.speed" is a String-type (as defined above in the "Args" struct). We upper-case
// it, then compare it to possible matches which are string-slices (&str), which requires the ".as_str()"
// conversion. We then "return" the selected value, which is assigned to "speed".
"Z" | "ZIPPY" | "ZIP" | "U" | "ULTRAFAST" => 10,
"F" | "FAST" => 20,
"M" | "MED" | "MEDIUM" => 50,
"S" | "SLOW" => 100,
"C" | "CRAWL" => 400,
_ => {
println!("Invalid input; must be '[z]ippy/[u]ltrafast, '[f]ast', '[[m]ed]ium', '[s]low', or '[c]rawl'. You entered '{}', so the default of 'medium' was used.", switches.speed);
50
}
};
// Set "kind".
let kind:String = match (&switches.kind.to_uppercase()).as_str() {
"D" | "D51" => String::from("D51"), // Alternative way of doing '"D51".to_string()'.
"C" | "C51" => String::from("C51"),
"L" | "LITTLE" => String::from("LITTLE"),
"J" | "JACK" => String::from("JACK"),
"B" | "BOAT" => String::from("BOAT"),
"P" | "PLANE" => String::from("PLANE"),
_ => {
// If none of them match, then ...
println!("Invalid input; run program with '--help' option for more information. You entered '{}', so the default of 'd51' was used.", switches.kind);
String::from("D51")
}
};
return (speed, switches.fly, kind, switches.oops);
} // end of parse_opts()