Sign in to follow this  
CodeTitan

Help with Tic-Tac-Toe: AI issues

Recommended Posts

CodeTitan    258
void Grid::draw()
{
	line(g->buffer, 
		0, 
		static_cast<int>(scale_factor / 1.5), 
		2*scale_factor, 
		static_cast<int>(scale_factor / 1.5), 
		makecol(255, 255, 255));
	line(g->buffer, 
		0, 
		static_cast<int>(scale_factor / 1.5) + static_cast<int>(scale_factor / 1.5), 
		2*scale_factor, 
		static_cast<int>(scale_factor / 1.5) + static_cast<int>(scale_factor / 1.5), 
		makecol(255, 255, 255));
	line(g->buffer, 
		static_cast<int>(scale_factor / 1.5),
		0,
		static_cast<int>(scale_factor / 1.5),
		2*scale_factor,
		makecol(255, 255, 255));
	line(g->buffer, 
		static_cast<int>(scale_factor / 1.5) + static_cast<int>(scale_factor / 1.5), 
		0,
		static_cast<int>(scale_factor / 1.5) + static_cast<int>(scale_factor / 1.5), 
		2*scale_factor, 
		makecol(255, 255, 255));

}

void Grid::blitFromInput()
{
	if(turn == 'O') AI_response();
	if(key[KEY_1_PAD])	blitToRect(0, 2);
	if(key[KEY_2_PAD])	blitToRect(1, 2);
	if(key[KEY_3_PAD])	blitToRect(2, 2);
	if(key[KEY_4_PAD])	blitToRect(0, 1);
	if(key[KEY_5_PAD])	blitToRect(1, 1);
	if(key[KEY_6_PAD])	blitToRect(2, 1);
	if(key[KEY_7_PAD])	blitToRect(0, 0);
	if(key[KEY_8_PAD])	blitToRect(1, 0);
	if(key[KEY_9_PAD])	blitToRect(2, 0);
}

void Grid::blitToRect(int x, int y)
{
	int temp_x = x;
	int temp_y = y;
	x *= static_cast<int>(scale_factor / 1.5) + 1;
	y *= static_cast<int>(scale_factor / 1.5) + 1;
	if(turn == 'X')
	{
		if(!(theGrid[temp_x][temp_y] == 'X' || theGrid[temp_x][temp_y] == 'O'))
		{
			draw_sprite(g->buffer,x_bitmap, x, y);
			theGrid[temp_x][temp_y] = 'X';
			changeTurn();
			AI_response();
		}
	}
	else if(turn == 'O')
	{	
		if(!(theGrid[temp_x][temp_y] == 'X' || theGrid[temp_x][temp_y] == 'O'))
		{	
			draw_sprite(g->buffer,o_bitmap, x, y);
			theGrid[temp_x][temp_y] = 'O';
			changeTurn();
			AI_response();
		}
	}
	
}

void Grid::fastBlitToRect(int x, int y)
{
	int temp_x = x;
	int temp_y = y;
	x *= static_cast<int>(scale_factor / 1.5) + 1;
	y *= static_cast<int>(scale_factor / 1.5) + 1;
	if(turn == 'X')
	{
		if(!(theGrid[temp_x][temp_y] == 'X' || theGrid[temp_x][temp_y] == 'O'))
		{
			draw_sprite(g->buffer,x_bitmap, x, y);
			theGrid[temp_x][temp_y] = 'X';
		}
	}
	else if(turn == 'O')
	{	
		if(!(theGrid[temp_x][temp_y] == 'X' || theGrid[temp_x][temp_y] == 'O'))
		{	
			draw_sprite(g->buffer,o_bitmap, x, y);
			theGrid[temp_x][temp_y] = 'O';
		}
	}
	
}

void Grid::changeTurn()
{
	if(turn == 'X')
	{
		turn = 'O';
	}
	else if(turn == 'O')
	{
		turn = 'X';
	}
}

