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
Spriteobjects into the framebuffer at the right locations.
I'm a bit confused here. Isn't the
Displayclass already handling the framebuffer stuff?
The
Displayclass models real displays in that it is an output device. When we send characters to theDisplay, they will be shown on the screen, but we can't read back what is currently on the screen. TheAsciimationclass holds a copy of what we want to show on the screen, and when we call itscopyToScreenfunction, it sends that content to theDisplay.
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 aSpriteobject as input and copy its characters into the correct positions in theframe_array, based on the sprite's position and themovieScreenBounds_.- 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 ofmovieScreenBounds_. 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
BoundingBoxclass'sfindIntersectionmember function to help with this.)
- Note, due to some last-minute changes when making the assignment, in a few places in the provided code, the name
copyToScreen: This function should loop through theframe_array and use theDisplayclass'sputCharAtmember function to copy each character to the display. (After this function is done, theplayfunction willrefreshthe 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,
- Make sure your terminal window is large enough (at least 80 columns by 40 rows).
- Run the program from the terminal with
./our-movie. - The animation will take over your terminal window.
- Press q to quit the animation.
Helpful Hints
Here are some hints for the
Asciimationclass.
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:
-
Don't print your debugging output to
std::cout. Instead, print it tostd::cerrand redirectstderrto a file on the command line when you run the program. For example,./our-movie 2> debug.txtwill save all output sent to
std::cerrinto the filedebug.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.) -
Switch the
Displayclass to use the file-output option. If yourMakefilehas aCPPFLAGSvariable, you can rebuild your program withmake clean make 'CPPFLAGS=-DDISPLAY_OUTPUT_FILE=\"the-movie.txtmov\"'These commands will build a completely different version of the
Displayclass (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 tostd::coutas usual, and it won't interfere with the display since all your animation output will go to the filethe-movie.txtmovinstead 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 runmaketo make sure the file-output version of theDisplayclass is used. If you want to switch back to using terminal output, just runmake cleanand thenmakewithout theCPPFLAGSpart. 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 runmake cleanwhen you want to switch.)
What's the deal with
2>in the command line?
In Unix-like systems,
std::cin(standard input) is file descriptor 0,std::cout(standard output) is file descriptor 1, andstd::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. The2>operator redirects file descriptor 2 (i.e.,std::cerr) to a file (debug.txt) instead of the terminal.
So
zshis doing the redirection, not theour-movieprogram?
Yes, exactly! The program doesn't need to know anything about it—it never sees the
2> debug.txtat all, because the shell handles it before the program even starts running.
(When logged in, completion status appears here.)