Sign in to follow this  
Shining Blue

Tic Tac Toe

Recommended Posts

I made this for one of my classes. Just thought i'd share the source code. It's a console application in c++.
// prog3.cpp     Tic Tac Toe
//
//The artificial intelligence is found in the compMove() method in the
//class Board. First it checks for a winning move, then it 
//checks for a move to avoid losing. If neither a winning move or
//a move to avoid losing is found, it will evaluate each position using
//the evaluation fuction discussed in class. 
#include <iostream>
#include <string>
using std::cin;
using std::cout;
using std::endl;

class Board
{        
public:
		
	// I won't use the 0 index of board[].  Just 1 through 9
	char board[10];

	char chip;     //player's X/O
	char compChip; //computers X/O

	//pointer to character is a String
	//These strings are used to draw the board on the command line.
    char * line1;
    char * line2;
    char * line3;
    char * line4;   
    char * line5;
    char * line6;

	//constructor sets the strings used to draw the board
    Board() 
    {
        line1 = " 1| 2| 3";
        line2 = "__|__|__";
        line3 = " 4| 5| 6";
        line4 = "__|__|__";
        line5 = " 7| 8| 9";
        line6 = "  |  |  ";
    }

	//draws the board
    void drawBoard()
    {
        cout << endl;
        cout << line1 << endl;
        cout << line2 << endl;
        cout << line3 << endl;
        cout << line4 << endl;
        cout << line5 << endl;
        cout << line6 << endl;
    }

	//checks for all 8 possible winning rows. If a winning row is found
	//the game is over.
	bool checkForWin(char chip)
	{
		if(board[1]==chip && board[2]==chip && board[3]==chip)
		{
			return 1; //end game;
		}
		else if(board[4]==chip && board[5]==chip && board[6]==chip)
		{
			return 1; //end game;
		}
		else if(board[7]==chip && board[8]==chip && board[9]==chip)
		{
			return 1; //end game;
		}
		else if(board[1]==chip && board[4]==chip && board[7]==chip)
		{
			return 1; //end game;
		}
		else if(board[2]==chip && board[5]==chip && board[8]==chip)
		{
			return 1; //end game;
		}
		else if(board[3]==chip && board[6]==chip && board[9]==chip)
		{
			return 1; //end game;
		}
		else if(board[1]==chip && board[5]==chip && board[9]==chip)
		{
			return 1; //end game;
		}
		else if(board[3]==chip && board[5]==chip && board[7]==chip)
		{
			return 1; //end game;
		}
		else
			return 0; //keep playing
	}
	
	//same as above function but with an extra array parameter.
	bool checkForWin2(char chip, char array[10])
	{
		if(array[1]==chip && array[2]==chip && array[3]==chip)
		{
			return 1; //end game;
		}
		else if(array[4]==chip && array[5]==chip && array[6]==chip)
		{
			return 1; //end game;
		}
		else if(array[7]==chip && array[8]==chip && array[9]==chip)
		{
			return 1; //end game;
		}
		else if(array[1]==chip && array[4]==chip && array[7]==chip)
		{
			return 1; //end game;
		}
		else if(array[2]==chip && array[5]==chip && array[8]==chip)
		{
			return 1; //end game;
		}
		else if(array[3]==chip && array[6]==chip && array[9]==chip)
		{
			return 1; //end game;
		}
		else if(array[1]==chip && array[5]==chip && array[9]==chip)
		{
			return 1; //end game;
		}
		else if(array[3]==chip && array[5]==chip && array[7]==chip)
		{
			return 1; //end game;
		}
		else
			return 0; //keep playing
	}

	//If all the spaces on the board are taken, the game is tied. This function is called
	//after checkForWin() so there is no mistaking a win for a tie.
	bool checkForTie()
	{
		if(board[1]!='\0' && board[2]!='\0' && board[3]!='\0' && board[4]!='\0' && board[5]!='\0' 
		&& board[6]!='\0' && board[7]!='\0' && board[8]!='\0' && board[9]!='\0')
			return true;
		else
			return false;
	}

	//edit's the strings used to draw the Board.
	void editStrings(int position, char chipp)
	{
		switch(position)
		{
			case 1:
				*(line1+1) = chipp;		
				break;
			case 2:
				*(line1+4) = chipp;
				break;
			case 3:
				*(line1+7) = chipp;
				break;
			case 4:
				*(line3+1) = chipp;
				break;
			case 5:
				*(line3+4) = chipp;
				break;
			case 6:
				*(line3+7) = chipp;
				break;
			case 7:
				*(line5+1) = chipp;
				break;
			case 8:
				*(line5+4) = chipp;
				break;
			case 9:
				*(line5+7) = chipp;
				break;
		}
	}

