Tic Tac Toe Game In C# For Practice

Introduction

I initiated this project alongside my studies on Pluralsight courses C# - The Big Picture by Mike Woodring and C# - Fundamentals by Gill Cleeren as part of my journey towards ASP.NET Core. My prior experiences had left me with reservations about C# due to its earlier days of limited compatibility and exclusivity. However, after dedicating a week to working with .NET and C#, I was pleasantly surprised by its capabilities and found my perceptions transformed.

Coming from a C++ background, I found C# to be a worthy successor with its intuitive syntax and significant quality-of-life features, including garbage collection. The transition was smooth, and the language's modern features enhanced my productivity. Additionally, using Visual Studio for the first time was a revelation; it offered an ease of use that stood in stark contrast to my experiences with Eclipse for Java development.

I chose to develop a Tic-Tac-Toe program as my initial foray into C# because of its inherent simplicity and potential for demonstrating fundamental and advanced programming concepts. Tic-Tac-Toe, while a straightforward game, offered a perfect canvas for me to explore and implement key features in C#, including object-oriented programming principles and unit testing.

The project allowed me to stretch my legs within the C# ecosystem, enabling me to create a structured, modular codebase that adheres to best practices. I leveraged classes and objects to encapsulate game logic and state, ensuring maintainable and scalable code. Additionally, I implemented comprehensive unit tests to validate the correctness of the game logic, which honed my skills in writing robust, testable code.

By working on this project, I gained hands-on experience with C#'s syntax and libraries, further solidifying my understanding and appreciation of the language's capabilities. This endeavor was instrumental in building a solid foundation for my future projects in C# and ASP.NET Core.

Features

IsWinner Method

The IsWinner method is a crucial part of my Tic-Tac-Toe program located in the Board class. This function determines whether a player, represented by the character p, has won the game by forming a straight line of their symbols on the game board. The method systematically checks for winning conditions across horizontal, vertical, and diagonal lines on a 3x3 grid. Below is a detailed explanation of how each part of the method works.

Code Implementation

public bool IsWinner(char p)
{
    // Check For Horizontal Wins
    if ((this.GetPosition(0, 0) == p && this.GetPosition(1, 0) == p && this.GetPosition(2, 0) == p) ||
             (this.GetPosition(0, 1) == p && this.GetPosition(1, 1) == p && this.GetPosition(2, 1) == p) ||
             (this.GetPosition(0, 2) == p && this.GetPosition(1, 2) == p && this.GetPosition(2, 2) == p))
    {
        return true;
    }
    // Check For Vertical Wins
    else if ((this.GetPosition(0, 0) == p && this.GetPosition(0, 1) == p && this.GetPosition(0, 2) == p) ||
        (this.GetPosition(1, 0) == p && this.GetPosition(1, 1) == p && this.GetPosition(1, 2) == p) ||
        (this.GetPosition(2, 0) == p && this.GetPosition(2, 1) == p && this.GetPosition(2, 2) == p))
    {
        return true;
    }
    // Check For Diagonal Wins
    else if ((this.GetPosition(0, 0) == p && this.GetPosition(1, 1) == p && this.GetPosition(2, 2) == p) ||
             (this.GetPosition(0, 2) == p && this.GetPosition(1, 1) == p && this.GetPosition(2, 0) == p))
    {
        return true;
    } 
    return false;
}

Detailed Explanation

Horizontal Wins

The first part of the method checks for horizontal wins. It evaluates if the character p occupies all three positions in any of the three rows. This is accomplished by checking:

  • The first row: positions (0,0), (1,0), and (2,0)
  • The second row: positions (0,1), (1,1), and (2,1)
  • The third row: positions (0,2), (1,2), and (2,2)

If any of these conditions are met, the method returns true, indicating a horizontal win.

Vertical Wins

Next, the method checks for vertical wins by verifying if the character p occupies all three positions in any of the three columns:

  • The first column: positions (0,0), (0,1), and (0,2)
  • The second column: positions (1,0), (1,1), and (1,2)
  • The third column: positions (2,0), (2,1), and (2,2)

If any of these vertical checks are satisfied, the method returns true.

Diagonal Wins

The final part of the method checks for diagonal wins. There are two possible diagonal winning conditions:

  • The main diagonal: positions (0,0), (1,1), and (2,2)
  • The anti-diagonal: positions (0,2), (1,1), and (2,0)

If the character p occupies all three positions in either diagonal, the method returns true.

Method Conclusion

If none of the horizontal, vertical, or diagonal conditions are met, the method concludes that the player has not won the game and returns false.

Usage

The IsWinner method is integral to determining the outcome of the Tic-Tac-Toe game. It is called after each player's move to check if they have achieved a winning line, thereby facilitating the progression and resolution of the game.

IsBoardFull Method

The IsBoardFull method is an essential function within the Board class of my Tic-Tac-Toe program. This method checks whether the game board is completely filled, meaning all positions on the 3x3 grid have been occupied by either player's symbol. Determining whether the board is full is crucial for identifying if the game has reached a draw. Below is a detailed explanation of how this method works.

Code Implementation

public bool IsBoardFull()
{
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            if (board[i, j] == '\0')
            {
                return false;
            }
        }
    }
    return true;
}

Detailed Explanation

Looping Through the Board

The IsBoardFull method uses nested for loops to iterate over each position on the 3x3 game board. The outer loop iterates over the rows, and the inner loop iterates over the columns. This approach ensures that every cell in the grid is examined systematically.

  • The outer loop runs three times (i = 0 to 2), covering each row.
  • The inner loop also runs three times (j = 0 to 2), covering each column within the current row.
