finished my first game with graphics

Started by
0 comments, last by Splinter of Chaos 16 years, 3 months ago
I finally finished my first game that uses graphics. I had problems earlier with it but I found some of the same game online and abstratced ideas from that. It's only Tic-Tac-Toe. If you win it will display that you have won and then clear the screen so you can play again. I know it so simple but I'm proud of what I've done and how much I've learned just from making Tic-Tac-Toe I was suprised. But this is the code and I always want to learn so any constructive critism would be welcomed. Oh ya and I am just using SDL.

// headers
#include "SDL/SDL.h"
#include "SDL/SDL_image.h"
#include "SDL/SDL_ttf.h"
#include <string>

// constants for screen
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
const int SCREEN_BPP = 32;

// the event structure
SDL_Event event;

// the font structure
TTF_Font *font = NULL;

// the board
int board[9] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };

// the dimensions for the mouse click 
SDL_Rect board_tiles[9];

// number of wins for player 1 and 2
int winPlayer1 = 0;
int winPlayer2 = 0;

// players turn 1 will be for player1(X's) and 2 will be for player2(O's)
int turn = 1;

// the text color
SDL_Color textColor = { 0, 0, 0 };

// the surfaces
SDL_Surface *screen = NULL;
SDL_Surface *background = NULL;
SDL_Surface *pieceX = NULL;
SDL_Surface *pieceO = NULL;
SDL_Surface *message = NULL;
SDL_Surface *winOne = NULL;
SDL_Surface *winTwo = NULL;

// load the images into the surfaces
SDL_Surface *load_image(std::string filename)
{
	SDL_Surface *loadedImage = NULL;
	SDL_Surface *optimizedImage = NULL;

	loadedImage = IMG_Load(filename.c_str());

	if(loadedImage != NULL)
	{
		optimizedImage = SDL_DisplayFormat(loadedImage);

		SDL_FreeSurface(loadedImage);

		if(optimizedImage != NULL)
		{
			SDL_SetColorKey(optimizedImage,SDL_SRCCOLORKEY,SDL_MapRGB(optimizedImage->format,0,0xFF,0xFF));
		}
	}

	return optimizedImage;
}

void apply_surface(int x, int y, SDL_Surface *source,SDL_Surface *destination,SDL_Rect *clip = NULL)
{
	SDL_Rect offset;

	offset.x = x;
	offset.y = y;

	SDL_BlitSurface(source,clip,destination,&offset);
}

bool init()
{
	if(SDL_Init(SDL_INIT_EVERYTHING) == -1)
		return false;

	screen = SDL_SetVideoMode(SCREEN_WIDTH,SCREEN_HEIGHT,SCREEN_BPP,SDL_SWSURFACE);

	if(screen == NULL)
		return false;

	if(TTF_Init() == -1)
		return false;

	SDL_WM_SetCaption("Tic-Tac-Toe 1.0",NULL);

	return true;
}

bool load_files()
{
	background = load_image("background.png");

	pieceX = load_image("pieceX.png");

	pieceO = load_image("pieceO.png");

	font = TTF_OpenFont("lazy.ttf",32);

	if(background == NULL)
		return false;

	if(pieceX == NULL)
		return false;

	if(pieceO == NULL)
		return false;

	if(font == NULL)
		return false;

	return true;
}

void clean_up()
{
	SDL_FreeSurface(background);
	SDL_FreeSurface(pieceX);
	SDL_FreeSurface(pieceO);
	SDL_FreeSurface(message);

	TTF_CloseFont(font);

	TTF_Quit();

	SDL_Quit();
}

// initalizes all the square piece of the Tic-Tac-Toe board
void initTiles()
{
	// top left
	board_tiles[0].x = 80;
	board_tiles[0].y = 115;
	board_tiles[0].w = 165;
	board_tiles[0].h = 185;

	// top middle
	board_tiles[1].x = 180;
	board_tiles[1].y = 115;
	board_tiles[1].w = 345;
	board_tiles[1].h = 185;

	// top right
	board_tiles[2].x = 350;
	board_tiles[2].y = 115;
	board_tiles[2].w = 440;
	board_tiles[2].h = 185;

	// middle left
	board_tiles[3].x = 75;
	board_tiles[3].y = 190;
	board_tiles[3].w = 170;
	board_tiles[3].h = 280;

	// middle middle
	board_tiles[4].x = 175;
	board_tiles[4].y = 190;
	board_tiles[4].w = 345;
	board_tiles[4].h = 280;

	// middle right
	board_tiles[5].x = 350;
	board_tiles[5].y = 190;
	board_tiles[5].w = 440;
	board_tiles[5].h = 280;

	// bottom left
	board_tiles[6].x = 75;
	board_tiles[6].y = 285;
	board_tiles[6].w = 165;
	board_tiles[6].h = 340;

	// bottom middle
	board_tiles[7].x = 175;
	board_tiles[7].y = 285;
	board_tiles[7].w = 345;
	board_tiles[7].h = 340;
	
	// bottom right
	board_tiles[8].x = 350;
	board_tiles[8].y = 285;
	board_tiles[8].w = 445;
	board_tiles[8].h = 340;
}