	//This fuction allows the user to enter a position on the board.
	void playerMove(char chip)
	{
		CHOOSEAGAIN: //goto this label if they input an incorrect board position.
		drawBoard();
		int position;
		cout << "Enter your position: ";
		cin >> position;
		if(position<1 || position>9)
		{
			cout << "Must enter number 1-9\n";
			goto CHOOSEAGAIN;
		}
		if(board[position] != '\0')
		{
			cout << "That position is taken\n";
			goto CHOOSEAGAIN;
		}
		//they have selected a valid position, so assign it to the array
		board[position] = chip;

		//edit the strings in class Board
		editStrings(position, chip);
	}

	//Handles the computer's move. First it looks ahead for a winning move. Then looks
	//ahead for a move to avoid losing. If no winning or losing moves are found, it 
	//will then evaluate each possilbe move and choose the one with the highest score.
	void compMove(char compChip)
	{
		//look ahead for a win
		for(int i=1; i<=9; i++)
		{
			if(board[i]=='\0')//if the space is free
			{
				//copy the board
				char tempBoard[10];
				for(int n=0; n<=9; n++)
				{
					tempBoard[n]= board[n];
				}

				//assigning to the temporary board lets me "look ahead" one move
				tempBoard[i]= compChip;

				//if it found a win, take it
				if(checkForWin2(compChip, tempBoard))
				{
					board[i] = compChip; //makes the move
					editStrings(i,compChip);
					return;
				}
			}	
		}
		
		//look ahead for a loss. Similar to looking ahead for a win,
		//but i "look ahead" with the opponents chip.
		for(int i=1; i<=9; i++)
		{
			if(board[i]=='\0')//if the space is free
			{
				//copy the board
				char tempBoard[10];
				for(int n=0; n<=9; n++)
				{
					tempBoard[n]= board[n];
				}

				//assigning to the temporary board lets me "look ahead" one move
				tempBoard[i]= chip;

				//if it found a position that let's your oppenent win, take it
				//so the oppenent can't.
				if(checkForWin2(chip, tempBoard))
				{
					board[i] = compChip; //makes the move
					editStrings(i,compChip);
					return;
				}
			}	
		}

		//evaluate moves
		//array[] used to hold the evaluation score for each position
		int array[10];

		//fill array with very small initial scores. Smaller than any position 
		//can evaluate to during the game.
		for(int i=0; i<=9; i++)
		{
			array[i] = -1000;
		}
		//evaluate each space on the board
		for(int i=1; i<=9; i++)
		{
			if(board[i] == '\0')//if space is free, evaluate it
			{
				//copy the board
				char tempBoard[10];
				for(int n=0; n<=9; n++)
				{
					tempBoard[n]= board[n];
				}

				//assigning to the tempBoard lets me Look ahead 1 move before i actually
				//make it and computes the score using the evaluation function discussed
				//in class.
				tempBoard[i] = compChip;
				int score = 0;
				int compTotal = 0;
				int humanTotal =0;
				int r=0;
				int c=0;
				int d=0;
	
				//add up ways computer can win.
				//rows
				if((tempBoard[1]=='\0'||tempBoard[1]==compChip) && (tempBoard[2]=='\0'||tempBoard[2]==compChip) && (tempBoard[3]=='\0'||tempBoard[3]==compChip))
					r++;
				if((tempBoard[4]=='\0'||tempBoard[4]==compChip) && (tempBoard[5]=='\0'||tempBoard[5]==compChip) && (tempBoard[6]=='\0'||tempBoard[6]==compChip))
					r++;
				if((tempBoard[7]=='\0'||tempBoard[7]==compChip) && (tempBoard[8]=='\0'||tempBoard[8]==compChip) && (tempBoard[9]=='\0'||tempBoard[9]==compChip))
					r++;
				//columns
				if((tempBoard[1]=='\0'||tempBoard[1]==compChip) && (tempBoard[4]=='\0'||tempBoard[4]==compChip) && (tempBoard[7]=='\0'||tempBoard[7]==compChip))
					c++;
				if((tempBoard[2]=='\0'||tempBoard[2]==compChip) && (tempBoard[5]=='\0'||tempBoard[5]==compChip) && (tempBoard[8]=='\0'||tempBoard[8]==compChip))
					c++;
				if((tempBoard[3]=='\0'||tempBoard[3]==compChip) && (tempBoard[6]=='\0'||tempBoard[6]==compChip) && (tempBoard[9]=='\0'||tempBoard[9]==compChip))
					c++;
				//diagonals
				if((tempBoard[1]=='\0'||tempBoard[1]==compChip) && (tempBoard[5]=='\0'||tempBoard[5]==compChip) && (tempBoard[9]=='\0'||tempBoard[9]==compChip))
					d++;
				if((tempBoard[3]=='\0'||tempBoard[3]==compChip) && (tempBoard[5]=='\0'||tempBoard[5]==compChip) && (tempBoard[7]=='\0'||tempBoard[7]==compChip))
					d++;
				compTotal = r+c+d;
				

				//add up ways the human can win.
				r=0;
				c=0;
				d=0;
				//rows
				if((tempBoard[1]=='\0'||tempBoard[1]!=compChip) && (tempBoard[2]=='\0'||tempBoard[2]!=compChip) && (tempBoard[3]=='\0'||tempBoard[3]!=compChip))
					r++;
				if((tempBoard[4]=='\0'||tempBoard[4]!=compChip) && (tempBoard[5]=='\0'||tempBoard[5]!=compChip) && (tempBoard[6]=='\0'||tempBoard[6]!=compChip))
					r++;
				if((tempBoard[7]=='\0'||tempBoard[7]!=compChip) && (tempBoard[8]=='\0'||tempBoard[8]!=compChip) && (tempBoard[9]=='\0'||tempBoard[9]!=compChip))
					r++;
				//columns
				if((tempBoard[1]=='\0'||tempBoard[1]!=compChip) && (tempBoard[4]=='\0'||tempBoard[4]!=compChip) && (tempBoard[7]=='\0'||tempBoard[7]!=compChip))
					c++;
				if((tempBoard[2]=='\0'||tempBoard[2]!=compChip) && (tempBoard[5]=='\0'||tempBoard[5]!=compChip) && (tempBoard[8]=='\0'||tempBoard[8]!=compChip))
					c++;
				if((tempBoard[3]=='\0'||tempBoard[3]!=compChip) && (tempBoard[6]=='\0'||tempBoard[6]!=compChip) && (tempBoard[9]=='\0'||tempBoard[9]!=compChip))
					c++;
				//diagonals
				if((tempBoard[1]=='\0'||tempBoard[1]!=compChip) && (tempBoard[5]=='\0'||tempBoard[5]!=compChip) && (tempBoard[9]=='\0'||tempBoard[9]!=compChip))
					d++;
				if((tempBoard[3]=='\0'||tempBoard[3]!=compChip) && (tempBoard[5]=='\0'||tempBoard[5]!=compChip) && (tempBoard[7]=='\0'||tempBoard[7]!=compChip))
					d++;
				humanTotal = r+d+c;
				score = compTotal - humanTotal;
				//assign the score to array[]
				array[i]=score;
			}
			else //space is not free
			{
				//if the space is already taken, give it a super
				//low score so the computer doens't move there.
				array[i]=-10000000;
			}
		}//end for loop

		//The index of the highest score in array[] will be the index
		//of board[] that the computer will put it's chip on.
		int bestScore = -100;
		int bestPosition;
		for(int i=1; i<=9; i++)
		{
			if(array[i]>bestScore)
			{
				bestScore=array[i];
				bestPosition=i;
			}
		}
		//bestPosition has been found so put the chip on the board.
		board[bestPosition] = compChip;

		//edit the strings that display the board on the command line
		editStrings(bestPosition, compChip);
	}
};//end class Board