Checking for Empty Positions

During each iteration, the method checks if the current position (i, j) on the board contains an empty value, represented by '\0'. The '\0' character indicates that a cell is unoccupied. If an empty cell is found, the method immediately returns false, signifying that the board is not yet full and the game can continue.

if (board[i, j] == '\0')
{
    return false;
}
Determining a Full Board

If the loops complete without encountering an empty cell, it means that every position on the board is occupied by either player's symbol. In this case, the method returns true, indicating that the board is full.

return true;

Method Conclusion

The IsBoardFull method plays a pivotal role in the game logic by determining if the game board is completely occupied. It helps in identifying draw scenarios where no more moves can be made, and no player has won. This method ensures that the game flow is appropriately managed by detecting when a stalemate has occurred.

Usage

The IsBoardFull method is typically called after each player's move to assess whether the game should continue or if it has ended in a draw. It complements the IsWinner method by providing a mechanism to detect draw conditions, thus allowing the game to conclude properly when no winning moves are available.

IsDraw Method

The IsDraw method is a vital component of the Board class in my Tic-Tac-Toe program. This method determines if the game has resulted in a draw, meaning the board is full, and neither player has won. It relies on the IsBoardFull and IsWinner methods to make this determination. Below is a detailed breakdown of how this method functions and contributes to the game's logic.

Code Implementation

public bool IsDraw()
{
    if (IsBoardFull() && !IsWinner('X') && !IsWinner('O'))
    {
        return true;
    }
    return false;
}

Detailed Explanation

Checking for a Full Board

The IsDraw method first calls the IsBoardFull method to check if all cells on the 3x3 grid are occupied. The IsBoardFull method returns true if there are no empty positions ('\0') left on the board. This is the first condition that must be satisfied for a draw.

if (IsBoardFull() ...
Verifying No Winners

Once it is confirmed that the board is full, the IsDraw method then checks if there is no winner. It does this by calling the IsWinner method twice:

  1. IsWinner('X'): Checks if player 'X' has formed a winning line.
  2. IsWinner('O'): Checks if player 'O' has formed a winning line.

The method evaluates these conditions using logical NOT operators (!). If neither IsWinner('X') nor IsWinner('O') returns true, it means that no player has won the game.

... && !IsWinner('X') && !IsWinner('O'))
Returning the Result

If both conditions are met—i.e., the board is full, and neither player 'X' nor player 'O' has won—the method returns true, indicating a draw.

{
    return true;
}

If either condition is not met, the method returns false, signifying that the game is not a draw, and it can continue.

return false;

Method Conclusion

The IsDraw method is a critical part of the game's logic, ensuring that the game properly recognizes when a stalemate has occurred. It checks for the fulfillment of conditions necessary for a draw, thereby allowing the game to end appropriately when no further moves can alter the game's outcome.

Usage

The IsDraw method is typically invoked after each move to determine if the game has reached a draw state. It complements the IsWinner method by providing a mechanism to detect draw scenarios, ensuring that the game logic accounts for all possible outcomes. This method helps to manage the game flow effectively, ensuring a proper conclusion to each game session.

Code

Program.cs

using TicTacToe;


Game game = new Game();

game.PlayGame();

