CS 70

Homework 3: Define the Sprite Class

The New Oxford American Dictionary defines a sprite as

  1. An elf or fairy.
  2. A computer graphic which may be moved on-screen and otherwise manipulated as a single entity.

As cool as it might be to summon supernatural beings from C++, we're going to focus on the second definition. A familiar example of a sprite (in the computer graphics sense) is your mouse cursor, which is free to move around on the screen on top of the other content, and can at any moment be seen as having not just a position but a velocity as well (i.e., how quickly it's moving and in what direction). In classic arcade games like Pac-Man, the player character is a sprite that moves around the screen, interacting with other sprites like the ghosts.

In this assignment, the terminal screen is our canvas, and our sprite is a collection of ASCII characters that form an image. But the concept is the same, they have a starting position, a size, and a velocity. We'll be implementing the Sprite class to represent these ASCII art sprites in our asciimation program.

Your Task

Complete the sprite.hpp Header File

  • Dog speaking

    Oooh… I noticed that there are // TODO comments in the sprite.hpp file for everything that needs to be done.

  • LHS Cow speaking

    That's right, but you should still read over this section before diving in.

  • Goat speaking

    Sure. I'll totally do that. cough cough

Add Private Data Members to the Sprite Class

Our Sprite class needs to store three things:

  1. A one-dimensional fixed-size array of characters (of size MAX_SPRITE_DATA) that holds the characters that make up the sprite's image.
  2. A bounding box that defines the position and size of the sprite on the screen.
  3. A velocity (in x and y directions) which defines how the sprite moves on the screen.

You will need to define private data members for these three things (the velocity will be two ints, one for each direction). Follow the CS 70 naming conventions for good and consistent naming of data members.

  • Horse speaking

    Hay! Why is the array a fixed size? Can't the sprites vary in size?

  • LHS Cow speaking

    At this point in the course, we haven't learned the techniques necessary to implement dynamically sized arrays, so we'll use a fixed-size array for simplicity. The maximum size is large enough to accommodate most sprites, and we can keep track of the actual size using the bounding box.

  • Duck speaking

    It's still a bit wasteful, isn't it? If I have a tiny sprite, I'm still using up space for the maximum size. I heard about std::vector, can't we use that?

  • LHS Cow speaking

    It's a bit like the difference between buying a cake and making one yourself. If you want to learn cookery, you need to learn all the basics of baking, but if you just want to celebrate someone's birthday, buying a cake from a shop might be the most expedient option. In other code you write, go ahead and use std::vector if you want, but don't use std::vector in code you write for CS 70 until after we've explored how it works inside.

Add Public Member Functions to the Sprite Class in sprite.hpp

We've already provided declarations of the class's two constructors in sprite.hpp:

  • A parameterized constructor that takes three parameters:
    • A reference to a string that represents the filename containing the characters we should read into the Sprite's contents, and
    • Two ints giving the initial location of the Sprite.
  • A second parameterized constructor that takes an input stream instead of a filename, to make testing easier, but otherwise works the same way.