int main()
{
    START:           //This label is used to reset the game.
    bool first;      //if human player goes first, then true
	Board b;         //Board object

	cout << "PRESS CONTROL + C TO QUIT THE GAME AT ANY TIME\n";

	//prompt for chip type
    cout << "Do you want to be X or O? ";
    cin >> b.chip;
    if(b.chip != 'x' && b.chip != 'o' && b.chip != 'X' && b.chip != 'O')
    {
        cout << "You must enter X or O \n\n";
        goto START; 
    }
	//the computer's chip is the opposite of the players.
	if(b.chip=='x' || b.chip=='X')
		b.compChip = 'O';
	else
		b.compChip = 'X';

	//ask whether 1st or 2cd
    cout << "Do you want to go first? Y/N ";
    char yn;
    cin >> yn;
    if(yn=='y' || yn=='Y')
        first = true;
    else if(yn=='n' || yn=='N')
        first = false;
    else
    {
        cout << "You must enter 'Y' or 'N'\n\n";
        goto START; 
    }

	//fill each position of b.board[] with null '\0'
    for(int i=0; i<=9; i++)
    {
		b.board[i] = '\0';
    }

	//infinite loop exited with break statment when the game is over.
    while(true)
    {
        if(first)//human player 1st
		{
			//human moves, then checks to see if you won or tied.
			b.playerMove(b.chip);
			if(b.checkForWin(b.chip))
			{
				b.drawBoard();
				cout << "You won!!!\n";
				break;
			}
			if(b.checkForTie())
			{
				b.drawBoard();
				cout << "Tie game!!!\n";
				break;
			}

			//computer moves, then checks if it won or tied.
			b.compMove(b.compChip);
			if(b.checkForWin(b.compChip))
			{
				b.drawBoard();
				cout << "You lose!!!\n";
				break;
			}
			if(b.checkForTie())
			{
				b.drawBoard();
				cout << "Tie game!!!\n";
				break;
			}
		}
		else //human player moves 2cd
		{
			//human moves, then checks to see if you won or tied.
			b.compMove(b.compChip);
			if(b.checkForWin(b.compChip))
			{
				b.drawBoard();
				cout << "You lose!!!\n";
				break;
			}
			if(b.checkForTie())
			{
				b.drawBoard();
				cout << "Tie game!!!\n";
				break;
			}

			//computer moves, then checks if it won or tied.
			b.playerMove(b.chip);
			if(b.checkForWin(b.chip)) /*check for win*/
			{
				b.drawBoard();
				cout << "You won!!!\n";
				break;
			}
			if(b.checkForTie()) /*check for tie*/
			{
				b.drawBoard();
				cout << "Tie game!!!\n";
				break;
			}
		}
    }//end infinite loop

	return 0;
}//end main()

