Well I finished my first game with Dark GDK. It's pretty basic. I've attached an installer of the game which you can run to grab the images that the source code below needs. Any feedback, suggestions, complaints are much appreciated.
Main.cpp-
#include "DarkGDK.h"
#include "Board.cpp"
#include "Enum.h"
void DarkGDK ( void )
{
// set game title and window position
char gameTitle[]="Minesweeper";
dbSetWindowTitle(gameTitle);
dbSetWindowPosition(dbScreenWidth()/2,dbScreenHeight()/2);
dbSyncOn ( );
dbSyncRate ( 60 );
// a call is made to this function so we can stop the GDK from
// responding to the escape key, we can then add in some code in our
// main loop so we can control what happens when the escape key is pressed
dbDisableEscapeKey ( );
// now we will set the random seed value to the timer, this will
// help us to get more random values each time we run the program
dbRandomize ( dbTimer ( ) );
// next we will load in some animated sprites, before doing this
// we need to adjust the image color key, by using this function we
// can make a specific color be transparent, in our case we want this
// to be bright pink
dbSetImageColorKey ( 255, 0, 255 );
DIFFICULTY difficulty=EASY;
GAME_STATE game_state=WAITING;
game_board current_board;
int temp_flag=1;
while ( LoopGDK ( ) )
{
// clear screen
dbCLS();
// record current key
int currentKey=dbScanCode();
// If they're on the menu set up the window one time to look a bit better.
if((game_state==WAITING || game_state==GAME_OVER || game_state==VICTORY) && temp_flag!=0)
{
dbSetDisplayMode(400,300,32);
dbSetWindowSize(400,300);
temp_flag=0;
}
// If they've won then let them know.
if(game_state==VICTORY)
{
char game_text[]=" Congratulations, you won! \n Instructions: \n 1-Easy \n 2-Medium \n 3-Hard \n Tab-Reset";
dbText(130,90,game_text);
}
// This only pops up on resets and the first time the game runs.
if(game_state==WAITING)
{
char game_text[]=" Instructions: \n 1-Easy \n 2-Medium \n 3-Hard \n Tab-Reset";
dbText(130,90,game_text);
}
// If they lost let them know.
if(game_state==GAME_OVER)
{
char game_text[]=" Sorry. You ran into a mine. \n Instructions: \n 1-Easy \n 2-Medium \n 3-Hard \n Tab-Reset";
dbText(130,90,game_text);
}
// recognize they hit a key and start generating the board.
if(currentKey!=KEY_NONE && (game_state==WAITING || game_state==GAME_OVER || game_state==VICTORY))
{
game_state=GENERATING;
}
// figure out how hard they want it
if(game_state==GENERATING)
{
switch(currentKey)
{
case KEY_ONE:
difficulty=EASY;
break;
case KEY_TWO:
difficulty=MEDIUM;
break;
case KEY_THREE:
difficulty=HARD;
break;
default:
difficulty=EASY;
break;
}
current_board.generate_game_board(difficulty);
game_state=PLAYING;
}
if(game_state!=GENERATING)
{
// play the game
if(game_state==PLAYING)
{
// Process reset button.
if(currentKey==KEY_TAB)
{
game_state=WAITING;
temp_flag=1;
}
if(game_state!=WAITING)
{
current_board.draw_board(game_state,temp_flag);
}
}
}
// end the game
if (currentKey==KEY_ESC )
break;
dbSync ( );
}
// and now everything is ready to return back to Windows
return;
}
Board.cpp-
#include "DarkGDK.h"
#include "Matrix.cpp"
#include "Enum.h"
class game_board : public Matrix<int>
{
private:
int width,height,num_mines,tile_width,tile_height,markers_placed;
bool mouse_clicked;
Matrix *mine_array,*tile_array,*display_array;
// mine array tracks mines, tile array tracks what is shown to the user
// and display_array tracks how many mines are nearby
bool check_win()
{
bool win=true;
for(int x=0;x<this->width;x++)
{
for(int y=0;y<this->height;y++)
{
if(this->tile_array->Get(y,x)==0)
{
// if they haven't clicked on all the tiles in some way they do
// not win.
win=false;
break;
}
if(this->tile_array->Get(y,x)==100 || this->tile_array->Get(y,x)==250)
{
// if any tiles is a question mark or just being hovered over
// then they didn't win either.
win=false;
break;
}
}
}
if(this->markers_placed<0)
{
// if they haven't placed markers on all the mines or placed too
// many then they lose also.
win=false;
}
return win;
}
void clear_tiles(int x,int y)
{
// find all the empty tiles near the empty tile they clicked on and show them.
// every call initialize all findings to -1 which isn't a valid value.
int tile_array_value=-1;
int display_array_value=-1;
int mine_array_value=-1;
if((y+1)<this->height)
{
// check up a tile
display_array_value=this->display_array->Get((y+1),x);
tile_array_value=this->tile_array->Get((y+1),x);
mine_array_value=this->mine_array->Get((y+1),x);
// if it is an empty tile with no mine in it and it hasn't all ready been clicked
// then display it and call the function for it's coords.
if(display_array_value==0 && tile_array_value!=150 && mine_array_value!=1)
{
this->tile_array->Set((y+1),x,150);
clear_tiles(x,(y+1));
}
else if(tile_array_value!=150)
{
// if it's a number and not a mine then show t but don't call
// the function again
if(mine_array_value!=1)
{
this->tile_array->Set((y+1),x,this->display_array->Get((y+1),x));
}
}
}
if((y-1)>=0)
{
display_array_value=this->display_array->Get((y-1),x);
tile_array_value=this->tile_array->Get((y-1),x);
mine_array_value=this->mine_array->Get((y-1),x);
if(display_array_value==0 && tile_array_value!=150 && mine_array_value!=1)
{
this->tile_array->Set((y-1),x,150);
clear_tiles(x,(y-1));
}
else if(tile_array_value!=150)
{
if(mine_array_value!=1)
{
this->tile_array->Set((y-1),x,this->display_array->Get((y-1),x));
}
}
}
if((x-1)>=0)
{
display_array_value=this->display_array->Get(y,(x-1));
tile_array_value=this->tile_array->Get(y,(x-1));
mine_array_value=this->mine_array->Get(y,(x-1));
if(display_array_value==0 && tile_array_value!=150 && mine_array_value!=1)
{
this->tile_array->Set(y,(x-1),150);
clear_tiles((x-1),y);
}
else if(tile_array_value!=150)
{
if(mine_array_value!=1)
{
this->tile_array->Set(y,(x-1),this->display_array->Get(y,(x-1)));
}
}
}
if((x+1)<this->width)
{
display_array_value=this->display_array->Get(y,(x+1));
tile_array_value=this->tile_array->Get(y,(x+1));
mine_array_value=this->mine_array->Get(y,(x+1));
if(display_array_value==0 && tile_array_value!=150 && mine_array_value!=1)
{
this->tile_array->Set(y,(x+1),150);
clear_tiles((x+1),y);
}
else if(tile_array_value!=150)
{
if(mine_array_value!=1)
{
this->tile_array->Set(y,(x+1),this->display_array->Get(y,(x+1)));
}
}
}
}
public:
game_board()
{
// This just controls how large the tiles are so if you change their
// size you need to update this
this->tile_width=25;
this->tile_height=25;
mouse_clicked=false;
}
void generate_game_board(int difficulty)
{
switch(difficulty)
{
case 0:
width=5;
height=5;
num_mines=10;
break;
case 1:
width=10;
height=10;
num_mines=25;
break;
case 2:
width=20;
height=20;
num_mines=100;
break;
}
markers_placed=num_mines;
mine_array=new Matrix(height,width);
tile_array=new Matrix(height,width);
display_array=new Matrix(height,width);
int placed_mines=0;
int total_tiles=width*height;
do
{
// this loop keeps happening until a full amount of mines are placed
placed_mines=0;
for(int y=0;y<height;y++)
{
for(int x=0;x<width;x++)
{
int mine=dbRND(100);
int number=x*width+y;
int tolerance=75;
// as they get closer to the end of the map mines become significantly
// more likely
if(placed_mines<num_mines)
{
int percent=number/total_tiles*100;
if(percent>90)
{
tolerance=0;
}
else if(percent>80)
{
tolerance=5;
}
else if(percent>70)
{
tolerance=10;
}
else if(percent>65)
{
tolerance=15;
}
else if(percent>55)
{
tolerance=20;
}
}
if(mine>tolerance && (placed_mines<num_mines))
{
mine_array->Set(y,x,1);
placed_mines++;
}
else
{
mine_array->Set(y,x,0);
}
}
}
}while(placed_mines<num_mines);
for(int y=0;y<height;y++)
{
for(int x=0;x<width;x++)
{
int local_mines=0;
// essentially if the tile is a minute move on to the next
// tile. Otherwise we figure out how many mines are local to this
// particular square and set it down.
if(mine_array->Get(y,x)==1)
{
display_array->Set(y,x,0);
continue;
}
if((y+1)<height)
{
if(mine_array->Get((y+1),x)==1)
{
local_mines++;
}
}
if((y-1)>=0)
{
if(mine_array->Get((y-1),x)==1)
{
local_mines++;
}
}
if((x+1)<width)
{
if(mine_array->Get(y,(x+1))==1)
{
local_mines++;
}
}
if((x-1)>=0)
{
if(mine_array->Get(y,(x-1))==1)
{
local_mines++;
}
}
if((y+1)<height && (x+1)<width)
{
if(mine_array->Get((y+1),(x+1))==1)
{
local_mines++;
}
}
if((y+1)<height && (x-1)>=0)
{
if(mine_array->Get((y+1),(x-1))==1)
{
local_mines++;
}
}
if((y-1)>=0 && (x-1)>=0)
{
if(mine_array->Get((y-1),(x-1))==1)
{
local_mines++;
}
}
if((y-1)>=0 && (x+1)<width)
{
if(mine_array->Get((y-1),(x+1))==1)
{
local_mines++;
}
}
display_array->Set(y,x,local_mines);
}
}
// all tiles start out as nothing being displayed
for(int y=0;y<height;y++)
{
for(int x=0;x<width;x++)
{
tile_array->Set(y,x,0);
}
}
// set the display size just a bit larger than the size of the map board.
dbSetDisplayMode(this->width*this->tile_width*1.5,this->height*this->tile_height*1.5,32);
dbSetWindowSize(this->width*this->tile_width*1.5,this->height*this->tile_height*1.5);
char base[]="sweeper_blank.jpg";
dbLoadImage(base,1,1);
char hover[]="sweeper_hover.jpg";
dbLoadImage(hover,2,1);
char one[]="sweeper_one.jpg";
dbLoadImage(one,3,1);
char two[]="sweeper_two.jpg";
dbLoadImage(two,4,1);
char three[]="sweeper_three.jpg";
dbLoadImage(three,5,1);
char four[]="sweeper_four.jpg";
dbLoadImage(four,6,1);
char five[]="sweeper_five.jpg";
dbLoadImage(five,7,1);
char six[]="sweeper_six.jpg";
dbLoadImage(six,8,1);
char seven[]="sweeper_seven.jpg";
dbLoadImage(seven,9,1);
char eight[]="sweeper_eight.jpg";
dbLoadImage(eight,10,1);
char nine[]="sweeper_nine.jpg";
dbLoadImage(nine,11,1);
char mine[]="sweeper_mine.jpg";
dbLoadImage(mine,12,1);
char question[]="sweeper_question.jpg";
dbLoadImage(question,13,1);
char background[]="sweeper_background.jpg";
dbLoadImage(background,14,1);
}
void draw_board(GAME_STATE &game_state,int &temp_flag)
{
dbPasteImage(14,0,0,0);
int x_space=this->tile_width,x_pos=0,y_space=1;
char text[45];
// If they've won no reason to keep going.
if(this->check_win())
{
game_state=VICTORY;
temp_flag=1;
return;
}
// running track of number of mine markers placed.
sprintf(text,"Mines Left: %d Press Tab to restart.",markers_placed);
dbText(0,0,text);
if(!dbMouseClick())
{
// they've let go of the mouse button.
mouse_clicked=false;
}
for(int y=0;y<this->height;y++)
{
for(int x=0;x<this->width;x++)
{
// set display and tile values for easy reference
int display_value=this->display_array->Get(y,x);
int tile_value=this->tile_array->Get(y,x);
// every row reset the x_pos counter so that the rows line up.
if(x==0)
{
x_pos=0;
}
//adjust the position by the space so that tiles don't over lap
x_pos+=x_space;
// this allows the tiles to move down
int y_pos=(y_space*this->tile_height);
// Checks to see if the mouse is within a tile
if(
((dbMouseX()<(x_pos+this->tile_width)) &&( dbMouseX()>x_pos) )
&&
((dbMouseY()<(y_pos+this->tile_height)) && (dbMouseY()>y_pos))
)
{
// check to see if they did a left click and make sure they're not
// just holding down the mouse
if(dbMouseClick()==LEFT_CLICK && !mouse_clicked)
{
mouse_clicked=true;
if(this->mine_array->Get(y,x)==1)
{
// if they just clicked on a mine that's the end of it.
game_state=GAME_OVER;
temp_flag=1;
}
else
{
if(display_value==0)
{
// if this is one of the areas where no mines are nearby
this->tile_array->Set(y,x,150);
clear_tiles(x,y);
}
else if(display_value!=tile_value)
{
// if mines are nearby how many exactly?
int nearby_mines=display_value;
this->tile_array->Set(y,x,nearby_mines);
}
}
}
else if(dbMouseClick()==RIGHT_CLICK && !mouse_clicked)
{
// if they do a right click this is where we show markers
mouse_clicked=true;
if(tile_value==100)
{
// 200 is code for a mine is there so decrease the markers
markers_placed--;
this->tile_array->Set(y,x,200);
}
else if(tile_value==200)
{
// 250 is for the question mark which doesn't count against them
// however it does keep them from winning
markers_placed++;
this->tile_array->Set(y,x,250);
}
else if(tile_value==250)
{
// return back to the normal tiles
this->tile_array->Set(y,x,0);
}
}
else
{
// if they're not clicking they're just hovering over it
if(tile_value==0)
{
this->tile_array->Set(y,x,100);
}
}
}
else
{
// keep the tile array's at 0.
if(tile_value==0 || tile_value==100)
{
this->tile_array->Set(y,x,0);
}
}
switch(tile_value)
{
case 0:
dbPasteImage(1,x_pos,y_pos,0);
break;
case 1:
dbPasteImage(3,x_pos,y_pos,0);
break;
case 2:
dbPasteImage(4,x_pos,y_pos,0);
break;
case 3:
dbPasteImage(5,x_pos,y_pos,0);
break;
case 4:
dbPasteImage(6,x_pos,y_pos,0);
break;
case 5:
dbPasteImage(7,x_pos,y_pos,0);
break;
case 6:
dbPasteImage(8,x_pos,y_pos,0);
break;
case 7:
dbPasteImage(9,x_pos,y_pos,0);
break;
case 8:
dbPasteImage(10,x_pos,y_pos,0);
break;
case 9:
dbPasteImage(11,x_pos,y_pos,0);
break;
case 150:
dbPasteImage(2,x_pos,y_pos,0);
break;
case 200:
dbPasteImage(12,x_pos,y_pos,0);
break;
case 250:
dbPasteImage(13,x_pos,y_pos,0);
break;
case 100:
dbPasteImage(2,x_pos,y_pos,0);
break;
}
// Uncomment the below line to do some epic cheating. :-D
/*if(this->mine_array->Get(y,x)==1)
{
char temp[5];
sprintf(temp,"%d",1);
dbText(x_pos,y_pos,temp);
}*/
}
y_space++;
}
}
~game_board()
{
// unlink all the images
dbDeleteImage(1);
dbDeleteImage(2);
dbDeleteImage(3);
dbDeleteImage(4);
dbDeleteImage(5);
dbDeleteImage(6);
dbDeleteImage(7);
dbDeleteImage(8);
dbDeleteImage(9);
dbDeleteImage(10);
dbDeleteImage(11);
dbDeleteImage(12);
dbDeleteImage(13);
dbDeleteImage(14);
}
};
Matrix.cpp-
/**
* 2D Matrix with non-contiguous 2D storage internally.
*/
template <typename TValue>
class Matrix
{
public:
/**
* the value type.
*/
typedef TValue ValueType;
/**
* the value type.
*/
typedef Matrix<ValueType> Self;
/**
* the pointer type.
*/
typedef ValueType* PointerType;
/**
* the storage type.
*/
typedef ValueType** StorageType;
/**
* the size type.
*/
typedef int SizeType;
/**
* contructor.
*/
Matrix( SizeType nrow, SizeType ncol )
: m_nrow( nrow )
, m_ncol( ncol )
, m_data( Create( nrow, ncol ) )
{
}
Matrix()
{
}
/**
* destructor.
*/
virtual ~Matrix()
{
/*for ( int row = 0; row < Rows(); ++row )
{
delete[] m_data[ row ];
}*/
/*delete[] m_data;*/
}
/**
* the i-th row;
* this allows you to write matrix[y][x] to access an element.
*/
PointerType operator[]( int i )
{
return m_data[ i ];
}
/**
* alias for Get(y,x) (const);
* this allows you to write int x = matrix(y,x) to access an element.
*/
ValueType operator()( SizeType y, SizeType x ) const
{
return Get( y, x );
}
/**
* alias for Get(y,x) (non-const);
* this allows you to write matrix(y,x) = x; to access an element.
*/
ValueType& operator()( SizeType y, SizeType x )
{
return Get( y, x );
}
/**
* the number of rows.
*/
SizeType Rows() const
{
return m_nrow;
}
/**
* the number of columns.
*/
SizeType Columns() const
{
return m_ncol;
}
/**
* the value of the given location (const).
*/
ValueType Get( SizeType y, SizeType x ) const
{
return m_data[ y ][ x ];
}
/**
* the value of the given location (non-const).
*/
ValueType& Get( SizeType y, SizeType x )
{
return m_data[ y ][ x ];
}
/**
* set the value of the given location.
*/
void Set( SizeType y, SizeType x, const ValueType& value )
{
m_data[ y ][ x ] = value;
}
/**
* the internal representation.
*/
friend StorageType GetImpl( Self& matrix )
{
return matrix.m_data;
}
protected:
/**
* convenience function to create the internal representation inside the
* member initialization list (MIL).
*/
StorageType Create( SizeType nrow, SizeType ncol )
{
StorageType m = new PointerType[ nrow ];
for ( int row = 0; row < nrow; ++row )
{
m[ row ] = new ValueType[ ncol ];
}
return m;
}
private:
/**
* the number of columns.
*/
SizeType m_ncol;
/**
* the number of rows.
*/
SizeType m_nrow;
/**
* the internal representation.
*/
StorageType m_data;
private:
/**
* prevent asignement.
*/
Self& operator= ( const Self& );
/**
* prevent copy-asignement.
*/
Matrix( const Self& );
};
Enum.h-
#ifndef ENUM_INCLUDE
#define ENUM_INCLUDE
enum KEYBOARD
{
KEY_NONE,KEY_ESC,KEY_ONE,KEY_TWO,KEY_THREE,KEY_FOUR,KEY_FIVE,KEY_SIX,
KEY_SEVEN,KEY_EIGHT,KEY_NINE,KEY_ZERO,KEY_DASH,KEY_EQUAL,KEY_BACK_SPACE,KEY_TAB,KEY_Q,KEY_W,
KEY_E,KEY_R,KEY_T,KEY_Y,KEY_U,KEY_I,KEY_O,KEY_P,KEY_LEFT_BRACKET,KEY_RIGHT_BRACKET,KEY_ENTER,
KEY_LEFT_CTRL,KEY_A,KEY_S,KEY_D,KEY_F,KEY_G,KEY_H,KEY_J,KEY_K,KEY_L,KEY_SEMI_COLON,KEY_APOSTROPHE,
KEY_TILDA,KEY_LEFT_SHIFT,KEY_BACK_SLASH,KEY_Z,KEY_X,KEY_C,KEY_V,KEY_B,KEY_N,KEY_M,KEY_COMMA,KEY_PERIOD,
KEY_FORWARD_SLASH,KEY_PRINT_SCREEN,KEY_ALT,KEY_SPACE,KEY_CAPS,KEY_F1,KEY_F2,KEY_F3,KEY_F4,
KEY_F5,KEY_F6,KEY_F7,KEY_F8,KEY_F9,KEY_F10,KEY_NUM_LOCK,KEY_SCROLL,KEY_NUM_7,KEY_NUM_8,KEY_NUM_9,
KEY_NUM_DASH,KEY_NUM_4,KEY_NUM_5,KEY_NUM_6,KEY_NUM_PLUS,KEY_NUM_1,KEY_NUM_2,KEY_NUM_3,
KEY_NUM_0,KEY_NUM_DEL,KEY_EMPTY_1,KEY_EMPTY_2,KEY_EMPTY_3,KEY_F11,KEY_F12,KEY_EMPTY_89,
KEY_EMPTY_90,KEY_EMPTY_91,KEY_EMPTY_92,KEY_EMPTY_93,KEY_EMPTY_94,KEY_EMPTY_95,
KEY_EMPTY_96,KEY_EMPTY_97,KEY_EMPTY_98,KEY_EMPTY_99,KEY_EMPTY_100,KEY_EMPTY_101,
KEY_EMPTY_102,KEY_HOME,KEY_UP,KEY_PAGE_UP,KEY_EMPTY_106,KEY_LEFT,KEY_EMPTY_108,KEY_RIGHT,
KEY_EMPTY_110,KEY_END,KEY_DOWN,KEY_PAGE_DOWN,KEY_INSERT,KEY_DELETE
};
enum DIFFICULTY{EASY,MEDIUM,HARD};
enum GAME_STATE{WAITING,GENERATING,PLAYING,GAME_OVER,VICTORY};
enum MOUSE{NO_CLICK,LEFT_CLICK,RIGHT_CLICK};
#endif
Charles Thompson