Tic-Tac-Toe

I am having trouble coding this Tic Tac Toe assignment for my CIS course. The prompt for the assignment is below.

"There is modification to the original assignment - mainly the input/output.

Instead of having the players enter the coordinates , the players will enter a number (1 - 9) instead.

The game board should be displayed as:

1 2 3

4 5 6

7 8 9

If player O enters a 5, the game board will become:

1 2 3

4 O 6

7 8 9

=================================================================================

If a player entered a character other than 1 - 9, the program will indicate the fact and let the same player try again.

If a player entered a position (1 - 9) that is already occupied, the program will indicate the fact and let the same player try again.

As soon as a winner emerges, the program announces the winner and terminate the game.

If all positions are taken and no winner, the program announces the game result as a tie.


*** Note 1: please avoid hard coding, think in the future you might expand the game from 3 x 3 to m x m.


*** Note 2: to convert between int and string:

int i = stoi("8");

string s = to_string(100);


*** Note 3:

0 1 2 -----------j

i----- 0 1 2 3

1 4 5 6

2 7 8 9

here i is index for row, j is index for column

if given value n (1- 9),

i = (n-1) / 3

j = (n-1) % 3


if given i and j,

n = 3 * i + j + 1"

My confusion lies within
A.) How to code to where the board is of an adjustable size with the variable through each row as it descends goes from 1 to whatever MAX has been entered.
B.) How to properly build a function that can read the players input, place their mark, and know when a mark has already been placed on the board till the games completion.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <iostream>
#include <iomanip>
#include <string>

using namespace std;

void player_X();

void player_O();

int main()
{
	cout << "Welcome to a game of Tic-Tac-Toe." << "\nEnter the size of the board you would like: ";
	int MAX{};
	cin >> MAX;
	int game_board[MAX][MAX]{};
	for(int i{}; i < MAX; i++)
	{
		int count{1};
		for(int j{}; j < MAX; j++)
		{
			game_board[i][j] = count;
			count += game_board[i - 1][j - 1];
		}
	}
	cout << "\nHere is the field of play: " << endl;
	
	for(unsigned i{}; i < MAX; i++)
	{
		for(unsigned j{}; j < MAX; j++)
		{
			cout << setw(MAX) << game_board[i][j] << " ";
		}
		cout << endl;
	}
	
	char deciscion{};
	int mark{};
	do
	{
		cout << "\nWhich player will take the first move? (X or O) " << endl;
		cin >> deciscion;
		if(deciscion == 'x' || deciscion == 'X')
		{
			do
			{
				cout << "Where would you like to place a mark: ";
				cin >> mark;
				if(mark >= 1 && mark <= 9)
				{
					
				}
				else
				{
					cout << "The mark needs to be an integer value and a value between 1 and 9." << endl;
					return 0;
				}
			}while(mark < 1 || mark > 9);
		}
		else if(deciscion == 'o' || deciscion == 'O')
		{
			do
			{
				cout << "Where would you like to place a mark: ";
				cin >> mark;
				if(mark >= 1 && mark <= 9)
				{
					
				}
				else
				{
					cout << "The mark needs to be an integer value and a value between 1 and 9." << endl;
					return 0;
				}
			}while(mark < 1 || mark > 9);
		}
		else
		{
			cout << "The player needs to be player X or Player O, please try again." << endl;
			deciscion = {};
		}
	}while(deciscion != 'x' && deciscion != 'X' && deciscion != 'o' && deciscion != 'O');
	
	return 0;
}
Last edited on
Hello DeathShaman1,

As a start lines 14 - 16 will not work. Line 16 needs a constant number like "10" or a variable defined as a constant with "constexpr" or "const" for the size not a variable that can change. Your compiler should complain about this.

Now if you want to assign a size by the user you will have to use a dynamic array.

What I would do is rewrite line 14 as constexpr int MAX{ 3 }; and then put it above main where it is easy to find and change. I tend to use the name "MAXSIZE" in this case because it is more descriptive than just "MAX".

So unless there is more to the adjustable size of the board that was not mentioned this should cover that part.

To setup "game_board" you could do this:
1
2
3
4
5
6
int game_board[MAX][MAX]
{
	{ 1, 2, 3 },
	{ 4, 5, 6 },
	{ 7, 8, 9 }
};

This would eliminate the need for the next two for loops which I will have to test because it just does not look right.