Share this post


Link to post
Share on other sites
The game crashes whenever I make a move. Also, have you thought about spreading your code over the span of more than one files? It makes everything much more easier to find and readable.

Share this post


Link to post
Share on other sites
It runs just fine on my home computer(windows XP) when complied it with Visual STudio.NET C++.

When i ftp'd it to ark (sun system) and compiled it with the g++ compiler, I got a segmentation fault whenever i made a move. Not sure why it is crashing on the non-windows systems, but it does work.

Ya, I probably should have the functions for the board Class in ther own file. compMove() is a huge function.

Share this post


Link to post
Share on other sites
I'm running Windows XP as well and compiled it in VC++.NET and there weere no compile errors but it still crashed when I made a move. Have you ever tried running your program in debug mode? It sure helps a lot when your facing a runtime error and you can't find what line(s) of code is causing it.

Share this post


Link to post
Share on other sites
See my comments in-line with the source.
I reformatted things to match my preferred style a bit more. :/


class Board {
public:
// I won't use the 0 index of board[]. Just 1 through 9
char board[10];
// no no no no no. Trust me, to be an effective programmer you must learn to
// work with zero-based indexing rather than against it.
// Also, you didn't initialize the board in your constructor. That's bad.
// It explains things working in debug mode and not release mode, too.
// Also, learn to use initializer lists in constructors.

char chip; //player's X/O
char compChip; //computers X/O
// public data is bad, btw. You should be determining the player's or
// computer's X/O in the constructor, according to a constructor parameter
// telling you who moves first. That makes sure the two values are consistent
// and don't get modified later.

//pointer to character is a String
// No, it isn't. You included <string>. Use it for heaven's sake.
// Also, use an array for these variables, so you can iterate over them
// in drawBoard, etc. Any time you want to put a number into a variable
// name, this is a sign that something's not right.

//checks for all 8 possible winning rows. If a winning row is found
//the game is over.
bool checkForWin(char chip) {
if(board[1]==chip && board[2]==chip && board[3]==chip) {
return 1; //end game;
} else if(board[4]==chip && board[5]==chip && board[6]==chip) {
return 1; //end game;
}
// etc.
// There are more concise ways to express this sort of thing, but it's
// OK for now. Writing the 1's and 0's for a function returning bool is
// bad stylistically though; return true and false like you do later on.
}

//same as above function but with an extra array parameter.
bool checkForWin2(char chip, char array[10]) {
// Why??? You shouldn't have arrays representing a board, but just separate
// Board objects, and you call the checkForWin on the appropriate Board.
// That's why you make a Board class in the first place. Although I don't
// see why you'd need that, even.
// Also, having cut-and-paste things like this is bad practice in general.
// If you must create something like this in the future, make one of the
// functions *call* the other, e.g.:
// bool checkForWin(char chip) {
// checkForWin2(chip, board);
// }
// Finally, methods that don't actually rely on the Board state should
// usually be declared 'static'.
}

//If all the spaces on the board are taken, the game is tied. This function is called
//after checkForWin() so there is no mistaking a win for a tie.
bool checkForTie() {
if(etc)
return true;
else
return false;
// Instead, please please please get in the habit of writing this as:
// return etc;
// If it's a long expression like it is here, you may want to put brackets
// around it for clarity.
// The if-else logic here adds code and removes clarity. Instead of saying
// "tell me whether every square is occupied", it says "if every square
// is occupied, tell me every square is occupied; otherwise, tell me
// not every square is occupied".
}

//edit's the strings used to draw the Board.
void editStrings(int position, char chipp) {
// It's ok to have a parameter with the same name as a member variable.
// Inside that method, the name will default to referring to the parameter.
// To force using the member variable, you can write this->chip.
switch(position) {
// AARGH. Use arithmetic to deal with these things and avoid the
// redundancy. This is why you must learn to use zero-based indexing
// and arrays of similar variables.
// BTW, it's true that asking the user to start counting at zero makes
// for bad UI. You should do conversions right at the "surface layer".
// The whole switch statement could then be reduced to:
// position -= 1; // convert to zero-based index
// if (position < 0 || position > 8) return; // reject bad input
// // now use div and mod to get a row/column value, and scale the
// // column value appropriately for the ascii graphics.
// line[position/3][position % 3 * 3 + 1] = chip;
// (Here I assume the "constant" lines are not stored in the array.)
}
}
// Although, it looks really bad that you have this "editStrings" thing that
// gets called to update the string contents each time a move is made, and
// you also record the moves inside a 'board' array.
// This duplicates the information about where the pieces are.
// Instead, try to make it so that the drawBoard() *looks at the board[]*
// and draws it piece by piece.

//This fuction allows the user to enter a position on the board.
void playerMove(char chip) {
CHOOSEAGAIN: //goto this label if they input an incorrect board position.
// Gotos have their place, but *emulating a while loop* is not that place.
// URRRGH. The baby Stroustrup cries.
drawBoard();
int position;
cout << "Enter your position: ";
cin >> position;
// NOTE: You are completely and utterly screwed if something non-numeric
// is entered here. For more information, see:
// http://www.augustcouncil.com/~tgibson/tutorial/iotips.html

// etc.
}
// Also, I/O type stuff like this generally should *not* go inside the class.
// You should get the move from the user in the main loop, and then call
// a Board method to actually make the move.

//Handles the computer's move. First it looks ahead for a winning move. Then looks
//ahead for a move to avoid losing. If no winning or losing moves are found, it
//will then evaluate each possilbe move and choose the one with the highest score.
void compMove(char compChip) {
//look ahead for a win
for(int i=1; i<=9; i++) {
if(board[i]=='\0') { //if the space is free
//copy the board
char tempBoard[10];
for(int n=0; n<=9; n++) {
tempBoard[n]= board[n];
}

//assigning to the temporary board lets me "look ahead" one move
tempBoard[i]= compChip;

// Oh, now I see what you're doing. But um... in tic-tac-toe, it is
// trivial to undo a move, so why not just make it on the existing
// board?

//if it found a win, take it
if(checkForWin2(compChip, tempBoard)) {
board[i] = compChip; //makes the move
editStrings(i,compChip);
// You see the burden you have with this duplication - every time,
// you have to update the array AND call editStrings. Doh.
return; // obviously not needed.
}
}
}

//look ahead for a loss. Similar to looking ahead for a win,
//but i "look ahead" with the opponents chip.
for(int i=1; i<=9; i++) {
// (snipped out loop body)
// Doesn't this part look rather familiar? o_O
// You need a function like "checkIfMoveWins(int location, char player)".
}

// etc.

//fill array with very small initial scores. Smaller than any position
//can evaluate to during the game.
for(int i=0; i<=9; i++) {
array[i] = -1000;
}
// There are cleaner ways to express "very small value", but this is OK
// for now.

//evaluate each space on the board
for(int i=1; i<=9; i++) {
if(board[i] == '\0') { //if space is free, evaluate it
// etc.

//add up ways computer can win.
//rows
if((tempBoard[1]=='\0'||tempBoard[1]==compChip) && (tempBoard[2]=='\0'||tempBoard[2]==compChip) && (tempBoard[3]=='\0'||tempBoard[3]==compChip))
r++;
// Doesn't this stuff look kind of like the checking you do for
// an outright win? Maybe you need a helper function like
// int fullLines(bool allowX, bool allowO, bool allowEmpty) {
// // calculate number of lines containing only allowed symbols
// }
// Oh, adding 'r', 'c', and 'd' separately is of no real value.

//add up ways the human can win.
// And again, duplication. Duplication is bad mm'kay?
// And then we get:
score = compTotal - humanTotal;
//assign the score to array[]
array[i]=score;
} else { //space is not free
//if the space is already taken, give it a super
//low score so the computer doens't move there.
array[i]=-10000000;
// This is bad; it's again a 'magic number', and when you come back
// you are going to wonder why this isn't the same '-1000' value as
// you used before for initialization.
// Actually, why do you bother changing it at all? Just leave in the
// initialized 'low value'.
}
}//end for loop

//The index of the highest score in array[] will be the index
//of board[] that the computer will put it's chip on.
int bestScore = -100;
// And again, a different number for the "low value", why?
int bestPosition;
for(int i=1; i<=9; i++) {
if(array[i]>bestScore) {
bestScore=array[i];
bestPosition=i;
}
}
// And the usual comment about placing pieces.
}
};//end class Board

