Poolball Refactoring¶

Are you tired of this yet? Well, unfortunately, real design projects go exactly like this one. An initial idea gets you moving, and as you experiment, better design ideas come up. We have a simple simulation pretty much under control, but it is time to turn it into something more impressive. Is our design ready for that?

Unfortunately, no!

Rack Em Up¶

A real pool game begins with the player(s) placing a number of balls in a “rack” that controls the arrangement of the balls. One player then places a “cue” ball on the table and smacks it with a “cue stick” starting the action. Have we modeled all of these objects, and should we?

For our purposes, we will ignore the “cue stick” part, since modeling that would be a challenge! However, we can simulate the “cue” ball by letting it have an initial speed and we can simulate the “rack” by placing a set of balls in some arrangement on the screen, and have all of those balls sit stationary until something happens.

To deal with a collection of poolballs, we need a place to store them. Sounds like a job for an array, but we need to pick a maximum size for the array, since we do not know how to handle an arbitrary number of objects in an array (go take a data structures course to fix this problem).

We will set an upper bound in the Pooltable class for now. An array of Poolball objects will make up our simulation. The ball in the first (0) location will serve as our “cue ball”.

A Simple Rack¶

It seems we need a way to define how many poolballs we need for our simulation, plus one more to serve as a “cue” ball. The logical place to set this up is in the constructor for the pooltable object.

The initial placement of the balls in the rack is something we will need to study later. For our purposes, the layout is not important, so we will just line up a collection of balls across the screen with some spacing between them, and let the “cue” ball start the action. You can experiment with other schemes later.

Test Driving this Version¶

I am going to develop this version of the simulation using TDD, but I am not going to show all of the code. Most of what I constructed uses code from our previous version, so building this should be fairly simple. I want you to build your own versions using the tests I provide!

The testing process follows the TDD pattern. Checking each step requires doing this on the command line:

$make test For this development, I uncommented the line to show all the tests. Improved Project Setup¶ I do not like having my test code in the middle of the project code, so it is common to set up a tests directory for your project, and keep all test-related code there. I have placed this directory below source code directory for this example. I still want to use make to manage this project, so I have modified the setup a bit. The new project Makefile manages building both the program, and running the tests. However, since the tests are now in a separate directory, I set up a second Makefile n the tests directory that controls building just the tests. Then, I have the first Makefile activate make in the tests folder to make things a bit easier on lazy me! Here are the new Makefiles: project/Makefile:  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 30 31 32 33 34 # Makefile - for Test Driven Development of Clock class SOURCES = main.cpp Pooltable.cpp Poolball.cpp OBJECTS =$(SOURCES:.cpp=.o) TARGET = pooltable # this line suppresses a few warning messages CFLAGS = -Wno-c++11-extensions -Wno-gnu-static-float-init # Uncomment the next line for GLUT projects LIBS = -framework GLUT -framework OpenGL -framework cocoa # set tools based on OS type include os_detect.mak # Targets follow below this line ==================== all: $(TARGET)$(EXT) test: $(TARGET)$(EXT) cd tests && $(MAKE) test$(TARGET): $(OBJECTS)$(CPP) $(CFLAGS)$(LIBS) -o $@$^ %.o: %.cpp $(CPP) -c$(CFLAGS) $< -o$@ clean: rm -f $(OBJECTS)$(TARGET)$(EXT) cd tests &&$(MAKE) clean
project/os_detect.mak:

 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 30 31 32 33 34 35 36 37 38 ifeq ($(OS),Windows_NT) OSTYPE = WIN CCFLAGS += -D WIN32 ifeq ($PROCESSOR_ARCHITECTURE), AMD64) CCFLAGS += -D AMD64 endif ifeq ($(PROCESSOR_ARCHITECTURE),x64) CCFLAGS += -D IA32 endif EXT = .exe RM = del CPP = g++.exe else UNAME_S :=$(shell uname -s) ifeq ($(UNAME_S), Linux) OSTYPE = LINUX CCFLAGS += -D LINUX EXT = RM = rm -f CPP = g++ endif ifeq ($(UNAME_S), Darwin) OSTYPE = MAC CCFLAGS += -D OSX EXT = RM = rm -f CPP = g++ CFLAGS += -Wno-deprecated endif UNAME_P = $(shell uname -p) ifeq ($(UNAME_P), x86_64) CCFLAGS += -D AMD64 endif ifneq ($(filter %86,$(UNAME_P)),) CCFLAGS += -D IA32 endif endif
project/tests/Makefile:

 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 30 31 32 33 34 35 36 37 # Makefile for Pooltable simulation tests # uncomment the next line to see all tests TOPS = -s FILES = \ test_pooltable.cpp \ test_poolball.cpp \ test_main.cpp # this line suppresses a few warning messages CFLAGS = -Wno-c++11-extensions -Wno-gnu-static-float-init OBJS = $(FILES:.cpp=.o) TOBJS = ../Poolball.o ../Pooltable.o INCS = -I.. include ../os_detect.mak # targets are below ----------------------------- all: testrunner$(EXT) test: testrunner$(EXT) ./testrunner$(TOPS) testrunner: $(OBJS)$(CPP) -o testrunner$(EXT)$(OBJS) $(TOBJS) clean: rm -f testrunner$(EXT) $(OBJS) # implicit rules are below ---------------------- %.o: %.cpp$(CPP) -c $(CFLAGS)$(INCS) -o $@$<