Game.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TicTacToe
{
    public class Game
    {
        private Status game_status;
        private int current_player = 0;

        private Board game_board;
        private Player player_1;
        private Player player_2;
        public enum Status {Win, Draw, InProgress}

        public Game()
        {
            this.game_status = Status.InProgress;
            this.current_player = 1;
            this.game_board = new Board();
            this.player_1 = new Player();
            this.player_2 = new Player();
        }

        public Status GetStatus() => this.game_status;
        public void SetStatus(Status status) => this.game_status = status;

        public int GetCurrentPlayer() => this.current_player;
        public void SetCurrentPlayer(int player) => this.current_player = player;

        public void PlayGame()
        {
            // Print The Title Screen
            this.PrintTitleScreen();

            // Get Player 1 Username
            Console.WriteLine("Player 1: Please Select a Username: ");
            string player_1_username = Console.ReadLine();

            Console.WriteLine("\n" + player_1_username + ", Please Choose Your Symbol (X or O): ");
            string player_1_symbol = Console.ReadLine().ToUpper();

            // Get Player 2 Username
            Console.WriteLine("\nPlayer 2: Please Select a Username: ");
            string player_2_username = Console.ReadLine();

            // Set Player 1 symbol
            if (player_1_symbol == "X")
            {
                player_1.SetSymbol('X');
            } 
            else if (player_1_symbol == "O")
            {
                player_1.SetSymbol('O');
            } else
            {
                player_1.SetSymbol('X');
            }

            // Give Player 2 The Opposite Symbol
            if (player_1.GetSymbol() == 'X')
            {
                player_2.SetSymbol('O');
            }
            else
            {
                player_2.SetSymbol('X');
            }

            // Set Player Selections in Their Objects
            if (player_1_username == "")
            {
                player_1.SetUsername("Player 1");
            } else
            {
                player_1.SetUsername(player_1_username);
            }

            if (player_2_username == "")
            {
                player_2.SetUsername("Player 2");
            }
            else
            {
                player_2.SetUsername(player_2_username);
            }

            // Review Selections
            Console.WriteLine("\nPlayer 1: " + player_1.GetUsername() + " - Symbol: " + player_1.GetSymbol());
            Console.WriteLine("Player 2: " + player_2.GetUsername() + " - Symbol: " + player_2.GetSymbol());

            // Play Game Till a Winner or Draw
            while (this.game_status == Status.InProgress)
            {
                if (this.current_player == 1)
                {
                    Console.WriteLine("\n" + player_1.GetUsername() + " it is your turn...\n");
                } else
                {
                    Console.WriteLine("\n" + player_2.GetUsername() + " it is your turn...\n");
                }

                game_board.PrintBoard();

                Console.WriteLine("Select X Coordinate: \n");
                string x_axis = Console.ReadLine();

                Console.WriteLine("\nSelect Y Coordinate: \n");
                string y_axis = Console.ReadLine();

                int x = int.Parse(x_axis);
                int y = int.Parse(y_axis);

                if (this.current_player == 1)
                {
                    game_board.MakeMove(player_1.GetSymbol(), x, y);
                } else
                {
                    game_board.MakeMove(player_2.GetSymbol(), x, y);
                }

                this.SwitchPlayer();
                this.UpdateCurrentGameStatus();
            }
            this.PrintEndGame();
        }
        public void UpdateCurrentGameStatus()
        {
            if (game_board.IsDraw())
            {
                this.game_status = Status.Draw;
            }
            else if (game_board.IsWinner(player_1.GetSymbol()) || game_board.IsWinner(player_2.GetSymbol()))
            {
                this.game_status = Status.Win;
            }
            else
            {
                this.game_status = Status.InProgress;
            }
        }
        public void SwitchPlayer()
        {
            if (this.current_player == 1)
            {
                this.current_player = 2;
            }
            else
            {
                this.current_player = 1;
            }
        }
        public void PrintTitleScreen()
        {
            Console.WriteLine("--------------------------[ TicTacToe ]--------------------------\nProgrammer: Isaac Replogle\nGithub Project: https://github.com/GristleIDR/TicTacToe-CSharp\n\nWelcome...\n\n");
        }
        public void PrintEndGame()
        {
            Console.WriteLine("\n--------------------------[ TicTacToe ]--------------------------");

            if (this.game_status == Status.Win)
            {
                if (this.current_player == 1)
                {
                    Console.WriteLine("Congratulations " + player_2.GetUsername() + " You Have Won!\n");
                }
                else
                {
                    Console.WriteLine("Congratulations " + player_1.GetUsername() + " You Have Won!\n");
                }
            }
            else if (this.game_status == Status.Draw)
            {
                Console.WriteLine("The Game is a Draw!\n");
            }
            game_board.PrintBoard();
        }
    }
}

Board.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TicTacToe
{
    public class Board
    {
        private char[,] board = { { '\0', '\0', '\0' }, { '\0', '\0', '\0' }, { '\0', '\0', '\0' }, };

        public void MakeMove(char p, int x, int y)
        {
            this.board[x, y] = p;
        }
        public char GetPosition(int x, int y) => board[x, y];

        public void PrintBoard()
        {
            // Helper method to format the cell content
            string FormatCell(char cell)
            {
                return cell == '\0' ? " " : cell.ToString();
            }

            // Display the board with formatting adjustments
            Console.WriteLine("  Y\n" +
                              "2 |  " + FormatCell(this.board[0, 2]) + " | " + FormatCell(this.board[1, 2]) + " | " + FormatCell(this.board[2, 2]) +
                              "\n  | -----------\n" +
                              "1 |  " + FormatCell(this.board[0, 1]) + " | " + FormatCell(this.board[1, 1]) + " | " + FormatCell(this.board[2, 1]) +
                              "\n  | -----------\n" +
                              "0 |  " + FormatCell(this.board[0, 0]) + " | " + FormatCell(this.board[1, 0]) + " | " + FormatCell(this.board[2, 0]) +
                              "\n  -------------- X\n" +
                              "     0   1   2");
        }

        public void ClearBoard()
        {
            for (int i = 0; i < 3; i++)
            {
                for (int j = 0; j < 3; j++)
                {
                    board[i, j] = '\0';
                }

            }
        }

        public bool IsBoardFull()
        {
            for (int i = 0; i < 3; i++)
            {
                for (int j = 0; j < 3; j++)
                {
                    if (board[i, j] == '\0')
                    {
                        return false;
                    }
                }
            }
            return true;
        }

        public bool IsWinner(char p)
        {
            // Check For Horizontal Wins
            if ((this.GetPosition(0, 0) == p && this.GetPosition(1, 0) == p && this.GetPosition(2, 0) == p) ||
                     (this.GetPosition(0, 1) == p && this.GetPosition(1, 1) == p && this.GetPosition(2, 1) == p) ||
                     (this.GetPosition(0, 2) == p && this.GetPosition(1, 2) == p && this.GetPosition(2, 2) == p))
            {
                return true;
            }
            // Check For Vertical Wins
            else if ((this.GetPosition(0, 0) == p && this.GetPosition(0, 1) == p && this.GetPosition(0, 2) == p) ||
                (this.GetPosition(1, 0) == p && this.GetPosition(1, 1) == p && this.GetPosition(1, 2) == p) ||
                (this.GetPosition(2, 0) == p && this.GetPosition(2, 1) == p && this.GetPosition(2, 2) == p))
            {
                return true;
            }
            // Check For Diagonal Wins
            else if ((this.GetPosition(0, 0) == p && this.GetPosition(1, 1) == p && this.GetPosition(2, 2) == p) ||
                     (this.GetPosition(0, 2) == p && this.GetPosition(1, 1) == p && this.GetPosition(2, 0) == p))
            {
                return true;
            } 
            return false;
        }

        public bool IsDraw()
        {
            if (IsBoardFull() && !IsWinner('X') && !IsWinner('O'))
            {
                return true;
            }
            return false;
        }
    }
}

