Need some help on Tic-Tac-Toe player turn

Started by
8 comments, last by Servant of the Lord 11 years, 8 months ago
So I am finally transitioning from console based programming into event-driven / 2d gfx programming. I have been looking through lazyfoos tutorials and they have helped a ton. I started to make a Tic-Tac-Toe game yesterday and it started off pretty good, I think. The problem I am running into right now is how to define if it is playerOne's turn or not.

I am using a bool for playerOne, true means it is playerOnes turn, false means it isn't. For now there are not actually 2 players, but I am trying to make it so when it playerOne == true, the box turns blue when clicked, and when playerOne == false, turn red. Later on I will switch it to images, but I want the logic down.

I suppose it will be easier if I post the code so here it is. Any help will be greatly appreciated:
[source lang="cpp"]#include "SDL.h"
#include "SDL_opengl.h"
#include "SDL_image.h"
#include <iostream>
#include <string>

const int HOLDERS = 9; // Number of holders for images

void setupWindow()
{
// Initialize SDL
SDL_Init(SDL_INIT_EVERYTHING);

// Allocate memory for GL
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE, 32);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

// Caption on the window pane
SDL_WM_SetCaption ("Tic-Tac-Toe", NULL);

// Setup the window
SDL_SetVideoMode(600, 600, 32, SDL_OPENGL);

// Clears the screen to this color
glClearColor(1,1,1,1);

// What is displayed of the screen
glViewport(0,0,600,600);

// Smooth color transition
glShadeModel(GL_SMOOTH);

// Sets to 2D
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

// Disable depth test since not 3D
glDisable(GL_DEPTH_TEST);
}

struct Holder
{
float xPos;
float yPos;
float width;
float height;
bool isVacant;
};


int main (int argc, char* args[])
{
// Function for window setup
setupWindow();

/*---------------------------------------------------------------------------------------------------*/
/*--------------------------- VARIABLE SETUP -----------------------------------------------------*/
/*---------------------------------------------------------------------------------------------------*/
// Handles game loop
bool isRunning = true;
bool playerOne = true;

// Handles SDL events
SDL_Event event;

// Array of Holders type
Holder holders[HOLDERS];

// Setup the holders
for (int a = 0, x = 20, y = 20; a < HOLDERS; a++, x+= 190)
{
if ( x > 400)
{
x = 20;
y += 190;
}
holders[a].xPos = x;
holders[a].yPos = y;
holders[a].width = 180;
holders[a].height = 180;
holders[a].isVacant = true;
}

/*---------------------------------------------------------------------------------------------------*/
/*--------------------------- MAIN LOOP -----------------------------------------------------*/
/*---------------------------------------------------------------------------------------------------*/

// Game loop
while (isRunning)
{
/*---------------------------------------------------------------------------------------------------*/
/*--------------------------- 1. EVENTS --------------------------------------------------*/
/*---------------------------------------------------------------------------------------------------*/
while (SDL_PollEvent(&event))
{
// If window is closed
if (event.type == SDL_QUIT)
{
isRunning = false;
}
// If escape key is pressed
if (event.type == SDL_KEYUP && event.key.keysym.sym == SDLK_ESCAPE)
{
isRunning = false;
}
// Mouse click
if (event.type == SDL_MOUSEBUTTONDOWN)
{
if (event.button.button == SDL_BUTTON_LEFT)
{
// Mouse offsets
int x = 0, y = 0;

x = event.button.x;
y = event.button.y;

for (int a = 0; a < HOLDERS; a++)
{
if ((x > holders[a].xPos) && (x < holders[a].xPos + holders[a].width) &&
(y > holders[a].yPos) && (y < holders[a].yPos + holders[a].height) && holders[a].isVacant == true)
{
OutputDebugString("Spot no longer vacant\n");
holders[a].isVacant = false;
}
}
}
}
}

/*---------------------------------------------------------------------------------------------------*/
/*--------------------------- 2. LOGIC -----------------------------------------------------*/
/*---------------------------------------------------------------------------------------------------*/



/*---------------------------------------------------------------------------------------------------*/
/*--------------------------- 3. RENDER -----------------------------------------------------*/
/*---------------------------------------------------------------------------------------------------*/
// Clears screen before rendering
glClear(GL_COLOR_BUFFER_BIT);

glPushMatrix(); // Start phase

glOrtho(0,600,600,0,-1,1); // Set the matrix

// THE HOLDERS
glBegin(GL_QUADS);

// Used to display vacant spaces black
for (int n = 0; n < HOLDERS; n++)
{
if (holders[n].isVacant == true)
{
glColor4ub(0,0,0,255);

glVertex2f(holders[n].xPos, holders[n].yPos);
glVertex2f(holders[n].xPos + holders[n].width, holders[n].yPos);
glVertex2f(holders[n].xPos + holders[n].width, holders[n].yPos + holders[n].height);
glVertex2f(holders[n].xPos, holders[n].yPos + holders[n].height);
}

else if (holders[n].isVacant == false && playerOne == true)
{
glColor4ub(0,0,255,255);

glVertex2f(holders[n].xPos, holders[n].yPos);
glVertex2f(holders[n].xPos + holders[n].width, holders[n].yPos);
glVertex2f(holders[n].xPos + holders[n].width, holders[n].yPos + holders[n].height);
glVertex2f(holders[n].xPos, holders[n].yPos + holders[n].height);

}

else if (holders[n].isVacant == false && playerOne == false)
{
glColor4ub(255,0,0,255);

glVertex2f(holders[n].xPos, holders[n].yPos);
glVertex2f(holders[n].xPos + holders[n].width, holders[n].yPos);
glVertex2f(holders[n].xPos + holders[n].width, holders[n].yPos + holders[n].height);
glVertex2f(holders[n].xPos, holders[n].yPos + holders[n].height);
}
}

glEnd();

glPopMatrix(); // End phase

// Draws everything above it to the screen
SDL_GL_SwapBuffers();
}

SDL_Quit;

return 0;
}[/source]
Advertisement
I also tried to do the 'turns' based on a counter and % 0 == this color, else another color. Couldn't figure that out either.
Have an enum for the player. Have a variable 'currentPlayer' (of that enum type) start off set to PlayerOne.
enum Player {PlayerOne, PlayerTwo};