With this setup, I only use “make test” and “make clean” in all of my development work!

The development steps I used are listed below:

Step 1: Test the Testing Setup¶

First, make sure the testing framework is set up correctly.

 1 2 3 4 5 6 7 8 9 10 // Test Driven Pooltable - test runner main // Created by Roie Black on 10/9/2014 #define CATCH_CONFIG_MAIN #include "catch.hpp" TEST_CASE( "1: Sanity Test", "[sanity]") { CHECK( 1 == 1 ); // check will allow tests to continue if it fails }

Step 2: Start Pooltable Class¶

In this step, we set up a basic, empty, Pooltable class, and make sure we can build an object from it.

 1 2 3 4 5 6 7 8 9 #include "catch.hpp" #include "Pooltable.h" // test 2: check pool table exists Pooltable table; Pooltable * table_ptr = &table; TEST_CASE("2: Test pooltable exists", "[pooltable]") { CHECK(table_ptr != NULL); }

Step 3: Start Poolball Class¶

In this step, we set up a basic, empty, Poolball class, and make sure we can build an object from it.

 1 2 3 4 5 6 7 8 9 #include "catch.hpp" #include "Poolball.h" // test 3: check pool ball exists Poolball ball; Poolball * ball_ptr = &ball; TEST_CASE("3: Test poolball exists", "[poolball]") { CHECK(ball_ptr != NULL); }

Step 4: Add Physical Attributes to Pooltable¶

Using information from the Brunswick web site, we add physical dimensions to our Pooltable class. To test this, we need to set up accessor methods, since these attributes are going to be kept private in the class.

The attributes I will add in this step are these:

• int regWidth - 96 inches
• int regHeight - 48 inches

Note

Even though the poolball dimensions should be telling you to use floating point numbers for this simulation, we will not do that. Since we want to draw things on the screen, using integers will work fine. We will increase the size of the ball so it is not too small to be seen easily on our simulated pool table.

The accessor methods will be these:

• int getRegWidth(void)
• int getRegHeight(void)

Actually testing that these values are set correctly might seem a bit over the top. However, the TDD philosophy says that every bit of code you add needs to be tested. So, we will add these tests using a simple pattern that sets up an accessor for every private variable. In a real project, this might not be practical, but we will do this here.

 1 2 3 4 5 6 7 // test 4: Test physical dimensions int width = table.getRegTableWidth(); int height = table.getRegTableHeight(); TEST_CASE("4: Test pool table dimensions", "[pooltable]") { CHECK(width == 96); CHECK(height == 48); }

Are there any other tests make sense? How about the “aspect ratio”? The width seems to be twice the height, and that should be the same no matter how we might scale this table as we draw it. Here is a test that will check that, using our accessor methods:

 1 2 3 4 // test 5: Test pool table aspect ratio TEST_CASE("5: Test pool table aspect ratio", "[pooltable]") { CHECK(width == 2 * height); }

Step 5: Add Physical Attributes to Poolball¶

Now, we repeat the same step with the Poolball. In this case, we only have one characteristics to deal with: size.

Note

We might also include color in this step, but that is a visual thing, and I am going to put off that design step until later.

Here is the attribute and accessor we will add in this step:

• regBallDiameter - 2 7/16 inches
• getRegBallDiameter
 1 2 3 4 5 // test 5: Test pool ball size int diameter = ball.getRegBallDiameter(); TEST_CASE("5: Test pool ball diameter", "[poolball]") { CHECK(diameter == 10); }

Step 6: Add Behavior to Poolball¶

As we saw earlier, the pool ball obeys Newton’s First Law, and we need to add that behavior to the class. This involves adding two more private attributes, two accessor methods, and a public method that will cause the ball to move. We also need routines to set the ball’s speed Since we have done this before, I will do it all in one step (not wise in general - violated “Baby Step”).

• int Xpos - current X position
• int Ypos - current Y position
• int Xspeed - speed in the X direction
• int Yspeed - speed in the Y direction
• int getXpos(void)
• int getYpos(void)
• void setXpos(int pos)
• void setYpos(int pos)
• int getXspeed(void)
• int getYspeed(void)
• void setXspeed(int speed)
• void setYspeed(int speed)
• void move(void)

That is a lot of code, but each routine is very small, so it is probably safe to add all of this. Just test as you go

Warning

If you make typing mistakes in these additions, the “catch.hpp” file may pitch fits. Be careful in looking at the error messages, and concentrate on your code.

Here is our new test:

 1 2 3 4 5 // test 6: Test pool ball speed TEST_CASE("6: Test pool ball speed", "[poolball]") { CHECK(ball.getXspeed() == 0); CHECK(ball.getYspeed() == 0); }