Player.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;

namespace TicTacToe
{
    public class Player
    {
        private string username;
        private char symbol;
        private int wins;
        private int losses; 
        public Player(string username, char symbol) 
        {
            this.username = username;
            this.symbol = symbol;
            this.wins = 0;
            this.losses = 0;
        }

        public Player()
        {
            this.username = "";
            this.symbol = '\0';
            this.wins = 0;
            this.losses = 0;
        }

        public void SetUsername(string username)
        {
            this.username = username;
        }
        public string GetUsername() => this.username;

        public void SetSymbol(char symbol)
        {
            this.symbol = symbol;
        }
        public char GetSymbol() => this.symbol;

        public void IncrementWins()
        {
            this.wins++;
        }
        public int GetWins() => this.wins;

        public void IncrementLosses()
        {
            this.losses++;
        }
        public int GetLosses() => this.losses;
    }
}

Tests

GameTest.cs

namespace TicTacToe.Tests
{
    public class GameTest
    {
        [Fact]
        public void ConsuctorTest()
        {
            // Create Test Object
            Game game = new Game();

            // Assert That Game Status is InProgress and The Current Player is 1
            Assert.Equal(Game.Status.InProgress, game.GetStatus());
            Assert.Equal(1, game.GetCurrentPlayer());
        }

        [Fact]
        public void SetStatusTest()
        {
            // Create Test Object
            Game game = new Game();

            // Set Status to Win
            game.SetStatus(Game.Status.Win);

            // Assert That Game Status is Win
            Assert.Equal(Game.Status.Win, game.GetStatus());

            // Set Status to Draw
            game.SetStatus(Game.Status.Draw);

            // Assert That Game Status is Draw
            Assert.Equal(Game.Status.Draw, game.GetStatus());

            // Set Status to InProgress
            game.SetStatus(Game.Status.InProgress);

            // Assert That Game Status is InProgress
            Assert.Equal(Game.Status.InProgress, game.GetStatus());
        }

        [Fact]
        public void SetCurrentPlayerTest()
        {
            // Create Test Object
            Game game = new Game();

            // Set Current Player to 2
            game.SetCurrentPlayer(2);

            // Assert That Current Player is 2
            Assert.Equal(2, game.GetCurrentPlayer());

            // Set Current Player to 1
            game.SetCurrentPlayer(1);

            // Assert That Current Player is 1
            Assert.Equal(1, game.GetCurrentPlayer());
        }

        [Fact]
        public void SwitchPlayerTest()
        {
            // Create a Test Object
            Game game = new Game();

            // Assert That Current Player is 1
            Assert.Equal(1, game.GetCurrentPlayer());

            game.SwitchPlayer();

            // Assert That The Current Player is 2
            Assert.Equal(2, game.GetCurrentPlayer());

            game.SwitchPlayer();

            // Assert That The Current Player is Back to 1
            Assert.Equal(1, game.GetCurrentPlayer());
        }
    }
}

BoardTest.cs

namespace TicTacToe.Tests
{
    public class BoardTest
    {
        [Fact]
        public void MakeMoveTest()
        {
            // Create Test Object
            Board board = new Board();

            // Assert That The Board Is Blank
            Assert.Equal('\0', board.GetPosition(0, 0));
            Assert.Equal('\0', board.GetPosition(0, 1));
            Assert.Equal('\0', board.GetPosition(0, 2));
            Assert.Equal('\0', board.GetPosition(1, 0));
            Assert.Equal('\0', board.GetPosition(1, 1));
            Assert.Equal('\0', board.GetPosition(1, 2));
            Assert.Equal('\0', board.GetPosition(2, 0));
            Assert.Equal('\0', board.GetPosition(2, 1));
            Assert.Equal('\0', board.GetPosition(2, 2));

            // Make a Move
            board.MakeMove('X', 0, 0);

            // Verify That The Move Was Made
            Assert.Equal('X', board.GetPosition(0, 0));
            Assert.Equal('\0', board.GetPosition(0, 1));
            Assert.Equal('\0', board.GetPosition(0, 2));
            Assert.Equal('\0', board.GetPosition(1, 0));
            Assert.Equal('\0', board.GetPosition(1, 1));
            Assert.Equal('\0', board.GetPosition(1, 2));
            Assert.Equal('\0', board.GetPosition(2, 0));
            Assert.Equal('\0', board.GetPosition(2, 1));
            Assert.Equal('\0', board.GetPosition(2, 2));
        }

