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
...
let mut 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.
let mut 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 --flycargo run -- -fcargo run -- -s f -fcargo run -- --kind=plane --speed fast -flycargo run -- --kind=plane -s f -fcargo run -- -sz -f -ktwincargo run -- --fly --speed ultrafastcargo run -- --helpBy 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.