3
\$\begingroup\$

I made this tic-tac-toe game in c# after learning the language around a week ago. Can somebody tell me if this code is good or bad?

int userChoice, computerChoice, userMovesPlayed = 0, computerMovesPlayed = 0;
string[] board =
{
    "1", "2", "3",
    "4", "5", "6",
    "7", "8", "9"
};
int[] takenSquares =
{
    9, 9, 9,
    9, 9, 9,
    9, 9, 9
},
userTakenSquares = { 9, 9, 9, 9, 9 }, computerTakenSquares = { 9, 9, 9, 9 };
Random rnd = new Random();
bool playing = true;

PrintBoard();

Console.Write("\nLets play Tic-Tac-Toe. ");

while (playing)
{
    try
    {
        Console.Write("Choose the square you would like to put an X on. ");
        userChoice = Convert.ToInt32(Console.ReadLine());

        if (takenSquares.Contains(userChoice - 1) || userChoice < 1 || userChoice > 9)
        {
            Console.Write("Invalid move, please choose a valid square: ");
        }
        else
        {
            Console.Clear();
            PlayMove("X", userChoice - 1);

            if (PrintWin())
            {
                PrintBoard();
                break;
            }

            GenerateComputerMove();

            if (PrintWin())
            {
                PrintBoard();
                break;
            }
            else
            {
                PrintBoard();
            }
        }
    }
    catch (FormatException)
    {
        Console.Write("Please choose a number between 1 and 9: ");
        continue;
    }
}

void PrintBoard()
{
    Console.WriteLine
    (
        $"       {board[0]} | {board[1]} | {board[2]} \n" +
        "      ---|---|---\n" +
        $"       {board[3]} | {board[4]} | {board[5]} \n" +
        "      ---|---|---\n" +
        $"       {board[6]} | {board[7]} | {board[8]}"
    );
}

void PlayMove(string turn, int square)
{
    takenSquares[square] = square;
    if (turn == "X")
    {
        userTakenSquares[userMovesPlayed] = square;
        userMovesPlayed++;
    }
    else
    {
        computerTakenSquares[computerMovesPlayed] = square;
        computerMovesPlayed++;
    }
    board[square] = turn;
}

void GenerateComputerMove()
{
    bool takenSquare = true;
    computerChoice = rnd.Next(0, 9);

    while (takenSquare)
    {
        if (takenSquares.Contains(1) && takenSquares.Contains(2) && takenSquares.Contains(3) && takenSquares.Contains(4) && takenSquares.Contains(5) && takenSquares.Contains(6) && takenSquares.Contains(7) && takenSquares.Contains(8) && takenSquares.Contains(0))
        {
            playing = false;
            Console.WriteLine("Tie!");
            break;
        }
        if (!takenSquares.Contains(computerChoice))
        {
            Console.WriteLine($"I put an O on square {computerChoice + 1}");
            PlayMove("O", computerChoice);
            takenSquare = false;
            break;
        }
        else
        {
            computerChoice = rnd.Next(0, 9);
            continue;
        }
    }
}


bool CheckForWin(int[] playedSquares)
{
    if (GenerateWinConditions(playedSquares, 0, 1, 2) || GenerateWinConditions(playedSquares, 0, 3, 6) || GenerateWinConditions(playedSquares, 0, 4, 8))
    {
        return true;
    }
    else if (GenerateWinConditions(playedSquares, 1, 4, 7))
    {
        return true;
    }
    else if (GenerateWinConditions(playedSquares, 2, 4, 6) || GenerateWinConditions(playedSquares, 2, 5, 8))
    {
        return true;
    }
    else if (GenerateWinConditions(playedSquares, 3, 4, 5))
    {
        return true;
    }
    else if (GenerateWinConditions(playedSquares, 6, 7, 8))
    {
        return true;
    }
    else
    {
        return false;
    }
}

bool GenerateWinConditions(int[] playedSquares, int squaretoCheck, params int[] squaresToCheck)
{
    if (playedSquares.Contains(squaretoCheck))
    {
        if (playedSquares.Contains(squaresToCheck[0]) && playedSquares.Contains(squaresToCheck[1]))
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    else
    {
        return false;
    }
}

bool PrintWin()
{
    if (CheckForWin(userTakenSquares))
    {
        Console.WriteLine("You Won!");
        playing = false;
    }
    else if (CheckForWin(computerTakenSquares))
    {
        Console.WriteLine("I Won!");
        playing = false;
    }
    return !playing;
}
\$\endgroup\$
1
  • \$\begingroup\$ From what perspective this code is good or bad? \$\endgroup\$ Commented Jul 18 at 8:01

1 Answer 1

3
\$\begingroup\$

Returning true and false

In your generate win conditions function, instead of returning true or false if the condition evaluates so, simply return the expression; as this evaluates to a boolean anyway.

bool GenerateWinConditions(int[] playedSquares, int squaretoCheck, params int[] squaresToCheck)
{
    if (playedSquares.Contains(squaretoCheck))
    {
        return playedSquares.Contains(squaresToCheck[0]) && playedSquares.Contains(squaresToCheck[1]);
    }
}

Your CheckForWin function can, and should, follow the same suit.

Guard clauses

In your main loop, you can reduce a level of indentation by continueing after the first input check, instead of shoving the bulk of the loop in an else branch. Take this example:

while (true) {
    if (some_expression) {
        // print some message to the user
    } else {
        // rest of loop
    }
}

This can be shorted and made more concise by immediate continuing inside the initial check.

while (true) {
    if (some_expression) {
        // print some message to the user
        continue;
    }
    // rest of loop
}

While this is a minute example, this video and this video are excellent resources that explain when and when not to do this.

\$\endgroup\$
0

Your Answer

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

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