I am guessing that "player_X" and "player_O" are the functions where you want to get the user input. Since "game_board" is defined in main you will need to pass this to the functions. When working on a program like this I like to pass this array by reference so that inside the function I can see the whole array when I am testing. As an example void player_X(int (&game_board)[MAX][MAX]) for the function and prototype. When passing by reference the dimension(s) of the array need to be known.

You use of the underscore "_" in the variable names is OK and works, but it is best to avoid using the underscore as it is used in many if not all header files that come with the compiler. You could very easily write a variable name that was used in a header file and pull your hair out, if you still have any, trying why the compiler is telling that your variable name is a redefinition and the compiler may not tell you where it was first defined.

Just as a FYI when naming a variable it is better to use the more accepted camel case way, i.e., "gameBoard". This may not be written down anywhere, but I tend to use a lower case letter to start a variable name and start classes, structs and functions with a capital letter, i.e., "PlayerX()", and as you have done partially any thing defined as a constant with capital letters. This makes the program easier to read and follow along with making it easier for others to read.

Anything past line 16 I will have to load up the program and test it.

Right now I am thing of three functions:
1
2
3
PlayerX
PlayerO
DisplayBoard(const gameBoard[][MAXSIZE])

In the last function the "const" is because you will only display the board. There is no need to change any thing in this function.

Hope that helps,

Andy
Hello DeathShaman1,

Is your code the original assignment or what you have changed from the original assignment?

I would like to see the original code before any modifications because I read the assignment as to modify the previous assignment.

Everything after line 26 needs reworked. It can be fixed to what you need , but it is still wrong. As a game none of the code you have right now will work. There is no way to check for a winner.

I put lines 41 and 42 above line 39 as this question should only be asked once not each time through the do/while loop. This means that the do/while loop is endless because there is no way to change "deciscion". Which is when I discovered there is no check for a winner where "deciscion" could be changed.

As a suggestion thought what I like to do is:
1
2
3
4
5
6
bool cont{ true };

do
{
    // Some code here.
} while (cont);

This way somewhere in the do/while loop you change "cont" to false when you are finished in the do/while loop.

I did find that one function like "PlayerMove" can replace both the "player_X" and "player_O" functions. Sending the function three parameters "gameBoard", "mark" and "decision" you can make the function generic to where it could be used for either "X" or "O". For what its worth I used a switch inside the function.

I am not sure what all this converting from "string" to "int" and "int" to "string" is for. You can make the 2D array a "char" array along with the variable "count" and it will work just fine. If you have to work with something else I do not know what that is.

At this point I will have to do some rethinking on the program to figure out what might work better.

Hope that helps,

Andy
Hello Andy, and thank you for the replies. No, there is no original code. I have the prompt as listed above and what the end results need be. Other than that though I did what I could to formulate what I have so far. I still need to go back and reimplement what you have listed here as a change in order to fix/make the program more efficient. And I do know that it is not completed yet to determine if there is a winner or not, but this early stage had me so stump I needed a little bit of guidance.
Definitely take Andy's advice and create MAXSIZE as a constexpr. The assignment says to make the code flexible but it doesn't say you should be able to pick a size at runtime.

Right now it looks like you're storing the position number in the game_board squares. What will you store when it's an X or an O? You could store the character value, but then suppose you expand the board to 10x10. How would you distinguish the difference between position 88 and an 'X', whose ASCII value is 88?

This points out the danger of representing the data the same way that you display it. Let's represent the board as an array of characters. A space means it is unoccupied. An 'X' or 'O' means it's occupied by that player. You'll have to modify the code to display the board to account for the change.

I'd write the functions to convert between board coordinates and a board position number. As you'll see below, you'll need these.

