Jump to content
  • Advertisement
Sign in to follow this  
eektor

My First Game - sort of

This topic is 4915 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Ok here's the updated code with no bugs in it I believe.


// New Tic Tac Toe
// A variation of the basic game of tic tac toe

#include <iostream>
#include <string>
#include <vector>
#include <sstream>

using namespace std;

//global constants
const char X = 'X';
const char O = 'O';
const char EMPTY = ' ';
const char NO_ONE = 'N';
const int NUM_SQUARES = 9;

// Board class
class Board
{
private:
char storage[NUM_SQUARES];
public:
Board();
char show(int where){return storage[where];}
void place(int where, char what){storage[where] = what;};
bool IsLegal(int where) const;
void display()const;
int SizeOf(){return NUM_SQUARES;};
};

Board::Board()
{
for(int i = 0; i < NUM_SQUARES; ++i)
storage = EMPTY;
}


bool Board::IsLegal(int where) const
{
return (storage[where] == EMPTY);
}

void Board::display() const
{
cout << "\n\t" << storage[0] << " | " << storage[1] << " | " << storage[2];
cout << "\n\t" << storage[3] << " | " << storage[4] << " | " << storage[5];
cout << "\n\t" << storage[6] << " | " << storage[7] << " | " << storage[8];
cout << "\n\n";
}

// function prototypes
void Instructions();
char AskYesNo(string question);
int AskNumber(string question, int high, int low = 1);
char HumanPiece();
char SwitchPiece(char piece);
char Winner(Board& board);
int HumanMove(Board& board, char player);
int ComputerMove(Board board, char computer);
void AnnounceWinner(char winner, char player, char computer);

// main function
int main()
{
int turn=0;
int current_move;
const int NUM_PIECES = 6;
vector<int> move(NUM_PIECES,10);
Board board;

Instructions();
char human = HumanPiece();
char computer = SwitchPiece(human);
char current_player = X;
board.display();

while (Winner(board) == NO_ONE)
{
if (current_player == human)
{
current_move = HumanMove(board, human);
if (move[turn%NUM_PIECES] < 10)
board.place(move[turn%NUM_PIECES], EMPTY);
move[turn%NUM_PIECES] = current_move;
board.place(current_move, human);
turn++;
}
else
{
current_move = ComputerMove(board, computer);
if (move[turn%NUM_PIECES] < 10)
board.place(move[turn%NUM_PIECES], EMPTY);
move[turn%NUM_PIECES] = current_move;
board.place(current_move, computer);
turn++;
}
board.display();
current_player = SwitchPiece(current_player);
}

AnnounceWinner(Winner(board), computer, human);

system("PAUSE");



return 0;
}

// function definitions
void Instructions()
{
cout << "Welcome to the new and improved Tic Tac Toe!\n\n";
cout << "This game is a little bit more complicated than the original.\n";
cout << "First of all each player can have only three pieces on the board,\n";
cout << "for a total of just 6 pieces.\n\n";
cout << "On the turn after you place your third piece, the first piece\n";
cout << "that you place will be taken out and you will have to place it\n";
cout << "somewhere else and not in the same spot.\n\n";

cout << "You can make your move by entering a number 1 - 9. The number\n";
cout << "corresponds to the desired board position, as illustrated:\n\n";

cout << " 1 | 2 | 3\n";
cout << " -----------\n";
cout << " 4 | 5 | 6\n";
cout << " -----------\n";
cout << " 7 | 8 | 9\n\n";

cout << "Let's get started!\n";
}

char AskYesNo(string question)
{
char response;
do
{
cout << question << " (y/n): ";
cin >> response;
}while (response != 'y' && response != 'n');

return response;
}

int AskNumber(string question, int high, int low)
{
int number;
do
{
cout << question << " (" << low << " - " << high << "): ";
cin >> number;
}while (number > high || number < low);

return number;
}

char HumanPiece()
{
char go_first = AskYesNo("Do you want to move first?");
if (go_first == 'y')
{
cout << "\nThen take the first move.\n";
return X;
}
else
{
cout << "\nAlright, I will go first.\n";
return O;
}
}

char SwitchPiece(char piece)
{
if (piece == X)
return O;
else
return X;
}


char Winner(Board& board)
{
// all possible winning rows
const int WINNING_ROWS[8][3] = { {0, 1, 2}, {3, 4, 5},
{6, 7, 8}, {0, 3, 6},
{1, 4, 7}, {2, 5, 8},
{0, 4, 8}, {2, 4, 6} };

const int TOTAL_ROWS = 8;

// if any winning row has three values that are the same (and not EMPTY),
// then we have a winner
for(int row = 0; row < TOTAL_ROWS; ++row)
{
if ( (board.show(WINNING_ROWS[row][0]) != EMPTY) &&
(board.show(WINNING_ROWS[row][0]) == board.show(WINNING_ROWS[row][1])) &&
(board.show(WINNING_ROWS[row][1]) == board.show(WINNING_ROWS[row][2])) )
{
return board.show(WINNING_ROWS[row][0]);
}
}


// since nobody has won, the game is not over
return NO_ONE;
}


