Professional Documents
Culture Documents
Lesson 5: Clickomania
by Marius Andra
Hi all! In this tutorial we will build a simple game in SDL called Clickomania.
Clickomania is actually a really simple game to build. Our version of the game
will consist of a 10x14 grid of balls. When you click (with the mouse) on a ball,
that's connected to 2 or more balls, all the connected balls of the same color as
the ball you clicked disappear. And the balls that were above the disappeared
balls will simply fall down. If you manage to clear a row of balls, then the other
rows move in to the left from the right. These 4 images illustrate the basic
aspects of the game:
Before After
After clicking one of the purple balls in the yellow region, all of them disappear
and the balls that were above them fall down.
1
After
Before
When we get rid of the balls in the yellow region the rest move from right to left.
Note that the rows of balls move from right to left, not the individual balls
themselves. Now let's get to coding the game.
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#include <SDL/SDL.h>
Since we'll also use the SDL Font routines (tutorial 4), we'll have to include
font.h
#include "font.h"
Now come some variables. The first two contain the dimensions of the playfield.
They will be set to some values later.
int rows;
int cols;
screen should be obvious. balls contain the images of the 10 possible ball
types. font1 and font2 shouldn't be too much to grasp as well.
playf contains the grid of all the balls. We will initialize it later.
2
scrwidth and scrheight contain the default width and height of the screen.
Later we'll "parse" the command line arguments checking, if the user wants to
run this program at some other screen resolution. Depending on the screen
resolution, the grid of balls might and might not fully cover the screen when
drawn from the screen coordinates (0,0). centx and centy tell us from where to
start drawing the array of balls. They will be calculated later.
The next variable, bls, tells us how many differently colored balls are there on
the screen. bla is used with the mouse. More specifically it's used to see
whether a mouse button has been clicked. score should be obvious.
Now the function swap takes references to 2 char variables (the playfield is an
array of type char, btw). It then simply swaps them.
The function grid takes the coordinates of the playfield as parameters and
returns the value (color of the ball) that's on that specific coordinate. It's used
almost everywhere in the rest of the program. The function returns a reference to
the char (instead of an instance of the char like we usually do) at the coordinate
so that we could manipulate it (swap it with some other, assign a value to it, etc)
outside the function.
3
// Returns the color of a ball in the playfield.
inline char &grid(int a, int b)
{
return playf[a*cols+b];
}
The next function, collapse, makes the level collapse. It's called right after we
remove a bunch of balls from the grid.
// This function makes balls fall down and rows move left.
void collapse()
{
First we check if we can make any balls fall down. For that we loop through all
the columns in the grid. With each column we loop through all the rows. If we find
some ball with the value -1, then we remember it's row and move the others onto
it. After we move one ball onto the previously marked spot, we decrease the
location of the marked spot so that the next ball we move goes onto the top of
the spot where we moved our previous ball. After we do this all the balls will be
fallen down.
// Make balls fall down
int to=-1;
for(int j=0;j<cols;j++)
{
to=-1;
for(int i=rows-1;i>=0;i--)
{
if(to==-1 && grid(i,j)==-1) {to=i;}
else if(to!=-1 && grid(i,j)!=-1)
{swap(grid(i,j),grid(to,j)); to--;}
}
}
Now we want to move the balls from right to left. For that we loop through all the
columns. If one of those columns should have the bottom cell in the row empty (-
1), then we know that the entire column is empty. And if it is empty, we mark the
location and move all the other columns onto it. After we move one column, we
increase the marked location counter and move our columns there.
4
to++;
}
}
After we have done all that we blank out all the leftover columns.
{
if(to!=-1)
{
for(int j=to;j<cols;j++)
{
for(int i=0;i<rows;i++)
{
grid(i,j)=-1;
}
}
}
}
}
}
Now the next function recursively clears out a bunch of balls. We use the flood fill
algorithm in it. How it works is really simple. We first make the current location
(passed as the parameters to this function) in the grid equal -1. We then check all
the sides of the current location (top, bottom, left, right). If they equal the color
the current location was before, then we call this same function on them. The
function runs until all of the connected balls get changed to the value -1. This
function also keeps track on the number of balls cleared and returns the cleared
number with the return statement at the end. The number of balls cleared is
important with the scoring system. You should also know that after we run this
function on a specific ball, we call the collapse function (look above for it).
The next function, bunch, is similar to the previous function. But it only returns 1
when the ball at the given coordinates is connected to any other ball of the same
color, or it returns 0, when there aren't any connected balls.
5
if(i!=0 && grid(i-1,j) == a) return 1;
if(j!=0 && grid(i,j-1) == a) return 1;
if(i<rows-1 && grid(i+1,j) == a) return 1;
if(j<cols-1 && grid(i,j+1) == a) return 1;
return 0;
}
The next function, DrawScene is really simple. We first hide the mouse cursor
(comment out that line to see why we do that). After that we fill the screen with
black.
Now we loop through the grid and draw all the balls on the screen.
// Draw the balls
for(int i=0;i<rows;i++)
{
for(int j=0;j<cols;j++)
{
DrawIMG(balls[grid(i,j)],j*45+centx,i*45+centy);
}
}
... flip the screen and show the mouse cursor again.
SDL_Flip(screen); // Flip the buffers
SDL_ShowCursor(1); // Show the cursor again
}
The next function, newgame, puts some random balls on the grid of balls over the
old ones and also sets the score to zero. After that it calls DrawScene to update
the changes.
6
// Reset the score
score=0;
And now the final function, main. We first initialize the random number
generator. After that we check if the first command line argument given to this
program equals 640, 800, 1024, 1152, 1280 or 1600. If so, we change the screen
dimensions to the requested ones.
Now we set up a random picture of a ball as the icon of the program. So that
(almost) every time you run the game, it's icon will be different.
7
char tempstring[100];
sprintf(tempstring,"data/balls/%d.bmp",rand()%9+1);
SDL_WM_SetIcon(SDL_LoadBMP(tempstring),NULL);
Now we see how many balls can the current screen resolution hold. We simply
divide the screen width and height (minus 16 because of the info text) by 45 (the
width/height of one ball).
// Calculate how much balls fit on the screen with the current
// screen resolution
rows=(scrheight-16)/45;
cols=(scrwidth)/45;
And now we calculate how much must we move the balls in order to get the
playfield centered on the screen. First we check how much pixels does the grid
occupy. We then subtract that value from the width/height of the screen. We then
get the amount of free space left over by the playfield. We then divide that value
by 2 and get the amount we must move the playfield on the x and y coordinates.
We now allocate memory to store the playfield and then make it contain random
balls.
8
// Allocate space for the playfield
playf = new char[rows*cols];
// Loop a bit
int done=0;
while(done == 0)
{
SDL_Event event;
If someone presses ESC, we quit. SPACE restarts the game. Gray + and gray -
increase/decrease the number of differently colored balls.
if ( event.type == SDL_KEYDOWN )
{
// If someone presses ESC, then quit
if ( event.key.keysym.sym == SDLK_ESCAPE ) { done = 1; }
// Space restarts the game
if ( event.key.keysym.sym == SDLK_SPACE ) { newgame(); }
// The gray keypad + increases the nr. of different balls
if ( event.key.keysym.sym == SDLK_KP_PLUS )
{ if(bls<9) {bls++;newgame();} }
// The gray keypad - decreases the nr. of different balls
if ( event.key.keysym.sym == SDLK_KP_MINUS )
{ if(bls>3) {bls--;newgame(); } }
}
}
And now we must check if the user clicked a ball. We first get the coordinates of
the mouse. After that we check over which ball the mouse currently is. Because
the playfield is centered on the screen, the ball coordinates may be negative. If
they are, we restart the loop hoping that on the next run they won't be. If they
aren't then we move on.
9
If at the moment the mouse button is released, then we make bla equal one.
We now check if the clicked ball coordinates are inside the allowed area and if
so, we check if coordinates don't point to an empty ball. And if they don't we
check if the ball has other balls connected to it.
If so we then remove all the connected balls, increase the score by the [number
of balls removed-2] squared, make the playfield collapse and redraw the screen.
return 0;
}
10
Here's the code.
11