Plan C - Add the Accident Mode

Plan C - Add the Accident Mode

Click here to return to "Plan C - Make Coalcar Optional".

The original "accident mode" in the C-version of "sl" has a couple of stick figures in the windows of the C51 and D51 trains, hollering "Help!". We should be able to emulate that behavior.

The C-version of "sl" has a drawing function for each type of train it can draw; a function for the D51, and one for the C51, and one for the Little train. This is how we started to write out version of "sl", but then we opted to write just one drawing function, and to feed to it the various images in the form of a vector of String vectors holding the drawing. In the C-version, each of these functions calls an "add_man" function when the "accident mode" is invoked. Here's what that call looks like for the C51 function in the C-version of "sl":

C51 Call to "add_man()" In "Accident Mode" in the C-Version of "sl"
if (ACCIDENT == 1) {
    add_man(y + 3, x + 45);
    add_man(y + 3, x + 49);
}

This code basically says that when the ACCIDENT mode is enabled, call the "add_man()" function twice, with the location to add a man being at 3 rows down from the top of the train, and 45/49 columns from the left-nose of the train.

Then the C-version of that "add_man()" function looks like this:

The "add_man()" function in the C-Version of "sl"
    void add_man(int y, int x)
{
    static char *man[2][2] = {{"", "(O)"}, {"Help!", "\\O/"}};
    int i;

    for (i = 0; i < 2; ++i) {
        my_mvaddstr(y + i, x, man[(LOGOLENGTH + x) / 12 % 2][i]);
    }
}

This function recieves an (Y,X) coordinate for drawing a man, and depending on some math, draws either the word "Help!" next to a tick-figure head with arms raised high, or no word with a stick-figure head with hands wrapped around the face in an "Oh No!" posture.

Now that we know the basic idea, we can do the same in our Rust version of "sl".

Each time we draw a frame of an image, we'll need to check to see if "accident mode" has been enabled and call an "accident()" function if so. We can call custom accident functions depending on the image that is being animated.

drawing.rs / draw_image()
...
for each_line in current_frame {
            my_mvaddstr(line, col, &each_line, screen_width, screen_height);
            line = line + 1;
        } 
        
        if oops {
          match kind {
            "D51" | "C51" => accident_d51(row, col),
            // "JACK" => accident_jack(row, col),
            // "BOAT" => accident_boat(row, col),
            // etc
            _ => println!("Not valid"),
          };
        }
  
        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);
...

And then we'll need the actual accident function. Here's a dummy function for testing:

drawing.rs
...
} // end of draw_image()
  
fn accident_d51(row: i32, col: i32) {
    if col % 14 == 0 {
        mv(2, 2);
        addstr("RED ALERT!");
    } else {
        mv(2, 2);
        addstr("          ");
    }    
} // end of accident_d51()

The above code should flash "RED ALERT!" in the top-left of the terminal window every 14th column as the train animates across the screen (when "col" divided by 14 has no remainder). I just picked "14" randomly; feel free to try other numbers; this is just a temporary test to make sure the basic logic/code works. Different math will get you different results. For example, try this:

    if col % 21 == 0 {
        mv(2, 2);
        addstr("RED ALERT!");
    } else if col % 7 == 0 {
        mv(2, 2);
        addstr("          ");
    }

Now let's change our message, and the location of the message. Because our message will be moving with the train, it might sometimes be off-screen. Therefore, we want to use our custom "my_mvaddstr" to prevent trying to display the parts of the message that might be off-screen. That means we'll have to provide the screen dimensions to the function:

drawing.rs
...
fn accident_d51(row: i32, col: i32, screen_width: i32, screen_height: i32) {
    if col % 14 == 0 {
        mv(2, 2);
        addstr("RED ALERT!");

        // We want our message to start about 37 columns from left-edge of train and about four lines down.
        mvmvaddstr(row + 4, col + 37, "Help!", screen_width, screen_height);
    }

    } else {
        mv(2, 2);
        addstr("          ");
    }    
} // end of accident_d51()

and ...

drawing.rs / draw_image()
  
if oops {
    match kind.as_str() {
        "D51" | "C51" => accident_d51(row, col, screen_width, screen_height),
        // "JACK" => accident_jack(line, col, screen_width, screen_height),
        // "BOAT" => accident_boat(line, col, screen_width, screen_height),
        // etc
        _ => println!("Not valid"),
    };
}

But when we run this (with something like $ cargo run -- --oops), not only is the "Help!" message flashing too fast, the program crashes as soon as the "Help!" message gets off-screen.

Looking at the "my_mvaddstr()" function, I see pretty quickly why the program is crashing. As the image moves off the left side of the screen, the line of the image being displayed is trimmed shorter and shorter; the "Help!" line, being shorter to begin with than the rest of the train, gets cut down to nothing, and then the cutting keeps going further into a string that has already been cut to nothing. This causes the crash. The easy fix is to put a check into the code, to make sure the string is not already down to nothing before trimming it further:

drawing.rs / my_mvaddstr()
 
    // 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,
            if line.len() > 0 {
                line.remove(0); // trim the first char off the string, repeatedly.
            }
        } // "line" should now have front end trimmed off.
        col = 0;
    }

I've been rethinking this project, and I think I'm ready to move on to Plan D.