Return back to the "Add C51 Train" Page
Return back to Plan C
/* 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()