In this homework, we will be looking at different ways to organize data. As we discussed, we are using structs to represent parts of the game (snake head, snake tail, apple). A large part of programming is organizing data.
Organizing data generally helps when you can find common functionality among different pieces of your program. In our example last homework, we organized our data into a
Pet struct. The
Pet struct had properties about the pet -- how hungry it was, how tired it was, its name, and what kind of animal it was. This helped to make it so we didn't have to write specialized code for each individual pet. We could write code for all pets, and then just build a list of them.
Where this helps is scalability. Scalability is the ability to take a small piece of software (or anything really) and scale it up as large as you want. With the
Pet struct we can keep track of thousands or millions of pets, and the code is always the same -- we only have to write it once.
If you can imagine a game that involves pets, it could increase their hunger and decrease their altertness, where the user has to pay attention and feed or give naps to various pets, and also keep track of which food to give to the pets. With just a simple struct we can scale this game to hundreds or thousands of users all playing the game.
Before getting into the discussion, remember that the Fundamentals page is a great reference for things you can do in D. I will be adding more to that page as we go along.
Let's say you want to program a game of checkers. There are 64 squares on a checkerboard (8 x 8), but we only use 32 of them. Each of the 32 squares can have a piece or not have a piece, and the piece can be a regular piece or a king. Using an integer to store the information about a square is reasonable. One way to represent the data would be:
int square1; int square2; int square3; ... int square32;
The squares would represent spaces on the board like this:
This way of storing variables is both inefficient and doesn't scale. For example, when you want to move a piece, to check if the piece is a valid move, you have to use a different function for every single square because the variables are each named differently. There is no way to programmatically access a given square given another square.
But if we store the squares in an array, the declaration looks like this:
And now the squares represent the spaces like this:
Now, isntead of using
square1, you use
square (remember that array indexes always start at 0). Figuring out which squares can be moved to is a matter of doing some math on the index, and accessing the squares surrounding it. Then we don't need a separate function for each square, we can write just one function and run the game based on that.
Plus, since we know it works for an 8x8, we can actually run the same code on a 16x16 board, and not have to change anything except maybe a number or 2! This power to scale allows you to focus on one piece at a time, and then just add more items to the array of items, and everything runs the same.
An associative array is similar to a normal array, except we can use arbitrary indexes. They don't have to start at 0, and they don't even need to be integers.
In the previous homework, we stored our pets in an array. But if you wanted to reference a specific pet, you had to use an index. Pet 0 or pet 1, or pet 2, instead of "Fred", "Fido" or "Big Boy". This isn't usually how a user might want to refer to their pets.
For example, when we wanted to feed our pets the code looked like:
// feed the pets myPets.feed("Cat", 3); myPets.feed("Dog", 3); myPets.feed("Rabbit", 1); myPets.feed("Cat", 1);
It's really hard to see from this code which pet is being fed in each line. One very important factor of writing code is making it readable, so someone can understand what the code is trying to do.
Instead of storing the pets in a list, we can store them in an associative array:
Pet[string] myPets = [ "Fred" : Pet("Fred", "Cat", 5, 3, 10), "Fido" : Pet("Fido", "Dog", 4, 6, 5), "Big Boy" : Pet("Big Boy", "Frog", 1, 2, 2) ];
Now our pets are stored by their name, instead of by some unrelated index. The code to feed them now looks much easier to read:
// feed the pets myPets["Fred"].feed("Cat", 3); myPets["Fido"].feed("Dog", 3); myPets["Big Boy"].feed("Rabbit", 1); myPets["Fred"].feed("Cat", 1);
Just one minor tweak in how we store data can make all the difference on how easy it is to write and read.
Arrays are good for storing large quantities or varying quantities of data. But if you want to store data together of different types, you need to use a struct. That is, you can't have an array where one item is an
int and another item is a
string. They all have to be of the same type. A struct can group data of different types together.
One important drawback is that you cannot add or remove fields from a struct while the program is running. You would have to recompile to do that. A struct may only store a finite amount of fields.
However, using arrays of structs, you can actually store any kind of data of any quantity together. An array is just another type, so it can be a field of a struct (as demonstrated by the
GameState struct in our snake game).
As discussed in the class, a struct also allows you to write methods that deal with all the data together as one cohesive piece.
So for homework this time, we are just going to answer some questions. There will be no coding for the homework this time!
Answer the following questions, and as you answer them, the correct answer will be shown. I'm not keeping track of this, so it's ok if you don't get them right. Make sure you read the above discussion to help guide your answer.
You have a game where enemies are going to be added as you enter new areas and removed as you defeat them. An enemy can be targeted using its name. Each enemy has health and coordinates for tracking its position.
1. How could you store all the enemies so it's easy to find them by name?
string, since we can store enemies according to their name, and adding and removing enemies is easy using an associative array. An array can be added to, but finding the right enemy based on its name would mean looping through the entire thing. A struct cannot be added to or removed from once compiled, so it would not be a good choice.
2. How should you store the enemy health and coordinates?
health. An array would be mostly meaningless because the order would have to match the enemy list.
In your game, when you shoot at an enemy or an enemy shoots at you, a bullet needs to be animated on the screen. It's not important the order that the bullets were added. Each bullet needs a color, direction, and who it can damage.
3. What should you use to store the properties of a bullet such as its color and direction?
bullet.coloris much easier than using an associative array like
bullet["color"]. Plus, in D, assocative arrays can only have one value type. A
Colortype is different than a
Vector2type. Storing as an array has the same problem.
4. How should you store the list of bullets?
©2021 Steven Schveighoffer