void Winner()
{
	SDL_Flip(screen);

	SDL_Delay(1000);
	
	apply_surface(0,0,background,screen);
}
void announceWinner()
{
	if(turn == 1)
	{
		message = TTF_RenderText_Solid(font,"Player 1 has won",textColor);
		
	}
	else
	{
		message = TTF_RenderText_Solid(font,"Player 2 has won",textColor);
		
	}

	apply_surface((SCREEN_WIDTH - message->w)/2,50,message,screen);

}

void checkWinner()
{
	if((board[0] == turn) && (board[1] == turn) && (board[2] == turn))
	{
		announceWinner();
		Winner();		
	}
	else if((board[0] == turn) && (board[4] == turn) && (board[8] == turn))
	{
		announceWinner();
		Winner();
	}
	else if((board[0] == turn) && (board[3] == turn) && (board[6] == turn))
	{
		announceWinner();
		Winner();
	}
	else if((board[1] == turn) && (board[4] == turn) && (board[7] == turn))
	{
		announceWinner();
		Winner();
	}
	else if((board[2] == turn) && (board[4] == turn) && (board[8] == turn))
	{
		announceWinner();
		Winner();
	}
	else if((board[2] == turn) && (board[5] == turn) && (board[6] == turn))
	{
		announceWinner();
		Winner();
	}
	else if((board[3] == turn) && (board[4] == turn) && (board[5] == turn))
	{
		announceWinner();
		Winner();
	}
	else if((board[6] == turn) && (board[7] == turn) && (board[8] == turn))
	{
		announceWinner();
		Winner();
	}

	
}

void placeXO(int x, int y)
{
	for(int i = 0; i < 9; ++i)
		{
			if(turn == 1)
			{
				if((x > board_tiles.x) && (x < board_tiles.w) && (y > board_tiles.y) && (y < board_tiles.h) && (board == 0))
					{						
						apply_surface(board_tiles.x,board_tiles.y,pieceX,screen);
						board = turn;
						checkWinner();
						turn = 2;
					}
			}			
			else if(turn == 2)
			{
				if((x > board_tiles.x) && (x < board_tiles.w) && (y > board_tiles.y) && (y < board_tiles.h) && (board == 0))
					{						
						apply_surface(board_tiles.x,board_tiles.y,pieceO,screen);
						board = turn;
						checkWinner();
						turn = 1;
					}
			}
		}
}


int main(int argc,char *args[])
{
	bool quit = false;

	int x =	0;
	int y = 0;

	initTiles();

	// initialize SDL
	if(init() == false)
		return 1;

	if(load_files() == false)
		return 1;

	apply_surface(0,0,background,screen);

	while(quit == false)
	{
		while(SDL_PollEvent(&event))
		{
			// get the mouse offsets
			if(event.type == SDL_MOUSEBUTTONDOWN)
			{
				if(event.button.button == SDL_BUTTON_LEFT)
				{
					x = event.button.x;
					y = event.button.y;
				}
			}
			else if(event.type == SDL_QUIT)
			{
				quit = true;
			}
		}

		placeXO(x,y);

		if(SDL_Flip(screen) == -1)
			return 1;

		x = 0;
		y = 0;
	}

	clean_up();

	return 0;
}




Advertisement
Your code looks pretty fine. I just have a few suggestions to make future projects better, but nothing that would help you a ton here.

