Cloud Breaker (C++ & Allegro)

Bournemouth University Software Programming: Assignment 2

Cloud Breaker was my first ever game I created for my University Assignments at Bournemouth, it taught me the fundamentals of how to draw images to buffers and screens and all the fundamentals for future games. Cloud Breaker was created using the open source Allegro API and C++;

Since Cloud Breaker my skills have been transferred into XNA where my concepts of OOP have greatly improved and become much more integrated into my entire programming experience.

 

About:

Cloud Breaker is a fairly simple game yet hugely addictive. With quirky graphics and funky sounds, in Cloud Breaker you must destroy / break the clouds in the sky to allow the sun to shine!

The aim of the game is to destroy the clouds by launching your bouncing ball at them which you must keep in the sky without touching the ground, otherwise it will damage your precious plants! When a cloud is destroyed it drops rain which can be used to bring plants back.

 

How the idea for the game developed:

When I first started learning the Allegro game programming library I had no idea what kind of game I wanted to create so I just stuck with the tutorials in the book (All in one game programming: Third Edition) which I used mainly to learn the library.

Through the learning process I started to get some idea of the kind of game I wanted to create, it wasn’t until the later tutorials came along which featured a ball simply bouncing around the corners of the screen when I had the head for a ball breaker kind of game. So I decided to create a game in which you destroy boxes in the sky and stop the ball from falling by deflecting it with a platform.

I wanted to get away from the grey and space themed types of this game I had seen before so I decided to go for a colourful green and plant themed game. I eventually decided that clouds would be a great thing to be placed in the air since they occur naturally in the air in the real world but I wanted to give it all a twist.

So I gave the clouds some character by adding faces which change every time they are hit, increasing in unhappiness and eventually shock up until the point when they burst and they release a rain drop which is happy and falls to the floor.

In an interesting way to represent the player’s lives, rather than text I decided to have plants along the bottom.

Game Screenshots:

 

How requirements of the Assignment have been met:

  • The game has to be in graphics mode: The game has been created using a wide variety of Bitmap images and animation.
  • The game must contain a user controllable object: The user controls the leaf which bounces the ball around the screen.
  • The game must contain at least one additional non-user controller object: The clouds, rain drop, flowers and ball are all objects which the user does not directly control.
  • The game must allow user input / control by both keyboard and mouse: Both forms of input are supported. You can control the base leaf with the two arrow keys or by using the mouse and clicking where you want it to go.
  • The game must provide the user with feedback: Noises are made when the ball hits an object including the leaf you control.
  • The game may contain sound: Game music and bouncing sounds are featured.
  • The game must provide the current score: Score is kept and presented at the end of the level if you win. It is also displayed at the top at all times.
  • The game should be single player only: Yes.
  • The game must provide the user with simple instructions: Prompts are given for the user to launch the ball and the rules can be viewed from the level select menu.
  • The game must begin and end in a controlled manner: Menus and dedicated closing and starting buttons are used (ESCAPE and ENTER).
  • The game must implement at least one C++ class: The ball which the game features is implemented using a class, which stores its features and functions directly related to it.

The code:

The code is constructed with 4 main files C++ files. These are:

  • Cloudbreaker.h
  • Levels.cpp
  • BallClass.cpp
  • Main.cpp

“Cloudbreaker.h”: The cloudbreaker.h file is included by all the .cpp files. The header file includes all the definitions in the file, such as #define BLACK makecol(0,0,0) and #define WHITE makecol(255,255,255) and #define WIDTH 480. Along with definitions which are used throughout the program, the cloudsremaining array is declared in here as it is used within the level.cpp and main.cpp file: extern int cloudsremaining[15][4];.

Functions used within the Levels.cpp files are declared in the header file, so that they can be used in the main program. The class for the ball is also declared in the header.

Levels.cpp”: The levels.cpp file consist of 5 functions which relates directly to the 5 levels which the user can play in the game.

Each function returns the number of clouds in the level, which is used throughout the main program and also uses the cloudsremaining variable to set which clouds are where and initialize them.

“BallClass.cpp”: The ballclass.cpp file creates the class for the ball and all the functions that are associated with it.

