CS 70

Homework 3: Define and Implement the Asciimation Class

In the previous parts of the assignment, you saw the BoundingBox, Sprite, and Display classes. Now it's time to put them together in the Asciimation class, which will manage the overall animation.

When you have completed this part of the assignment, you will be able to run the our-movie executable, which will display a simple animation of a sprite moving across the screen.

What the Asciimation Class Does

The job of the Asciimation is twofold. It acts as

  • A framebuffer, storing the content that needs to be shown on the display.
  • A sprite manager, putting the content of one or more Sprite objects into the framebuffer at the right locations.
  • Cat speaking

    I'm a bit confused here. Isn't the Display class already handling the framebuffer stuff?

  • LHS Cow speaking

    The Display class models real displays in that it is an output device. When we send characters to the Display, they will be shown on the screen, but we can't read back what is currently on the screen. The Asciimation class holds a copy of what we want to show on the screen, and when we call its copyToScreen function, it sends that content to the Display.

The play Function

This class only has one public member function, play, which runs the main loop of the animation. It repeatedly updates the framebuffer with the current state of the sprites and then copies that to the display, until the user decides to quit.

Because we've only covered how to make fixed-size arrays thus far, there is also a header file numsprites.hpp that defines the constant NUM_SPRITES which is the number of sprites that the program will manage. Currently, NUM_SPRITES is set to 1, but later on when you get to be creative, you can change it to a larger number to have more than one sprite on the screen at once.

The type signature of the play function combines two concepts we've seen recently, fixed-size arrays and references:

void play(Sprite (&sprites)[NUM_SPRITES]);

The notation may look rather funky, but it just means that play takes a reference to an array of Sprite objects of size NUM_SPRITES. This way, the caller can create an array of sprites and pass it to play, which will then render all of them to the screen.

Your Task

The public facing code for the Asciimation class is written, including the constructor and the play function. One thing that isn't yet done is disabling the copy constructor and assignment operator, which you should do now.

More importantly, the private member functions that do the actual work of managing the framebuffer and rendering sprites are not yet implemented.

Implementing the Private Member Functions

Your task is to implement these private member functions:

  • clearFrame: This function should loop through the entire character array and set each character to be a space (' ').
  • renderSprite: This function should take a Sprite object as input and copy its characters into the correct positions in the frame_ array, based on the sprite's position and the movieScreenBounds_.
    • Note, due to some last-minute changes when making the assignment, in a few places in the provided code, the name movieBounds_ was used instead of movieScreenBounds_. Either is fine; the key thing is that it's the same member variable.
    • The sprite may be partially or completely outside the bounds of the movie screen. In that case, only the part of the sprite that is within the bounds should be copied to the frame. (You can use the BoundingBox class's findIntersection member function to help with this.)
  • copyToScreen: This function should loop through the frame_ array and use the Display class's putCharAt member function to copy each character to the display. (After this function is done, the play function will refresh the display to show the new frame.)

When you have these functions implemented, the Asciimation class will be able to manage and display sprites on the screen.

Running the Program

To run the program, you will need to compile the asciimation.cpp file along with the other source files. Once compiled, you can run the our-movie executable to see the animation in action.

When you run your animation,

  1. Make sure your terminal window is large enough (at least 80 columns by 40 rows).
  2. Run the program from the terminal with ./our-movie.
  3. The animation will take over your terminal window.
  4. Press q to quit the animation.

Helpful Hints

  • RHS Cow speaking

    Here are some hints for the Asciimation class.

Accessing the frame_ Array

Just like the Sprite class, the Asciimation class uses a one-dimensional array to represent a two-dimensional grid of characters. To access the character at row r and column c, you can use the formula:

frame_[y * MOVIE_WIDTH + x]

where MOVIE_WIDTH is the width of the display area (80 characters).

Terminal Size Matters

If your terminal is too small, the animation may look garbled or parts may be cut off. You can resize your terminal window by dragging its edges in VS Code, or by using your terminal emulator's settings if connecting via SSH. (Most modern terminal applications will automatically set the terminal size for you when you resize the window on your machine.)

Debugging Output Is Tricky

When you're trying to figure out what's wrong with your code, you might want to print out some debugging information. However, because the play function takes over the entire terminal window, any output you print to stdout will be immediately overwritten by the animation or spoil your display!

You have two options:

  1. Don't print your debugging output to std::cout. Instead, print it to std::cerr and redirect stderr to a file on the command line when you run the program. For example,

    ./our-movie 2> debug.txt
    

    will save all output sent to std::cerr into the file debug.txt, which you can then open and read. (If you open the file in VS Code, it will notice when the file changes and update what it shows. So you can see your animation in the terminal and the debugging messages sent to the log file at the same time.)

  2. Switch the Display class to use the file-output option. If your Makefile has a CPPFLAGS variable, you can rebuild your program with

    make clean
    make 'CPPFLAGS=-DDISPLAY_OUTPUT_FILE=\"the-movie.txtmov\"'
    

    These commands will build a completely different version of the Display class (via the preprocessor and #ifdef DISPLAY_OUTPUT_FILE) to write the output to a file instead of the terminal. You can then print debugging information to std::cout as usual, and it won't interfere with the display since all your animation output will go to the file the-movie.txtmov instead of the terminal.

    If you choose to send the animation frames to a file, you will need to use the CPPFLAGS=... argument every time you run make to make sure the file-output version of the Display class is used. If you want to switch back to using terminal output, just run make clean and then make without the CPPFLAGS part. If you build half the time with file output and half the time with terminal output, you may end up with a mix of object files that don't work together and the program will fail to link (or, worse, link but crash at runtime). So be consistent! (And run make clean when you want to switch.)

  • Duck speaking

    What's the deal with 2> in the command line?

  • LHS Cow speaking

    In Unix-like systems, std::cin (standard input) is file descriptor 0, std::cout (standard output) is file descriptor 1, and std::cerr (standard error) is file descriptor 2. By default they all read from and write to the terminal, but your command shell (zsh) allows you to change the source or destination for each file descriptor. The 2> operator redirects file descriptor 2 (i.e., std::cerr) to a file (debug.txt) instead of the terminal.

  • Duck speaking

    So zsh is doing the redirection, not the our-movie program?

  • LHS Cow speaking

    Yes, exactly! The program doesn't need to know anything about it—it never sees the 2> debug.txt at all, because the shell handles it before the program even starts running.

To Complete This Part of the Assignment

You'll know you're done with this part of the assignment when you've done all of the following:

(When logged in, completion status appears here.)