You need to add the following public member functions to the Sprite class:

  • A getBoundingBox() function that returns the sprite's bounding box indicating where it is on the screen and how big it is.
    • The function doesn't take any parameters.
    • The return type should be BoundingBox.
  • A setVelocity(vx,vy) function that sets the sprite's velocity.
    • The function should take two int parameters.
    • The function doesn't return anything.
  • A setPosition(x,y) function that moves the sprite to a new location.
    • The function should take two int parameters.
    • The function doesn't return anything.
  • A getCharAt(x,y) function that returns the character at the specified position in the sprite's character array.
    • The function should take two int parameters: the column and row within the sprite. These coordinates are relative to the top-left corner of the sprite, so (0,0) is the top-left character, (1,0) is the character to its right, and so on. They are not screen coordinates.
    • The function should return a char.
    • You can assume that the inputs will always be valid (i.e., within the bounds of the sprite's dimensions).
  • A move(screenBounds) function that updates the sprite's position based on its velocity, wrapping around the screen if necessary.
    • screenBounds is a BoundingBox representing the size of the screen and should be passed by constant reference.
    • The function doesn't return anything.

Add the declarations of these functions to sprite.hpp with appropriate interface comments (in the next section, you'll implement them in sprite.cpp).

Disable the Default Constructor, Copy Constructor, and Assignment Operator

It won't make any sense to have default Sprites, and we also don't want anyone to accidentally work with a copy of a Sprite rather than the original. So we want to disable the default constructor, copy constructor, and assignment operator. We've already shown you how to disable the copy constructor and assignment operator in class BoundingBox, so you can follow that example.

  • RHS Cow speaking

    We cover how to do this in the lesson pages on synthesized constructors and the assignment operator. If you've not got to that part of this week's lessons yet, you can skip this part and leave it as a // TODO comment for now, and come back to it later.

Implement the Member Functions in sprite.cpp

  • LHS Cow speaking

    With the header file done, it's time to implement the member functions in sprite.cpp.

  • RHS Cow speaking

    If you want to be able to test your code as you write it, you can implement stub functions for your unwritten functions that either return default values (or do nothing for void functions), or you could have them throw a std::logic_error exception to indicate that they haven't been implemented yet. Then you can run the tests and see which ones fail, and as you implement each function, the corresponding tests will start to pass. See below for how to build and run the tests.

Finish the Constructors by Implementing Sprite Loading

sprite.cpp already contains implementations of the two constructors and some of the code for readFromStream. You will need to fill in the rest of their implementations.

Note: To receive full credit for writing idiomatic C++ code, all your constructors must properly use the member-initialization list. We recently covered this topic in lessons , and we also remind you about how we expect you to use this C++ feature in Section 4.1 of the CS 70 Idioms Page. These constructors are a good reminder of what member-initialization lists look like.

The main work left to do is to finish the readFromStream function, which reads the sprite data from a stream. The sprite files have a specific format:

  • Line 1: The width (must be positive)
  • Line 2: The height (must be positive)
  • Line 3: All width × height characters of the sprite in a single line.

The provided code in readFromStream includes error checking for the dimensions. You'll need to read the characters into your contents_ array. Because the sprite format itself collapses the 2-D grid of characters into a single line and we also store the data in a 1-D array, you can just read the characters in order into the array. In fact, we require you do it that way rather than use a nested loop to read the characters row by row.

The readFromStream function should throw a std::runtime_error exception if the sprite file is malformed. There is already some checking code but your loop should also throw an error if it can't read enough characters.

Implement getCharAt

The getCharAt function should return the character at the specified position in the sprite's character array. Remember that the sprite's character array is a 1-D array, so you'll need to do a bit of arithmetic to convert the 2-D coordinates into a 1-D index. You can assume that the inputs will always be valid (i.e., within the bounds of the sprite's dimensions), but see the hints below for some ideas about how you might handle invalid inputs if you wanted to.

  • LHS Cow speaking

    The testing code in sprite_test.cpp includes a test for getCharAt that you can use to check your implementation. And check out the Helpful Hints below for some more tips on how to implement this function if you get stuck.

Implement setPosition, setVelocity, and getBoundingBox

These should be very straightforward.

Implement move

The move function should update the sprite's position based on its velocity. If the sprite moves off the edge of the screen, it should wrap around to the other side. For example, if a sprite moves off the right edge of the screen, it should reappear on the left edge. The BoundingBox class has a handy member function, wrapPoint(int& x, int& y), that you can use to help with this aspect of the implementation.

Testing

As you implement the member functions, you can test them using the provided sprite_test.cpp file. This file contains a suite of tests that will check whether your Sprite class behaves as expected. As provided, some of the tests are commented out as the functions they test hadn't been implemented yet. As you implement each function, you can uncomment the corresponding test(s) to check your work.

If your Makefile is working correctly, you should be able to compile and run the tests using the commands:

cs70-make sprite-test
./sprite-test

(You can run regular make if you prefer.)

Helpful Hints

  • RHS Cow speaking

    These hints will help you write your Sprite class!

Static Constants

The Sprite class has a public static constant:

    static constexpr int MAX_SPRITE_DATA = 800;

This constant defines the fixed size for the character array in our program. You should use this constant in your code to avoid using “magic numbers”.

  • Hedgehog speaking

    Why is it static?

  • LHS Cow speaking

    Because it's a property of the class itself, not of any particular instance of the class. All Sprite objects share the same value for this constant. We covered this in the mini-lesson on static class members.

Array Arithmetic

If it's not clear to you how to convert the 2-D coordinates into a 1-D index, consider drawing a picture of your sprite as a 2-D grid and numbering each item with its index in the array. What formula will turn the row and column into the index?

  • RHS Cow speaking

    This is a general handy tip. Human beings aren't actually very efficient at thinking about things abstractly (it's too easy to get muddled), but we do well working with concrete examples. So if you're ever stuck on a problem, try working through a specific example to see if that helps you see the general case.

Out-of-Bounds Access

Think about what you want your getCharAt function to do if it's given an invalid row or column. From the standpoint of the assignment specification, providing out-of-range coordinates is against the rules, and so the resulting behavior is undefined.

One option is to just assume that you'll always get good inputs—hey, it could happen!

But more robust choices can make debugging easier. For example, you might choose a deterministic character that you'll always return if you get bad inputs to the function. Or you might throw an exception or fail an assertion. Your choice here won't affect your grade, but might make your coding process smoother.

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.)