You have to display the board in several places (before each move, at the end of the game, etc. Let's make a function for that.

Also, look at the code that prompts for who goes first. It's spread all over the place: lines 41-43, 60-61, and 77-82. The distance will only grow as you add more code. Let's make that a function too so it's easy to tell what's going on:

Here is the code with these changes. I hope you'll agree that it's easier to read and should be easier to expand.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#include <iostream>
#include <iomanip>
#include <string>

using namespace std;

constexpr size_t MAXSIZE {10};

void player_X();
void player_O();

unsigned indexToPos(size_t i, size_t j)
{
    return MAXSIZE*i + j + 1;
}

void posToIndex(unsigned pos, size_t &i, size_t &j)
{
    --pos;
    i = pos / MAXSIZE;
    j = pos % MAXSIZE;
}

void displayBoard(char game_board[MAXSIZE][MAXSIZE])
{
    for (unsigned i {}; i < MAXSIZE; i++) {
	for (unsigned j {}; j < MAXSIZE; j++) {
	    if (game_board[i][j] == ' ') {
		cout << indexToPos(i,j); // show the position number
	    }  else {
		cout << game_board[i][j]; // show the mark
	    }
	    cout << ' ';
	}
	cout << endl;
    }
}

unsigned getMovePos(char player, char game_board[MAXSIZE][MAXSIZE])
{
    unsigned mark;
    size_t i,j;
    do {
	cout << "Where would you like " << player << " to place a mark: ";
	cin >> mark;
	if (mark > MAXSIZE*MAXSIZE || mark == 0) {
	    cout << "Mark must be between 1 and " << MAXSIZE * MAXSIZE << '\n';
	    continue;
	}
	posToIndex(mark, i, j);
	if (game_board[i][j] != ' ') {
	    cout << "Position " << mark << " is taken\n";
	    continue;
	}
	// If you get here, the mark is good
	return mark;
    } while (true);
}

// Prompt for the first player. reprompt if they don't answer 'X' or "O'
char firstPlayer()
{
    char decision;
    do {
	cout << "\nWhich player will take the first move? (X or O) " << endl;
	cin >> decision;
	switch( decision) {
	case 'X':
	case 'x':
	    return 'X';
	case 'O':
	case 'o':
	    return 'O';
	}

	// If you get here, the decision was invalid
	cout << "The player needs to be player X or Player O, please try again.\n";
    } while (true);
}

int
main()
{
    cout << "Welcome to a game of Tic-Tac-Toe.\n";

    // Create and initialize the board
    char game_board[MAXSIZE][MAXSIZE] {};
    for (size_t i {}; i < MAXSIZE; i++) {
	for (size_t j { }; j < MAXSIZE; j++) {
	    game_board[i][j] = ' ';
	}
    }
    char player = firstPlayer();
    do {
	cout << "\nHere is the field of play: " << endl;
	displayBoard(game_board);

	unsigned pos = getMovePos(player, game_board);
	size_t i,j;
	posToIndex(pos, i, j);
	game_board[i][j] = player;
	player = (player == 'X' ? 'O' : 'X');
    } while (true);
    return 0;
}

How should you handle the end of the game? I'd write a function that examines the board and returns it's current state:
1
2
3
4
5
// Examine the board and return it's state:
// 'X' or 'O' means the given player won
// 'F' means the board is full so no more moves are possible.
// 'M' means the board is not full and you can move again.
char boardState(char game_board[MAX][MAX]);
@DeathShaman1

Looks like an interesting program.
Last edited on
Hi DeathShaman1, can you please clarify a few things here

1. Is this an update on a previous version for your class that took used to take two inputs (row, col) ?

1a. If not, and you're writing from scratch, it might be simpler with the 1-9 input requirements in mind, to store the data as a 1D array. (Note you'll need a bit of conversion math based on SIDE, or w/e the var is, for proper display with newlines)
1b. From your code, there are two human players. Didn't see any specification for this in the problem. Will this always be the case or are you expected to eventually replace one with random-based AI, for example?

2. When designing, why start with user input? While it may be tempting to design as the workflow goes, I think it'd make a lot more sense, here and with other problems, to initially fill in all the logic parts and test that with dummy data.

The logic parts:
- As per the problem spec, one of the obstacles is conversion of (1-9) to your internal data structure storage. If you choose to store as 2D array, then the helper methods dhayden wrote look really useful. If instead an internal 1D array is used, then it may be as simple as decrementing the input.
- Another obstacle is calculation of a game being over. With every move, you could check the whole board for a win, or be more clever and check the row, column, and diagonal related to the last move only. Keep a tally of total moves played too, so that you can compare to SIDE*SIDE -- when total moves is equal to that, then you know that it's over as well. Much cheaper to keep a counter than to do a nested loop check to confirm all available spaces were used.

@dhayden, You should harden the input a bit more. For your question of whether 'X' or 'O' should start, the user could enter garbage like "yyyyyyy" and cause problems. Or for your move input, the user could enter '1', and then later enter '1.5' causing infinite loop.

