In previous lessons, most notably Get User-provided Options From the Command-line, we provided a means for the user to specify which image of several to animate. But we need to be able to get several more functions, such as:
Let's start by adding the option to fly. We'll have to make a change to our "Args" struct, and make relevant changes to the main loop in the "main()" function.
... /* Define the inputs we might expect on the command-line. */ struct Args { /// Which image 'D51', 'C51', 'Jack', 'Boat', 'Plane', 'Twinengine', 'Little', 'Motorcycle', or 'Ferris'? #[arg(short, long, default_value_t = String::from("D51"))] // Tells clap that the next line is an argument, that can be entered in as "k" or "kind". kind: String,/// Fly? #[arg(short, long, default_value_t = false)] fly: bool, } // end of Args definitions ... letmut row: i32 = screen_height - height; let distance_to_travel = image_width + screen_width; for col in (0..distance_to_travel).rev() { let frame_num = (col % num_of_frames) as usize; // The remainder of this math tells us which frame to display, in descending order. let frame = &image_vecvec[frame_num]; draw_frame(row, col - image_width, frame);// If flying, then fly up only every x'th frame. let fly_upward_per_how_many_frames = 20; if switches.fly && (col % fly_upward_per_how_many_frames == 0) { row -= 1; // Fly up one row. // If we go off the top of screen, if row < -image_height { row = screen_height; // start over at bottom. } } thread::sleep(delay_between_frames); } ...
Note that "fly" is of type boolean, rather than String. What this means in practice is that if the option is provided on the command-line, then it's true, and if it's not provided, then it's false. This means that instead of having a command that looks like this:
$ cargo run -- --fly = true
we can have one that looks like this:
$ cargo run -- --fly
It saves the user a bit of typing. Sweet!
When I chose to fly the Twin-Engine ($ cargo run -- --fly --kind=twinengine
), I discovered that it leaves behind an unwanted trail of plane pieces. To fix this, let's erase the bottom-most line of the previous frame:
... fn draw_frame(mut row: i32, col: i32, frame: &Vec<String>) { for each_line in frame { my_mvaddstr(row, col, &each_line); row += 1; }// Also erase the bottom line of the previous frame (to erase left-over pieces if we're flying). // Create a line with as many space characters as the image is wide. let erasure_line = " ".repeat(frame[0].len()); // "row" should currently be pointing one row beneath current frame. Erase it. my_mvaddstr(row, col, &erasure_line); refresh(); } // end of draw_frame() ...
Or quarter-speed. Or a crawl. Whatever.
... /* Define the inputs we might expect on the command-line. */ struct Args { /// Which image 'D51', 'C51', 'Jack', 'Boat', 'Plane', 'Twinengine', 'Little', 'Motorcycle', or 'Ferris'? #[arg(short, long, default_value_t = String::from("D51"))] // Tells clap that the next line is an argument, that can be entered in as "k" or "kind". kind: String, /// Fly? #[arg(short, long, default_value_t = false)] fly: bool,/// Speed of animation? '[c]rawl', '[s]low', '[n]ormal', '[f]ast', '[z]ippy', '[[u]ltra]fast', '[z]oom', '[2]fast2see' [default: NORMAL] #[arg(short, long, default_value_t = String::from("N"))] speed: String, } // end of Args definitions fn main() { // Get command-line options using Clap. let switches: Args = Args::parse(); // This will be our delay, in milliseconds, between frame images. letmut delay = 50;match (&switches.speed.to_uppercase()).as_str() { "C" | "CRAWL" => delay = 400, "S" | "SLOW" => delay = 200, "N" | "NORMAL" => delay = 50, "F" | "FAST" => delay = 30, "Z" | "ZIPPY" => delay = 20, "U" | "ULTRA" | "ULTRAFAST" => delay = 10, "ZOOM" => delay = 1, "2" | "2fast2see" => delay = 0, _ => delay = delay, } let delay_between_frames = time::Duration::from_millis(delay); // Initialize the ncurses screen. ...
At this point, you can run the program with a command like one of the following:
cargo run -- --speed zippy --fly
cargo run -- -f
cargo run -- -s f -f
cargo run -- --kind=plane --speed fast -fly
cargo run -- --kind=plane -s f -f
cargo run -- -sz -f -ktwin
cargo run -- --fly --speed ultrafast
cargo run -- --help
By the way, if for some reason you wanted to use different letters/words for the options, say, "t" and "takeoff" instead of "f" and "fly", you could write the "arg" line like this: #[arg(short = "t", long = "takeoff")]
.
You can also change the value of the option in the Help screen like this: #[arg(short, long, default_value_t = String::from("N"), value_name="Velocity, Baby, yeah!")]
, which would generate this:
$ cargo run -q -- -h Usage: sl [OPTIONS] Options: -k, --kind <KIND> Which image 'D51', 'C51', 'Jack', 'Boat', 'Plane', 'Twinengine', 'Little', 'Motorcycle', or 'Ferris'? [default: D51] -f, --fly Fly? -s, --speed <Velocity, Baby, yeah!> Speed of animation? '[c]rawl', '[s]low', '[n]ormal', '[f]ast', '[z]ippy', '[[u]ltra]fast', '[z]oom', '[2]fast2see' [default: NORMAL] [default: N] -h, --help Print help $
And when entering your program arguments, the following are all identical:
$ cargo run -- -s f $ cargo run -- -sf $ cargo run -- -s=f $ cargo run -- -s fast $ cargo run -- -sfast $ cargo run -- -s=fast $ cargo run -- --speed=fast $ cargo run -- --speed fast (But "--speedf" and "--speedfast" won't work.)
And just as a reminder, cargo run -- -s f
is equivalent to ./target/debug/sl -s f
, because when you do a "cargo run", cargo is compiling your source code into a binary executable named "sl" which it places into "./target/debug", and then running that. You can run either command to get the same result.
Be aware that if you don't provide a default for an option (using the default_value_t
bit), the option is required to be entered on the command-line.
/* Define the argument inputs we might expect; put them in a struct named "Args". */ struct Args { /// Which image 'D51', 'C51', 'Jack', 'Boat', 'Plane', 'Twinengine', 'Little', 'Motorcycle', or 'Ferris'? #[arg(short, long, default_value_t = String::from("D51"))] // Tells clap that the next line is an argument, that can be entered in as "k" or "kind". kind: String, /// Fly? #[arg(short, long, default_value_t = false)] fly: bool, /// Speed of animation? '[c]rawl', '[s]low', '[n]ormal', '[f]ast', '[z]ippy', '[[u]ltra]fast', '[z]oom', '[2]fast2see' [default: NORMAL] #[arg(short, long, default_value_t = String::from("N"), value_name="Velocity, Baby, yeah!")] speed: String,/// Leave off the coalcar? #[arg(short, long, default_value_t = false)] no_trailer: bool, } // end of Args definition
//██████████████████████████ get_image() ██████████████████████████ //█ █ fn get_image(kind: &str) -> Vec<Vec<String>> { // Convert "kind" to upper-case. let upcased_kind = &kind.to_uppercase() as &str; // Our string is stored in a constant in the "images.rs" file. let const_str: &str = match upcased_kind { "D" | "D51" => D51, "L" | "LITTLE" => LITTLE, "C" | "C51" => C51, "B" | "BOAT" => BOAT, "T" | "TWINENGINE" | "TWIN" => TWINENGINE, "P" | "PLANE" => PLANE, "M" | "MOTORCYCLE" => MOTORCYCLE, "J" | "JACK" => JACK, "F" | "FERRIS" | "MASCOT" => FERRIS, _ => D51, // The default. }; //The first character in the string, after the 'r"', is a newline; lose it. let image_string = &const_str[1..const_str.len()]; // Create new blank vector of vector of Strings. let mut outer_vec: Vec<Vec<String>> = Vec::new(); // This keeps track of which frame/inner vector we're processing. let mut inner_vec_num: usize = 0; // Create first inner vector in the outer vector. outer_vec.push(Vec::new()); for mut each_line in image_string.lines() { if each_line != "" { // If a blank line is not found ... // ... remove single-quote mark at end of each line, if &each_line[each_line.len() - 1..] == "'" { each_line = &each_line[0..each_line.len() - 1]; } // Convert 'each_line' to a String. let mut line = each_line.to_string(); // ... and make sure last column of each line is a blank space ... line.push(' '); // ... and then add the string to the current inner vector. outer_vec[inner_vec_num].push(line); } else { // If a blank line is found... // ... we're done with the current vector, so create a new inner vector... outer_vec.push(Vec::new()); // ... and increase the count of vectors by one. inner_vec_num += 1; } }// We may need to add the coalcar to the D51 or C51 trains. outer_vec = if !switches.no_trailer && ((upcased_kind == "D51") || (upcased_kind == "C51")) { let coalcar_vecvec = get_image(&"coalcar"); outer_vec = merge_vecs(outer_vec, coalcar_vecvec); outer_vec } else { outer_vec }; // Return the finished vector of String vectors. outer_vec } // end of get_image() //█ █ //████████████████████ end of get_image() █████████████████████████//███████████████████████ end of merge_vecs() █████████████████████ //█ █ fn merge_vecs(vec1: Vec<Vec<String>>, vec2: Vec<Vec<String>>) -> Vec<Vec<String>> { vec2 // We'll flesh this function out in a bit. } // end of merge_vecs() //█ █ //███████████████████████ end of merge_vecs() █████████████████████
We can add "accident" (which I'll call "oops") in a similar fashion. In "src/main.rs":
let (speed, fly, kind, oops ) = parse_opts(switches);
Also in "src/main.rs", we will need to feed this option to the "draw_d51()" function, so it can draw the accident features.
fn main() { let (speed, fly, kind, oops) = parse_opts(); draw_d51(speed, fly, oops ); } // end of main()
And in "src/parsing.rs":
/* Define the inputs we might expect on the command-line. */ struct Args { /// How fast to move the object? ultrafast | zippy | fast | medium | slow | crawl #[arg(short, long, default_value_t = String::from("med"))] 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 ) { ... ... ... return (speed, switches.fly, kind, switches.oops ) } // end of parse_opts()
And in "src/d51.rs":
... pub fn draw_d51(delay: u64, fly: bool, oops: bool ) { ...
When you compile this, the compiler will complain about all the unused variables, but the program should compile and run as it did before.