Convert "sl" Source Code from C to Rust

Complete Code Listing Up To This Page

Draw All Frames of Train

In the previous lesson, Draw a Train, we displayed one frame of the D51 train using "println!" statements. But we'll need to be able to display all six frames in rapid succession in order to create the illusion of animation. To do that, we need to have all six frames (or cels) of the image in our program. This lesson will focus on expanding what we did earlier with the vector, essentially duplicating that vector five more times, once for each frame of the train, and then we'll put all six of those vectors into an outer vector. The result will be an outer vector holding six inner vectors, each of the six containing ten or so Strings, one String for each row of the frame image. This will be a "vector of String vectors", or a "vector of vector of Strings".

If you need to see the complete program code listing up to this point, there's a link to do so above, near the menu button.

The vector we've been working with has been structured like this:

d51 vector[
  "first line of ASCII-art train",
  "next line",
  "next line",
  "etc"
]

What we're about to end up with looks more like this example, which has three inner vectors stuffed inside an outer vector:

d51 outer vector of vectors[
  first inner vector[
    "first line of first frame of ASCII-art train",
    "next line",
    "next line",
    "etc"
  ],
  second inner vector[
    "first line of second frame of ASCII-art train",
    "next line",
    "next line",
    "etc"
  ],
  a third inner vector[
    "etc",
    "etc"
  ]
]

Or as a type declaration, it looks like Vec<Vec<String>>. Right now, as just a simple vector of strings, the declaration looks like Vec<String>.

Imagine a vector as a single row of cubby holes, as in the image below:

cubby holes