“Main.cpp”: The main.cpp file is where the main bulk of the game will occur, it contains the large loops which the game and make use of all the other C++ files mentioned above.

 

Code Listings will be found on the next pages:

 

cloudbreaker.h

 

 

#ifndef CLOUDBREAKER_H // Makes sure the code in the header is only run once

#define CLOUDBREAKER_H

 

#include “allegro.h”

#include <stdlib.h>

#include <stdio.h>

 

 

 

// Definitions

#define BLACK makecol(0,0,0)

#define WHITE makecol(255,255,255)

#define WIDTH 480

#define HEIGHT 800

#define GAMEPLAYDISTANCEFROMSCREENWIDTH 18

#define GAMEPLAYDISTANCEFROMSCREENTOP 208

#define GAMEPLAYDISTANCEFROMSCREENBOTTOM 44

#define MODE GFX_AUTODETECT_WINDOWED

#define BASEYPOSITION 680

// Definitons

 

extern int cloudsremaining[15][4]; // CLOUD NUMBER, CLOUD FACE, CLOUD X POSITION, CLOUD Y POSITION

//function prototypes

int loadpresetslevel_1();

int loadpresetslevel_2();

int loadpresetslevel_3();

int loadpresetslevel_4();

int loadpresetslevel_5();

class ballclass;

//function prototypes

 

 

//class

class ballclass

{

public:

int x,y;

int width,height;

int xspeed,yspeed;

int xdelay,ydelay;

int xcount,ycount;

int curframe,maxframe,animdir;

int framecount,framedelay;

bool balllaunched;

BITMAP *ballimage;

 

//Constructor and Descructor

ballclass(BITMAP *theballimg);

~ballclass();

void reset();

void updateposition();

bool ballmissed(int basexpos,int baselength, bool isballbelow);

};

//class

 

#endif

 

 

levels.cpp

 

 

#include “cloudbreaker.h”

 

 

int loadpresetslevel_1()

{

//for level 1

cloudsremaining[0][0] = 1;

cloudsremaining[1][0] = 1;

cloudsremaining[2][0] = 1;

cloudsremaining[0][1] = 0;

cloudsremaining[1][1] = 0;

cloudsremaining[2][1] = 0;

cloudsremaining[0][2] = 72 + GAMEPLAYDISTANCEFROMSCREENWIDTH;

cloudsremaining[0][3]= 90 + GAMEPLAYDISTANCEFROMSCREENTOP;

cloudsremaining[1][2] = 172 + GAMEPLAYDISTANCEFROMSCREENWIDTH;

cloudsremaining[1][3]= 90 + GAMEPLAYDISTANCEFROMSCREENTOP;

cloudsremaining[2][2] = 272 + GAMEPLAYDISTANCEFROMSCREENWIDTH;

cloudsremaining[2][3]= 90 + GAMEPLAYDISTANCEFROMSCREENTOP;

//for level 1

 

 

return 3; //Return number of clouds in the level

}

 

int loadpresetslevel_2(){

 

//for level 2

 

//Set ALIVE and reset faces

cloudsremaining[0][0] = 1;

cloudsremaining[1][0] = 1;

cloudsremaining[2][0] = 1;

cloudsremaining[3][0] = 1;

cloudsremaining[4][0] = 1;

cloudsremaining[0][1] = 0;

cloudsremaining[1][1] = 0;

cloudsremaining[2][1] = 0;

cloudsremaining[3][1] = 0;

cloudsremaining[4][1] = 0;

//Set ALIVE and reset faces

 

//Positions

cloudsremaining[0][2] = GAMEPLAYDISTANCEFROMSCREENWIDTH;

cloudsremaining[0][3]= 90 + GAMEPLAYDISTANCEFROMSCREENTOP;

cloudsremaining[1][2] = 172 + GAMEPLAYDISTANCEFROMSCREENWIDTH;

cloudsremaining[1][3]= 90 + GAMEPLAYDISTANCEFROMSCREENTOP;

cloudsremaining[2][2] = (480 – GAMEPLAYDISTANCEFROMSCREENWIDTH) – 100;

cloudsremaining[2][3]= 90 + GAMEPLAYDISTANCEFROMSCREENTOP;

cloudsremaining[3][2] = 172 + GAMEPLAYDISTANCEFROMSCREENWIDTH;

cloudsremaining[3][3]= 90 + GAMEPLAYDISTANCEFROMSCREENTOP – 50;

cloudsremaining[4][2] = 172 + GAMEPLAYDISTANCEFROMSCREENWIDTH;

cloudsremaining[4][3]= 90 + GAMEPLAYDISTANCEFROMSCREENTOP + 50;

//Positions

 

//for level 2

 

return 5;//Return number of clouds in the level

}

 

 

 

 

 

 

