2
\$\begingroup\$

I'm new to C++ and made this basic Tic-Tac-Toe game from scratch. I would like to know if there are simpler or more efficient ways of doing the things that are covered in the code. As I said, I'm new to all this and would like to try and take advantage of all of my resources that are available instead of sticking to web tutorials only and would like for this to be a learning experience more than anything.

#include <iostream>


int main()
{
    // Initializes Game Board and Example Board
    const int ROWS = 3;
    const int COLUMNS = 3;
    const int FULL_BOARD = 10;
    int numMoves = 0;
    char board[ROWS][COLUMNS] = {{'*', '*', '*'},
                                    {'*', '*', '*'},
                                        {'*', '*', '*'}};

    char example[ROWS][COLUMNS] = {{'0', '1', '2'},
                                    {'1', ' ', ' '},
                                        {'2', ' ', ' '}};

    int x;
    int y;
    char move;

    // Shows Example Board
    std::cout << "Here's the layout of the tic-tac-toe board:\n";
            for (int i=0; i < ROWS; ++i)
            {
                for (int j=0; j < COLUMNS; ++j)
                {
                    std::cout << example[i][j];
                }

                std::cout << std::endl;
            }

    do{

    if (numMoves == 3 || numMoves == 6)
        {
            std::cout << "Here's the layout of the tic-tac-toe board:\n";
            for (int i=0; i < ROWS; ++i)
            {
                for (int j=0; j < COLUMNS; ++j)
                {
                    std::cout << example[i][j];
                }

                std::cout << std::endl;
            }
        }

    if (numMoves == 0)
        std::cout << "\nPlayer 1 is X and goes first." << std::endl;

    // Determine what character is placed
    if (numMoves % 2)
        {
            move = '0';
            std::cout << std::endl;
        }

    else
        {
            move = 'X';
            std::cout << std::endl;
        }

    // Input for move location
    std::cout << "Row number: "; std::cin >> x; std::cout << std::endl;
    std::cout << "Column number: "; std::cin >> y; std::cout << std::endl;

    board[x][y] = move;

    // Shows updated Game Board
    std::cout << "Now the tic-tac-toe board is:\n";
    for (int i = 0; i < ROWS; ++i)
    {
        for (int j = 0; j < COLUMNS; ++j)
        {
            std::cout << board[i][j];
        }

        std::cout << std::endl << std::endl;
    }

    // Check if someone has won the game
    if (
        board[0][0] == 'X' && board[0][1] == 'X' && board[0][2] == 'X' || 
        board[0][0] == 'X' && board[1][0] == 'X' && board[2][0] == 'X' ||
        board[0][0] == 'X' && board[1][1] == 'X' && board[2][2] == 'X' ||
        board[0][2] == 'X' && board[1][1] == 'X' && board[2][0] == 'X' ||
        board[0][2] == 'X' && board[1][2] == 'X' && board[2][2] == 'X' ||
        board[2][0] == 'X' && board[2][1] == 'X' && board[2][2] == 'X' ||
        board[0][1] == 'X' && board[1][1] == 'X' && board[2][1] == 'X' ||
        board[1][0] == 'X' && board[1][1] == 'X' && board[1][2] == 'X'
        )
            {
                std::cout << "Good job. Player 1 is the winner.";
                break;
            }

    else if (
        board[0][0] == '0' && board[0][1] == '0' && board[0][2] == '0' || 
        board[0][0] == '0' && board[1][0] == '0' && board[2][0] == '0' ||
        board[0][0] == '0' && board[1][1] == '0' && board[2][2] == '0' ||
        board[0][2] == '0' && board[1][1] == '0' && board[2][0] == '0' ||
        board[0][2] == '0' && board[1][2] == '0' && board[2][2] == '0' ||
        board[2][0] == '0' && board[2][1] == '0' && board[2][2] == '0' ||
        board[0][1] == '0' && board[1][1] == '0' && board[2][1] == '0' ||
        board[1][0] == '0' && board[1][1] == '0' && board[1][2] == '0'
        )
            {
                std::cout << "Good Job. Player 2 is the winner. ";
                break;
            }

     ++numMoves;



} while (numMoves < FULL_BOARD);

return 0;
}
\$\endgroup\$
3
  • \$\begingroup\$ This appears to ask about adding new implementation, therefore it's off-topic. \$\endgroup\$
    – Jamal
    Apr 27, 2016 at 1:50
  • \$\begingroup\$ @Jamal I've fixed the question. Care to check and unlock if it's right? \$\endgroup\$
    – McLemore
    Apr 27, 2016 at 2:57
  • \$\begingroup\$ The game works perfectly. And I've added that to the code since then, but haven't updated here. \$\endgroup\$
    – McLemore
    Apr 27, 2016 at 14:16