        [Fact]
        public void ClearBoardTest()
        {
            // Create Test Board
            Board board = new Board();

            /* Test Fill The Board
             *    X | X | X
             *   -----------
             *    O | X | O
             *   -----------
             *    X | O | O
            */
            board.MakeMove('X', 0, 0);
            board.MakeMove('O', 0, 1);
            board.MakeMove('X', 0, 2);
            board.MakeMove('O', 1, 0);
            board.MakeMove('X', 1, 1);
            board.MakeMove('X', 1, 2);
            board.MakeMove('O', 2, 0);
            board.MakeMove('O', 2, 1);
            board.MakeMove('X', 2, 2);

            // Assert That The Board Was Filled
            Assert.Equal('X', board.GetPosition(0, 0));
            Assert.Equal('O', board.GetPosition(0, 1));
            Assert.Equal('X', board.GetPosition(0, 2));
            Assert.Equal('O', board.GetPosition(1, 0));
            Assert.Equal('X', board.GetPosition(1, 1));
            Assert.Equal('X', board.GetPosition(1, 2));
            Assert.Equal('O', board.GetPosition(2, 0));
            Assert.Equal('O', board.GetPosition(2, 1));
            Assert.Equal('X', board.GetPosition(2, 2));

            // Clear The Board
            /* Expected After Clearing The Board
             *      |   |  
             *   -----------
             *      |   |  
             *   -----------
             *      |   |  
            */
            board.ClearBoard();

            // Verify That The Board Cleared
            Assert.Equal('\0', board.GetPosition(0, 0));
            Assert.Equal('\0', board.GetPosition(0, 1));
            Assert.Equal('\0', board.GetPosition(0, 2));
            Assert.Equal('\0', board.GetPosition(1, 0));
            Assert.Equal('\0', board.GetPosition(1, 1));
            Assert.Equal('\0', board.GetPosition(1, 2));
            Assert.Equal('\0', board.GetPosition(2, 0));
            Assert.Equal('\0', board.GetPosition(2, 1));
            Assert.Equal('\0', board.GetPosition(2, 2));
        }

        [Fact]
        public void IsBoardFullTest()
        {
            // Create a Test Object
            Board board = new Board();

            // Verify That The Board Should Not Be Full
            Assert.False(board.IsBoardFull());

            /* Test Fill The Board
             *    X | X | X
             *   -----------
             *    O | X | O
             *   -----------
             *    X | O | O
            */
            board.MakeMove('X', 0, 0);
            board.MakeMove('O', 0, 1);
            board.MakeMove('X', 0, 2);
            board.MakeMove('O', 1, 0);
            board.MakeMove('X', 1, 1);
            board.MakeMove('X', 1, 2);
            board.MakeMove('O', 2, 0);
            board.MakeMove('O', 2, 1);
            board.MakeMove('X', 2, 2);

            // Assert That The Board Should Be Full
            Assert.True(board.IsBoardFull());

            // Clear The Board
            board.ClearBoard();

            // Assert That The Board Is Not Full
            Assert.False(board.IsBoardFull());

            /* Partially Fill The Board
             *    X | X | X
             *   -----------
             *      | X | O
             *   -----------
             *    X | O | O
            */
            board.MakeMove('X', 0, 0);
            board.MakeMove('\0', 0, 1);
            board.MakeMove('X', 0, 2);
            board.MakeMove('O', 1, 0);
            board.MakeMove('X', 1, 1);
            board.MakeMove('X', 1, 2);
            board.MakeMove('O', 2, 0);
            board.MakeMove('O', 2, 1);
            board.MakeMove('X', 2, 2);

            // Assert That The Board Is Not Full
            Assert.False(board.IsBoardFull());
        }