Player currentPlayer = PlayerOne;


Each time the player clicks, check it like this:
- If that is valid location to click...
- ...then make sure the location has not already been taken...
- ...then set the location to this player's mark...
- ...then change the 'currentPlayer' to the next player's turn.

Also, you'll need another enum. In your struct 'Holder' you have a bool 'isVacant'. It actually needs three different settings (Empty, Player one has marked it, Player two has marked it)
enum Marker {EmptyMarker, PlayerOneMarker, PlayerTwoMarker};

struct Holder
{
float xPos;
float yPos;
float width;
float height;
Marker marker;
};


When you get a mouse click, you can do something similar to this:

for (int a = 0; a < HOLDERS; a++)
{
//Check if we are within a marker location.
if ((x > holders[a].xPos) && (x < holders[a].xPos + holders[a].width) &&
(y > holders[a].yPos) && (y < holders[a].yPos + holders[a].height))
{
//Check if the location is empty.
if(holder[a].marker == EmptyMarker)
{
//Change the marker, depending on what player's turn it is.
if(currentPlayer == PlayerOne)
{
holders[a].marker == PlayerOneMarker;

//Change who's turn it is.
currentPlayer = PlayerTwo;
}
else if(currentPlayer == PlayerTwo)
{
holders[a].marker == PlayerTwoMarker;

//Change who's turn it is.
currentPlayer = PlayerOne;
}

}
}
}


Once you get it working, here's something fun to try: Add a third player, and make the grid larger.
From what I can see it looks like your switching the colors based on the current player and not based on the player that did the selection.
This means that when it's player 1's turn everything (X's and O's) will draw blue and when it's player 2's turn everything will draw red.

You could try something like this:

[source]
struct Holder
{
float xPos;
float yPos;
float width;
float height;
bool isVacant;
bool isX;
};

// set color to draw
if (holders[n].isVacant == true) {
glColor4ub(0,0,0,255); // black
}
else if (holders[n].isX) {
glColor4ub(0,0,255,255); // blue
}
else {
glColor4ub(255,0,0,255); // red
}

// draw rectangle

[/source]

Have an enum for the player. Have a variable 'currentPlayer' (of that enum type) start off set to PlayerOne.
.......


Thanks a ton man, that worked perfectly. I forgot all about enums and how well they work. I have almost everything working now including the checking for winner logic.

I am having one problem though:

After a person has been won (3 in a row, duh), i have a bool set up named isGameover that is then set to True. Now after that happens I wanted to load a texture that says "Player One/Two Wins" have a 2000ms Delay, and then exit. However, I am not even able to setup the textures like I did the other ones. I am not sure if there is a certain max size you can have for loading textures onto an unsigned int or not.