1 Answer 1

2
\$\begingroup\$

Self Documenting Code.

You should break your code up into functions. If your functions are well named the flow of the code will document itself and make it easier to read.

int main()
{
    int  playersTurn = 0;

    while(!gameOver())
    {
        getPlayerMove(playersTurn);
        updateBoard();
        if (isWinningMove(playerTurn))
        {
            displaySaluatation(playerTurn);
            break;
        }
        playerTurn = (playerTurn + 1) % 2; // Player is 0 or 1
    }
}

Does the above not make the whole processes more readable. Then you can check each function to see if it solves the named problem.

Objects

You have everything as lots of variables in main. A popular style of programming for C++ is refered to as OO (Object Oriented) programming. This is were you wrap all variables that belong together into a single object then defines the actions ("methods") that can be performed on that object.

This helps in creating systems that don't get accidentally damaged by updating the wrong value accidently, as the only way you can change the state of the variable is through methods on the object.

Class Board
{
    static const int maxRows   = 3;
    static const int maxCols   = 3;
    static const int fullBoard = 10;
    int    numMoves;
    char   board[maxRows][maxCols];

    Board()
        :  numMoves(0)
    {
        for(int row=0; row < maxRows; ++row) {
            for(int col=0; col < maxCols; ++col) {
                board[row][col] = ' ';
            }
        }
    }

    bool playMove(int r, int c, char piece); // Adds a piece to a board
                                             // returns true for a win
    void printBoard(std::ostream& s);        // prints a board.

    // Define the output operator for board.
    // So you can print it like all other normal C++ types.
    friend std::ostream& operator<<(std::ostream& s, Board const& b)
    {
        b.printBoard(s);
        return s;
    }
}

Naming Convention.

    const int ROWS = 3;
    const int COLUMNS = 3;
    const int FULL_BOARD = 10;

All uppercase variables by convention are reserved for Macros. Don't use them its actually quite dangerous and can have unexpected results.

A normal naming convention for C++ is to use "CamelCase" (As you are). BUT use UpperCamelCase for user defined types and lowerCamelCase for the name of objects (variables/functions).

Prefer to use "\n" over std::endl

The only difference of the two is that std::endl forces a flush. But the stream (especially cout) automatically flush when they need to. So having a human flush them is just inefficient and it the worst case can cause severe efficiency problems. So let the code do its work and flush when it needs to (it will do a better job than you).

 std::cout << std::endl << std::endl;

Forcing a flush twice. Why!

DRY Code

DRY => Don't repeat yourself.

These two tests look basically identical

    if (
        board[0][0] == 'X' && board[0][1] == 'X' && board[0][2] == 'X' || 
        board[0][0] == 'X' && board[1][0] == 'X' && board[2][0] == 'X' ||
        board[0][0] == 'X' && board[1][1] == 'X' && board[2][2] == 'X' ||
        board[0][2] == 'X' && board[1][1] == 'X' && board[2][0] == 'X' ||
        board[0][2] == 'X' && board[1][2] == 'X' && board[2][2] == 'X' ||
        board[2][0] == 'X' && board[2][1] == 'X' && board[2][2] == 'X' ||
        board[0][1] == 'X' && board[1][1] == 'X' && board[2][1] == 'X' ||
        board[1][0] == 'X' && board[1][1] == 'X' && board[1][2] == 'X'
        )

And

    else if (
        board[0][0] == '0' && board[0][1] == '0' && board[0][2] == '0' || 
        board[0][0] == '0' && board[1][0] == '0' && board[2][0] == '0' ||
        board[0][0] == '0' && board[1][1] == '0' && board[2][2] == '0' ||
        board[0][2] == '0' && board[1][1] == '0' && board[2][0] == '0' ||
        board[0][2] == '0' && board[1][2] == '0' && board[2][2] == '0' ||
        board[2][0] == '0' && board[2][1] == '0' && board[2][2] == '0' ||
        board[0][1] == '0' && board[1][1] == '0' && board[2][1] == '0' ||
        board[1][0] == '0' && board[1][1] == '0' && board[1][2] == '0'
        )

The only difference is the value being tested. If you move this two a function and provide that as a parameter it makes validating that the code is correct much easier.

Return at the end of main()

Main is special.

return 0;

If you don't explicitly put a return in the code then the compiler will add one for you (that returns 0).

It is sort of traditional for programs that can not fail (ie they never return anything but zero) to have no return value in main().

The other side to that coin is that if you see a return 0 at the end of main, it is an indication that main() can fail and exit with a non zero value (ie you have another return point in main() that indicates failure).

So if your code never fails (a in this case) just leave out the return 0.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.