        [Fact]
        public void IsWinnerTest()
        {
            // Create a Test Object
            Board board = new Board();

            // Test All Possible Winning Combinations

            /* ------------------------------------
            * Combination 1: Vertical Left Column X
            * -------------------------------------
            *    X |   | O
            *   -----------
            *    X | O |  
            *   -----------
            *    X |   | O
            */
            board.MakeMove('X', 0, 0);
            board.MakeMove('X', 0, 1);
            board.MakeMove('X', 0, 2);
            board.MakeMove('\0', 1, 0);
            board.MakeMove('O', 1, 1);
            board.MakeMove('\0', 1, 2);
            board.MakeMove('O', 2, 0);
            board.MakeMove('\0', 2, 1);
            board.MakeMove('O', 2, 2);

            // Expect That X Is The Winner
            Assert.True(board.IsWinner('X'));

            // Expect That O Is Not The Winner
            Assert.False(board.IsWinner('O'));

            // Clear The Board
            board.ClearBoard();

            /* ------------------------------------
            * Combination 2: Vertical Left Column O
            * -------------------------------------
            *    O |   | X
            *   -----------
            *    O | X |  
            *   -----------
            *    O |   | X
            */
            board.MakeMove('O', 0, 0);
            board.MakeMove('O', 0, 1);
            board.MakeMove('O', 0, 2);
            board.MakeMove('\0', 1, 0);
            board.MakeMove('X', 1, 1);
            board.MakeMove('\0', 1, 2);
            board.MakeMove('X', 2, 0);
            board.MakeMove('\0', 2, 1);
            board.MakeMove('X', 2, 2);

            // Expect That O Is The Winner
            Assert.True(board.IsWinner('O'));

            // Expect That X Is Not The Winner
            Assert.False(board.IsWinner('X'));

            // Clear The Board
            board.ClearBoard();

            /*---------------------------------------
            * Combination 3: Vertical Middle Column X
            * ---------------------------------------
            *      | X | O 
            *   -----------
            *    O | X |  
            *   -----------
            *      | X | O 
            */
            board.MakeMove('\0', 0, 0);
            board.MakeMove('O', 0, 1);
            board.MakeMove('\0', 0, 2);
            board.MakeMove('X', 1, 0);
            board.MakeMove('X', 1, 1);
            board.MakeMove('X', 1, 2);
            board.MakeMove('O', 2, 0);
            board.MakeMove('\0', 2, 1);
            board.MakeMove('O', 2, 2);

            // Expect That X Is The Winner
            Assert.True(board.IsWinner('X'));

            // Expect That O Is Not The Winner
            Assert.False(board.IsWinner('O'));

            // Clear The Board
            board.ClearBoard();

            /*---------------------------------------
            * Combination 4: Vertical Middle Column O
            * ---------------------------------------
            *      | O | X
            *   -----------
            *    X | O |  
            *   -----------
            *      | O | X
            */
            board.MakeMove('\0', 0, 0);
            board.MakeMove('X', 0, 1);
            board.MakeMove('\0', 0, 2);
            board.MakeMove('O', 1, 0);
            board.MakeMove('O', 1, 1);
            board.MakeMove('O', 1, 2);
            board.MakeMove('X', 2, 0);
            board.MakeMove('\0', 2, 1);
            board.MakeMove('X', 2, 2);

            // Expect That O Is The Winner
            Assert.True(board.IsWinner('O'));

            // Expect That X Is Not The Winner
            Assert.False(board.IsWinner('X'));

            // Clear The Board
            board.ClearBoard();

            /* -------------------------------------
            * Combination 5: Vertical Right Column X
            * --------------------------------------
            *      | O | X 
            *   -----------
            *    O |   | X
            *   -----------
            *      | O | X
            */
            board.MakeMove('\0', 0, 0);
            board.MakeMove('O', 0, 1);
            board.MakeMove('\0', 0, 2);
            board.MakeMove('O', 1, 0);
            board.MakeMove('\0', 1, 1);
            board.MakeMove('O', 1, 2);
            board.MakeMove('X', 2, 0);
            board.MakeMove('X', 2, 1);
            board.MakeMove('X', 2, 2);

            // Expect That X Is The Winner
            Assert.True(board.IsWinner('X'));

            // Expect That O Is Not The Winner
            Assert.False(board.IsWinner('O'));

            // Clear The Board
            board.ClearBoard();

            /* -------------------------------------
            * Combination 6: Vertical Right Column O
            * --------------------------------------
            *      | X | O
            *   -----------
            *    X |   | O
            *   -----------
            *      | X | O
            */
            board.MakeMove('\0', 0, 0);
            board.MakeMove('X', 0, 1);
            board.MakeMove('\0', 0, 2);
            board.MakeMove('X', 1, 0);
            board.MakeMove('\0', 1, 1);
            board.MakeMove('X', 1, 2);
            board.MakeMove('O', 2, 0);
            board.MakeMove('O', 2, 1);
            board.MakeMove('O', 2, 2);

            // Expect That O Is The Winner
            Assert.True(board.IsWinner('O'));

            // Expect That X Is Not The Winner
            Assert.False(board.IsWinner('X'));

            // Clear The Board
            board.ClearBoard();

            /* -------------------------------------
            * Combination 7: Horizontal Bottom Row X
            * --------------------------------------
            *    O | X | O
            *   -----------
            *    O | O | X
            *   -----------
            *    X | X | X
            */
            board.MakeMove('X', 0, 0);
            board.MakeMove('O', 0, 1);
            board.MakeMove('O', 0, 2);
            board.MakeMove('X', 1, 0);
            board.MakeMove('O', 1, 1);
            board.MakeMove('X', 1, 2);
            board.MakeMove('X', 2, 0);
            board.MakeMove('X', 2, 1);
            board.MakeMove('O', 2, 2);

            // Expect That X Is The Winner
            Assert.True(board.IsWinner('X'));

            // Expect That O Is Not The Winner
            Assert.False(board.IsWinner('O'));

            // Clear The Board
            board.ClearBoard();

            /* -------------------------------------
            * Combination 8: Horizontal Bottom Row O
            * --------------------------------------
            *    X | O | X
            *   -----------
            *    X | X | O
            *   -----------
            *    O | O | O
            */
            board.MakeMove('O', 0, 0);
            board.MakeMove('X', 0, 1);
            board.MakeMove('X', 0, 2);
            board.MakeMove('O', 1, 0);
            board.MakeMove('X', 1, 1);
            board.MakeMove('O', 1, 2);
            board.MakeMove('O', 2, 0);
            board.MakeMove('O', 2, 1);
            board.MakeMove('X', 2, 2);

            // Expect That O Is The Winner
            Assert.True(board.IsWinner('O'));

            // Expect That X Is Not The Winner
            Assert.False(board.IsWinner('X'));

            // Clear The Board
            board.ClearBoard();

            /* -------------------------------------
            * Combination 9: Horizontal Middle Row X
            * --------------------------------------
            *    O | O | X
            *   -----------
            *    X | X | X
            *   -----------
            *    O | X | O
            */
            board.MakeMove('O', 0, 0);
            board.MakeMove('X', 0, 1);
            board.MakeMove('O', 0, 2);
            board.MakeMove('X', 1, 0);
            board.MakeMove('X', 1, 1);
            board.MakeMove('O', 1, 2);
            board.MakeMove('O', 2, 0);
            board.MakeMove('X', 2, 1);
            board.MakeMove('X', 2, 2);

            // Expect That X Is The Winner
            Assert.True(board.IsWinner('X'));

            // Expect That O Is Not The Winner
            Assert.False(board.IsWinner('O'));

            // Clear The Board
            board.ClearBoard();

            /* --------------------------------------
            * Combination 10: Horizontal Middle Row O
            * ---------------------------------------
            *    X | X | O
            *   -----------
            *    O | O | O
            *   -----------
            *    X | O | X
            */
            board.MakeMove('X', 0, 0);
            board.MakeMove('O', 0, 1);
            board.MakeMove('X', 0, 2);
            board.MakeMove('O', 1, 0);
            board.MakeMove('O', 1, 1);
            board.MakeMove('X', 1, 2);
            board.MakeMove('X', 2, 0);
            board.MakeMove('O', 2, 1);
            board.MakeMove('O', 2, 2);

            // Expect That O Is The Winner
            Assert.True(board.IsWinner('O'));

            // Expect That X Is Not The Winner
            Assert.False(board.IsWinner('X'));

            // Clear The Board
            board.ClearBoard();

            /* -----------------------------------
            * Combination 11: Horizontal Top Row X
            * ------------------------------------
            *    X | X | X
            *   -----------
            *    O | O | X
            *   -----------
            *    O | X | O
            */
            board.MakeMove('O', 0, 0);
            board.MakeMove('O', 0, 1);
            board.MakeMove('X', 0, 2);
            board.MakeMove('X', 1, 0);
            board.MakeMove('O', 1, 1);
            board.MakeMove('X', 1, 2);
            board.MakeMove('O', 2, 0);
            board.MakeMove('X', 2, 1);
            board.MakeMove('X', 2, 2);

            // Expect That X Is The Winner
            Assert.True(board.IsWinner('X'));

            // Expect That O Is Not The Winner
            Assert.False(board.IsWinner('O'));

            // Clear The Board
            board.ClearBoard();

            /* -----------------------------------
            * Combination 12: Horizontal Top Row O
            * ------------------------------------
            *    O | O | O
            *   -----------
            *    X | X | O
            *   -----------
            *    X | O | X
            */
            board.MakeMove('X', 0, 0);
            board.MakeMove('X', 0, 1);
            board.MakeMove('O', 0, 2);
            board.MakeMove('O', 1, 0);
            board.MakeMove('X', 1, 1);
            board.MakeMove('O', 1, 2);
            board.MakeMove('X', 2, 0);
            board.MakeMove('O', 2, 1);
            board.MakeMove('O', 2, 2);

            // Expect That O Is The Winner
            Assert.True(board.IsWinner('O'));

            // Expect That X Is Not The Winner
            Assert.False(board.IsWinner('X'));

            // Clear The Board
            board.ClearBoard();

            /* -----------------------------------
            * Combination 13: Diagonal Top Right X
            * ------------------------------------
            *    O | O | X
            *   -----------
            *    X | X | O
            *   -----------
            *    X | O | X
            */
            board.MakeMove('X', 0, 0);
            board.MakeMove('X', 0, 1);
            board.MakeMove('O', 0, 2);
            board.MakeMove('O', 1, 0);
            board.MakeMove('X', 1, 1);
            board.MakeMove('O', 1, 2);
            board.MakeMove('X', 2, 0);
            board.MakeMove('O', 2, 1);
            board.MakeMove('X', 2, 2);

            // Expect That X Is The Winner
            Assert.True(board.IsWinner('X'));

            // Expect That O Is Not The Winner
            Assert.False(board.IsWinner('O'));

            // Clear The Board
            board.ClearBoard();

            /* -----------------------------------
            * Combination 14: Diagonal Top Right O
            * ------------------------------------
            *    X | X | O
            *   -----------
            *    O | O | X
            *   -----------
            *    O | X | O
            */
            board.MakeMove('O', 0, 0);
            board.MakeMove('O', 0, 1);
            board.MakeMove('X', 0, 2);
            board.MakeMove('X', 1, 0);
            board.MakeMove('O', 1, 1);
            board.MakeMove('X', 1, 2);
            board.MakeMove('O', 2, 0);
            board.MakeMove('X', 2, 1);
            board.MakeMove('O', 2, 2);

            // Expect That O Is The Winner
            Assert.True(board.IsWinner('O'));

            // Expect That X Is Not The Winner
            Assert.False(board.IsWinner('X'));

            // Clear The Board
            board.ClearBoard();

            /* ----------------------------------
            * Combination 15: Diagonal Top Left X
            * -----------------------------------
            *    X | O | X
            *   -----------
            *    O | X | O
            *   -----------
            *    O | O | X
            */
            board.MakeMove('O', 0, 0);
            board.MakeMove('O', 0, 1);
            board.MakeMove('X', 0, 2);
            board.MakeMove('O', 1, 0);
            board.MakeMove('X', 1, 1);
            board.MakeMove('O', 1, 2);
            board.MakeMove('X', 2, 0);
            board.MakeMove('O', 2, 1);
            board.MakeMove('X', 2, 2);

            // Expect That X Is The Winner
            Assert.True(board.IsWinner('X'));

            // Expect That O Is Not The Winner
            Assert.False(board.IsWinner('O'));

            // Clear The Board
            board.ClearBoard();

            /* ----------------------------------
            * Combination 16: Diagonal Top Left O
            * -----------------------------------
            *    O | X | O
            *   -----------
            *    X | O | X
            *   -----------
            *    X | X | O
            */
            board.MakeMove('X', 0, 0);
            board.MakeMove('X', 0, 1);
            board.MakeMove('O', 0, 2);
            board.MakeMove('X', 1, 0);
            board.MakeMove('O', 1, 1);
            board.MakeMove('X', 1, 2);
            board.MakeMove('O', 2, 0);
            board.MakeMove('X', 2, 1);
            board.MakeMove('O', 2, 2);

            // Expect That O Is The Winner
            Assert.True(board.IsWinner('O'));

            // Expect That X Is Not The Winner
            Assert.False(board.IsWinner('X'));

            // Clear The Board
            board.ClearBoard();
        }

