Since src/tic_tac_toe.cpp does not have a main(), it is not a complete program. I have three different programs that have main()'s, and make use of the Tic_Tac_Toe class. These are:
#include "tic_tac_toe.hpp" |
That means that every .cpp file has the same specification of the Tic_Tac_Toe class. To compile each source file into an object file, we will do the following, using src/tic_tac_toe.cpp as an example:
UNIX> g++ -std=c++98 -Wall -Wextra -Iinclude -c -o obj/tic_tac_toe.o src/tic_tac_toe.cppUNIX> Let's go over each part of this:
UNIX> g++ -std=c++98 -Wall -Wextra -Iinclude -o bin/ttt_tester obj/ttt_tester.o obj/tic_tac_toe.o UNIX>Since we don't include the -c flag, it makes the executable. The main() is defined in src/ttt_tester.o. It uses the Tic_Tac_Toe class, so we include src/tic_tac_toe.o, which implements all of the methods of the class. If we don't include src/tic_tac_toe.o, then we'll get a compiler error, saying that methods have not been implemented:
UNIX> g++ -std=c++98 -Wall -Wextra -Iinclude -o bin/ttt_tester obj/ttt_tester.o Undefined symbols for architecture x86_64: "Tic_Tac_Toe::Clear_Game()", referenced from: _main in ttt_tester.o "Tic_Tac_Toe::Make_Move(char, unsigned long, unsigned long)", referenced from: _main in ttt_tester.o "Tic_Tac_Toe::Tic_Tac_Toe()", referenced from: _main in ttt_tester.o "Tic_Tac_Toe::Game_State() const", referenced from: _main in ttt_tester.o "Tic_Tac_Toe::Board_String() const", referenced from: _main in ttt_tester.o "Tic_Tac_Toe::Print() const", referenced from: _main in ttt_tester.o "Tic_Tac_Toe::Stats(std::__1::vectorNow, the file makefile automates the compilation. If you type "make clean", then it will remove all of the object files and executables. Then, if you type "make" or "make all", it will make all of the object files and executables.>&) const", referenced from: _main in ttt_tester.o ld: symbol(s) not found for architecture x86_64 clang: error: linker command failed with exit code 1 (use -v to see invocation) UNIX>
UNIX> make clean rm -f obj/* bin/* UNIX> make g++ -std=c++98 -Wall -Wextra -Iinclude -c -o obj/ttt_tester.o src/ttt_tester.cpp g++ -std=c++98 -Wall -Wextra -Iinclude -c -o obj/tic_tac_toe.o src/tic_tac_toe.cpp g++ -std=c++98 -Wall -Wextra -Iinclude -o bin/ttt_tester obj/ttt_tester.o obj/tic_tac_toe.o g++ -std=c++98 -Wall -Wextra -Iinclude -c -o obj/ttt_player.o src/ttt_player.cpp g++ -std=c++98 -Wall -Wextra -Iinclude -o bin/ttt_player obj/ttt_player.o obj/tic_tac_toe.o g++ -std=c++98 -Wall -Wextra -Iinclude -c -o obj/ttt_random.o src/ttt_random.cpp g++ -std=c++98 -Wall -Wextra -Iinclude -o bin/ttt_random obj/ttt_random.o obj/tic_tac_toe.o UNIX>Let me draw a picture of what is going on. This shows how the source files all include the include file, how they are compiled to object files, and which object files are linked together to make which executables:
#pragma once #include <vector> #include <string> class Tic_Tac_Toe { public: Tic_Tac_Toe(); /* Constructor */ void Clear_Game(); /* Turn the current board into an empty board */ char Game_State() const; /* Return the state of the game: 'B' = beginning of game 'X' = X's turn 'O' = O's turn 'x' = Game is over and X has won. 'o' = Game is over and O has won. 'd' = Game is over and it's a draw. */ char Make_Move(char xo, size_t row, size_t col); /* This does the move. xo must be 'X' or 'O'. Returns 'E' on an error. Otherwise it returns resulting game state. */ void Print() const; /* Prints the board. */ std::string Board_String() const; /* Returns a 9-character string of X's, O's and -'s */ void Stats(std::vector <int> &xod) const; /* Sets xod to a three element vector: X wins, O wins, draws */ protected: std::vector <std::string> Board; /* The board */ char State; /* Game state -- same values as Game_State() above */ std::vector <int> X_O_D; /* The three stats */ int Open_Squares; /* The number of open squares on the board. */ }; |
First, you'll note that the methods are public. That means that anyone who creates an instance of the class can use the methods. The methods give you enough power to run tic-tac-toe games, and keep track of the winners. Second, you should see the use of the const keyword. Here it is put at the end of any method that doesn't change the class. For example, Print() prints the game board, but doesn't change anything. That allows the compiler to double-check that indeed your implementation doesn't change anything, and it helps you find bugs. Third, there's no "using namespace std" in the header file, so when I need to use things that are part of the "std" namespace, I need to put "std::" in front of them. You see that I've done this with vector and string. This is good practice, because some programmers don't want to have "using namespace std" in their code, and this way, you don't force them to do so. You can always put it in your own source (.cpp) files. Fourth, the variables are all protected. That means that they can only be accessed by the methods of the class, and not by any other code. Thus, the only way that you can use the class is through its methods.
Last, I have no executable code in the header file. This is something in which I believe firmly -- header files should have no executable code. That includes having a constructor that sets default values. That should be in the implementation.
/* This implementation file simply has dummy methods for every method in the class. It allows me to write a testing program and have it compile. Then, I'll start to implement the methods. */ #include "tic_tac_toe.hpp" using namespace std; Tic_Tac_Toe::Tic_Tac_Toe() {} void Tic_Tac_Toe::Clear_Game() {} void Tic_Tac_Toe::Print() const {} char Tic_Tac_Toe::Game_State() const { return '-'; } string Tic_Tac_Toe::Board_String() const { return "-"; } void Tic_Tac_Toe::Stats(vector <int> &xod) const { xod.resize(3, 0); } char Tic_Tac_Toe::Make_Move(char xo, size_t row, size_t col) { (void) xo; // These statements shut the compiler up about not using the parameters. (void) row; (void) col; return 'E'; } |
You can see that I've put "using namespace std" here -- that's because I'm happy to use the "std" namespace, and make my code more readable.
I've added a "make develop" to my makefile, which is where I specify how to compile the code that I'm using while I'm writing the program. I won't use it yet -- here, I'll simply type "make obj/tic_tac_toe_1.o", because I've configured that to just compile src/tic_tac_toe_1.cpp:
UNIX> make obj/tic_tac_toe_1.o g++ -std=c++98 -Wall -Wextra -Iinclude -c -o obj/tic_tac_toe_1.o src/tic_tac_toe_1.cpp UNIX>
#include "tic_tac_toe.hpp" #include <iostream> #include <sstream> #include <vector> #include <cstdio> #include <string> using namespace std; /* It's good to put this in a procedure at the beginning of your file -- that way you know where it is for reference, while you're writing the program. */ void print_commands() { cout << "usage: ttt_tester -- commands on stdin." << endl; cout << endl; cout << "commands:" << endl; cout << " C - Clear game state." << endl; cout << " GS - Print the game state char." << endl; cout << " P - Print the board." << endl; cout << " BS - Print the Board String." << endl; cout << " S - Print stats." << endl; cout << " M X/O R C - Move X or O to space at row R, col C." << endl; cout << " Q - Quit." << endl; cout << " ? - Print commands." << endl; } int main() { string s, line; // I use these to read a line of text and turn it into a vector <string> sv; // vector of strings (which is in sv). istringstream ss; Tic_Tac_Toe ttt; // Here's my tic-tac-toe game. vector <int> stats; // This is for when I call ttt.Stats() int row, col; // These are for the ttt.Make_Move(xo, row, col) call. char xo; while (1) { /* Print a prompt, and read in a line. */ cout << "TTT> "; cout.flush(); if (!getline(cin, line)) return 0; /* Use a stringstream to turn the line into a vector of words. */ sv.clear(); ss.clear(); ss.str(line); while (ss >> s) sv.push_back(s); /* Ignore blank lines and lines that start with the pound sign. */ if (sv.size() == 0 || sv[0][0] == '#') { /* Handle the simple commands: */ } else if (sv[0] == "P") { ttt.Print(); } else if (sv[0] == "C") { ttt.Clear_Game(); } else if (sv[0] == "GS") { printf("%c\n", ttt.Game_State()); } else if (sv[0] == "BS") { printf("%s\n", ttt.Board_String().c_str()); /* Stats */ } else if (sv[0] == "S") { ttt.Stats(stats); printf("X Wins: %4d\n", stats[0]); printf("O Wins: %4d\n", stats[1]); printf("Draws: %4d\n", stats[2]); /* Make a move. You'll note that I'm not using a stringstream for row/col. This code is simpler, and since row and col have to be between 0 and 2, it works just fine. You'll note that I'm not doing much error-checking here. That will be handled in Make_Move() which returns 'E' if it is called incorrectly. */ } else if (sv[0] == "M") { if (sv.size() != 4) { printf("Usage M X/O row col\n"); } else { xo = sv[1][0]; row = sv[2][0] - '0'; col = sv[3][0] - '0'; printf("Result of move: %c\n", ttt.Make_Move(xo, row, col)); } /* Quit, print commands or a bad command. */ } else if (sv[0] == "Q") { return 0; } else if (sv[0] == "?") { print_commands(); } else { printf("Unknown command %s\n", sv[0].c_str()); } } } |
The program is really simple, and it lets me write tic_tac_toe.cpp incrementally, by writing a few methods at a time, and then testing. This compiles with src/tic_tac_toe_1.cpp, and now you can "test" it. Of course, it doesn't really do anything, but at least you have everything compiling together are are ready to start implementing. In my makefile, I have this compile with src/tic_tac_toe_1.cpp to make the executable bin/ttt_tester_1:
UNIX> make clean rm -f obj/* bin/* UNIX> make bin/ttt_tester_1 g++ -std=c++98 -Wall -Wextra -Iinclude -c -o obj/ttt_tester.o src/ttt_tester.cpp g++ -std=c++98 -Wall -Wextra -Iinclude -c -o obj/tic_tac_toe_1.o src/tic_tac_toe_1.cpp g++ -std=c++98 -Wall -Wextra -Iinclude -o bin/ttt_tester_1 obj/ttt_tester.o obj/tic_tac_toe_1.o UNIX>I'll run it, and it won't do much, but it lets me see that the methods are being called:
UNIX> bin/ttt_tester_1 TTT> ? usage: ttt_tester -- commands on stdin. commands: C - Clear game state. GS - Print the game state char. P - Print the board. BS - Print the Board String. S - Print stats. M X/O R C - Move X or O to space at row R, col C. Q - Quit. ? - Print commands. TTT> C # call ttt.Clear() TTT> GS # call ttt.Game_State() - TTT> P # call ttt.Print() TTT> BS # call ttt.Board_String() - TTT> S # call ttt.Stats() X Wins: 0 O Wins: 0 Draws: 0 TTT> M - - - # call ttt.Make_Move() Result of move: E TTT> Q UNIX>
/* In this program, I implement the easy methods, which is all of the methods besides Make_Move() */ #include "tic_tac_toe.hpp" #include <iostream> using namespace std; /* The constructor calls Clear_Game() to set up the empty board. It also creates the X_O_D vector and sets its entries to zero. */ Tic_Tac_Toe::Tic_Tac_Toe() { Clear_Game(); X_O_D.resize(3, 0); } /* Clear_Game() creates an empty board with all dashes. It sets the game state to 'B', for "Beginning", and sets the number of open squares to 9, since all of the squares are empty. */ void Tic_Tac_Toe::Clear_Game() { State = 'B'; Board.clear(); Board.push_back("---"); Board.push_back("---"); Board.push_back("---"); Open_Squares = 9; } /* Print() is simple, printing out the Board, one row per line. */ void Tic_Tac_Toe::Print() const { size_t i; for (i = 0; i < Board.size(); i++) cout << Board[i] << endl; } /* Game_State() is also simple, simply returning the State variable. */ char Tic_Tac_Toe::Game_State() const { return State; } /* Board_String() concatenates the three rows of the Board together to make a single string without any newlines. */ string Tic_Tac_Toe::Board_String() const { string rv; rv = Board[0] + Board[1] + Board[2]; return rv; } /* State() just copies X_O_D to its argument, which is a reference parameter. */ void Tic_Tac_Toe::Stats(vector <int> &xod) const { xod = X_O_D; } /* Make_Move() is still unwritten. */ char Tic_Tac_Toe::Make_Move(char xo, size_t row, size_t col) { (void) xo; (void) row; (void) col; return '.'; } |
We can go ahead and test to see if these all work -- again, they are pretty simple, and since you can't make a move, they don't ever change. The makefile will compile this and src/ttt_tester.o into bin/ttt_tester_2:
UNIX> make clean rm -f obj/* bin/* UNIX> make bin/ttt_tester_2 g++ -std=c++98 -Wall -Wextra -Iinclude -c -o obj/ttt_tester.o src/ttt_tester.cpp g++ -std=c++98 -Wall -Wextra -Iinclude -c -o obj/tic_tac_toe_2.o src/tic_tac_toe_2.cpp g++ -std=c++98 -Wall -Wextra -Iinclude -o bin/ttt_tester_2 obj/ttt_tester.o obj/tic_tac_toe_2.o UNIX> bin/ttt_tester_2 TTT> P --- --- --- TTT> GS B TTT> S X Wins: 0 O Wins: 0 Draws: 0 TTT> BS --------- TTT> C TTT> P --- --- --- TTT> Q UNIX>
/* Make_Move() goes through the following steps: - Error check the arguments - Update the Board and Open_Squares - Test to see if whoever called Make_Move() has now won the game. If so, update the stats and set the State to 'x' or 'o'. - Otherwise, test to see if the game is a Draw, and set its state to 'D'. - Finally, if the game isn't over, set the game state to 'X' or 'O' to indicate whose turn it is. */ char Tic_Tac_Toe::Make_Move(char xo, size_t row, size_t col) { bool win; // This is used to record whether the caller has won the game. /* Error Check */ if (xo != 'X' && xo != 'O') return 'E'; if (xo == 'X' && State != 'B' && State != 'X') return 'E'; if (xo == 'O' && State != 'B' && State != 'O') return 'E'; if (row >= 3) return 'E'; if (col >= 3) return 'E'; if (Board[row][col] != '-') return 'E'; /* Update the Board and decrement the number of open squares */ Board[row][col] = xo; Open_Squares--; /* Test to see if whoever calls Make_Move has won. The tests go in the following order: - Check to see if the move completed a row. - Check to see if the move completed a column. - Check to see if the move completed the \ diagonal - Check to see if the move completed the / diagonal */ if (Board[row][0] == Board[row][1] && Board[row][0] == Board[row][2]) { win = true; } else if (Board[0][col] == Board[1][col] && Board[0][col] == Board[2][col]) { win = true; } else if (row == col && Board[0][0] == Board[1][1] && Board[0][0] == Board[2][2]) { win = true; } else if (row+col == 2 && Board[0][2] == Board[1][1] && Board[2][0] == Board[1][1]) { win = true; } else { win = false; } /* If the player won the game, update the stats and state accordingly. */ if (win) { if (xo == 'X') { State = 'x'; X_O_D[0]++; } else if (xo == 'O') { State = 'o'; X_O_D[1]++; } /* Otherwise, if the game is a draw, then update the stats and state accordingly. */ } else if (Open_Squares == 0) { State = 'D'; X_O_D[2]++; /* Otherwise, set the State to whoever's turn it is. */ } else if (xo == 'X') { State = 'O'; } else { State = 'X'; } /* Finally, return the state. */ return State; } |
We can now compile bin/ttt_tester, and test the program:
UNIX> make clean rm -f obj/* bin/* UNIX> make bin/ttt_tester g++ -std=c++98 -Wall -Wextra -Iinclude -c -o obj/ttt_tester.o src/ttt_tester.cpp g++ -std=c++98 -Wall -Wextra -Iinclude -c -o obj/tic_tac_toe.o src/tic_tac_toe.cpp g++ -std=c++98 -Wall -Wextra -Iinclude -o bin/ttt_tester obj/ttt_tester.o obj/tic_tac_toe.o UNIX> bin/ttt_tester TTT> M Fred Binky Luigi # Error checking Result of move: E TTT> M X 5 3 Result of move: E TTT> M X 1 1 # Put an X in the middle square (remember, 0-indexing) Result of move: O TTT> M O 0 1 # Put an O into the top middle square Result of move: X TTT> P # Print the board -O- -X- --- TTT> M X 2 2 # Put an X in bottom right square Result of move: O TTT> M O 0 0 # Put an O in the top left square Result of move: X TTT> P # Print the board OO- -X- --X TTT> M X 0 2 # Put an X in top right square. O is in trouble. Result of move: O TTT> M O 0 2 # Error check putting an O into a square with an X Result of move: E TTT> M O 1 2 # Put an O in the middle right square. Result of move: X TTT> P # Print the board OOX -XO --X TTT> M X 2 0 # X will now win the game. Result of move: x # This is confirmed by the state of 'x' TTT> P OOX -XO X-X TTT> GS # Confirm the game state x TTT> S # Show the stats. X Wins: 1 O Wins: 0 Draws: 0 TTT> Q UNIX>You can see that the testing program is a pain to use to play the game, but it's good at testing. It's a good idea here to test all of Make_Move() -- best thing is to create a file that has commands, and then confirm that running the program on the file gives you the output that you want. Let me give you a simple example that plays the same game as above, but just prints out the board strings that result. The program sed is really useful here to strip out those "TTT>" strings, and to remove the lines that say "Result of move".
The input file is in data/test_game_input_1.txt
# This plays the game where X wins on the left-to-right diagonal: M X 1 1 BS M O 0 1 BS M X 2 2 BS M O 0 0 BS M X 0 2 BS M O 1 2 BS M X 2 0 BS GS P |
When we run it, the output is pretty clunky:
UNIX> bin/ttt_tester < data/test_game_input_1.txt TTT> TTT> Result of move: O TTT> ----X---- TTT> Result of move: X TTT> -O--X---- TTT> Result of move: O TTT> -O--X---X TTT> Result of move: X TTT> OO--X---X TTT> Result of move: O TTT> OOX-X---X TTT> Result of move: X TTT> OOX-XO--X TTT> Result of move: x TTT> OOX-XOX-X TTT> x TTT> OOX -XO X-X TTT> UNIX>However, piping it through a few sed commands makes it pretty clean:
UNIX> bin/ttt_tester < test_game_input_1.txt | sed 's/TTT> //' | sed '/Result/d' ----X---- -O--X---- -O--X---X OO--X---X OOX-X---X OOX-XO--X OOX-XOX-X x OOX -XO X-X UNIX>
/* This program runs a more natural command-line version of tic-tac-toe than src/ttt_tester.cpp. Note how it makes use of the game state to help it play the game. */ #include "tic_tac_toe.hpp" #include <iostream> #include <cstdio> #include <sstream> #include <vector> #include <string> using namespace std; int main() { Tic_Tac_Toe ttt; // The game player vector <int> stats; // This is for reading the stats size_t row, col; // These are entered on standard input for Make_Move() char start; // 'X' or 'O' for who starts the game char turn; // 'X' or 'O' for whose turn it is char state; // The game state. /* Set it up so that 'X' plays the first game. We will alternate this between games. */ start = 'X'; while (1) { /* Get the game state, and print the board. */ state = ttt.Game_State(); cout << endl; ttt.Print(); cout << endl; /* If we're playing the game, then figure out whose turn it is, and then get the player's move from standard input. */ if (state == 'B' || state == 'X' || state == 'O') { turn = (state == 'B') ? start : state; cout << turn << "'s Move: "; cout.flush(); if (!(cin >> row >> col)) return 0; /* If the move is illegal, then we print an error message. Then, regardless of whether the move was legal or illegal, we're simply going to go to the top of the while loop. If there was an error, we'll simply repeat this code. Otherwise, the game will move on. */ if (ttt.Make_Move(turn, row, col) == 'E') { cout << endl << "Bad input -- try again, please." << endl; } /* Otherwise, the game is over. We're going to do the following things: - Print the winner (or whether it's a draw). - Print the stats. - Start a new game - Set the starting player to the other player. */ } else { if (state == 'D') { printf("Draw\n"); } else { printf("%c Wins!\n", state + ('A' - 'a')); // This converts the lower-case to upper-case. } ttt.Stats(stats); printf("Stats: X:%d O:%d D:%d\n", stats[0], stats[1], stats[2]); ttt.Clear_Game(); start = (start == 'X') ? 'O' : 'X'; } } } |
Let's compile it, and play the same game as above -- you'll note, it's a lot easier than using bin/ttt_tester. (However, I'll contend that bin/ttt_tester is a better program for testing).
UNIX> make bin/ttt_player g++ -std=c++98 -Wall -Wextra -Iinclude -c -o obj/ttt_player.o src/ttt_player.cpp g++ -std=c++98 -Wall -Wextra -Iinclude -c -o obj/tic_tac_toe.o src/tic_tac_toe.cpp g++ -std=c++98 -Wall -Wextra -Iinclude -o bin/ttt_player obj/ttt_player.o obj/tic_tac_toe.o UNIX> bin/ttt_player --- --- --- X's Move: 1 1 --- -X- --- O's Move: 0 1 -O- -X- --- X's Move: 2 2 -O- -X- --X O's Move: 0 0 OO- -X- --X X's Move: 0 2 OOX -X- --X O's Move: 1 2 OOX -XO --X X's Move: 2 0 OOX -XO X-X X Wins! Stats: X:1 O:0 D:0 --- --- --- O's Move: Q UNIX>
Go ahead and read the comments for an explanation of how the code works. The important part of the code is how we read the board string, in the variable bs, and figure out the legal moves, which correspond to the dashes in the string. Each of those is pushed onto the vectors legal_moves_r and legal_moves_c, and we choose a random one by choosing a random index into these vectors:
/* This program allows us to do a scientific experiment on tic-tac-toe. The hypothesis is that if you start the game in the middle square, and then play the game randomly, you will do betting than simply playing randomly. The program is a nice demonstration of how our Tic_Tac_Toe class can serve multiple goals (playing the game interactively, and doing a scientific experiment. */ #include "tic_tac_toe.hpp" #include "MOA.hpp" #include <iostream> #include <sstream> #include <vector> #include <string> #include <cstdio> using namespace std; /* On the command line, we're going to read a number of iterations and a seed for a random number generator (this is why we included "MOA.hpp" above). */ int main(int argc, char **argv) { Tic_Tac_Toe ttt; // The game player vector <int> stats; // For the final Stats() call MOA rng; // Random number generator istringstream ss; // For parsing the command line arguments size_t iterations; // The number of games to play size_t seed; // Seed for the random number generator size_t games_played; // Keeping track of the number of games played char start; // Who starts the game char turn; // Whose turn it is char state; // The game state string bs; // The board string, which helps do the random choosing vector <size_t> legal_moves_r; // Legal moves -- the row numbers vector <size_t> legal_moves_c; // Legal moves -- the column numbers size_t i; /* Parse and error check the command line */ try { if (argc != 3) throw((string) "usage: bin/ttt_random iterations seed"); ss.clear(); ss.str(argv[1]); if (!(ss >> iterations)) throw((string) "Bad iterations"); ss.clear(); ss.str(argv[2]); if (!(ss >> seed)) throw((string) "Bad seed"); } catch (string s) { cout << s << endl; return 1; } /* Initialize everything */ rng.Seed(seed); start = 'X'; games_played = 0; /* Keep going until you complete enough games. You only increment games_played when a game is over. */ while (games_played < iterations) { // printf("%s\n", ttt.Board_String().c_str()); This is useful for debugging. /* If the game isn't over, then determine whose turn it is, and then determine the legal moves that can be made. Choose one of them randomly. When it's X's turn and it's the beginning of a game, set it up so that the only legal move is to use the center square. */ state = ttt.Game_State(); if (state == 'B' || state == 'X' || state == 'O') { turn = (state == 'B') ? start : state; legal_moves_r.clear(); legal_moves_c.clear(); /* This is how to handle when the game is starting and it's X's turn. */ if (state == 'B' && turn == 'X') { legal_moves_r.push_back(1); legal_moves_c.push_back(1); /* Otherwise, use the dashes in the board string to determine the legal moves. */ } else { bs = ttt.Board_String(); for (i = 0; i < bs.size(); i++) { if (bs[i] == '-') { legal_moves_r.push_back(i/3); legal_moves_c.push_back(i%3); } } } /* Choose a random legal move and make it. */ i = rng.Random_Integer()%legal_moves_r.size(); ttt.Make_Move(turn, legal_moves_r[i], legal_moves_c[i]); /* Otherwise, the game is over. Update the games played, and set the starting player to the other player. */ } else { games_played++; ttt.Clear_Game(); start = (start == 'X') ? 'O' : 'X'; } } /* At the end, print the stats. */ ttt.Stats(stats); printf("Stats: X:%d O:%d D:%d\n", stats[0], stats[1], stats[2]); return 0; } |
To debug this, you'll note that I have printf() statement that I have commented out. Those let me look at the Board_String strings to make sure that everything looks good. Let's call it on 1,000,000 games:
UNIX> bin/ttt_random 1000000 40 Stats: X:490563 O:388761 D:120676 UNIX> bin/ttt_random 1000000 41 Stats: X:490699 O:388937 D:120364 UNIX> bin/ttt_random 1000000 42 Stats: X:490144 O:388883 D:120973 UNIX> bin/ttt_random 1000000 43 Stats: X:491100 O:387998 D:120902 UNIX> ≈You can see that X is winning over O significantly and consistently across seeds, so I think this is a pretty conclusive experiment!