When you have a problem initializing SDL, you "return false". That's what Lazy Foo told you to do (I'm guessing you used him because you use his font--aside from using his techniques), but when you get into larger projects, you'll want to know why it didn't initialize. It doesn't matter so much if you're doing such a small thing and there aren't many places to look at when there's a problem, but I had a problem with my font on my project. I couldn't nail it down until I turned my init function into this:

 bool initSDL( const int SCREEN_WIDTH = 640, const int SCREEN_HEIGHT = 480, const int SCREEN_BPP = 32 ){    // NOTE: errors is an output file I set up.    // INIT EVERYTHING //    if( SDL_Init( SDL_INIT_EVERYTHING ) == -1 )    {        errors << "SDL Init error: " << SDL_GetError() << endl;         exit( -1 );    }    errors << "SDL initialized!" << endl;    // INIT TRUE TYPE FONT //    if( TTF_Init() == -1 )    {        errors << "TTF Init error: " << TTF_GetError() << endl;         exit( -1 );    }    errors << "True Type Font initialized!" << endl;    // because I'm lazy.    atexit(TTF_Quit);    // INIT JOYSTICK //    if( SDL_NumJoysticks() < 1 )    {        errors << "SDL joystick Init error: " << SDL_GetError() << endl;     }    else    {        joystick = SDL_JoystickOpen(0);    }    errors << "Joystick initialized!" << endl;    // INIT SCREEN //    screen = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE );    if( screen == NULL )     {        errors << "SDL Video Mode error: " << SDL_GetError() << endl;        exit( -1 );    }    errors << "SDL Video mode initialized!" << endl;    // Set up the audio.    // frequency originally was 22050.    if( Mix_OpenAudio(44100,MIX_DEFAULT_FORMAT,2,4096) == -1 )         return false;    // TODO set up the font and a joystick.    // Get rid of what this header file's created when you leave.    atexit( finish );    // Finally, update the following globals.    screenWidth  = SCREEN_WIDTH;    screenHeight = SCREEN_HEIGHT;    return true; // Made it this far, huh?}


As you can see, I stopped doing the checks at the audio (because I'm lazy), but not only did this help me spot the problem, it showed me that (and I really should have guessed) other systems would not initialize because I was returning. But really, I don't want the program to continue on, so I quit. They say that it's a bad practice to quit like that, but I don't think there's anything I can do if the very thing my program relies on rolls over and dies.

Also, you comment more than you need to in the beginning, but stop doing this altogether by the end. It was simple enough to understand, but I would have appreciated something.

You create an array of nine SDL_Rects. This was a neat way of doing things, but the comment "// the dimensions for the mouse click " was nothing short of perplexing. It didn't help me at all. Something like "// For checking collision against the mouse when a player makes a move" might have been helpful. Or, if you would have just suggested to look at the implementation (in main) for more info. This is also an unintended, although a good one, use of the class, so that should have been clarified. Usually, the SDL_Rect class (er...I guess I should call it a struct) means the staring x and y location, and how wide and high.

You hard coded the tiles you were checking for collision with. Nothing wrong with that, but what if there were twenty nine. Always look for mathematical solutions to tedious problems; this is especially for when the problem only has to do with what number gets assigned here.

You also hard coded the win checks. The way you did it, this would probably be easier than coming up with a mathematical solution, but that's only because you used a one dimensional array.
void checkWinner(){	if((board[0] == turn) && (board[1] == turn) && (board[2] == turn))	{		announceWinner();		Winner();			}	else if((board[0] == turn) && (board[4] == turn) && (board[8] == turn))	{		announceWinner();		Winner();	}	else if((board[0] == turn) && (board[3] == turn) && (board[6] == turn))	{		announceWinner();		Winner();	}	else if((board[1] == turn) && (board[4] == turn) && (board[7] == turn))	{		announceWinner();		Winner();	}	else if((board[2] == turn) && (board[4] == turn) && (board[8] == turn))	{		announceWinner();		Winner();	}	else if((board[2] == turn) && (board[5] == turn) && (board[6] == turn))	{		announceWinner();		Winner();	}	else if((board[3] == turn) && (board[4] == turn) && (board[5] == turn))	{		announceWinner();		Winner();	}	else if((board[6] == turn) && (board[7] == turn) && (board[8] == turn))	{		announceWinner();		Winner();	}}

could have been
/* UNTESTED */// Returning a boolean then deciding how to act on in works a little better// because you can decide different actions to take based on situations this // function might not know about.bool checkWinner(){    // The offsets for the check in the for loop coming up.    int x = 0;    int y = 0;    bool isMatch = true;    // Check all the verticle matches for every horizontal    for( ; x < 3; x++ )    {        // Assume the whole row is a match so that if only one isn't,        // this will be false.        isMatch = true;        // For every verticle         for( ; y < 3; y++ )        {            if( board[x][y] != turn )	            isMatch = false        }        // If the whole row was a match, return so.        if( isMatch )            return true;    }    // Check every horizontal for a verticle match.    for( y=0 ; y < 3; y++ )    {        isMatch = true;        for( x=0 ; x < 3; x++ )        {            if( board[x][y] != turn )	            isMatch = false        }        if( isMatch )            return true;    }    // Now check hardcoded diagonals.     // Yeah, checking diagonals is probably easier hardcoded.    if((board[0][0] == turn) && (board[1][1] == turn) && (board[2][2] == turn))        return true;    else if((board[2][0] == turn) && (board[1][1] == turn) && (board[0][2] == turn))	    return true;        // If there were no matches...    return false;}


EDIT
Forgot to mention: You do a lot all in one file. I keep SDL functions such as loadImage() in a file called (prepare for crappy name) SDL_Work.h. This also makes it easier for when I start a new project. No copying parts of a file, I just copy the file! I also keep files that may be multi-purpose and ones that are definitely specialized separate.

[Edited by - Splinter of Chaos on January 2, 2008 2:58:37 AM]

This topic is closed to new replies.

Advertisement