Exercise 18.1: Time the flow in Dambuilder
Let's go back to the original inspiration for Dambuilder. It has this name in memory of the childhood activity of making dams in little streams. The goal in building dams like this is to make the water move through the system as slowly as possible, but without the water getting stuck in any one place (and overflowing one of the dams).
You can use the cursor tools to make new walls and move them. Click with the Equals cursor to copy, prick with the Pin cursor to delete, drag with the Hand cursor to move. This is how you 'play the game'.
How do you tell how well you're doing in Dambuilder? We need to have a score that calculates the average time that it takes a critter to fall from the top to the bottom of the screen. We reset the critters' ages when they wrap bottom to top.
And we need some kind of penalty if a critter gets stuck for too long. Perhaps if a critter's age gets larger than some MUSTBESTUCK time value, then the critter explodes and destroys everything near it, including the dam that it's stuck behind. So your goal is to get the critters moving along as slowly as possible just short of being stuck and destroying the dams.
Exercise 18.2: Rotate the walls
Add a tool that lets you rotate the walls of the dams. The cCritterWall might need some special override of the turn code for this. An easy way to call this method would be to give the cCritterWall a scooter -style listener that turns it provided that the wall is currently the pfocus() of its owner.
If you feel more ambitious you could make a new cursor tool that uses the left click to rotate left and the right click to rotate right.
Exercise 18.3: Make a maze game
If you remove the gravity forces from the critters in Dambuilder and start with a cGraphicsOpenGL preference and set the critter viewer to ride the player, you get a fairly effective-looking first-person shooter game. But rather than changing Dambuilder, you might simply add walls to the GameStub game, which is already set up with enemies and health- packs . Note that this exercise is basically similar to Exercises 14.5 and 14.7 combined.
Exercise 18.4: Make a Pinball game
The hardest thing about pinball is making flippers. You would probably have cCritterFlipper be a child of cCritterWall . See if you can make a cCritterFlipper and put it into the Dambuilder game. You will also need to write a listener that uses the Left Arrow and Right Arrow to rotate the flipper around one of its ends.
Exercise 18.5: Make a Pachinko game
First get Pinball working, then find out what the popular Japanese arcade game Pachinko looks like and emulate that. Quite briefly , Pachinko is like a pinball game in which you have dozens of balls active at once. But there's more to it than that. Like pinball, many Pachinko machines are quite beautiful.
Exercise 18.6: PacMan
We all have a pretty clear idea of what PacMan looks like. Let your player be a cCritterPacman with a standard cListenerArrow . You can use cCritterWall objects.
Students have indeed written this game using the Pop Framework, and one issue is that in PacMan we have 100 or so cCrittterPowerpellet objects which are little yellow dots lining the maze paths. If you aren't careful, having so many critters can drastically slow down your program's execution speed. Make sure to set the _fixedflag to TRUE for these critters, and make sure that the various collision-controlling parameters are set so that the game's cCollider will ignore all Powerpellet collisions except those featuring a cCritterPowerpellet and a cCritterPacman . And in these collisions, have the cCritterPowerpellet get eaten: calling delete_me and adding some score to the cCritterPacman .
Another thing to keep in mind if you're worried about speed is that the Release build will run very noticeably faster than the Debug build. If the speed were still to be unacceptable, you could use a different approach for eating power pellets. Rather than having the PacMan check its distance from each and every power pellet at every update, you could use a sniff method and make the power pellets a distinctive color .
The biggest difficulty students find in making PacMan style games is in having the player's enemies be good at chasing the player or, if they are to be victims, be good at running away from the player. We discuss this in the next exercise.
Exercise 18.7: Smarter enemies for maze games
Suppose you write an adventure game in which the enemy critters use a cForceObjectSeek to run towards the player. Suppose also that your game has cCritterWall walls in it “ think of something like a PacMan game in which the ghosts chase the player (or, if the player is powered -up, run away from the player).
If you use a simple cForceObjectSeek , the enemies will often get stuck pushing against a wall they can't get through. The easiest way out is simply to provide more enemies and expect that some of them will manage to be a threat.
But really you'd want to create a 'smarter' kind of seeking force. Let's discuss three increasingly sophisticated ways in which we can try and improve the situation.
Exercise 18.8: A labyrinth game
There's a popular wooden maze game in which you move a ball by manipulating two knobs on the sides of the box. The knobs tilt the top surface of the game east/west or north/south. On the board is a ball- bearing , some little walls, and about 50 holes. There's a path drawn on the top, and your goal is to manipulate the knobs so that the ball rolls along the path from beginning to end, missing all 50 of the holes.
To implement this as a cGameRollingMaze , we'll have a cCritterRollingball as our player. We can use cCritterWall objects for the pieces of the maze. We should have some cCritterHole objects for the balls to fall into: make them fixed critters with perhaps a black cBubble for their sprite, and override their collide method so that when they collide with a pcritter they (a) add an acceleration to pcritter which points towards their center if the pcritter isn't fully inside (the way a ball speeds up towards a hole when it's partly over one edge) and (b) the call pcritter->delete_me() if the pcritter is inside. Well, actually doing a delete_me on a game's _pplayer has no effect (because all hell breaks loose if you have NULL player, and cBiota has a 'foolproofing' feature of ignoring delete requests on active players). But we'd like to keep the delete_me for the cCritterHole as we might sometime want to reuse the holes. What we really want to happen when the player falls in the hole is that the player goes back to the starting position. So you should override your cCritterRollingball::delete_me to call an overridden reset() which will indeed put the ball at the starting gate.
Whenever you have a lot of critters, you want to be careful not to try and compute unnecessary collisions as this will make the program run too slow. Regarding the many cCritterHole objects, the only kind of collisions we're interested in is between a hole and the player. And since we've overridden the cCritterHole::collide method for the special behavior, we want to call these collisions in the form phole->collide(pplayer) . So set the relevant _collidepriority or collidesWith methods accordingly .
How about the knobs? Probably you can use a cListenerTipper listener that increments or decrements the acceleration in the x and y directions with the Left/Right Up/Down keys. You will want to put a good amount of friction on the ball's motions and/or give it a low _maxspeed so it doesn't get out of control.
How best to add all the cCritterHole and cCritterWall objects to the world? We might consider something like the cMaze object described in Exercise 18.7.b. Call it a cRollerBoardlayout . But, on second thought, maybe our class cRollerBoardlayout needn't hold critters as in 18.7b. Maybe it should just have geometrical information. It could store the coordinate information for the various walls and holes, and have a constructor like, perhaps, cRollerBoardlayout(int wallcount, Real wallthickness, Real wallenda, Real wal lendb, int holecount, Real holeradius, Real holecenter) . And it could have an all-important putBoardInGame(cGame *pgame) method to create and add the desired cCritterHole and cCritterWall objects to pgame . So then our code could have some easily tweaked Real numbers WALLCOUNT, WALLTHICKNESS, HOLECOUNT, HOLERADIUS and three Real arrays WALLENDA, WALLENDB and HOLECENTER as statics or #define at the top of the cgamerollingmaze.cpp. And the cGameRollingMaze constructor would construct a temporary cRollerBoardlayout layout(WALLCOUNT, WALLTHICKNESS, WALLENDA, WALLENDB, HOLECOUNT, HOLERADIUS, HOLECENTER WALLENDS, HOLES) ¼ and then call layout.putBoardInGame(this).
Exercise 18.9: Slot Car Racer
Use the trail-sniffing technique to make a car-racing game. Have your race course be a bitmap with a track marked in pure white pixels, give your player a cListenerCar listener, and make a robot car to race with you.
You can count on the update with the sniff method to keep the robot on the track, but how do you keep the robot going in the right direction? Assuming your track is roughly a closed curve around the origin you might try giving the robot a reasonably strong cForceVortex to keep it going in generally the right direction.
Rather than using sniffing, you may find it works better to keep your player on the track simply by lining the track by cCritterWall . If you are careful not to use too many walls (use a few long ones rather than a lot of short ones), the speed will be okay.
Whether or not you use use sniffing, you will need a reliable method to make your robot driver opponent do a good job. What works the best is to use a cForceWaypoint as described in Exercise 7.2.
Once you get this working you may want to tweak the listener controls to make the driving experience better. It should be possible to have a world larger than you see on the screen, provided you set the track player option so that the player is always on a visible part of the screen.
Try implementing a two-player mode, and have the other player car have a listener that's controlled by some letter keys, such as VK_S and so on.
Exercise 18.10: Feeding
Make a game in which you can feed the critters. One approach would be to have a player who scatters new cCritterFood when you press, say the left mouse button. Basically the player could be like a cCritterArmed that shoots bullets that don't move. In fact cCritterFood could be a child of cCritterBullet . And maybe in your feeding process you are trying to lure the critters into a pen. Like catching chickens.
Exercise 18.11: Flocking
Make a cCritterBoid class and give its members a cForceClassFlock force which makes the ' boids ' do the following.
Now for a few words on each of these behaviors.