Plan C - Redesign Our Program As We Convert Debian's "sl" Toy from the C Language to Rust

Plan C - Redesign Our Program As We Convert Debian's "sl" Toy from the C Language to Rust

Click Here if you would like to see a complete code listing to this point.

Click here to return to Plan B of the "Convert 'sl' in Debian from the C Language to Rust" project, at the "Add C51 Train" step.

When I started this project, I thought I'd pretty much just duplicate the structure of the C-version of "sl", as found in the Debian ecosystem. Then I thought it'd be good to package all the coding/data bits belonging to any one image, say, the D51 train, into its own "module". I'm calling that revised plan, "Plan A". By the time I got to the "Add C51 Train" step, I realized that was going to create a lot of code duplication, and I started following a different plan, which I'm now calling "Plan B". But it still had a lot of complexity, and when I went back through the tutorial to clean it up, and got to that same point, I decided I wanted to try to make it simpler/better. That plan is this route, Plan C.

Since Plan B provides a lot of learning potential for someone learning Rust, I decided to leave it in place, even though it has not reached maturation. You can follow that path by clicking on the link above. But I'm hoping/planning that this Plan C willl produce a better product.

Let's start by dividing the "d51.rs" file into two pieces, one for the ASCII-art image bits, and one for the drawing code. Let's rename the file:

$ mv src/d51.rs src/drawing.rs

Then create a new file named "d51.txt", and copy the entire function named "get_d51()" out of "drawing.rs" and into the newly-created "d51.txt". You should now no longer have a "d51.rs" file, and you should have the following two new files:

drawing.rs
/* 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 = 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()
d51.txt
fn get_d51() -> Vec<Vec<String>> {
    let d51: Vec<Vec<String>> = vec![
        vec![
            /* Frame-set #0 */	//  <--- The stack below of ten long boxes makes up a vector of Strings.
            "      ====        ________                ___________ ".to_string(), //  <--- This is one long box containing a String of character-containing boxes.
            "  _D _|  |_______/        \\__I_I_____===__|_________| ".to_string(), //  <--- And this is another.
            "   |(_)---  |   H\\________/ |   |        =|___ ___|   ".to_string(), // And so on....
            "   /     |  |   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(),
        ],
        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()

You should also have "main.rs" and "parsing.rs", as listed here (same link as at the top of this page).

Let's clean up "d51.txt" first. Edit the file to look like this:

d51.txt
"      ====        ________                ___________"
"  _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/      \_/           " 
    

Much simpler. That's probably the best way of creating the ASCII-art image; it has the quotation marks so that the author can see the boundaries of his/her drawing, but it has no other clutter, except for the blank lines separating the six image frames.

Each one of these six image frames are slightly different in the wheel area, so that as each frame is displayed on the screen one after the other in rapid succession, it will create the illusion that the wheels are rotating. You should already understand this process from the first attempt we made at drawing the D51 train, in the Plan A path mentioned earlier.

But the problem with the above "d51.txt" file is that it requires the "d51.txt" file (and all relevant "[image].txt" files) to travel around with the "sl" executable, and if the "[image].txt" file can't be read for any reason, the program fails.

It would be better if we could incorporate the image data into the program in some way during compilation so that the separate text file[s] didn't have to remain available to and travel around with the compiled executable. That's why we originally put the data into a vector of vectors of Strings, accessible via the "get_d51()" function. But that original method added a bit of complexity to the process, especially for the ASCII-artist trying to get all the bits in place within his/her ASCII-art drawing vector.

However, we can use a compromise method, which adds a little complexity to the ASCII-art drawing, but not as much as with the vector of String vectors we used with the D51. With just a plain .txt file like we have now, we'd have to carry the .txt files around with us. With the vector method, the artist has to deal with the complexities of the vector structure. With this compromise method, of we turn the raw imge data into a constant value, there's a little complexity that the ASCII-artist must deal with , bu tnot much, and then the data will already be in the program, and not need to be separate files we have to keep close to the executable. This additional complexity is pretty simple, and looks like the following. Notice the following points:

d51.rs
/* d51.rs */
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

In that Plan A path, we followed the basic outline of the C-version of the "sl" program, and created what in Rust-speak is known as a vector of Strings within a vector, or a vector of vector of Strings, or a vector of String vectors, based on the ASCII-artists creating artwork that is pre-configured as such type of variable. This time, we'll do that again, but instead of having the ASCII-artist create the vector of String vectors format, we'll have the artist create this simpler constant which holds the data as one long &str string, and let the program do the necessary conversion.

If we were to look inside of our new constant, we'd see this, representing the first three lines of the "d51.text" file:

&str constant/variable holding the first three lines of data from d51.txt
'      ====        ________                ___________'\n'  _D _|  |_______/        \__I_I_____===__|_________|'\n'   |(_)---  |   H\________/ |   |        =|___ ___|  '\n ... and so on and so on

Let's setup a test-print in "main.rs" to see if we're on the right track.

main.rs
/* 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);
    println!("{}", D51);
} // end of main()

If cargo run displays the six frames, and it should, then we're on the right track.

You'll notice that the single-quote marks get included in the string (which we'll want to strip out, but we wanted them in the "D51" constant so that the ASCII-artist could see the boundaries of his/her artwork), and there is an unseen newline character ("\n") at the end of each line, which create the line-breaks at the end of each line in the "D51" const. There's also an unseen newline at the very beginning of this string, which will be intepreted by the program as an unwanted empty frame if we don't deal with it; we'll deal with it shortly.

To see the string in a non-formatted way, use the debug formatter:

main.rs
/* main.rs */

mod d51;
// mod parsing;

use d51::*;
// 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);
    println!("{:?}", D51);
} // end of main()

Run the program now and you'll basically see the one long string that is the constant, including the "\n" newlines that represent line-breaks.

Let's re-enable the parsing and make sure that part works:

main.rs
/* main.rs */

mod d51;
// mod parsing;

use d51::*;
// 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);
    if kind == "D51" {
        println!("{}", D51);
    } else {
        println!("You entered {}.", kind);
    } 
    println!("{:?}", D51);
} // end of main()

If you specify a kind that is not the "d51', say, with something like cargo run -- --kind=c51, you'll get the "You entered..." message. But if you specify the "d51" or you specify no kind, or a kind we haven't specified in our program, then you'll see the six frames. Everything seems to be working so far.

Notice, though, that since the "parse_opts()" function is only returning the kind, "main()" doesn't yet know the image it needs to draw; it has to have some sort of test, such as the "if" statement we're currently using, or a "match" statement similar to what we already have in "parse_opts()", to determine which image to draw. So if an ASCII-artist were to create a new ASCII-art image, that artist would need to make changes to two match sections, in two different parts of the program. That increases the risk of human-error, and duplicates code. So rather than returning only the kind from "parse_opts()", let's take advantage of that function's match section to also return the image string as well. We'll tackle that task in the next lesson, Getting the Correct Image From the Specified Kind.