// Actually, having a Board class isn't very useful at all for this kind of
// game. I assume they want you to learn some OO programming or something.
// If the justification is that thin, you really need to find a more competent
// teacher who can give you a better problem. :P
int main() {
START: //This label is used to reset the game.
// Same comment about while loops.
bool first; //if human player goes first, then true
// Why not instead give this a name that reflects its meaning? e.g.
// "bool playerGoesFirst;" <-- now no comment is needed.
Board b; //Board object
// Don't construct the board until you know who goes first (see comments
// in the Board class about improving the constructor).

cout << "PRESS CONTROL + C TO QUIT THE GAME AT ANY TIME\n";
// This is something the OS implements for you, and probably isn't worth
// advertising ;)

//prompt for chip type
cout << "Do you want to be X or O? ";
cin >> b.chip;
if(b.chip != 'x' && b.chip != 'o' && b.chip != 'X' && b.chip != 'O')
{
cout << "You must enter X or O \n\n";
goto START;
}
//the computer's chip is the opposite of the players.
if(b.chip=='x' || b.chip=='X')
b.compChip = 'O';
else
b.compChip = 'X';
// nonononononono. This is what constructors are for.
// This is logic specific to the board, so it goes in the Board class.
// (Again, assuming you bother with one after all.)
// Anything worth doing is worth doing right, and that goes for OO too.

//ask whether 1st or 2cd
cout << "Do you want to go first? Y/N ";
char yn;
// etc.
// I've never heard of a tic-tac-toe variant where the symbol did not
// determine who played first. Or did you want to support both "tic-tac-toe"
// and "noughts and crosses"? :)

//fill each position of b.board[] with null '\0'
for(int i=0; i<=9; i++) {
b.board[i] = '\0';
}
// Ditto here. This goes in your constructor dammit.

//infinite loop exited with break statment when the game is over.
while(true) {
if(first) { //human player 1st
//human moves, then checks to see if you won or tied.
b.playerMove(b.chip);
if(b.checkForWin(b.chip)) {
b.drawBoard();
cout << "You won!!!\n";
break;
}
if(b.checkForTie()) {
b.drawBoard();
cout << "Tie game!!!\n";
break;
}

//computer moves, then checks if it won or tied.
b.compMove(b.compChip);
if(b.checkForWin(b.compChip)) {
b.drawBoard();
cout << "You lose!!!\n";
break;
}
if(b.checkForTie()) {
b.drawBoard();
cout << "Tie game!!!\n";
break;
}
} else { //human player moves 2cd
// GAH! Now you duplicated all the code, just in order to reverse the
// order of movement. Checking for a win/tie should be an integral part
// of making the move; you could have the move-making function return
// some value to indicate the result of the move (player win/comp win/
// tie/game still going).

// And guess what? You *didn't* actually reverse the order, so you
// introduced a *bug* by your copy-and-paste. See how evil duplication is!

// All you really need to do is this:
// if (!first), do a compMove() before the loop.
// Within the loop, do a playerMove(), which *calls compMove()* since
// play will always alternate, and returns the game status.
// If it's not "game still going", then output something and break.
// Also, you should set it up so that you always draw the board *after*
// a move, so that you don't have to draw it before and then also draw
// after in each game-ending case. That way you avoid having to write
// the drawBoard() call four times.
}
}//end infinite loop

return 0; // not needed
}//end main()


Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Zahlman, I'm very impressed. I wish I had that much patience as you. Very good job. I hope Shining Blue will take the this as a constructive criticism.