xTexture, oTexture, and hTexture all load and work fine. They are 180x180, .png
playerOneTexture and playerTwoTexture are 600x600, .png, and saved the exact same way as the other three.
I am guessing that there is a max image size that can be loaded this way.
[source lang="cpp"] // TEXTURES
// X Texture
unsigned int xTexture = 0;
xTexture = loadTexture("X.png");
// O Texture
unsigned int oTexture = 0;
oTexture = loadTexture("O.png");
// Holder Texture
unsigned int hTexture = 0;
hTexture = loadTexture ("Holder.png");
// PlayerOne texture
unsigned int playerOneTexture = 0;
playerOneTexture = loadTexture ("playerOneWins.png");
// PlayerTwo texture
unsigned int playerTwoTexture = 0;
playerTwoTexture = loadTexture ("playerTwoWins.png");[/source]
Couple tips:
A) You can just use 'unsigned' instead of 'unsigned int', it's a valid shorthand.
B) You can declare and initialize the variables in the same line.
unsigned hTexture = loadTexture ("Holder.png");

Also, you aren't loading a texture into an int. An int is only 4 bytes (or more or less, depending on your hardware and OS and stuff), a single pixel is usually about 4 bytes, let alone an entire image. With OpenGL you are loading an image into your videocard's memory, and the function is returning a ID number. That ID number (oftentimes called a 'handle') is being stored in the integer, not the image itself.

Yes, there are maximum sizes for textures, and they depend on what videocard you have... but any videocard made in the past 8 years would be able to hold over 600x600 easily.

My guess as to the problem: The function can't find the images, because playerOneWins.png and playerTwoWins.png are probably not named the exact same way in the folder. Maybe the actual images are named playerOneWin.png without the 's' after 'win'? Or maybe they are .jpegs not .pngs? Or something similar.

To find out what's going on, the function loadTexture() probably does something or another to report an error if something messes up. What does the documentation say for loadTexture()? That's not a SDL or OpenGL function, as far as I know. Did you write it itself or find it online? If so, post the code, please!
If it's part of a library, what does that library's documentation say it does when it fails? Does it return 0 instead of the correct ID? Does it throw an exception? Does it set some error flag somewhere? It definitely does something when it fails, to let you know why it failed.
the loadTexture() was from a youtube tutorial,


it is a function at the top of my program:

[source lang="cpp"]GLuint loadTexture(const std::string &fileName)
{
SDL_Surface *image = IMG_Load(fileName.c_str());

SDL_DisplayFormatAlpha(image);

unsigned object(0);

glGenTextures(1, &object);

glBindTexture(GL_TEXTURE_2D, object);

glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image->w, image->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, image->pixels);

//Free surface
SDL_FreeSurface(image);

return object;
}[/source]

The image must have been to big, I just resized it to 180x180 and it worked.
Don't assume that because it wasn't working and now it is, that the problem is solved or that you understand what the problem was.
Until you actually track down the problem for sure, and not just guess that something might've somewhere could've been the source of the issue, then your code has a hidden problem that may pop up later at unexpected times.

I strongly strongly doubt that it was a texture size limitation, but it is possible. However, we can be sure of it by checking the results of the function.
In the function you posted, the code doesn't do any error checking whatsoever, despite calling a bunch of OpenGL functions. That's not good, it just assumes that everything worked, and when it doesn't work (like in your situation), it doesn't give you any indication of what the problem was.

Step 1: Print out the value of 'GL_MAX_TEXTURE_SIZE'. If that size is less than 600x600, then we can be confident that the problem has been resolved. If it's greater than 600x600, then the problem still exists somewhere, and proceed to step two.
std::cout << "The maximum texture size is: " << GL_MAX_TEXTURE_SIZE << "x" << GL_MAX_TEXTURE_SIZE << std::endl;

Step 2: Error-proof the SDL half of the function, by making sure IMG_Load and SDL_DisplayFormatAlpha didn't fail.
//Try to load the image.
SDL_Surface *image = IMG_Load(fileName.c_str());

//Make sure nothing went wrong.
if(!image)
{
//If something did go wrong, print the error and exit the function.
std::cout << "IMG_Load() failed: " << IMG_GetError());
return 0;
}

//Format the image.
SDL_Surface *formattedImage = SDL_DisplayFormatAlpha(image);

