We'll look at three different methods of getting the train (or any) image into our program, and discuss the pros and cons of each. You're welcome to try any of this coding (perhaps in a new temporary project), but it's not part of our "sl" project; it's just looking at possible ways we want to move forward.
Pre-Define the Train as a Vector of String Vectors
This is the method we've been using. For completeness, we'll show this method, abbreviated, here. The "main()" function calls two subfunctions; the first one, "get_d51()", reads the vector and assigns (or binds) the variable name "image_vecvec" to the vector, and then that variable (or binding) is fed to the second subfunction which displays the image to the screen.
Running this program should display all six of the image frames.
The biggest advantage of this method is that it makes it easier to program the rest of the program, because the image frames are pre-defined in the packaging in which we want them.
The biggest disadvantage is that it puts the burden on the ASCII-artist to get the packaging just right. As long as no future ASCII-artist wants to add a new image, this may not be much of a disadvantage. But if a future artist wants to add an image, say, a flying cow, that artist will need to make sure all the T's are crossed and all the "I"s are dotted.
Define the Train as a constant and Convert it To a Vector of String Vectors
This is probably the compromise method, as it's considerably easier on the ASCII-artist to provide a working image, but it requires more work on the part of our program to prep it for use by the rest of the program. It looks like this:
Notice that the constant definition is just "out in the open" of the "main.rs" file, not part of any function. This makes it "globally" accessible to everything in the "main.rs" file.
Note that the name of a constant should be in upper-case ("D51" vs "d51").
Note also that the string is a slice rather than a String.
Note that there's an "r" just before the opening quote of the string, which means the characters are to be read in "raw" mode, meaning that a backslash will be read as a backslash rather than an introduction to a control character. In other words, the text sequence of "\n" would be read as a backslash followed by a lower-case "n", instead of being read as a newline character.
Finally, the single-quotes at the end of each line are optional; I have demonstrated that by leaving them off on every other frame. They are there only so that the ASCII-artist knows where is the right-edge of the art (because ideally each line will be of equal length with the others; at a minimum, the first line needs to be the longest if the others are not equal to it, because that's the line we will read to get the overall length of the train.
When the compiler ("cargo") is compiling the program, any place it sees the constant "D51", that constant will be "replaced" with the actual contents of the constant.
The function "get_d51()" converts the constant (a string slice) into a vector of String vectors. The first thing it does is it removes the newline after the r"; if that newline is kept, it will be interpreted as a blank line further down in the function, and will begin a new, second, vector, leaving a first, empty vector. The next thing it does is to create a new blank inner vector within the outer vector. It then creates a counter, to keep count of which vector we're working with. The first vector is the zero'th vector. Then the function cycles through each line of the entire constant/string, and if it does not find a blank line, it adds that line to the current vector it's building. If it does see a blank line, it creates a new vector, and then proceeds on to the next cycle to read the next line, and so on.
Read the Train from a Text File and Convert it To a Vector of String Vectors
This method puts the train image definition in a file, named "d51.txt". There can be nothing else in this file; no comments, no function declarations, no use statements, etc. The program reads the entire contents of the file into a single string, and then processes it in a similar manner to how the constant is processed above.
I have also included the contents of a "coalcar.txt" file and a "jack.txt" file. To use these, you can simply change the one line - let image_str = include_str!("d51.txt");, although ideally you'd also change the name of the function and its call, and the comments, etc, to reflect the change.
A disadvantage with this method is that the several separate .txt files have to be distributed with the main executable file, and have to be findable by that executable file. This requires more programming in the program itself, and more care by the program-packager/distributor/installer.
Conclusion
We've looked at three methods for getting the image data into our program:
Pre-define the image in a somewhat-complicated vector of String vectors setup.
Define the image in a fairly-simple constant value.
Put the image in a separate text file, one image-set per text file.
Each method has its pros and cons, but overall, I think the best solution is the middle option, using constants. Let's put the image constants in a separate file, named "images.rs". This will be somewhat analagous to the "sl.h" file in the original C-version of "sl". Let's Move the Images to a Separate File.