Something like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
while (true)
{

    int n;
    while (cout << "Enter position (1-" << (SIDE*SIDE) << ") ? " && !(cin>>n))
    {
        cin.clear();
        cin.ignore(numeric_limits<streamsize>::max(), '\n');
    }

    // Check input range and availability
    if (! (1<=n && n<=SIDE*SIDE) )
    {
        cout << "Input out of range (1-"<<(SIDE*SIDE)<<")\n";
        continue;
    }
    int row, col;
    posToIndex(n, row, col);
    if (board[row][col]!=DEFAULT_SYMBOL)
    {
        cout << "Position " << n << " is taken!\n";
        continue;
    }

    // break or whatever
}
Last edited on
Would this be something along the proper bit of function to analyze the board and determine if there is a win? And if so, how would I clean it up to more cleanly scan the board for matches?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
char boardState(char game_board[MAXSIZE][MAXSIZE])
{
	bool WIN{true};
	char victory{'WIN'};
	int state{};
	do
	{
		if(game_board[0][0] == game_board[0][1] && game_board[0][0] == game_board[0][3])
			state = WIN;
		else if(game_board[0][0] == game_board[1][0] && game_board[0][0] == game_board[3][0])
			state = WIN;
	}while(state != WIN);
	return victory;
}
Hello DeathShaman1,

On line 4 "victory" is defined as a single "char" yet you are trying to put a string, which should be in double quotes, into a single "char".

Line 3 is a variable that should be used, but you do not and it should start as "false". Also the "bool" should not be in capital letters. Since this goes with line 3 line 12 the while condition can be just "(!win)". Of course this makes lines 9 and 11 a bit pointless.

In lines 8 and 10 and considering that the 2D array is 3 X 3 that would put the end of each line past the boundary of the array.

Overall if you are working with the program as a tic-tac-toe game then you would need to check each row and each column plus two diagonals for a winner. The real trick here is to be able to check each row and column of whatever size the 2D array is.

Line 13. If you are expecting to return a string it will not. You would be lucky if "victory" would hold a 'W'. A better choice would be to scrap "victory", or change it to a string, and return the bool "win". Which should work better with the rest of the program.

I will see if I can work up a different idea in a bit.

Hope that helps,

Andy
Hello DeathShaman1,

I came up with this. It is more of an idea as it is untested for now, but should give you an idea of what to do.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bool boardState(char game_board[MAXSIZE][MAXSIZE])
{
	bool win{ false };
	//char victory{ 'WIN' };  // <--- If you need the word win this should be a string.
	//int state{};  // <--- No longer needed.

	do
	{
		if (game_board[0][0] == game_board[0][1] && game_board[0][0] == game_board[0][2])  // <--- Row 0, first row.
			win = true;
		else if (game_board[0][0] == game_board[1][0] && game_board[0][0] == game_board[3][0])  // <--- Should be row 1, second row.
			win = true;
		else if ()  // <--- Should be row 2, third row.
			win = true;
		// <--- Need else if statements for each column

		// <--- Need check for a tic-tac-toe diagonals.

	} while (!win);

	return win;
}


To keep with the 2D array being a variable size something in a nested for loop would work better.

Your if conditions will work for the rows and columns, but diagonals may be a problem depending on the size of the array. It may not have a diagonal.

You have seen how "MAXSIZE" works, but in the case of a 2D array "MAXROWS" and "MAXCOLS" tend to work better. Also in a nested for loop "row" and "col" make it easier to understand than "i" and "j".

Hope that helps,

Andy
To answer an earlier question,
DeathShaman1 wrote:
How to code to where the board is of an adjustable size

This implies a dynamic board. When dynamic, think vectors. Board could be a 2D dynamic structure of strings or chars, e.g. vector<vector<string>>

And if so, how would I clean it up to more cleanly scan the board for matches?

1. To answer the 'cleanly' part: Strongly consider packaging all your TicTacToe stuff into a class. You can have a private board_ , a private total_moves_, private side_, perhaps a private winner_ string, etc. So you won't have to clutter all your methods extra parameters (like board), because the class would already know it.
2. Checking game is over:
I advise doing this in a vacuum with dummy data and perhaps splitting the problem into parts. The implementation details will depend on if you've gone with the class route. You can have a CheckGameOver(int row, int col) method that calls other helper methods like CheckRowWin(row, col) or CheckColWin(row, col). Feed the CheckGameOver method different boards with various row, col coordinates representative of the last move played.
Example test suite that has example for testing a tie (draw) and row win, with other checks as stubs:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#include <iostream>
#include <string>
#include <vector>

using namespace std;

struct LastMove
{
    string symbol;
    int row;
    int col;
};