Share this post


Link to post
Share on other sites
Thanks for taking the time look at the code. I think i'll go back and re-write the entire thing using your suggestions and give it a graphical interface.

The one thing I disagree is about the 0 index of arrays. I never use the 0 index. What's the big deal if you waste 1 word of memory? Using 1 through 9 lets the index of the array directly coorelate the the 1-9 of the tic tac toe board. You don't have to do array[i-1] each time and can just do array[i]. Other than wasting a word of memory, i don't see anything wrong with it.

Share this post


Link to post
Share on other sites
I'd just like to point out that in your version of the code you're writing to readonly memory.

For example, " 1| 2| 3" is read only, it's part of the program and you can't change it. You set line1 to point to it, and then later you try to change it through line1.

In an ideal world assigning a const array to a non const pointer would generate an error, or at least a warning. I assume they don't because of all the old code it would mess up.

With GCC you can use '-Wcast-qual' to warn you when you do that. Strangely it isn't enabled by -Wall or -Wextra. It really should be. All well.

Share this post


Link to post
Share on other sites
re arrays starting at 0... please, it's more logical! Don't fight logic, work with it!


const int numsquares = 9;

int squares[numsquares];
for(int s = 0; s < numsquares; ++s)
{
blah;
}


no -1's in that. Also, I can write this:

int squareat(x, y)
{
return(squares[y * 3 + x]);
}



Now, you start at 1. Do you start at 1 in both directions? It looks to me as though you start at 0 in the y direction, hence getting this:
return(squares[y * 3 + x - 1]);

surely your array should start at element 4 if you want to index from 1?
return(squares[(y - 1] * 3 + x - 1]);

The lack of logic begins to show.

Real world examples of arrays starting at 0 or 1:

English houses start on the ground floor, then the first floor. US houses start on the first floor, then the second.

The first year of the CE was year number 1, and was the first year of the first millenium, hence the 3rd millenium began on the first day of 2001, and all but one of the years of the 19th century began with 18xx. Do you really think this is sensible?

When I was 6 months old my age was 0. (We got that one right atleast! Ages are so much less confusing than centuries because of that).

Anyone else want to give an example? ;)

In pure coding sense, the Nth element of an array is at the address of the array + N * the size of the element, so long as you start at 0.
array[n] == *(array + n)


The most horrible one I've ever seen is Visual Basic, which when you ask for an array of length 9 gives you an array of length 10, starting at 0, so you can choose which system you want, and then uses an inconsistent convention in it's own APIs. BLAH!

Share this post


Link to post
Share on other sites
Well just think about the tic-tac-toe board of 1-9. If i changed it to 0-8, then i would see a reason to make my array from 0-8 also.

But since the board is 1-9, the user will enter a number, say 5. Now I can say board[5] = chip. I don't have to say
board[5-1] = chip. Using the 0 index makes me always have to think "-1" in my head. In the culture i was raised you start counting at 1, and starting at 1 works best for me.

If your from america, you never hear anyone say "please turn to chapter 0 of your book". Everything starts at 1 here. The word "first" is synamomous with "one" in america. But i've noticed a few of my comp Sci books start at chapter 0. :)






Share this post


Link to post
Share on other sites
It's not so bad, after a while you'll get the hang of starting at 0. Soon you'll find your self in a building trying to get to the zeroth floor, not be able to find it, panic, and jump out a window.

Just kidding about that last part. Maybe.

The reason it starts at 0 is because it's really just an offset. *array is the first element, and *(array+1) is the second, and *(array+n) is the n-1th. array[n] is just a shortcut way of writing *(array+n). It'll make sense some day after you've learned about pointers and all those other scary things people always seem to screw up.

Share this post


Link to post
Share on other sites
well see when your using the 0 index, you dont do the -1 every time. the only time that the number should be on greater than the actual index is for user clearity. Ok, you have 1-9, and the user enters 1, instead of wasting that one index (which depending on what its an array OF can be a fairly large impact on memory) just have the very next line take 1 away. The user never has to know, so the still enter the "normal" numbers, and you dont have to use board[x-1] every time. Like this...


cout<<"Enter 1-9: ";
cin>>x;
x--;
board[x]="X";



wasting the memory isnt a big deal when your dealing with little data types, but if you get into large classes, and then have multiple arrays, your quickly going to be throwing memory out the window.

Alima

Share this post


Link to post
Share on other sites
Alima. You still had to subtract 1 from x.......
That's no different than array[x-1].

The program actually gets a small speed boost by starting the array at 1. I don't have to subtract 1 from X each time the user enters an index. Maybe i save a billioth of a second. Not much, but neither is the 1 word of memory you save by starting at 0.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
"I never use the 0 index. What's the big deal if you waste 1 word of memory?"

what about an array of a structure or class that is say 64 bytes large, and you have 100 arrays of it in memory? that's 6.25MB of memory wasted..