An ASCII-string vector would have one ASCII character per cubby hole. So the image above could hold something like "Hello" or "HI!!!". But strings in Rust are not ASCII, but UTF-8 (so that more characters, such as international characters like Chinese and Japanese, and emojis, etc, are available). UTF-8 does not necessarily store a single character in a single same-size cubby-hole as the others; sometimes a character takes up two, three, or even four cubby slots. The result is that any particular character might take up, say, slots 3 and 4, whereas another character might take up slots 5 and 6 and 7 and 8, and a third character might just take slot 9 (the above image only has five slots, so you'll have to use your imagination to envision a row of 9 or more cubby holes in this row). So you can't rely on slot numbering (indexing) to get a complete character, when using UTF-8, unlike the good ol' days of using ASCII. It's only a coincidence that our train drawing is composed of ASCII characters, which is a subset of the UTF-8 character set, so that each character of the train drawing does take up just one cubby hole of a String vector. But it is bad practice to rely on indexing, even if technically it might work.

Each line of the train drawing fills one row of cubby holes (each row having about 55 cubbies). Then the next line of the train drawing fills a second row of cubby holes. Those cubby holes are stacked on top of each other, until we have ten rows of cubby holes holding ten lines of train drawing. Each of those rows is a String vector. As we stack them, we stack them in a container which is also a vector. The outer vector holds the inner vectors of Strings. Then, when we add a different frame (the second of six, total) of the train drawings, with a different wheel arrangement, we put those ten cubby hole rows into a vector, stacked below the first vector, and both of those vectors of String vectors are put into the outer vector. And so on for the remaining four frames of the train drawing. Eventually we have six vectors in an outer vector, with each of the six containing ten vectors of Strings -- a vector of String vectors.

Here's what our single frame looks like as a single vector of Strings:

    let d51: Vec<String> = vec![
        r"      ====        ________                ___________ ".to_string(),
        r"  _D _|  |_______/        \__I_I_____===__|_________| ".to_string(),
        r"   |(_)---  |   H\________/ |   |        =|___ ___|   ".to_string(),
        r"   /     |  |   H  |  |     |   |         ||_| |_||   ".to_string(),
        r"  |      |  |   H  |__--------------------| [___] |   ".to_string(),
        r"  | ________|___H__/__|_____/[][]~\_______|       |   ".to_string(),
        r"  |/ |   |-----------I_____I [][] []  D   |=======|__ ".to_string(),
        r"__/ =| o |=-~~\  /~~\  /~~\  /~~\ ____Y___________|__ ".to_string(),
        r" |/-=|___|=    ||    ||    ||    |_____/~\___/        ".to_string(),
        r"  \_/      \O=====O=====O=====O_/      \_/            ".to_string(),
    ];

And if we put this single vector of Strings into an outer vector (even though it's just one inner vector, instead of several), the outer vector becomes a vector containing a vector of Strings:

d51.rs / get_d51()
  let d51: Vec<Vec<String>> = vec![
    vec![ /* Frame-set #0 */
        r"      ====        ________                ___________ ".to_string(),
        r"  _D _|  |_______/        \__I_I_____===__|_________| ".to_string(),
        r"   |(_)---  |   H\________/ |   |        =|___ ___|   ".to_string(),
        r"   /     |  |   H  |  |     |   |         ||_| |_||   ".to_string(),
        r"  |      |  |   H  |__--------------------| [___] |   ".to_string(),
        r"  | ________|___H__/__|_____/[][]~\_______|       |   ".to_string(),
        r"  |/ |   |-----------I_____I [][] []  D   |=======|__ ".to_string(),
        r"__/ =| o |=-~~\  /~~\  /~~\  /~~\ ____Y___________|__ ".to_string(),
        r" |/-=|___|=    ||    ||    ||    |_____/~\___/        ".to_string(),
        r"  \_/      \O=====O=====O=====O_/      \_/            ".to_string(),
    ],
  ];

Then all we have to do is add the other five vectors of Strings (and to modify the function's return declaration):

d51.rs
fn get_d51() -> Vec<Vec<String>> {

  let d51: Vec<Vec<String>> = vec![
    vec![ /* Frame-set #0 */
        r"      ====        ________                ___________ ".to_string(),
        r"  _D _|  |_______/        \__I_I_____===__|_________| ".to_string(),
        r"   |(_)---  |   H\________/ |   |        =|___ ___|   ".to_string(),
        r"   /     |  |   H  |  |     |   |         ||_| |_||   ".to_string(),
        r"  |      |  |   H  |__--------------------| [___] |   ".to_string(),
        r"  | ________|___H__/__|_____/[][]~\_______|       |   ".to_string(),
        r"  |/ |   |-----------I_____I [][] []  D   |=======|__ ".to_string(),
        r"__/ =| o |=-~~\  /~~\  /~~\  /~~\ ____Y___________|__ ".to_string(),
        r" |/-=|___|=    ||    ||    ||    |_____/~\___/        ".to_string(),
        r"  \_/      \O=====O=====O=====O_/      \_/            ".to_string(),
      ],
      vec![ /* Frame-set #1 */
        r"      ====        ________                ___________ ".to_string(),
        r"  _D _|  |_______/        \__I_I_____===__|_________| ".to_string(),
        r"   |(_)---  |   H\________/ |   |        =|___ ___|   ".to_string(),
        r"   /     |  |   H  |  |     |   |         ||_| |_||   ".to_string(),
        r"  |      |  |   H  |__--------------------| [___] |   ".to_string(),
        r"  | ________|___H__/__|_____/[][]~\_______|       |   ".to_string(),
        r"  |/ |   |-----------I_____I [][] []  D   |=======|__ ".to_string(),
        r"__/ =| o |=-~~\  /~~\  /~~\  /~~\ ____Y___________|__ ".to_string(),
        r" |/-=|___|=O=====O=====O=====O   |_____/~\___/        ".to_string(),
        r"  \_/      \__/  \__/  \__/  \__/      \_/            ".to_string(),
      ],
      vec![ /* Frame-set #2 */
        r"      ====        ________                ___________ ".to_string(),
        r"  _D _|  |_______/        \__I_I_____===__|_________| ".to_string(),
        r"   |(_)---  |   H\________/ |   |        =|___ ___|   ".to_string(),
        r"   /     |  |   H  |  |     |   |         ||_| |_||   ".to_string(),
        r"  |      |  |   H  |__--------------------| [___] |   ".to_string(),
        r"  | ________|___H__/__|_____/[][]~\_______|       |   ".to_string(),
        r"  |/ |   |-----------I_____I [][] []  D   |=======|__ ".to_string(),
        r"__/ =| o |=-O=====O=====O=====O \ ____Y___________|__ ".to_string(),
        r" |/-=|___|=    ||    ||    ||    |_____/~\___/        ".to_string(),
        r"  \_/      \__/  \__/  \__/  \__/      \_/            ".to_string(),
      ],
      vec![ /* Frame-set #3 */
        r"      ====        ________                ___________ ".to_string(),
        r"  _D _|  |_______/        \__I_I_____===__|_________| ".to_string(),
        r"   |(_)---  |   H\________/ |   |        =|___ ___|   ".to_string(),
        r"   /     |  |   H  |  |     |   |         ||_| |_||   ".to_string(),
        r"  |      |  |   H  |__--------------------| [___] |   ".to_string(),
        r"  | ________|___H__/__|_____/[][]~\_______|       |   ".to_string(),
        r"  |/ |   |-----------I_____I [][] []  D   |=======|__ ".to_string(),
        r"__/ =| o |=-~O=====O=====O=====O\ ____Y___________|__ ".to_string(),
        r" |/-=|___|=    ||    ||    ||    |_____/~\___/        ".to_string(),
        r"  \_/      \__/  \__/  \__/  \__/      \_/            ".to_string(),
      ],
      vec![ /* Frame-set #4 */
        r"      ====        ________                ___________ ".to_string(),
        r"  _D _|  |_______/        \__I_I_____===__|_________| ".to_string(),
        r"   |(_)---  |   H\________/ |   |        =|___ ___|   ".to_string(),
        r"   /     |  |   H  |  |     |   |         ||_| |_||   ".to_string(),
        r"  |      |  |   H  |__--------------------| [___] |   ".to_string(),
        r"  | ________|___H__/__|_____/[][]~\_______|       |   ".to_string(),
        r"  |/ |   |-----------I_____I [][] []  D   |=======|__ ".to_string(),
        r"__/ =| o |=-~~\  /~~\  /~~\  /~~\ ____Y___________|__ ".to_string(),
        r" |/-=|___|=   O=====O=====O=====O|_____/~\___/        ".to_string(),
        r"  \_/      \__/  \__/  \__/  \__/      \_/            ".to_string(),
      ],
      vec![ /* Frame-set #5 */
        r"      ====        ________                ___________ ".to_string(),
        r"  _D _|  |_______/        \__I_I_____===__|_________| ".to_string(),
        r"   |(_)---  |   H\________/ |   |        =|___ ___|   ".to_string(),
        r"   /     |  |   H  |  |     |   |         ||_| |_||   ".to_string(),
        r"  |      |  |   H  |__--------------------| [___] |   ".to_string(),
        r"  | ________|___H__/__|_____/[][]~\_______|       |   ".to_string(),
        r"  |/ |   |-----------I_____I [][] []  D   |=======|__ ".to_string(),
        r"__/ =| o |=-~~\  /~~\  /~~\  /~~\ ____Y___________|__ ".to_string(),
        r" |/-=|___|=    ||    ||    ||    |_____/~\___/        ".to_string(),
        r"  \_/      \_O=====O=====O=====O/      \_/            ".to_string(),
      ],
    ];

    return d51;
} // end of get_d51()

Now we have a vector of vectors of strings.

If you included the optional data-type declaration in the "let d51..." statement in "draw_d51()", it will also need to be modified:

    let d51: Vec<String> = get_d51();
    let d51: Vec<Vec<String>> = get_d51();

Although we now have all six animation cel frames in our variable "d51", let's just print the first one as a test. To print just the first animation cel, d51[0], the way we've been doing, we need to temporarily make a mod to our for each_line... statement:

...
  for each_line in &d51[0] {
    mv(row,col);
	addstr(&each_line);
	row = row + 1;
  }
...

We're only printing the first frame of the six here, just to go slow. And again, we have to use an ampersand to tell the compiler that we're only viewing the data at this memory location rather than trying to take ownership of it. Our code should currently print just the first frame of those six.

Of course, we don't want to display just one frame, but all six. Let's do that.

d51.rs
...
pub fn draw_d51() {
    let d51: Vec<Vec<String>> = get_d51();
    for each_line in d51 {
        println!("{}", each_line);

    for each_inner_vec in d51 {
        for each_line in &each_inner_vec {
            println!("{}", each_line);
        }
    }
} // end of draw_d51()  

Run this, and you should see all six frames of the D51 displayed.

All this is well and good, but it puts some burden on the ASCII-artist, having to surround his/her ASCII-art with all the infrastructure required to make it a vector of String vectors. We can take some of the burden off the artist, and let the program itself do some of the heavy lifting. Let's Take A Look At Alternative Ways to Read the Train Images.