class TicTacTester
{
public:
    TicTacTester(const vector<vector<string>>& board, int total_moves) : 
        side_(3),
        board_(board),
        total_moves_(total_moves),
        game_over_(false),
        winner_("No One")        
    {        
    }

    void Reset(const vector<vector<string>>& board, int total_moves)
    {
        board_ = board;
        total_moves_ = total_moves;
        game_over_ = false;
        winner_ = "No One";
    }

    // Conditions for game over:
    // 1. Number of moves equal to SIDE*SIDE
    // 2. Related rows/columns/diagonals filled with last symbol
    bool IsGameOver(int row, int col)
    {
        // Can change game_over_
        CheckTotalMoves();

        last_.row = row;
        last_.col = col;
        last_.symbol = board_[row][col];

        // All of these can change game_over_ and winner_
        CheckRowWin() || CheckColWin() || CheckDiagWin();

        return game_over_;
    }

    string Winner() { return winner_; }

private:
    void CheckTotalMoves() 
    { 
        game_over_ = total_moves_==side_*side_;
    }

    bool CheckRowWin()
    {        
        int down = last_.col-1;
        while (down >= 0)
        {
            if (board_[last_.row][down] != last_.symbol)
                return false;
            down--;
        }
        int up = last_.col+1;
        while (up < side_)
        {
            if (board_[last_.row][up] != last_.symbol)
                return false;
            up++;
        }
        
        game_over_ = true;
        winner_ = last_.symbol;
        return true;
    }

    //TODO: implement
    bool CheckColWin()
    {
        return false;
    }

    //TODO: implement
    bool CheckDiagWin()
    {
        return false;
    }

    const unsigned int side_;
    vector<vector<string>> board_;    
    int total_moves_;
    bool game_over_;
    string winner_;
    LastMove last_;  // Last legal move played
};

int main() 
{
    vector<vector<vector<string>>> boards
    {
       {{ "X", "O", "X" },
        { "O", "O", "X" },
        { "X", "X", "O" }},

       {{ "X", "X", "X" },
        { "˘", "˘", "˘" },
        { "O", "˘", "O" }},

       {{ "X", "˘", "O" },
        { "X", "˘", "˘" },
        { "X", "˘", "O" }},
    };

    // 0. Test a Draw
    TicTacTester t(boards[0], 9);
    cout << "0. [Draw] ";
    if (t.IsGameOver(2,0))
    {
        cout << "Winner is " << t.Winner() << endl;
    }
    else
    {
        cout << "Game is still going." << endl;
    }

    // 1. Test a Row win
    t.Reset(boards[1], 5);
    cout << "1. [Row]  ";
    if (t.IsGameOver(0,2))
    {
        cout << "Winner is " << t.Winner() << endl;
    }
    else
    {
        cout << "Game is still going." << endl;
    }

    // 2. Test a Column win
    t.Reset(boards[2], 5);
    cout << "2. [Col]  ";
    if (t.IsGameOver(2,0))
    {
        cout << "Winner is " << t.Winner() << endl;
    }
    else
    {
        cout << "Game is still going." << endl;
    }

    return 0;
}


0. [Draw] Winner is No One
1. [Row]  Winner is X
2. [Col]  Game is still going.
Regarding boardState()
Why is there a do/while loop? This function just figures out the current state of the board.

Some pseudocode:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
for each row {
   if all postions in the row are the same {
        // winner
        return the character at position 0 in the row. This will be 'X' or 'O'
   }
}

for each column {
    if all postions in the column are the same {
        return the character at position 0 in the column
    }
}

if all positions in the major diagonal are the same {
    return the character that's in the major diagonal
}
if all positions in the minor diagonal are the same {
    return the characters in the minor diagonal
}

// If you get here then there's no winner
if (any position is empty) {
    return 'M'; // board not full. Move again.
}
return 'F'; // board is full 


How do you determine if all the positions in a row/column/diagonal are the same? You have to use another loop for that. Just compare each position to position zero. For example, to check if column 2 is the same:
1
2
3
4
5
6
7
bool theSame=true;  // assume they are the same.
for (unsigned i=1; i<MAXSIZE; ++i) {
    if (!game_board[i][2] == game_board[i][0]) {
        theSame = false;
        break;
    }
}
Icy1 wrote:
@dhayden, You should harden the input a bit more

I'm always torn over stuff like that on the beginners forum. On the one hand it's good practice, but on the other, I don't want to overwhelm a beginner with a bunch of stuff that they might not know.
Topic archived. No new replies allowed.