Designing a Pool Simulation

For our first big adventure in building a significant graphics program, one that features animation and showcases object oriented programming in a fairly realistic way, we will build an interesting pool simulation. This one will feature many pool balls bouncing into each other

Warning

This problem will force some of you into math anxiety mode. There is a bit of heavy math involved. but I will try to keep it as simple as possible. No, you will not see any of it on a test!`

Where do we start?

In thinking about a simulation (I prefer simulation to game), the first thing to do is identify the objects that we need to model. Hey, this is easy, we need to model the pool ball and the table! Anything else? Possibly, but this will let us get started.

Pool balls

Obviously, we need a set of pool balls to do much of a simulation. Exactly how many depends on the game we want to model. So, we will start off by building a basic class for a single pool ball:

include/Poolball.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#pragma once

class Poolball {
    private:
        int x;
        int y;
        float vx;
        float vy;
        int radius;
    public:
        // constructors
        Poolball();
        Poolball( int x, int y, float vx, float vy );

        // accessors
        int getRadius( void );

        // mutators
        void setX( int x );
        void setY( int y );
        void setVx( float vx );
        void setVy( float vy );
        void move ( void );
};

Let’s think about this for a moment. The idea here is to collect all the attributes about a single pool ball into this template we call a class. Do we have everything we need to accurately model a single pool ball in this design?. It is pretty good for a start, but not really good enough for a world class simulation. If you know pool really well, you might be thinking about things like putting “English” on the ball and making it curve around other balls to hit something. There is no way we can model that kind of behavior with just a simple position and velocity data item for each ball. We would need a lot more data to figure out that kind of motion. For our simple simulation, this will probably do well enough, though.

We have made all of the attributes private, meaning no outside entity can modify them directly. Instead, the only way those attributes can be modified is through methods we do make available to the public. This is a good design decision. Why might not be obvious at first, but as you get more experience with this kind of programming, you discover that it frees you to change the entire design without ever changing how the public deals with your pool balls. If your methods do not change, every other aspect of your design can change freely. That is a powerful design feature for enhancing your design in the future!

Notice that we have two constructors as well. There is a generic one that does not take any parameters, and one that can initialize most of the attributes. What surprises many beginning programmers is the fact that we have two functions with the same name. However, they have two different signatures, and C++ thinks this is just fine. The signature is the number and type of parameters that the function takes. As long as they are different, the compiler can distinguish between two different calls and make sure to call the right version, so there is no problem. This is quite a useful feature, that lets us build very useful programs as we will see as we build more general classes later on.

The Pool Table

We also need to model the pool table, but we probably only need one of these. This one is much simpler:

include/Pooltable.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#pragma once

class Pooltable {
    private:
        int width;
        int height;
        int screenX;
        int screenY;
    public:
        // constructor
        Pooltable( int x, int y, int w, int h );

        // accessors
        int getWidth( void );
        int getHeight( void );
        int getX( void );
        int getY( void );
};

This class is much simpler, and less obvious. We are not so clear on what this class will do now. Obviously we need to think through how the simulation will run and what will happen as the balls move around.

Initial simulation thnking

We might as well do some of that thinking now. The idea behind this simulation is to model balls moving around on the table. In a real game, a pool cue hits one of the balls, sending things into motion. Obviously, we could model the cue, or we could simple assign an initial velocity to one of the balls as a way to start things moving. Once we do so, the question remains: what happens next?

Modeling time

In any simulation involving motion, we model the passing of time using some kind of loop. Each pass through the loop means some amount of time has passed. Since we are recording the velocity of each ball in the x and y direction, we can calculate how far each ball moved in the x and y direction n that amount of time with a bit of simple math. We can use that math to update the position of each ball. Since we are building a graphics program, we can redraw the screen showing where all the balls are after this amount of time. Simple.

Except for one thing!

Bouncing!

Balls bounce off of things! They bounce off of walls, and they bounce off of each other! What a mess. How are we going to deal with all of this?

One way to answer this question is to ask more questions!

What does a single ball know

If you look at the data a single ball owns, it should be obvious that a single ball knows where it is at any moment in time, and how fast it is moving. It does not have any obvious idea about where it is on the table so it is not really aware of where the edges of the table are. So the ball itself should not be expected to handle bouncing. That responsibility probably belongs elsewhere.

What does the table know

The table has a better view of things. We can let the table control the placement of the balls, and let the table watch them as they move. After each “step” of out simulation, we can let the table check for collisions between the balls and the edges of the table, and between the balls and each other. This starts to makes more sense. We need to alter our table design a bit to deal with this. Here is a slightly modified design that can do this:

include/Pooltbale.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#pragma once

#include "Poolball.h"
const int MAXBALLS = 5;

class Pooltable {
    private:
        int width;
        int height;
        int screenX;
        int screenY;
        Poolball balls[MAXBALLS];
        void checkCollisions( int i, int j );
    public:
        // constructor
        Pooltable( int x, int y, int w, int h, int argc, char *argv[] );

        // accessors
        int getWidth( void );
        int getHeight( void );
        int getX( void );
        int getY( void );

        // mutators
        void placeBalls( void );
        void run( void );
};


We have a few more methods in the design now. and have added an array of pool balls for the table to manage. The private checkCollision method will be used to figure out if we have had any collisions between balls after a single time step. The public placeBall method` will be used to position balls on the table to start a simulation, and the run method will be used to start the actual simulation.

This is enough to begin thinking about how we might code things. We have two different kinds of programming to do to build this application. Part of it deals with the physics of the balls moving around, and part of it deals with making the graphics look nice on the computer screen. Hopefully, we can get both parts running without a lot of effort.

Your job for now will be to get your computer set up to do the graphics work.