bool Grid::AI_response()
{
	srand(time(0));	
	int x = rand()%3;
	int y = rand()%3;
	if(!(turn == 'O')) return false;
	if(theGrid[x][y] == 0)
	{
		fastBlitToRect(x, y);
		changeTurn();
		return true;
	}
	else
	{
		for(int i = 0; i < 3; i++)
			for(int j = 0; j < 3; j++)
			{
				if(theGrid[i][j] == 0) blitToRect(i, j);
				break;
			}
		return true;
	}
}


After getting the grid to work, I am now troubled with AI. I tried to get the AI to randomly select a square on the grid, and blit the 'O' to that square, but for some reason it's blitting either more than once per turn, or not at all. I've tried to find the error. I think that somewhere the turn is getting changed when it's not supposed to. This is the final hurdle before I complete the game! The end is so near! [Edited by - CodeTitan on March 26, 2005 4:28:40 PM]

Share this post


Link to post
Share on other sites
Zahlman    1682
Quote:

bool Grid::AI_response()
{
srand(time(0));
int x = rand()%3;
int y = rand()%3;
if(!(turn == 'O')) return false;
if(theGrid[x][y] == 0)
{
fastBlitToRect(x, y);
changeTurn();
return true;
}
else
{
for(int i = 0; i < 3; i++)
for(int j = 0; j < 3; j++)
{
if(theGrid[i][j] == 0) blitToRect(i, j);
break;
}
// OOPS! No changeTurn() here!
return true;
}


You're also calling AI_response in a few more places than I think you should be doing...

But actually, you shouldn't manage the turn logic like this at all, it's clumsy and (evidently) error-prone. Instead of tracking whose turn it is explicitly, just call AI_response() immediately after every successful move by the player (unless the player ended the game by winning or tying). That is, in the innermost game loop you just have a call to get player move (I guess that's "blitFromInput", immediately followed by a call to get AI move. Then check if the AI has won or tied the game as a result; if so, break out of the loop.

For drawing, since you no longer track whose turn it is explicitly, you'll need to tell blitToRect which symbol to draw. This is easy; you add a parameter for the symbol, then tell it to draw 'X' when you call it from blitFromInput, and 'O' when you call it from AI_response. Your blit function should not call AI_response (since that's handled already, and isn't part of the act of blitting anyway - Single Responsibility Rule, you know), and of course won't call ChangeTurn(), which you won't need any more.

Often, the solution is to simplify, not to make things more compliated. :) Speaking of which, I must insist that you fix this:


if(turn == 'X')
{
if(!(theGrid[temp_x][temp_y] == 'X' || theGrid[temp_x][temp_y] == 'O'))
{
draw_sprite(g->buffer,x_bitmap, x, y);
theGrid[temp_x][temp_y] = 'X';
changeTurn();
AI_response();
}
}
else if(turn == 'O')
{
if(!(theGrid[temp_x][temp_y] == 'X' || theGrid[temp_x][temp_y] == 'O'))
{
draw_sprite(g->buffer,o_bitmap, x, y);
theGrid[temp_x][temp_y] = 'O';
changeTurn();
AI_response();
}
}


Hmm, notice anything similar about the two cases? In the final code (once we eliminate the changeTurn() and AI_response() calls), this should end up like just:


if(!(theGrid[temp_x][temp_y] == 'X' || theGrid[temp_x][temp_y] == 'O')) {
// The only part that really changes here is which bitmap to
// use. If you're clever enough, there are ways to avoid the
// ugly 'if' here, too. Consider table or map lookup. Or
// represent the "turn" as a numeric 0 or 1, and use array
// lookup. (You'll need a separate array to look up the 'X'
// and 'O' symbols corresponding to the turn number in that case.)
if (turn == 'X') {
draw_sprite(g->buffer,x_bitmap, x, y);
} else {
draw_sprite(g->buffer,o_bitmap, x, y);
}
// "turn" is passed in now, rather than being a global.
// Notice how all we need to do is just supply it directly
// to theGrid.
theGrid[temp_x][temp_y] = turn;
}


Making your code shorter should make you happy. :) (Yes, this is quite a bit "shorter"; it doesn't look it because I put in a big block comment :P )

Share this post


Link to post
Share on other sites
garyfletcher    250
In order to decide on which players turn it is wouldn't it be easier to have a base player and 2 derived Classes. 1 for a human player and then a 2nd for a computer player.

The Classes would have a virtual makeMove() function, amongst other member data. The human class makeMove() function would simply take input and put the players playing piece (X or O) in the appropriate place. The computer class makeMove() function would contain the AI for making a move.

The game engine would have a pointer to a base player class and simply "switch" the current player at the end of each turn:

Classes:

class Player
(
Public:
Player();
virtual ~Player()
.
.
other member entries
.
.
virtual makeMove();
.
.
Rest of class
.
.
)

class HumanPlayer : Public Player
(
Public:
Player();
virtual ~Player()
.
.
other member entries
.
.
virtual makeMove();
.
.
Rest of class
.
.
)

class ComputerPlayer : Public Player
(
Public:
Player();
virtual ~Player()
.
.
other member entries
.
.
virtual makeMove();
.
.
Rest of class
.
.
)





Game Engine:


enum gameMode{HUMANVCOMP, HUMANVHUMAN, DEMO};

class GameEngine
{
Public:
GameEngine();
~GameEngine();
void Play(gameMode mode);
.
.
other member entries
.
.
Player *player1;
Player *player2;
Player *currPlayer;
.
.
Rest of class
.
.
}





Game Engine code (well a little):

void GameEngine::Play(gameMode mode)
{
.
.
Some Code
.
.
switch(mode)
{
case HUMANVCOMP:
{
player1 = new HumanPlayer();
player2 = new ComputerPlayer();
currPlayer = player1;
}
break;

case HUMANVHUMAN:
{
player1 = new HumanPlayer();
player2 = new HumanPlayer();
currPlayer = player1;
}
break;

case DEMO:
{
player1 = new ComputerPlayer();
player2 = new ComputerPlayer();
currPlayer = player1;
}
break;
}
.
.
Some Code
.
.
while (gameNotOver)
{
.
.
Some Code
.
.
if(player1->playing())
{
currPlayer = player2;
}
else
{
currPlayer = player1;
}

}
.
.
Some Code
.
.
}





Or something similar. If you'd like a quick look on how I did it then look here. It's only a text version but does have a semi decsent structure and AI.

Hope this helps a little.

Share this post


Link to post
Share on other sites
CodeTitan    258
So now the code looks like this:


void Grid::blitFromInput()
{
if(key[KEY_1_PAD]) blitToRect(0, 2, 'X');
if(key[KEY_2_PAD]) blitToRect(1, 2, 'X');
if(key[KEY_3_PAD]) blitToRect(2, 2, 'X');
if(key[KEY_4_PAD]) blitToRect(0, 1, 'X');
if(key[KEY_5_PAD]) blitToRect(1, 1, 'X');
if(key[KEY_6_PAD]) blitToRect(2, 1, 'X');
if(key[KEY_7_PAD]) blitToRect(0, 0, 'X');
if(key[KEY_8_PAD]) blitToRect(1, 0, 'X');
if(key[KEY_9_PAD]) blitToRect(2, 0, 'X');
}


void Grid::blitToRect(int x, int y, char turn)
{
int temp_x = x;
int temp_y = y;
x *= static_cast<int>(scale_factor / 1.5) + 1;
y *= static_cast<int>(scale_factor / 1.5) + 1;
if(!(theGrid[temp_x][temp_y] == 'X' || theGrid[temp_x][temp_y] == 'O')) {
if (turn == 'X') {
draw_sprite(g->buffer,x_bitmap, x, y);
} else {
draw_sprite(g->buffer,o_bitmap, x, y);
}
theGrid[temp_x][temp_y] = turn;
}


}


bool Grid::AI_response()
{
srand(time(0));
int x = rand()%3;
int y = rand()%3;
if(theGrid[x][y] == 0)
{
blitToRect(x, y, 'O');
return true;
}
else
{
for(int i = 0; i < 3; i++)
for(int j = 0; j < 3; j++)
{
if(theGrid[i][j] == 0) blitToRect(i, j, 'O');
break;
}
return true;
}
}




And my main loop has a call to Grid::blitFromInput().

What I'm confused about is where to call AI_reponse().

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this