int loadpresetslevel_3(){

 

//for level 3

//Set ALIVE and Face

cloudsremaining[0][0] = 1;

cloudsremaining[1][0] = 1;

cloudsremaining[2][0] = 1;

cloudsremaining[3][0] = 1;

cloudsremaining[4][0] = 1;

cloudsremaining[5][0] = 1;

 

cloudsremaining[0][1] = 0;

cloudsremaining[1][1] = 0;

cloudsremaining[2][1] = 0;

cloudsremaining[3][1] = 0;

cloudsremaining[4][1] = 0;

cloudsremaining[5][1] = 0;

//Positions

cloudsremaining[0][2] = 19;

cloudsremaining[0][3]= 208;

 

cloudsremaining[1][2] = 363;

cloudsremaining[1][3]= 208;

 

cloudsremaining[2][2] = 19;

cloudsremaining[2][3]= 310;

 

cloudsremaining[3][2] = 363;

cloudsremaining[3][3]= 310;

 

cloudsremaining[4][2] = 19;

cloudsremaining[4][3]= 408;

 

cloudsremaining[5][2] = 363;

cloudsremaining[5][3]= 408;

//Positions

//for level 3

 

//Return number of clouds in the level

return 6;

}

int loadpresetslevel_4(){

 

//for level 3

//Set ALIVE and Face

cloudsremaining[0][0] = 1;

cloudsremaining[1][0] = 1;

cloudsremaining[2][0] = 1;

cloudsremaining[3][0] = 1;

cloudsremaining[4][0] = 1;

cloudsremaining[5][0] = 1;

cloudsremaining[6][0] = 1;

cloudsremaining[7][0] = 1;

cloudsremaining[8][0] = 1;

 

cloudsremaining[0][1] = 0;

cloudsremaining[1][1] = 0;

cloudsremaining[2][1] = 0;

cloudsremaining[3][1] = 0;

cloudsremaining[4][1] = 0;

cloudsremaining[5][1] = 0;

cloudsremaining[6][1] = 0;

cloudsremaining[7][1] = 0;

cloudsremaining[8][1] = 0;

//187

//Positions

cloudsremaining[0][2] = 19;

cloudsremaining[0][3]= 208;

 

cloudsremaining[1][2] = 363;

cloudsremaining[1][3]= 208;

 

cloudsremaining[2][2] = 19;

cloudsremaining[2][3]= 310;

 

cloudsremaining[3][2] = 363;

cloudsremaining[3][3]= 310;

 

cloudsremaining[4][2] = 19;

cloudsremaining[4][3]= 408;

 

cloudsremaining[5][2] = 363;

cloudsremaining[5][3]= 408;

 

cloudsremaining[6][2] = 187;

cloudsremaining[6][3]= 208;

 

cloudsremaining[7][2] = 187;

cloudsremaining[7][3]= 310;

 

cloudsremaining[8][2] = 187;

cloudsremaining[8][3]= 408;

//for level 3

 

//Return number of clouds in the level

return 9;

}