        [Fact]
        public void IsDrawTest()
        {
            // Create a Test Object
            Board board = new Board();

            // Assert That The Game Is Not A Draw
            // The Board Is Empty
            /*
            *      |   |  
            *   -----------
            *      |   |  
            *   -----------
            *      |   |  
            */
            Assert.False(board.IsDraw());

            // Board Is Full But No Winner Variation 1
            /*
            *    X | X | O
            *   -----------
            *    O | O | X
            *   -----------
            *    X | O | O
            */
            board.MakeMove('X', 0, 0);
            board.MakeMove('O', 0, 1);
            board.MakeMove('X', 0, 2);
            board.MakeMove('O', 1, 0);
            board.MakeMove('O', 1, 1);
            board.MakeMove('X', 1, 2);
            board.MakeMove('O', 2, 0);
            board.MakeMove('X', 2, 1);
            board.MakeMove('O', 2, 2);

            // Assert That The Game Is A Draw
            Assert.True(board.IsDraw());

            // Clear The Board
            board.ClearBoard();

            // Board Is Full But No Winner Variation 2
            /*
            *    X | O | O
            *   -----------
            *    O | O | X
            *   -----------
            *    X | X | O
            */
            board.MakeMove('X', 0, 0);
            board.MakeMove('O', 0, 1);
            board.MakeMove('X', 0, 2);
            board.MakeMove('X', 1, 0);
            board.MakeMove('O', 1, 1);
            board.MakeMove('O', 1, 2);
            board.MakeMove('O', 2, 0);
            board.MakeMove('X', 2, 1);
            board.MakeMove('O', 2, 2);

            // Assert That The Game Is A Draw
            Assert.True(board.IsDraw());

            // Clear The Board
            board.ClearBoard();

            // Board Is Full But No Winner Variation 3
            /*
            *    O | X | O
            *   -----------
            *    X | X | O
            *   -----------
            *    X | O | X
            */
            board.MakeMove('X', 0, 0);
            board.MakeMove('X', 0, 1);
            board.MakeMove('O', 0, 2);
            board.MakeMove('O', 1, 0);
            board.MakeMove('X', 1, 1);
            board.MakeMove('X', 1, 2);
            board.MakeMove('X', 2, 0);
            board.MakeMove('O', 2, 1);
            board.MakeMove('O', 2, 2);

            // Assert That The Game Is A Draw
            Assert.True(board.IsDraw());

            // Clear The Board
            board.ClearBoard();

            // Board Is Full But There Is a Winner
            /*
            *    X | X | X
            *   -----------
            *    O | O | X
            *   -----------
            *    X | O | O
            */
            board.MakeMove('X', 0, 0);
            board.MakeMove('O', 0, 1);
            board.MakeMove('X', 0, 2);
            board.MakeMove('O', 1, 0);
            board.MakeMove('O', 1, 1);
            board.MakeMove('X', 1, 2);
            board.MakeMove('O', 2, 0);
            board.MakeMove('X', 2, 1);
            board.MakeMove('X', 2, 2);

            // Assert That The Game Is Not A Draw
            Assert.False(board.IsDraw());

            // Clear The Board
            board.ClearBoard();
        }
    }
}

