Thread: My first big program: X/O game

  1. #1
    Registered User
    Join Date
    Apr 2017
    Posts
    3

    My first big program: X/O game

    I recently started learning C++ from the internet.
    This is my first big program, it's a simple naughts and crosses game. There is a 2 player mode and a player vs computer mode.
    Making this game was much harder than I expected, and I must admit I'm proud of myself.
    I'd like to know what you think about it and where I could improve it. I would really appreciate feedback! (Sorry in advance for the messy code).
    Since the code is admittedly long and hard to read, I understand if you do not wish to devote so much of your time to it.

    Code:
    #include <cstdlib>
    #include <time.h>
    #include <stdio.h>
    #include <iostream>
    using namespace std;
    
    
    string places[3][3] =     {{"Empty 1   ","Empty 2   ","Empty 3   "},           /* Places[3][3] is the game's board. This is where the Xs and Os will go */
                               {"Empty 4   ","Empty 5   ","Empty 6   "},
                               {"Empty 7   ","Empty 8   ","Empty 9   "}};
    int lastmove;                                                                  // When playing against the computer, lastmove is 1 after the player's move and 0 after the computer's move. It thus enables us to know after whose move was a win achieved. Hence: who won.
    char compXO;                                                                   // Determines whether the computer will play as X or as O
    int win(int lastmove, int mode, string places[3][3], char XO, char compXO);
    char XorO();
    void table();
    void placingX(char XO);
    void printplaces(string places[3][3]);
    int  gamemode();
    void computermove(string places[3][3], char compXO, int mode);
    string whowon(string places[3][3]);
    
    
    
    
    int main()
    
    
    {
    srand(time(NULL));                                                             // srand enables use of random number generation (necessary for the computer's move)
    table();
    int mode = gamemode();
    char XO = XorO();
    if (XO == 'X') compXO = 'O'; else compXO = 'X';                               // Computer always plays for the opposite team
    
    
    for (int turn = 1; turn <=9; turn++)                                          // The game loop. It's up to 9 because after 9 loops the board will be full and the game concluded
    {
    printplaces(places);                                                          // Prints the board to screen
    placingX(XO);                                                                 // Let's the player make a move
    lastmove = 1;
    
    
    if (win(lastmove, mode, places, XO, compXO) == 1) goto END;                                      // Checks if the player's move won the game
    if (turn == 9) {printplaces(places); break;}                                                                 // If we are in the last turn and the player's move didn't win the game, it is a draw. (This line prevents the computer from trying to make a move after the board has been filled completely and a win was not achieved
    if (mode == 0)
        {
            computermove(places, compXO, mode);                                                              // Lets the computer make a move
            turn++;                                                             
            lastmove = 0;
            if (win(lastmove, mode, places, XO, compXO) == 1) goto END;                              //  Checks if the computer's move won the game
        }
    if (mode == 1)                                                        // If we are in 2 player mode: After each turn the player will play as the opposite team
        {
            if (XO == 'X')   XO = 'O'; else if (XO == 'O')  XO = 'X';    // Alternates between X and O every turn
        }
    }
    cout << "\nGame Result: Draw!" << endl;                             // If the board has been filled completely without a win achieved, it is a draw.
    
    
    END: system("pause");
    return 0;
    }
    
    
    
    
    char XorO()          /* This function lets the player choose whether they will play as X or as O */
    {
        char XO;
        int X0;
    
    
        choose:
        cout << "\n1 for X\n0 for O" << endl;
        scanf(" %d", &X0);
        if (X0 == 1)  XO = 'X'; else if (X0 == 0)   XO = 'O'; else {printf("\nYou did not follow the instruction\n"); goto choose;}
        return XO;
    }
    
    
    void table()         /* This function draws an example table which corresponds to where the user commands 1..9 will draw his X/O */
    {
        int i,k;
        char table[3][3] = {{'1','2','3'},
                            {'4','5','6'},
                            {'7','8','9'}};
        cout << "Placing controls: " << endl;
        cout << "\n\n";
        for (i=0; i<3; i++)
        {
            for (k=0; k<3; k++)
            {
                cout << table[i][k] << "             ";
            }
          cout << "\n\n";
        }
        cout << "\n-----------------------------------------------------\n\n";
        return;
    }
    
    
    
    
    void placingX(char XO)          /* This function lets the user place his X/O in a chosen location in the "places" array (the game's board) and checks for the legality of his move */
    {
        int place;
    
    
        again:
        printf("Where do you want to place your %C?\n", XO);
        cin >> place;
    
    
        switch(place)
        {
         case 1: if ((places[0][0] != "X         ") && (places[0][0] != "O         ")) {(places[0][0] = XO) += "         "; break;} else {cout << "\nThis spot is already taken!\n\n\n"; goto again;}
         case 2: if ((places[0][1] != "X         ") && (places[0][1] != "O         ")) {(places[0][1] = XO) += "         "; break;} else {cout << "\nThis spot is already taken!\n\n\n"; goto again;}
         case 3: if ((places[0][2] != "X         ") && (places[0][2] != "O         ")) {(places[0][2] = XO) += "         "; break;} else {cout << "\nThis spot is already taken!\n\n\n"; goto again;}
         case 4: if ((places[1][0] != "X         ") && (places[1][0] != "O         ")) {(places[1][0] = XO) += "         "; break;} else {cout << "\nThis spot is already taken!\n\n\n"; goto again;}
         case 5: if ((places[1][1] != "X         ") && (places[1][1] != "O         ")) {(places[1][1] = XO) += "         "; break;} else {cout << "\nThis spot is already taken!\n\n\n"; goto again;}
         case 6: if ((places[1][2] != "X         ") && (places[1][2] != "O         ")) {(places[1][2] = XO) += "         "; break;} else {cout << "\nThis spot is already taken!\n\n\n"; goto again;}
         case 7: if ((places[2][0] != "X         ") && (places[2][0] != "O         ")) {(places[2][0] = XO) += "         "; break;} else {cout << "\nThis spot is already taken!\n\n\n"; goto again;}
         case 8: if ((places[2][1] != "X         ") && (places[2][1] != "O         ")) {(places[2][1] = XO) += "         "; break;} else {cout << "\nThis spot is already taken!\n\n\n"; goto again;}
         case 9: if ((places[2][2] != "X         ") && (places[2][2] != "O         ")) {(places[2][2] = XO) += "         "; break;} else {cout << "\nThis spot is already taken!\n\n\n"; goto again;}
         default: goto again; break;
        }
        return;
    }
    
    
    
    
    void printplaces(string places[3][3])     /* This function re-draws the "places" array (the game's board) when necessary */
    {
        int row,column;
        cout << "\n\nGame Board:\n\n";
        for (row=0; row<3; row++)
        {
            for (column=0; column<3; column++)
            {
                cout << places[row][column] << " ";
            }
          cout << "\n\n";
        }
        cout << "\n\n";
        return;
    }
    
    
    int  gamemode()                         /* This function lets the player select a game mode: Either Player vs Player or Player vs Computer */
    {
        int mode;
        modeselect:
        cout << "\n1 for a two player game\n0 for a game against the computer\n";
        scanf(" %d", &mode);
        if (mode != 1 && mode != 0) {printf("\nYou did not follow the instruction\n"); goto modeselect;}
        return mode;
    }
    
    
    void computermove(string places[3][3], char compXO, int mode)             /* This function generates a random location on the board and then checks whether that spot is free. It keeps generating coordinates until a free spot is found, then places the X/O in that spot */
    {
        if (mode == 1) return;
        random:
        int randomA = rand()%3;
        int randomB = rand()%3;
        if ((places[randomA][randomB] != "X         ") && (places[randomA][randomB] != "O         "))
        {
             (places[randomA][randomB] = compXO) += "         ";
        }
        else goto random;
        printplaces(places);
        cout << whowon(places);
        return;
    }
    
    
    string whowon(string places[3][3])                                      /* This function defines what a "win" is and checks if one has been achieved yet */
    {
        string winstate;
        if ( ((places[0][0] == places[0][1]) && (places[0][2] == places[0][0])) || ((places[1][0] == places[1][1]) && (places[1][2] == places[1][0])) || ((places[2][0] == places[2][1]) && (places[2][2] == places[2][0])) || // Horizontal combo
             ((places[0][0] == places[1][0]) && (places[2][0] == places[0][0])) || ((places[0][1] == places[1][1]) && (places[2][1] == places[0][1])) || ((places[0][2] == places[1][2]) && (places[2][2] == places[0][2])) || // Vertical combo
             ((places[0][0] == places[1][1]) && (places[2][2] == places[0][0])) || ((places[0][2] == places[1][1]) && (places[2][0] == places[0][2])) ) // Diagonal combo
    
    
            {
            winstate = "Result: Win!";
            return winstate;
            }
            else
            {
            winstate = "\nStatus: No Winner Yet\n";
            return winstate;
            }
    }
    
    
    int win(int lastmove, int mode, string places[3][3], char XO, char compXO)                 /* This function will generate an output when a win is achieved */
    {
            int win_achieved = 0;
            if (whowon(places) == "Result: Win!")
            {
                    win_achieved = 1;
                    printplaces(places);
                    if (lastmove == 1 || mode == 1) {cout << "Game Result: " << XO << " WON!" << endl;}        // The win was achieved after a player's move. The player who made the last move is awarded the win
    
    
                    else {cout << "Game Result: " << compXO << " WON!" << endl;}                              // A win was achieved, but it was not after a player's move, hence the computer must have scored the win
            }
            return win_achieved;
    }
    Last edited by crazy_zimuki; 04-28-2017 at 07:18 PM.

  2. #2
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    You really need to work on your formatting, including indentation. Avoid bunching multiple statements on a single line. Placing comments at the end of a statement works well when you have say, a declaration and wish to make a short elaboration about it, but when you are using comments to introduce a block of code, place them in a separate line before the code. You should also leave it out comments that pretty much say the same thing as the code, and sometimes the need to comment on a function call may indicate that your function is poorly named (e.g., "Prints the board to screen": if "printplaces" doesn't convey that to you, then rename printplaces, e.g., printBoardToScreen, after which you don't need the comment).

    Here is how I might format your main function:
    Code:
    int main()
    {
        srand(time(NULL));
        table();
        int mode = gamemode();
        char XO = XorO();
        // Computer always plays for the opposite team
        if (XO == 'X')
            compXO = 'O';
        else
            compXO = 'X';
    
        // The game loop. It's up to 9 because after 9 loops the board will be full and the game concluded
        for (int turn = 1; turn <= 9; turn++)
        {
            // Prints the board to screen
            printplaces(places);
            // Let's the player make a move
            placingX(XO);
            lastmove = 1;
    
            // Checks if the player's move won the game
            if (win(lastmove, mode, places, XO, compXO) == 1)
                goto END;
            // If we are in the last turn and the player's move didn't win the game, it is a draw.
            // (This prevents the computer from trying to make a move after the board has been filled completely and a win was not achieved)
            if (turn == 9)
            {
                printplaces(places);
                break;
            }
    
            if (mode == 0)
            {
                computermove(places, compXO, mode);
                turn++;
                lastmove = 0;
                //  Checks if the computer's move won the game
                if (win(lastmove, mode, places, XO, compXO) == 1)
                    goto END;
            }
            // If we are in 2 player mode: After each turn the player will play as the opposite team
            if (mode == 1)
            {
                // Alternates between X and O every turn
                if (XO == 'X')
                    XO = 'O';
                else if (XO == 'O')
                    XO = 'X';
            }
        }
        // If the board has been filled completely without a win achieved, it is a draw.
        cout << "\nGame Result: Draw!" << endl;
    
    END:
        system("pause");
        return 0;
    }
    I suspect that you are doing too much on each iteration: you iterate 9 times, ostensibly once for each tile on the board, so why does each iteration involve playing two moves, one per player, when mode == 0? I suspect this is why you need your turn == 9 check. With some restructuring (possibly pulling out the turn by turn gameplay code into another function), you may find that you also don't need the goto statements.

    Speaking of goto statements: a glance at your computermove function shows that you used a goto to loop to some point above in the code. This means that you are overly reliant on goto. For practice, forbid yourself from using goto entirely. No goto. At all. So I'd change my mind on your main function and say that not only do you not need the goto statements, you need to get rid of them. Force yourself to use loop constructs. At some time you might become proficient enough and free from attachment to goto where say, you might legitimately break out of a nested loop with a goto, but for now, don't even do that.

    Furthermore, you should use named constants, not magic numbers like 0 and 1 for the mode. mode == HUMAN_VS_COMPUTER (or mode == ONE_PLAYER) and mode == TWO_PLAYER would be easier to make sense of than mode == 0 and mode == 1.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  3. #3
    Registered User
    Join Date
    Jun 2015
    Posts
    1,640
    It's not really a C++ program. It's a "C with cout, cin, and string" program (although you also throw in a few printfs and scanfs for good measure). To be a C++ program you should make a class for your grid.

    You are using global variables, which are generally a bad idea.

    You are using goto! Although there are a small number of legitimate uses for goto, yours are not those. The way you are using them demonstrates a lack of knowledge of proper loops.

    There's no reason for the grid to be a matrix of strings. Simple chars would be better.

    Your comments are mostly unnecessary.

    Perhaps the worst function is the misnamed "placingX". Whenever you find yourself copying/pasting code that differs in only a couple of values, stop and think for a minute. Those values can probably be calculated.

    The computer is very bad at playing tic-tac-toe.

    As an example of a more C++y program, consider the following. It's still pretty bad as I wrote it in the last half hour and I'm really not that great at C++. The computer makes or blocks a winning move if possible. If I took another half hour it would be possible to make it play a perfect game.

    If your system doesn't work with the color codes (I understand that some versions of Windows actually do work with them), then change "#if 1" to "#if 0" to use the non-color print function.
    Code:
    #include <iostream>
    #include <random>
    
    using std::cout;
    
    namespace Rnd {
        std::random_device rd;
        std::default_random_engine re(rd());
    }
    
    int get_num() {
        using std::cin;
        int n;
        while (true) {
            cin >> n;
            if (cin)
                break;
            cin.clear();
            cin.ignore(10000, '\n');
            cout << "You must enter a number.\n";
        }
        return n;
    }
    
    const int Size = 3;
    
    class Grid {
        char g[Size][Size];
    public:
        Grid();
        void position   (int n, int &r, int &c);
        char value      (int n);
        bool is_empty   (int n);
        void set        (int n, char v);
        bool get_move   (char v);
        bool rnd_move   (char v);
        bool win        (int n);
        void print      ();
        int  count      (char v, int a, int b, int c);
        int  find_empty (int a, int b, int c);
        int  near_win   (char v);
    };
    
    Grid::Grid() {
        for (int row = 0; row < Size; row++)
            for (int col = 0; col < Size; col++)
                g[row][col] = '1' + row * Size + col;
    }
    
    void Grid::position(int n, int &r, int &c) {
        class OutOfBounds {};
        if (n < 1 || n > Size * Size) throw OutOfBounds();
        r = (n - 1) / Size;
        c = (n - 1) % Size;
    }
    
    char Grid::value(int n) {
        int r, c;
        position(n, r, c);
        return g[r][c];
    }
    
    bool Grid::is_empty(int n) {
        char v = value(n);
        return v != 'X' && v != 'O';
    }
    
    void Grid::set(int n, char v) {
        int r, c;
        position(n, r, c);
        g[r][c] = v;
    }
    
    bool Grid::get_move(char v) {
        int n;
        while (true) {
            cout << "Move: ";
            n = get_num();
            if (n < 1 || n > Size * Size) {
                cout << "Please enter a value from 1 to "
                     << Size * Size << '\n';
                continue;
            }
            if (is_empty(n))
                break;
            cout << "That position is not empty.\n";
        }
        set(n, v);
        return win(n);
    }
    
    bool Grid::rnd_move(char v) {
        int n = near_win(v);
        if (n == 0) {
            n = near_win(v == 'X' ? 'O' : 'X');
            if (n == 0) {
                std::uniform_int_distribution<int> dist(1, 9);
                while (!is_empty(n = dist(Rnd::re)))
                    ;
            }
        }
        set(n, v);
        return win(n);
    }
    
    bool Grid::win(int n) {
        int r, c;
        position(n, r, c);
        char v = g[r][c];
        return (g[r][0] == v && g[r][1] == v && g[r][2] == v) ||
               (g[0][c] == v && g[1][c] == v && g[2][c] == v) ||
               (g[0][0] == v && g[1][1] == v && g[2][2] == v) ||
               (g[0][2] == v && g[1][1] == v && g[2][0] == v);
    }
    
    int Grid::count(char v, int a, int b, int c) {
        return (value(a)==v) + (value(b)==v) + (value(c)==v);
    }
    
    int Grid::find_empty(int a, int b, int c) {
        if (is_empty(a)) return a;
        if (is_empty(b)) return b;
        if (is_empty(c)) return c;
        return 0;
    }
    
    int Grid::near_win(char v) {
        int n = 0;
        if (count(v, 1, 2, 3) == 2 && (n = find_empty(1, 2, 3)) != 0)
            return n;
        if (count(v, 4, 5, 6) == 2 && (n = find_empty(4, 5, 6)) != 0)
            return n;
        if (count(v, 7, 8, 9) == 2 && (n = find_empty(7, 8, 9)) != 0)
            return n;
        if (count(v, 1, 4, 7) == 2 && (n = find_empty(1, 4, 7)) != 0)
            return n;
        if (count(v, 2, 5, 8) == 2 && (n = find_empty(2, 5, 8)) != 0)
            return n;
        if (count(v, 3, 6, 9) == 2 && (n = find_empty(3, 6, 9)) != 0)
            return n;
        if (count(v, 1, 5, 9) == 2 && (n = find_empty(1, 5, 9)) != 0)
            return n;
        if (count(v, 3, 5, 7) == 2 && (n = find_empty(3, 5, 7)) != 0)
            return n;
        return 0;
    }
    
    #if 1
    void Grid::print() {
        for (int row = 0; row < Size; row++) {
            for (int col = 0; col < Size; col++) {
                char c = g[row][col];
                cout << "\033[0;2;37m[";
                if      (c == 'X') cout << "\033[1;33m";
                else if (c == 'O') cout << "\033[1;34m";
                else               cout << "\033[0;2;32m";
                cout << c << "\033[0;2;37m]\033[m";
            }
            cout << '\n';
        }
    }
    #else
    void Grid::print() {
        for (int row = 0; row < Size; row++) {
            for (int col = 0; col < Size; col++)
                cout << '[' << g[row][col] << ']';
            cout << '\n';
        }
    }
    #endif
    
    int main() {
        bool player_is_X = true, players_turn = true;
        while (true) {
            int n;
            cout << "Who goes first? 1 - You, 2 - Computer: ";
            n = get_num();
            if (n == 1 || n == 2) {
                if (n == 2) player_is_X = players_turn = false;
                break;
            }
            cout << "Please enter 1 or 2.\n";
        }
    
        Grid grid;
        int turns = 0;
        while (true) {
            if (players_turn) {
                grid.print();
                if (grid.get_move(player_is_X ? 'X' : 'O')) {
                    cout << "Player wins\n";
                    break;
                }
            }
            else if (grid.rnd_move(player_is_X ? 'O' : 'X')) {
                    cout << "Computer wins\n";
                    break;
            }
            if (++turns >= Size * Size) {
                cout << "Tie\n";
                break;
            }
            players_turn = !players_turn;
        }
    
        grid.print();
    
        return 0;
    }

  4. #4
    Registered User
    Join Date
    Apr 2017
    Posts
    3
    I am very new to the C/C++ world. I know my code is horrible, that is exactly the reason I posted it: Because I want to get feedback and become better.
    To me, almost every aspect of the language is still new, so please excuse my utter lack of skill.
    While being very critical, your comment contains a lot of valid advice.
    I do intend to take your advice and try to become better at loops and classes. Currently, classes do still confuse me.
    For the computer I used random number generation, so obviously he is very bad haha.

    Thank you for your time and advice! I am currently learning your program.
    May I ask though, why are global variables "bad"? To me they are the easiest to use and can be passed between functions with ease.

  5. #5
    Registered User
    Join Date
    May 2009
    Posts
    4,183
    Quote Originally Posted by crazy_zimuki View Post
    May I ask though, why are global variables "bad"? To me they are the easiest to use and can be passed between functions with ease.
    They are a simple and lazy way to do things that in large programs tend to result in hard to test code.

    Once you learn to use functions and pointers you will rarely find a true need for global variables; the one common exception that I find is that in embedded programming they are often needed to handle interrupts and on some embedded systems needed for other reasons.

    Tim S.
    "...a computer is a stupid machine with the ability to do incredibly smart things, while computer programmers are smart people with the ability to do incredibly stupid things. They are,in short, a perfect match.." Bill Bryson

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. how do i program a guessing game?
    By Gayathri in forum Windows Programming
    Replies: 3
    Last Post: 03-28-2012, 11:46 AM
  2. Game of life program
    By paranoidgnu in forum C Programming
    Replies: 31
    Last Post: 05-05-2011, 05:10 AM
  3. Can I program a game in 2-D?
    By Bluesun159 in forum Game Programming
    Replies: 6
    Last Post: 07-07-2003, 08:00 AM
  4. I want to program a game, what do i need?
    By DarkViper in forum Game Programming
    Replies: 53
    Last Post: 11-19-2002, 05:39 PM
  5. How do you program a game w/ C++ ???
    By Kazikame in forum Game Programming
    Replies: 22
    Last Post: 10-24-2001, 09:33 AM

Tags for this Thread