int loadpresetslevel_5(){

 

//for level 3

//Set ALIVE and Face

cloudsremaining[0][0] = 1;

cloudsremaining[1][0] = 1;

cloudsremaining[2][0] = 1;

cloudsremaining[3][0] = 1;

cloudsremaining[4][0] = 1;

cloudsremaining[5][0] = 1;

cloudsremaining[6][0] = 1;

cloudsremaining[7][0] = 1;

cloudsremaining[8][0] = 1;

cloudsremaining[9][0] = 1;

cloudsremaining[10][0] = 1;

cloudsremaining[11][0] = 1;

cloudsremaining[12][0] = 1;

cloudsremaining[13][0] = 1;

cloudsremaining[14][0] = 1;

cloudsremaining[8][0] = 1;

cloudsremaining[0][1] = 0;

cloudsremaining[1][1] = 0;

cloudsremaining[2][1] = 0;

cloudsremaining[3][1] = 0;

cloudsremaining[4][1] = 0;

cloudsremaining[5][1] = 0;

cloudsremaining[6][1] = 0;

cloudsremaining[7][1] = 0;

cloudsremaining[8][1] = 0;

cloudsremaining[9][1] = 0;

cloudsremaining[10][1] = 0;

cloudsremaining[11][1] = 0;

cloudsremaining[12][1] = 0;

cloudsremaining[13][1] = 0;

cloudsremaining[14][1] = 0;

//left

cloudsremaining[0][2] = 19;

cloudsremaining[0][3]= 208;

cloudsremaining[1][2] = 19;

cloudsremaining[1][3]= 258;

cloudsremaining[2][2] = 19;

cloudsremaining[2][3]= 308;

cloudsremaining[3][2] = 19;

cloudsremaining[3][3]= 358;

cloudsremaining[4][2] = 19;

cloudsremaining[4][3]= 408;

cloudsremaining[5][2] = 119;

cloudsremaining[5][3]= 408;

//left

//right

cloudsremaining[6][2] = 363;

cloudsremaining[6][3]= 208;

cloudsremaining[7][2] = 363;

cloudsremaining[7][3]= 258;

cloudsremaining[8][2] = 363;

cloudsremaining[8][3]= 308;

cloudsremaining[9][2] = 363;

cloudsremaining[9][3]= 358;

cloudsremaining[10][2] = 363;

cloudsremaining[10][3]= 408;

cloudsremaining[11][2] = 263;

cloudsremaining[11][3]= 408;

//right

 

//middle clouds

cloudsremaining[12][2] = 190;

cloudsremaining[12][3]= 208;

 

cloudsremaining[13][2] = 190;

cloudsremaining[13][3]= 258;

 

cloudsremaining[14][2] = 190;

cloudsremaining[14][3]= 308;

//middle clouds

 

 

//Return number of clouds in the level

return 15;

}

 

 

BallClass.cpp

 

 

#include “cloudbreaker.h”

//ballcalss

 

 

ballclass::ballclass(BITMAP *theballimg)

{

ballimage = theballimg;

x = SCREEN_W / 2 + ballimage->w;

y = BASEYPOSITION – ballimage->h;

width = ballimage->w;

height = ballimage->h;

xdelay = 1;

ydelay = 1;

xcount = 0;

ycount = 0;

 

 

if (rand() % 2 == 0) // chooses a random x speed for the ball, whether it goes left or right

{

xspeed = rand() % 2 + 1;

}

else

{

xspeed = rand() % 1 -2;

}

 

yspeed = rand() % 2 – 3;

curframe = rand() % 64;

maxframe = 63;

framecount = 0;

framedelay = rand() % 5 + 1;

balllaunched = false;

}

 

ballclass::~ballclass()

{

//Destructor

}

void ballclass::reset() // resets all variables in the class to their standard values

{

x = SCREEN_W / 2 + ballimage->w;

y = BASEYPOSITION – ballimage->h;

width = ballimage->w;

height = ballimage->h;

xdelay = 1;

ydelay = 1;

xcount = 0;

ycount = 0;

 

if (rand() % 2 == 0)

{

xspeed = rand() % 2 + 1;

}

else

{

xspeed = rand() % 1 -2;

}

 

yspeed = rand() % 2 – 3;

curframe = rand() % 64;

maxframe = 63;

framecount = 0;

framedelay = rand() % 5 + 1;

balllaunched = false;

}

 

void ballclass::updateposition()

 

{

//update x position

if (++xcount > xdelay)

{

xcount = 0;

x += xspeed;

}

 

//update y position

if (++ycount > ydelay)

{

ycount = 0;

y += yspeed;

}

 

 

}

 