//Make sure nothing went wrong.
if(!formattedImage)
{
//If something did go wrong, print the error and exit the function.
std::cout << "SDL_DisplayFormatAlpha() failed: " << SDL_GetError());
return 0;
}

//Free the old surface, so we don't leak memory.
SDL_FreeSurface(image);

//...all the OpenGL stuff...

//Free the converted image, now that it has been copied by OpenGL onto the videocard.
SDL_FreeSurface(formattedImage);


By the way, we discovered the function is actually already doing something (two things!) wrong, just by adding error checking!
SDL_DisplayFormatAlpha() copies the surface passed in, converts it to the proper video format, and returns the new surface.
The loadTexture() function isn't catching the return value! This means two things:
1) It's not having any effect at all, since you aren't use the result.
2) You're leaking memory, since you aren't freeing the result either!

Step 3: Error-proof the OpenGL half of the function, by making sure glTexImage2D() and the others didn't fail.
Out of laziness and unfamiliarity with OpenGL, I'm just going to steal the OpenGL error-checking code from here, though I'm going to modify it to fit our desires better.
Here's my modified version: (basically the same thing, except using std::cout, and return a bool.
#define GL_ErrorOccured() GL_ErrorOccured_private(__FILE__, __LINE__)
bool GL_ErrorOccured_private(char *file, int line)
{
GLenum errorCode;
errorCode = glGetError();

//If an error didn't occur, just return false.
if(errorCode == GL_NO_ERROR)
return false;

//Print the error.
std::cout << "An OpenGL error occured in the file '" << file << "' on line " << line << "\n"
<< "Error: " << gluErrorString(errorCode) << std::endl;
//Return true - an error occured.
return true;
}


We need to add 'GL_ErrorOccured()' (from the above link) after each OpenGL function call.

Final result:
GLuint loadTexture(const std::string &fileName)
{
//Try to load the image.
SDL_Surface *image = IMG_Load(fileName.c_str());

//Make sure nothing went wrong.
if(!image)
{
//If something did go wrong, print the error and exit the function.
std::cout << "IMG_Load() failed: " << IMG_GetError());
return 0;
}
//Format the image.
SDL_Surface *formattedImage = SDL_DisplayFormatAlpha(image);
//Make sure nothing went wrong.
if(!formattedImage)
{
//If something did go wrong, print the error and exit the function.
std::cout << "SDL_DisplayFormatAlpha() failed: " << SDL_GetError());
return 0;
}
//Free the old surface, so we don't leak memory.
SDL_FreeSurface(image);

//This variable should have a more descriptive name. 'object'? Really? 'textureID' is much better.
unsigned textureID = 0;

//Generate a texture ID and assign it to our variable.
glGenTextures(1, &textureID);

//Bind our textureID as the texture currently being worked on.
glBindTexture(GL_TEXTURE_2D, textureID);
if(GL_ErrorOccured()) return 0;

//Set up the texture generation parameters for glTexImage2D().
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
if(GL_ErrorOccured()) return 0;

glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
if(GL_ErrorOccured()) return 0;
//Generate the texture, taking that data from our SDL_Surface, and moving it into the Video card's memory in the format we've specified to OpenGL.
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, formattedImage->w, formattedImage->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, formattedImage->pixels);
if(GL_ErrorOccured()) return 0;

//Free the converted image, now that it has been copied by OpenGL onto the videocard.
SDL_FreeSurface(formattedImage);

return textureID ;
}


Three notes:
1) This won't actually solve the error, but it will go a long way closer to identifying the error, since right now we don't have a clue what the error is, aside from guesswork.
2) On failure, the function returns 0 instead of the texture ID. It also leaks 'formattedImage' if it fails. If it was code in my own project, I'd handle things still differently, but I don't want to complete rewrite the function and it's usage into something radically different from what you already have.
3) I haven't actually compiled and tested the code (I don't have SDL installed on this machine anymore), so I may have made a dumb mistake or typos.
Thank you a ton for taking the time to explain all of that to me. I was at work and couldn't test anything, but now that I am home I am going to see if I can find where the error occurred.

..Neither cout or std::cout prints anything to the console for me?

--Nevermind, I'm an idiot, was using an empty project.
std::cout sends the text to the standard text output. If using a console, it displays it on the console, if using SDL it displays it to a file called sdlout.txt next to your game's executable. The stream can be rerouted to other locations, so for alot of other APIs it is just ignored unless you manually direct it to where you want.

This topic is closed to new replies.

Advertisement