/* main.rs */ mod get_options; mod images; use get_options::*; fn main() { let (speed, fly, kind, oops, image_string) = parse_opts(); // Get the program options from the command-line, if any. // draw_d51(speed, fly, oops); let image_vecvec = string_to_stringvecvec(&image_string); // Temporary testing code for each_frame in image_vecvec { for each_line in each_frame { println!("{}", each_line); } } } // end of main()
/* get_options.rs */ use crate::images::*; // 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, String) { // 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, image_string) = match (&switches.kind.to_uppercase()).as_str() { "D" | "D51" => ("D51".to_string(), D51.to_string()), "C" | "C51" => (String::from("C51"), String::from(C51)), "L" | "LITTLE" => ("LITTLE".to_owned(), LITTLE.to_owned()), "J" | "JACK" => (String::from("JACK"), JACK.to_string()), "B" | "BOAT" => (String::from("BOAT"), D51.to_string()), "P" | "PLANE" => (String::from("PLANE"), D51.to_string()), _ => { // 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"), D51.to_string()) } }; return (speed, switches.fly, kind, switches.oops, image_string); } // end of parse_opts() pub fn string_to_stringvecvec(image_str: &str) -> Vec<Vec<String>> { // First, replace, with spaces, the quote-marks that delimit the side boundaries of the ASCII-art image. let mut image_string: String = image_str.replace("\'", " "); // Second, remove newline at start of image. image_string.remove(0); // Then, third, convert each frame in the string to a string element, then add the element to the vector: // - Create an empty parent vector. let mut outer_vec: Vec<Vec<String>> = Vec::new(); // - Create a counter to track which inner vector/frame we're working with. let mut inner_vec: usize = 0; // - Create the first, empty, inner vector for the first image frame. outer_vec.push(Vec::new()); // Create first frame vec in outer vec, named "frames". // - Process each line of image_string, breaking that string at newlines, into separate lines. for each_line in image_string.lines() { // If the current line is not blank... if each_line != "" { // ... then push that line into the current inner vector. outer_vec[inner_vec].push(each_line.to_string()); } else { // Else, if the line is blank, then we're between frames, so ignore the blank line, // and increment the count of inner vectors, and ... inner_vec += 1; // ... create a new inner vector for the next image frame... outer_vec.push(Vec::new()); } // ... before looping around to the next line. } return outer_vec; } // end of string_to_stringvecvec()
/* drawing.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 = frame_line.to_string(); // 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 on-screen, so it's okay to print 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); 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 check to see if we're flying. If so... row -= 1; //... raise the next drawing one row up, and ... // ... erase last (bottom) line drawn, using enough repeated spaces for train's length. line = row + height; 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()
/* images.rs This project is a conversion of the "sl" application as found in Debian "Bullseye", from the C programming language to Rust. The copyright blurb of the original is below: *======================================== * sl.h: SL version 5.02 * Copyright 1993,2002,2014 * Toyoda Masashi * (mtoyoda@acm.org) * Last Modified: 2014/06/03 *======================================== This conversion is based on a tutorial for this project, written by Kent West, 2023. To add an art image, just follow the pattern of the images below, making sure to give a unique name as the constant value, and making sure to put a blank line between each animation frame, and making sure to enclose each side of each line of each frame in single-quotes (which will be converted by the program into spaces). Then add an entry for your constant in both the documentation string of the "What kind of object" arg in the "struct Args" section of the "get_options.rs" file, and in the "Set 'kind'." section of the "parse_opts()" function in that same file. */ pub const D51: &str = r" ' ==== ________ ___________' ' _D _| |_______/ \__I_I_____===__|_________|' ' |(_)--- | H\________/ | | =|___ ___| ' ' / | | H | | | | ||_| |_|| ' ' | | | H |__--------------------| [___] | ' ' | ________|___H__/__|_____/[][]~\_______| | ' ' |/ | |-----------I_____I [][] [] D |=======|__' '__/ =| o |=-~~\ /~~\ /~~\ /~~\ ____Y___________|__' ' |/-=|___|= || || || |_____/~\___/ ' ' \_/ \O=====O=====O=====O_/ \_/ ' ' ==== ________ ___________' ' _D _| |_______/ \__I_I_____===__|_________|' ' |(_)--- | H\________/ | | =|___ ___| ' ' / | | H | | | | ||_| |_|| ' ' | | | H |__--------------------| [___] | ' ' | ________|___H__/__|_____/[][]~\_______| | ' ' |/ | |-----------I_____I [][] [] D |=======|__' '__/ =| o |=-~~\ /~~\ /~~\ /~~\ ____Y___________|__' ' |/-=|___|=O=====O=====O=====O |_____/~\___/ ' ' \_/ \__/ \__/ \__/ \__/ \_/ ' ' ==== ________ ___________' ' _D _| |_______/ \__I_I_____===__|_________|' ' |(_)--- | H\________/ | | =|___ ___| ' ' / | | H | | | | ||_| |_|| ' ' | | | H |__--------------------| [___] | ' ' | ________|___H__/__|_____/[][]~\_______| | ' ' |/ | |-----------I_____I [][] [] D |=======|__' '__/ =| o |=-O=====O=====O=====O \ ____Y___________|__' ' |/-=|___|= || || || |_____/~\___/ ' ' \_/ \__/ \__/ \__/ \__/ \_/ ' ' ==== ________ ___________' ' _D _| |_______/ \__I_I_____===__|_________|' ' |(_)--- | H\________/ | | =|___ ___| ' ' / | | H | | | | ||_| |_|| ' ' | | | H |__--------------------| [___] | ' ' | ________|___H__/__|_____/[][]~\_______| | ' ' |/ | |-----------I_____I [][] [] D |=======|__' '__/ =| o |=-~O=====O=====O=====O\ ____Y___________|__' ' |/-=|___|= || || || |_____/~\___/ ' ' \_/ \__/ \__/ \__/ \__/ \_/ ' ' ==== ________ ___________' ' _D _| |_______/ \__I_I_____===__|_________|' ' |(_)--- | H\________/ | | =|___ ___| ' ' / | | H | | | | ||_| |_|| ' ' | | | H |__--------------------| [___] | ' ' | ________|___H__/__|_____/[][]~\_______| | ' ' |/ | |-----------I_____I [][] [] D |=======|__' '__/ =| o |=-~~\ /~~\ /~~\ /~~\ ____Y___________|__' ' |/-=|___|= O=====O=====O=====O|_____/~\___/ ' ' \_/ \__/ \__/ \__/ \__/ \_/ ' ' ==== ________ ___________' ' _D _| |_______/ \__I_I_____===__|_________|' ' |(_)--- | H\________/ | | =|___ ___| ' ' / | | H | | | | ||_| |_|| ' ' | | | H |__--------------------| [___] | ' ' | ________|___H__/__|_____/[][]~\_______| | ' ' |/ | |-----------I_____I [][] [] D |=======|__' '__/ =| o |=-~~\ /~~\ /~~\ /~~\ ____Y___________|__' ' |/-=|___|= || || || |_____/~\___/ ' ' \_/ \_O=====O=====O=====O/ \_/ ' "; // end of D51 pub const C51: &str = r" ' ___ ' ' _|_|_ _ __ __ ___________' ' D__/ \_(_)___| |__H__| |_____I_Ii_()|_________|' ' | `---' |:: `--' H `--' | |___ ___| ' ' +|~~~~~~~~++::~~~~~~~H~~+=====+~~~~~~|~~||_| |_|| ' ' || | :: H +=====+ | |:: ...| ' '| | _______|_::-----------------[][]-----| | ' '| /~~ || |-----/~~~~\ /[I_____I][][] --|||_______|__' '------'|oOo|==[]=- || || | ||=======_|__' '/~\____|___|/~\_| O=======O=======O |__|+-/~\_| ' '\_/ \_/ \____/ \____/ \____/ \_/ ' ' ___ ' ' _|_|_ _ __ __ ___________' ' D__/ \_(_)___| |__H__| |_____I_Ii_()|_________|' ' | `---' |:: `--' H `--' | |___ ___| ' ' +|~~~~~~~~++::~~~~~~~H~~+=====+~~~~~~|~~||_| |_|| ' ' || | :: H +=====+ | |:: ...| ' '| | _______|_::-----------------[][]-----| | ' '| /~~ || |-----/~~~~\ /[I_____I][][] --|||_______|__' '------'|oOo|===[]=- || || | ||=======_|__' '/~\____|___|/~\_| O=======O=======O |__|+-/~\_| ' '\_/ \_/ \____/ \____/ \____/ \_/ ' ' ___ ' ' _|_|_ _ __ __ ___________' ' D__/ \_(_)___| |__H__| |_____I_Ii_()|_________|' ' | `---' |:: `--' H `--' | |___ ___| ' ' +|~~~~~~~~++::~~~~~~~H~~+=====+~~~~~~|~~||_| |_|| ' ' || | :: H +=====+ | |:: ...| ' '| | _______|_::-----------------[][]-----| | ' '| /~~ || |-----/~~~~\ /[I_____I][][] --|||_______|__' '------'|oOo|===[]=- O=======O=======O | ||=======_|__' '/~\____|___|/~\_| || || |__|+-/~\_| ' '\_/ \_/ \____/ \____/ \____/ \_/ ' ' ___ ' ' _|_|_ _ __ __ ___________' ' D__/ \_(_)___| |__H__| |_____I_Ii_()|_________|' ' | `---' |:: `--' H `--' | |___ ___| ' ' +|~~~~~~~~++::~~~~~~~H~~+=====+~~~~~~|~~||_| |_|| ' ' || | :: H +=====+ | |:: ...| ' '| | _______|_::-----------------[][]-----| | ' '| /~~ || |-----/~~~~\ /[I_____I][][] --|||_______|__' '------'|oOo|==[]=- O=======O=======O | ||=======_|__' '/~\____|___|/~\_| || || |__|+-/~\_| ' '\_/ \_/ \____/ \____/ \____/ \_/ ' ' ___ ' ' _|_|_ _ __ __ ___________' ' D__/ \_(_)___| |__H__| |_____I_Ii_()|_________|' ' | `---' |:: `--' H `--' | |___ ___| ' ' +|~~~~~~~~++::~~~~~~~H~~+=====+~~~~~~|~~||_| |_|| ' ' || | :: H +=====+ | |:: ...| ' '| | _______|_::-----------------[][]-----| | ' '| /~~ || |-----/~~~~\ /[I_____I][][] --|||_______|__' '------'|oOo|=[]=- O=======O=======O | ||=======_|__' '/~\____|___|/~\_| || || |__|+-/~\_| ' '\_/ \_/ \____/ \____/ \____/ \_/ ' ' ___ ' ' _|_|_ _ __ __ ___________' ' D__/ \_(_)___| |__H__| |_____I_Ii_()|_________|' ' | `---' |:: `--' H `--' | |___ ___| ' ' +|~~~~~~~~++::~~~~~~~H~~+=====+~~~~~~|~~||_| |_|| ' ' || | :: H +=====+ | |:: ...| ' '| | _______|_::-----------------[][]-----| | ' '| /~~ || |-----/~~~~\ /[I_____I][][] --|||_______|__' '------'|oOo|=[]=- || || | ||=======_|__' '/~\____|___|/~\_| O=======O=======O |__|+-/~\_| ' '\_/ \_/ \____/ \____/ \____/ \_/ ' "; // end of C51 pub const LITTLE: &str = r" ' ++ +------ ____ ____________________ ' ' || |+-+ | | \@@@@@@@@@@@ | ___ ___ ___ ___ | ' ' /---------|| | | | \@@@@@@@@@@@@@_ | |_| |_| |_| |_| | ' ' + ======== +-+ | | | |__________________| ' ' _|--O========O~\-+ |__________________| |__________________| ' '//// \_/ \_/ (O) (O) (O) (O) ' ' ++ +------ ____ ____________________ ' ' || |+-+ | | \@@@@@@@@@@@ | ___ ___ ___ ___ | ' ' /---------|| | | | \@@@@@@@@@@@@@_ | |_| |_| |_| |_| | ' ' + ======== +-+ | | | |__________________| ' ' _|--/O========O\-+ |__________________| |__________________| ' '//// \_/ \_/ (O) (O) (O) (O) ' ' ++ +------ ____ ____________________ ' ' || |+-+ | | \@@@@@@@@@@@ | ___ ___ ___ ___ | ' ' /---------|| | | | \@@@@@@@@@@@@@_ | |_| |_| |_| |_| | ' ' + ======== +-+ | | | |__________________| ' ' _|--/~O========O-+ |__________________| |__________________| ' '//// \_/ \_/ (O) (O) (O) (O) ' ' ++ +------ ____ ____________________ ' ' || |+-+ | | \@@@@@@@@@@@ | ___ ___ ___ ___ | ' ' /---------|| | | | \@@@@@@@@@@@@@_ | |_| |_| |_| |_| | ' ' + ======== +-+ | | | |__________________| ' ' _|--/~\------/~\-+ |__________________| |__________________| ' '//// \_O========O (O) (O) (O) (O) ' ' ++ +------ ____ ____________________ ' ' || |+-+ | | \@@@@@@@@@@@ | ___ ___ ___ ___ | ' ' /---------|| | | | \@@@@@@@@@@@@@_ | |_| |_| |_| |_| | ' ' + ======== +-+ | | | |__________________| ' ' _|--/~\------/~\-+ |__________________| |__________________| ' '//// \O========O/ (O) (O) (O) (O) ' ' ++ +------ ____ ____________________ ' ' || |+-+ | | \@@@@@@@@@@@ | ___ ___ ___ ___ | ' ' /---------|| | | | \@@@@@@@@@@@@@_ | |_| |_| |_| |_| | ' ' + ======== +-+ | | | |__________________| ' ' _|--/~\------/~\-+ |__________________| |__________________| ' '//// O========O_/ (O) (O) (O) (O) ' "; // end of LITTLE pub const JACK: &str = r" ' \ 0 / ' ' \|/ ' ' | ' ' / \ ' '_/ \_ ' ' ' ' __0__ ' '/ | \ ' ' / \ ' ' _\ /_ ' ' ' ' o ' ' /\ /\ ' ' |/ \| ' ' _\ /_ ' "; // end of JACK