bool ballclass::ballmissed(int basexpos,int baselength,bool isballbelow)

{

if (y > BASEYPOSITION) // IF YOU HAVE MISSED THE BALL

{

isballbelow = true;

if(y + height > (SCREEN_H – GAMEPLAYDISTANCEFROMSCREENBOTTOM))

{

isballbelow = true;

x = (basexpos + (baselength / 2) – (width / 2));

y = BASEYPOSITION – height;

xspeed = 3;

yspeed = -2;

return true;

}

}

return false;

 

}

//ballclass

Main.cpp

 

 

As the Main.cpp file is the largest one in the game I have only included the functions that are included within the main program. To view all code in Main.cpp check the DVD.

//function prototypes

int main(void);

void init();

void erasesprite(BITMAP *dest);

void bouncesprite(ballclass &spr);

int collidedcheck(ballclass &ball, int cloudsleft[][4],int numberofclouds);

void drawcloudslevel(BITMAP *dest, int cloudsleft[][4],int noofclouds);

void resetcloudarray(int cloudsleft[][4]);

void restingcallback(void);

void mousecontrols (bool currentgameover, int &basexpos, BITMAP *base);

void updatebasepos(int &basexpos,bool currentgameover, BITMAP *base);

void basehitcheck(int basexpos, ballclass &ball, int baselength);

bool ballmissed(ballclass &ball,int basexpos,int baselength);

void drawflowers(BITMAP *dest);

void Drawmainscreen(BITMAP *dest, ballclass &ball, int basexpos, BITMAP *ballimage, int currentlevelselection);

void gameovercheckanddrawrain(bool &currentgameover, BITMAP *dest);

void levelselectionloop(BITMAP *buffer1, BITMAP *arrow,BITMAP *levelbackground,BITMAP *rules);

void drawsunrotate(BITMAP *sunface, BITMAP *sunflare, BITMAP *dest, float &angle);

void updateframerate();

void waitforusertolaunch(ballclass &theball,int &basexpos,BITMAP *buffer1, BITMAP *theballimg,bool &currentgameover,int &temploop);

void WINorLOSE(bool &currentgameover, BITMAP *buffer1,ballclass &theball, int basexpos, BITMAP *theballimg,BITMAP *sunface, BITMAP *sunflare, float angle);

//function prototypes

Testing & problems:

One of the biggest problems I faced while creating the game was the collision detection of the ball with the clouds. I came up with the algorithm to detect whether the cloud had been hit fairly quickly as it was simple seeing if the x and y positions are inside the clouds.

The big problems raised when I had to detect which side the cloud was hit and what the ball should do afterwards since. I drew out lots of ideas on paper in order to try and get the ball to collide and then bounce off in the opposite direction but there was always something that went wrong, usually the ball colliding and the direction of the ball not changing properly so the cloud would collide with the ball over 5 times and would be destroyed instantly with just one hit.

After much thought and design I eventually came up with a design which decides where the ball collides by using a different corner of the ball to decide which edge has been hit because there is always one corner that cannot be above one edge of the cloud if another is elsewhere so drawing that out I managed to work out the algorithm.

The image below shows my notes for my final design of the ball collision and the code can be found in the function: int collidedcheck(ballclass &ball, int cloudsleft[][4],int numberofclouds);

Other tests once the game was complete and expected results:

Test Expected Result Actual Result
Pressing Enter at   the welcome screen Game progresses to   level select screen Game progresses to   level select screen
Pressing up and   down arrow keys moves on the level selection Pressing down   button moves arrow down 1 level until the bottom and the same for the up   button. Pressing down   button moves arrow down 1 level until the bottom and the same for the up   button.
Press “R” to view   rules Rule screen appears Rule screen appears
When playing the   game, lose all lives Game over screen   displayed and game ends Game over screen displayed   and game ends
Win a game on any   level Game win screen and   score displayed Game win screen and   score displayed
When ball hits the   darkest grey cloud Cloud destroyed and   rain starts falling Cloud destroyed and   rain starts falling
When the main game   background music ends Main game   background music restarts Main game   background music restarts