11. Rain Catcher Game¶
In Shiffman’s book, he creates a very simple game based on a simple ball-like objects and a few other simple objects like a timer object and a catcher object. He uses arrays of objects so that we can use a loop to update objects for each iteration of the draw loop.
- In Chapter 10, Shiffman introduces the idea of using algorithmic thinking to figure out
- how to design our program. So one idea is to first try to figure out the components of any program that we’re trying to create. Let’s follow Shiffman’s project where he designs the RainCatcher game. Below he specifies the details of the game.
11.1. Rain Game Code¶
The object of this game is to catch raindrops before they hit the ground. Every so often (depending on the level of difficulty), a new drop falls from the top of the screen at a random horizontal location with a random vertical speed. The player must catch the raindrops with the mouse with the goal of not letting any raindrops reach the bottom of the screen
He breaks down the game program design into 4 steps:
- Develop code for a circle object that is controlled by the mouse. This is the ‘rain catcher’
- Write a program to see if the 2 circles intersect
- Write a timer program that executes a function every N seconds
- Write a program with circles falling from the top of the screen.
11.2. Psudocode¶
Psudocode is a way to write the main concepts for the program
- Setup:
- Initialize catcher object
- Draw:
- Erase Background Set catcher location to mouse location Display catcher
11.3. Catcher Class Code¶
Here is code for the Catcher class:
class Catcher{
float r; //radius
PVector position;
Catcher(){
this(15);
}
Catcher(float _r){
r=_r;
position =new PVector(width/2, height/2);
}
void setposition(float _x, float _y){
position.x=_x;
position.y=_y;
}
void display(){
stroke(0);
fill(175);
ellipse(position.x,position.y,r*2, r*2);
}
}
It’s critical to note that in the constructor Catcher(float r)
, we are initializing the PVector object.
This is an important function of a constructor: to create any objects that are instance variables of the
class. We can’t use any of these objects until they’ve been initialized.
11.4. Ball Class Code¶
Here is the code for the Ball class Note that we’re using PVector for speed and location:
class Ball{
// instance variables
color currentColor; //current color of the ball
color ballColor; //store color to reset after highlighting
color highlightColor; //highlight color of the ball
PVector position;
PVector speed;
float diameter;
//Constructor
Ball(){ //default constructor
this(color(255,0,0), width/2, height/2, 5, 3, 5 ); //call the constructor with initialization values
}
// constructor with initialization arguments
Ball(color _c, float _x,float _y, float _d, float _xspeed, float _yspeed){
currentColor=_c;
ballColor=currentColor;
highlightColor=color(255,255,0,40);
position=new PVector(_x,_y);
speed=new PVector(_xspeed,_yspeed);
diameter=_d;
}
// class methods
// this method is responsible for creating the displayed ball object
void display(){
fill(currentColor); //this may be highlighted or ballColor
ellipse(position.x,position.y,diameter,diameter);
currentColor=ballColor; //reset ballColor back to original color
}
//this method is responsible for determining movement of the ball using the PVector function ``add()``
void move(){
position.add(speed);
if(position.x > (width-diameter/2) || position.x < (0+diameter/2)){
speed.x *= -1;
}
if(position.y > (height-diameter/2) || position.y <(0+diameter/2)){
speed.y *=-1;
}
}
//comparison method: do comparison and return true or false
boolean isIntersecting(Ball otherBall){
float distance= PVector.dist(this.position, otherBall.position); //PVector distance between 2 points
if( distance <= (this.diameter / 2) + (otherBall.diameter / 2)){
return true;
}
return false;
}
void highlight(){
this.currentColor = this.highlightColor;
}
} //end of Ball class
This is the end of the code for the Ball class. This class has 4 different methods. Each of these methods does a simple task. It is best to have your object methods designed to perform one well defined task. If we have a more complex task, we can break that down into simpler methods we can also call methods from within other methods if it makes our code easier to understand.
11.5. Timer Class Code¶
Here is the code for the timer class. It uses the processing function millis()
which counts milliseconds since the sketch started. Shiffman uses
the timer to generate an event to create a new Drop that can fall from the top of the canvas.
class Timer{
int startedTime;
int totalTime;
//constructors
Timer(int _totalTime){ //constructor
totalTime=_totalTime;
}
//methods
void start(){
startedTime=millis(); //set the start time to the current millis value
}
boolean isFinished(){ //this timer determines if the timer has completed the timed interval
int passedTime=millis()-startedTime;
if(passedTime>totalTime){
println("timer finished");
return true;
}
else{
return false;
}
}
} //end of Timer class
11.6. Object Inheritance¶
Here, we are going to use Object Inheritance is the code for the Drop class, it is a child class of the Ball class and it inherits the instance variables
and methods from the Ball class. we use the super
keyword to refer to methods in the parent Ball class:
class Drop extends Ball{
boolean isActive; //this is instance variable for drop class
color dropColor;
Drop(){
this(random(width), -10);
}
Drop(float _x, float _y){
// call the Ball constructor
super();
this.position.x=_x;
this.position.y=_y;
this.diameter=5;
this.speed.x=0;
this.speed.y=3;
dropColor=color(0,50,255,100);
this.ballColor=dropColor;
isActive=true;
}
void move(){
if(isActive){
position.add(speed); //we've set x speed to 0;
if(position.y>=height+10){
isActive=false;
}
}
}
void display(){
super.display();
}
}
In the above code, we have created a class that’s a child class of the Ball class. We have
used the keyword super
within the constructor so that we’re calling the constructor for the
Ball
class. We have used the extends
keyword in the first line of the class declaration
to show that this class is a child class of the Ball
class. Any Drop object has access to the
methods and instance parameters of the Ball
class. Since the Drop
class has it’s own
move()
method, then when a Drop object calls the move()
method, it is this version that
will be executed.
11.7. The Main Program¶
Here is a start of a main program where we are testing each of our classes. It’s important to
keep straight the fact that we’re declaring our classes in separate tabs, but all of the code
to execute the program is all contained in the first processing tab. In that tab, we have our
processing setup function and the draw function. As we’ve done before, we declare any global variables
above and outside of the setup()
and draw()
functions. These are object variables so
we use the Class name, then the name of the object instance to declare the global object
class name:
Catcher
object instance name:
myCatcher
Here’s the code for executing the beginning of our game:
//rain catcher game: main file
Catcher myCatcher; //declare a Catcher object named myCatcher
Ball ball1;
Timer timer1;
Drop drop1;
void setup(){
size(300,300);
myCatcher=new Catcher(); // initialize using the Catcher default constructor
timer1=new Timer(2000); // initialize a Timer object
timer1.start(); //call the start( ) method
ball1=new Ball(color(0,255,100),15,25,20,3,8);
smooth();
drop1=new Drop(14,5); //initialize drop1 using the Drop constructor
}
void draw(){
background(255);
myCatcher.setposition(mouseX, mouseY); //
myCatcher.display();
drop1.move();
drop1.display();
ball1.move();
ball1.display();
if(timer1.isFinished()){
timer1.start(); //reset the timer when it is finished
}
}
In the code above, the first thing we determine is the location of the catcher object based on the user’s mouse position. Then we display the myCatcher object. Similarly, with the drop1 and ball1 objects, first we move the objects, then we display the objects.
So far, we have several objects moving on the screen, but we need to re-factor this code in order to make some type of a game. We’ll want to have lots of drop objects moving on the canvas. Also, let’s make use a paddle object instead of Shiffman’s catcher object. The paddle object will be controlled by keyboard movement, then collisions will be determined based on whether a falling drop object intersects with the paddle. We’ll cover this in the next section.
11.8. Test Driven Development¶
Below is an example of the program, here we’re just testing the code for each object that we’ve created. It’s a good idea to create your code in an incremental manner, so that you can discover errors early on. For each section of code that you create, identify some way that you can test whether your code is functioning correctly before moving on to create new code. This is the idea behind test-driven development (TDD), where you would create some series of tests for each section of code, to insure it’s working correctly, and the code for the tests is actually written before the code that you will be testing.
11.9. Questions:¶
- How can we test whether the method
isIntersecting( )
works correctly?- How can we test whether the timer object is working correctly?