Full Code Listing Up to the "Add C51 Train" Page

Return back to the "Add C51 Train" Page

Return back to Plan C

src/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);
} // end of main()
src/d51.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 = String::from(frame_line);  // 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 within screen bounds, so it's okay to display 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);  // Dims are now in screen_height/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 if we're flying ...
          row -= 1; // ... raise the next drawing one row up, and ...
          line = row + height; // ... erase last (bottom) line drawn, using enough repeated spaces for train's length.
          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()


fn get_d51() -> Vec<Vec<String>> {

  let d51: Vec<Vec<String>> = vec![
    vec![ /* Frame-set #0 */                                                          // The next ten lines form a "Vector of Strings".
        "      ====        ________                ___________ ".to_string(),         // Each of these ten lines is a String.
        "  _D _|  |_______/        \\__I_I_____===__|_________| ".to_string(),        // Think of each character in a String as a single Pringle's Potato Chip.
        "   |(_)---  |   H\\________/ |   |        =|___ ___|   ".to_string(),        // Each of these lines is about 48 "Pringle's chips" long.
        "   /     |  |   H  |  |     |   |         ||_| |_||   ".to_string(),         // Now imagine those 48 chips in a Pringle's can. The can is the "String".
        "  |      |  |   H  |__--------------------| [___] |   ".to_string(),         // Now imagine ten Pringle's cans in a cardboard carton. The carton is the "Vector".
        "  | ________|___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()
src/parsing.rs
/* parsing.rs */

// Use c[ommand] l[ine] a[rgument] p[arser] to get command-line arguments.
use clap::Parser;

// This tells clap to derive info it needs from the struct we build below, from the "#[" lines..
#[derive(Parser)]  

/* Define the argument inputs we might expect; put them in a struct named "Args". */
struct Args {

  /// Move the train '[[z]ip]py', '[f]ast', '[[m]ed]ium', '[s]low' or '[c]rawl'?
  #[arg(short, long, default_value_t = String::from("med"))]
  // The above tells clap that the next line is an argument, that can be entered in as "s" or "speed".
  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) {
  // Get command-line options using Clap.
  let switches: Args = Args::parse();
  
  // Set "speed".
  let speed:u64 = match (&switches.speed.to_uppercase()).as_str() {
    // The variable "switches.speed" is a String-type (as defined above in the "Args" struct). We upper-case
    // it, then compare it to possible matches which are string-slices (&str), which requires the ".as_str()"
    // conversion. We then "return" the selected value, which is assigned to "speed".
    "Z" | "ZIPPY" | "ZIP" | "U" | "ULTRAFAST" => 10,
    "F" | "FAST" => 20,
    "M" | "MED" | "MEDIUM" => 50,                                       
    "S" | "SLOW" => 100, 
    "C" | "CRAWL" => 400,
    _ =>  {
            println!("Invalid input; must be '[z]ippy/[u]ltrafast, '[f]ast', '[[m]ed]ium', '[s]low', or '[c]rawl'. You entered '{}', so the default of 'medium' was used.", switches.speed);
            50
          }
  };

  // Set "kind".                    
  let kind:String = match (&switches.kind.to_uppercase()).as_str() {
    "D" | "D51" => String::from("D51"),         // Alternative way of doing '"D51".to_string()'.
    "C" | "C51" => String::from("C51"),
    "L" | "LITTLE" => String::from("LITTLE"),
    "J" | "JACK" => String::from("JACK"),
    "B" | "BOAT" => String::from("BOAT"),
    "P" | "PLANE" => String::from("PLANE"),
    _ =>  { 
            // If none of them match, then ...
            println!("Invalid input; run program with '--help' option for more information. You entered '{}', so the default of 'd51' was used.", switches.kind);
            String::from("D51")
          }
  };

  return (speed, switches.fly, kind, switches.oops);
} // end of parse_opts()

Return back to the "Add C51 Train" Page