arrays in c, c++ and other languages are zero based. it's much more logical. stop fighting it.

Share this post


Link to post
Share on other sites
Given a somewhat more intuitive data format (a 2 dimensional array for a 2 dimensional grid) we have:
int grid[width][height]

or in your case
int grid[width+1][height+1]

where width = height = 3.

now, the user would enter "row 2, column 2" meaning the center square.

In my grid, that is grid[2-1][2-1], which is the -1's you dont like.
In your case it would be grid[2][2]. I agree. You save 2 subtractions.
You would also use 16 grid cells to represent 9 (wasting 7!) :o)

What I was trying to say before is that you are using a 1 dimensional array to represent 2 dimensional data, and in 1 dimension you begin your array at 1, and in the other dimension you begin your array at 0. Hence you waste 1 grid cell and confuse the life out of everybody else who is ever unfortunate enough to have to read your code :)

Share this post


Link to post
Share on other sites
To be honest, I find it easier to check with a 1 dimensional array.

This is our board:

0 1 2
3 4 5
6 7 8


Finding all the locations to check to see if someone has won is simple:

Starting at 0, 3, and 6, and stepping by 1:

* * *
. . .
. . .


. . .
* * *
. . .


. . .
. . .
* * *


Starting at 0, 1, and 2, and stepping by 3:

* . .
* . .
* . .


. * .
. * .
. * .


. . *
. . *
. . *


Starting at 0, and stepping by 4:

* . .
. * .
. . *


Starting at 2, and stepping by 2:

. . *
. * .
* . .

Share this post


Link to post
Share on other sites
The loss of one word isn't that big of a deal...in THIS program.

But as others pointed out, this is a bad precedent. C and C++ use 0 based arrays. To ignore the first element now might start a bad habit of always ignoring the first element. You find it more convenient, but when you use iterators, you'll confuse yourself. You'll find it confusing to use things like:

sort(array.begin(), array.end());

array.begin() gives an iterator that points to the first element in array, whatever array is. array.end() gives another iterator that points to one-past-the-end of array.

While starting at 0 now results in you taking someone's input and incrementing it before you use it, you'll find it also works much more naturally for other things, such as Squirm's code above.

This program isn't a big deal, but don't start a bad habit because they are tough to break.

Share this post


Link to post
Share on other sites
Ya, if you use a 2-d array, then the wasted memory is:
(dimension1 + (dimension2-1)).

But since i'm not using a multidimensional array of huge structures, the only memory you can complain about is 1 word. And that's nothing to complain about.

Share this post


Link to post
Share on other sites
What the heck is this, the inquisition?

If he likes to skip the first entry in the array for simplicities sake, thats his choise. After all, it is his program, he can adopt any programming style he want in it. If you dont like it, you can change it or just dont use it.

This is no better than the "C is better than C++ ", "OpenGL rocks, DX sucks" wars. It's a matter of taste and preference.

He is aware that arrays start at zero, but in this case he made an exception. It's not like the world comes to an end.

Share this post


Link to post
Share on other sites
Quote:
Original post by GBGames
The loss of one word isn't that big of a deal...in THIS program.

But as others pointed out, this is a bad precedent. C and C++ use 0 based arrays. To ignore the first element now might start a bad habit of always ignoring the first element. You find it more convenient, but when you use iterators, you'll confuse yourself. You'll find it confusing to use things like:

sort(array.begin(), array.end());

array.begin() gives an iterator that points to the first element in array, whatever array is. array.end() gives another iterator that points to one-past-the-end of array.

While starting at 0 now results in you taking someone's input and incrementing it before you use it, you'll find it also works much more naturally for other things, such as Squirm's code above.

This program isn't a big deal, but don't start a bad habit because they are tough to break.


you make a good point about iterators, he was complaining about having to add a -1, but if he used iterators, with his current style, his code would look like this:

sort(array.begin()+1, array.end()+1);

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Quote:
Original post by Shining Blue
Alima. You still had to subtract 1 from x.......
That's no different than array[x-1].


actualy, its a lot different. i didnt say you would NEVER have to subtract 1 just that you dont have to subtract 1 EVERY time you call the appropriate index of that array. You do the -1 right away so when you call the index of the board array for what ever reason (assigning a value, comparing, etc) you use board[x] instead of board[x-1] every time. That makes it a lot more convinient when you use the array say 50 times, you can still use the standard indexing, and not have to type -1 in for each of those 50 times.

You are right though that the memory doesnt really matter here, but as i said earlier, and as the AP very nicley elaborated on, when you get into multiple arrays of structs and classes, the wasted memory can quickly add up.

Alima

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