int HumanMove(Board& board, char player)
{
int move = AskNumber("Where will you move?", (board.SizeOf()));
while(!(board.IsLegal(move-1)))
{
cout << "\nSorry, you cannot move there.\n";
move = AskNumber("Where will you move?", (board.SizeOf()));
}
cout << "Ok.\n";
return move-1;
}

int ComputerMove(Board board, char computer)
{
cout << "I shall take square number ";

// if coumputer can win on next move, make that move
for(int move = 0; move < board.SizeOf(); ++move)
{
if (board.IsLegal(move))
{
board.place(move, computer);
if (Winner(board) == computer)
{
cout << move + 1 << endl;
return move;
}
// done checking this move, undo it
board.place(move, EMPTY);
}
}

// if human can win on next move, block that move
char human = SwitchPiece(computer);
for(int move = 0; move < board.SizeOf(); ++move)
{
if(board.IsLegal(move))
{
board.place(move, human);
if (Winner(board) == human)
{
cout << move + 1 << endl;
return move;
}
// done checking this move, undo it
board.place(move, EMPTY);
}
}

// the best moves to make, in order
const int BEST_MOVES[] = {4, 0, 2, 6, 8, 1, 3, 5, 7};
// since no one can win on next move, pick the best open square
for(int i = 0; i < board.SizeOf(); ++i)
{
int move = BEST_MOVES;
if (board.IsLegal(move))
{
cout << move + 1 << endl;
return move;
}
}
}

void AnnounceWinner(char winner, char computer, char human)
{
if (winner == computer)
{
cout << winner << "'s won!\n";
cout << "Good game. Practice some more and then play me again\n";
}

else
{
cout << winner << "'s won!\n";
cout << "Wow! You got lucky. Let's have a rematch soon.\n";
}
}


Also I tried implementing the stringstream into the AskNumber function but I couldn't get rid of the bugs. I had no problem compiling it, but somehow when the program asked for your move it would keep cycling over the question. This is the code I had for that function when I was trying to get it to work:


int AskNumber(string question, int high, int low)
{
int number;
stringstream ss;
do
{
cout << question << " (" << low << " - " << high << "): ";
string input;
getline(cin,input);
ss.str(input);
}while (!(ss >> number) || number > high || number < low);

return number;
}


Thank you so much for the help so far. I've learned a lot from this thread.


Share this post


Link to post
Share on other sites
Advertisement
Quote:
Original post by eektor
That was the problem Deniz. Thank you very much.


Yeah, I keep doing that. :(

Quote:
I had no problem compiling it, but somehow when the program asked for your move it would keep cycling over the question. This is the code I had for that function when I was trying to get it to work:


Oh. Lol. Of course, since the stream construction is now pulled out of the loop, you don't get a new one each time, and garbage input the first time around makes the stream 'fail' and so on.

Potential fixes:

1) move the construction back into the loop, and do the check inside the loop: if the value isn't a number, then 'continue;' (that skips the while-check).

For example, this works:


do
{
cout << question << " (" << low << " - " << high << "): ";
string input;
getline(cin,input);
if (!(stringstream(input) >> number)) continue;
} while (number > high || number < low);



2) Extract the parsing to a separate function in order to solve the scoping problem. Something like


bool getNumber(int& result, string source) {
stringstream ss(source);
return ss >> result;
}

// ...
do
{
cout << question << " (" << low << " - " << high << "): ";
string input;
getline(cin,input);
} while (!getNumber(number, input) || number > high || number < low);



3) Use boost::lexical_cast, which is basically another way of wrapping the parsing up in another function, and is more robust too. Although this way, you'll have to catch an exception to handle the failure case and I'm not exactly sure how the logic would go.

4) Leave the stringstream outside the loop, and reset it manually each time. (I'm rather disturbed by the revelation that setting the buffer contents with .str() apparently doesn't reset the stream. :( ) This way loses some of the advantage of not having to manipulate weird stuff with the stream, but you still don't have to .ignore(), and you can still do further parsing on the input (and think about the input a line at a time). Also, this way *might* be more efficient - not that it matters; the actual outputting of text will take way longer anyway, and waiting for the user to type something will take wayyyyyyyyyyy longer :)

Share this post


Link to post
Share on other sites
Thanks again Zahlman. I ended up doing it the first way you mentioned, but I'll definitely remember about putting it into a small function (maybe call it CheckInput) for later projects to make sure the user puts in a number instead of a letter or vice versa.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!