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.
I'm a bit confused here. Isn't the
Display
class already handling the framebuffer stuff?The
Display
class 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. TheAsciimation
class holds a copy of what we want to show on the screen, and when we call itscopyToScreen
function, 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 aSprite
object 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
BoundingBox
class'sfindIntersection
member 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 theDisplay
class'sputCharAt
member function to copy each character to the display. (After this function is done, theplay
function willrefresh
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,
- 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
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:
-
Don't print your debugging output to
std::cout
. Instead, print it tostd::cerr
and redirectstderr
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 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
Display
class to use the file-output option. If yourMakefile
has aCPPFLAGS
variable, 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
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 tostd::cout
as usual, and it won't interfere with the display since all your animation output will go to the filethe-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 runmake
to make sure the file-output version of theDisplay
class is used. If you want to switch back to using terminal output, just runmake clean
and thenmake
without theCPPFLAGS
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 runmake clean
when 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
zsh
is doing the redirection, not theour-movie
program?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.
(When logged in, completion status appears here.)