Next, we need to add the motion method. This one is simple:

To test this, we need to place a ball, move it and make sure it gets to the spot we want.

Here is the movement test

 1 2 3 4 5 6 ball.setXspeed(5); ball.setYspeed(10); ball.move(); CHECK(ball.getXpos() == 15); CHECK(ball.getYpos() == 20); }

The Poolball class is pretty much done. We need to add behavior to the Pooltable, and this is where all the action really is!

Step 7: Add behavior to Pooltable¶

What, exactly, does a pooltable do? As we decided in our last lecture, it is responsible for detecting if any ball on the playing surface needs to have its motion altered. Once case involves a ball hitting the edge of the table, another involves balls hitting each other. We have now added any code to manage a set of balls yet, so perhaps that is a place to start!

If you think about it, we can simulate the pool table without actually seeing it. A lot of science is done this way. We have the basic table dimensions set, and we know what the motion is all about. Can we continue with development without setting up the visual part of all this. Actually, Yes! And doing so let’s us defer worrying about all the graphics for a bit. It also forces you to think about the problem more, and not rely on the visual side of this project.

Any give ball on the table moves along and eventually will hit a table edge. We need a way to test this, and adjust the ball’s speed to make it “bounce”. We have looked at the code to do this before, so all we need to do is add the behavior routines.

We will set up a basic private routine that checks for wall collisions. The single parameter to this method will be a ball object reference, so we can modify the speed of that ball if it hits a wall:

• void check_wall_collision(Poolball &ball)

At this time, we “couple” the Pooltable class and the Poolball class. Up until this point, they were completely separate things, but it makes sense to tie them together, since they are part of a single game. You do want to limit coupling between classes as much as you can, as a good design rule.

If we are going to continue our design without moving to the graphics world, the physical dimensions in our simulation will represent inches. How speed maps into time is something we will figure out later. For now, each “tick” of our clock will move objects by “speed” units in the desired direction. We can use this to test the new code.

One issue we have not addressed is where do the Poolball objects get created, and how we should place them on the table. We could bury that logic in the Pooltable class, but that means the user of our classes cannot play with individual Poolball objects in their code, they have to create them indirectly, buy asking the Pooltable class to do the work for them.

A solution might be to add methods to the Pooltable class that let us add Poolball objects to the table as the user creates them. That lets the user decide how many poolballs to use, not our code.

Here is how we might do that:

There is a subtle design decision here. By passing in a reference to the caller’s Poolball objecr, we seem to want to allow the Pooltable class to modify the caller’s Poolball object attributes. We need to do that if the caller feels obligated to reach in and mess with a ball on the table.

We have seen code that lets this happen for single Poolball objects, but what about a whole bunch of them.

We cannot store the Poolball object reference in an array, since it is not a “real” object, but just a reference to such an object. (It is actually an address, and dealing with this is a topic for a later course).

Inside the Pooltable class, we can set up an array to hold a set of Poolball objects, but these objects are inside the Pooltable object, not back in the user’s world. If they have a Poolball object they want to place on our table, they can give us a copy of that ball, we can use that copy to initialize one of our Poolball objects, and we can restrict our attention to our set of Poolball objects. The user will not see anything change in their versions of the ball.

Unfortunately, that is how we are going to deal with this problem for now. In fact, the user will not be allowed to create a Poolball object at all, we will restrict that to the Pooltable class. We will provide methods the user can use to set up each ball, up to the limit we define in the class.

Note

Our testing routines are free to set up individual Poolball objects so they can test them, but not the user!

Inside the Pooltable class, we will set up an array to hold these objects. We will also track the number of such objects we have added to the table, so we can control the motion of all objects in a loop. We will wrap up all the logic needed to deal with that problem in a table method named move. (Not to be confused with the Poolball move method).

Copying Objects¶

Before we go further with this, we need to examine how C++ makes copies of objects. Remember that these are not the same thing as simple variables you have yused before!

By default, when C++ is asked to create another version of an object, it uses a default “copy constructor” that simple copies the entire object with the current values for all attributes. This happens when you use an assignment operation, for example. It also happens when you place an object in a value parameter position when you call a function.

For our purposes, this default is fine, so we can let the user hand us a Poolball object, we can use it, but you must remember that it is a copy of the original.

We can use this mechanism to let the user set up a Poolball, and hand it to the Pooltable. The problem is that once the Pooltable object starts moving these copies around, they have no connection to the callers objects. We might as well not ask the user to create then at all. Instead, it makes sense for the user to direct us to set up a new ball for them.

We will use an array of Poolball objects, but track how many are active (zero when we start). The user will call this routine

• void addBall(int x, int y, int r) to set up a new ball at a specified location

To simplify things, all Poolball objects set up with this method will be stationary on the table. We will need a way to start the action, so I will add another method that lets the user start off the first ball they create, simulating the “cue ball”

• void setCueBallSpeed(int xs, int ys)

Once a ball is in motion, the collision logic will keep it on the table.