PlayerTest.cs

namespace TicTacToe.Tests
{
    public class PlayerTest
    {
        [Fact]
        public void ConstructorTest()
        {
            // Create Test Object
            Player testPlayer1 = new Player("Bob", 'X');
            Player testPlayer2 = new Player();

            // Assertions
            Assert.True(testPlayer1.GetUsername() == "Bob");
            Assert.True(testPlayer1.GetSymbol() == 'X');
            Assert.True(testPlayer2.GetUsername() == "");
            Assert.True(testPlayer2.GetSymbol() == '\0');
        }

        [Fact]
        public void SetUsernameTest()
        {
            // Create Test Object
            Player testPlayer = new Player();

            // Set The Username
            testPlayer.SetUsername("test");

            // Assertions
            Assert.Equal("test", testPlayer.GetUsername());
            Assert.Equal('\0', testPlayer.GetSymbol());
        }

        [Fact]
        public void SetSymbolTest()
        {
            // Create Test Object
            Player testPlayer = new Player();

            // Set The Symbol
            testPlayer.SetSymbol('O');

            // Assertions
            Assert.Equal("", testPlayer.GetUsername());
            Assert.Equal('O', testPlayer.GetSymbol());
        }

        [Fact]
        public void IncrementWinsTest()
        {
            // Create Test Object
            Player player = new Player();

            // Assert Default Values
            Assert.Equal("", player.GetUsername());
            Assert.Equal('\0', player.GetSymbol());
            Assert.Equal(0, player.GetWins());
            Assert.Equal(0, player.GetLosses());

            // Increment Wins
            player.IncrementWins();

            // Verify That Wins Incremented and Didn't Change Other Values
            Assert.Equal("", player.GetUsername());
            Assert.Equal('\0', player.GetSymbol());
            Assert.Equal(1, player.GetWins());
            Assert.Equal(0, player.GetLosses());
        }

        [Fact]
        public void IncrementLossesTest()
        {
            // Create Test Object
            Player player = new Player();

            // Assert Default Values
            Assert.Equal("", player.GetUsername());
            Assert.Equal('\0', player.GetSymbol());
            Assert.Equal(0, player.GetWins());
            Assert.Equal(0, player.GetLosses());

            // Increment Losses
            player.IncrementLosses();

            // Verify That Wins Incremented and Didn't Change Other Values
            Assert.Equal("", player.GetUsername());
            Assert.Equal('\0', player.GetSymbol());
            Assert.Equal(0, player.GetWins());
            Assert.Equal(1, player.GetLosses());
        }
    }
}

Sources: