Skip to main content

Building a Tic Tac Toe Console Game in C#

When learning a new programming language, a common exercise is to create a classic game, such as Tic Tac Toe. In this post, I'll guide you through creating a simple, console-based Tic Tac Toe game in C#.

First, let's start with the challenge we're trying to solve:

Tic Tac Toe is a two-player game. We need to handle input from two players and alternate between them.

The game is played on a 3x3 grid. We need to keep track of the state of this grid.

A player wins by marking three spots in a row, either horizontally, vertically, or diagonally.

The game continues until a player has won or all spots on the grid have been marked, in which case the game is a draw.

To address these challenges, we need to develop a problem-solving mindset. Let's break down the problem into smaller, manageable pieces and think about how we could solve each one. This process is known as "decomposition" and it's a fundamental skill in programming and problem-solving in general.

Step 1: Storing the Game State

We'll use a simple one-dimensional array to store the state of the 3x3 game grid. Each element of the array corresponds to a spot on the grid, like this:

1 | 2 | 3
--+---+---
4 | 5 | 6
--+---+---
7 | 8 | 9

Each spot can be empty, marked by player 1 (we'll use 'X'), or marked by player 2 (we'll use 'O').

Here's how we can initialize our game state with a simple array:

class Board
{
    private readonly char[] positions;

    public Board()
    {
        positions = new[] { '1', '2', '3', '4', '5', '6', '7', '8', '9' };
    }
}

Step 2: Player Input

We need to take input from the players in turn. We'll prompt the current player to choose a spot to mark by entering a number from 1 to 9. After they choose a spot, we mark it and it's the next player's turn.

We create a Player class to handle the player input:

class Player
{
    public char Symbol { get; }

    public Player(char symbol)
    {
        Symbol = symbol;
    }
}

Then, we can get input from the players in the main game loop:

Console.WriteLine($"Player {player.Symbol}, it's your turn.");
Console.Write("Enter position (1-9): ");
var position = int.Parse(Console.ReadLine());

Step 3: Checking for a Winner

We'll check after each move if that move has made the current player a winner. There are eight ways to win Tic Tac Toe (three rows, three columns, two diagonals), so we could write eight separate conditions to check for a win. However, to reduce code duplication (following the DRY, or "Don't Repeat Yourself", principle), we'll store these combinations in a 2D array and use a loop to check them.

In the Board class, we define all winning combinations and provide a method to check if a player has won:

class Board
{
    // ... existing code ...

    private static readonly int[][] winningCombinations =
    {
        new[] { 0, 1, 2 },
        new[] { 3, 4, 5 },
        new[] { 6, 7, 8 },
        new[] { 0, 3, 6 },
        new[] { 1, 4, 7 },
        new[] { 2, 5, 8 },
        new[] { 0, 4, 8 },
        new[] { 2, 4, 6 }
    };

    // ... existing code ...

    public bool HasPlayerWon(char symbol)
    {
        return winningCombinations.Any(combination =>
            combination.All(index => positions[index] == symbol));
    }
}

Step 4: End of the Game

The game ends when a player wins, or when all spots on the grid have been marked. In the latter case, the game is a draw.

We've just followed the "Single Responsibility Principle" which is a part of SOLID principles, where each function in our program does one and only one thing.

Here's the code for our Tic Tac Toe game, implemented with these problem-solving steps in mind. Refer to the previous conversation to understand the code in detail.

We also add methods in the Board class to check if a position is taken, to mark a position, and to check if the board is full:

class Board
{
    // ... existing code ...

    public bool IsPositionTaken(int position)
    {
        return positions[position - 1] == 'X' || positions[position - 1] == 'O';
    }

    public void MakeMove(int position, char symbol)
    {
        positions[position - 1] = symbol;
    }

    public bool IsFull()
    {
        return positions.All(position => position == 'X' || position == 'O');
    }
}

Bringing It All Together

Now that we have the building blocks for our game, let's see how they all fit together in the main game loop:

static void Main()
{
    var board = new Board();
    var players = new[] { new Player('X'), new Player('O') };

    while (true)
    {
        foreach (var player in players)
        {
            Console.Clear();
            Console.WriteLine(board);

            Console.WriteLine($"Player {player.Symbol}, it's your turn.");
            Console.Write("Enter position (1-9): ");

            var position = int.Parse(Console.ReadLine());

            if (board.IsPositionTaken(position))
            {
                Console.WriteLine("Position is already taken, try again.");
                Console.ReadKey();
                continue;
            }

            board.MakeMove(position, player.Symbol);

            if (board.HasPlayerWon(player.Symbol))
            {
                Console.Clear();
                Console.WriteLine(board);
                Console.WriteLine($"Player {player.Symbol} wins!");
                return;
            }

            if (board.IsFull())
            {
                Console.Clear();
                Console.WriteLine(board);
                Console.WriteLine("The game is a draw!");
                return;
            }
        }
    }
}

Overcoming Technical Challenges

During the development, some challenges might have arisen. For example, we need to handle the possibility that a player could try to mark a spot that has already been marked. We overcome this by checking if a spot is already marked before we try to mark it.

Another challenge is keeping the code maintainable as we add more features. For example, if we decided to add a feature to let the players choose their own symbols instead of 'X' and 'O', it could involve many changes if the symbols were hardcoded throughout the code. To keep our code maintainable and easy to change, we encapsulate the concept of a player into a Player class and use instances


You can download the source code from here.

And that's it! You now have a simple, console-based Tic Tac Toe game in C#. It's a simple application, but it involves a variety of fundamental concepts in programming, such as loops, arrays, and classes. Happy coding!


Comments

Some of My Bests

ডাটা স্ট্রাকচার- স্ট্যাক (Stack)

Programming is all about data manipulation. Data structure is way of storing data for further manipulation. ডাটা স্ট্রাকচার আমাদেরকে বিভিন্ন ডাটা সাজিয়ে রাখার ব্যবস্থা করে দেয়। ডাটা সাজিয়ে রাখার অনেক গুলো "তরিকা" আছে। কোনকিছু আমরা কেন সাজিয়ে রাখি? যেন পরে নির্দিষ্ট একটা ডাটা সহজে খুঁজে পেতে পারি। "তরিকা" গুলোর নাম Array, Stack, Queue, Linked List, Tree, Graph. এগুলা শ খানেক ডাটা স্ট্রাকচারের মধ্যে কিছুর নাম, যেগুলো অনেক বেশি ব্যবহার হয়। এই পোস্টটা স্ট্যাক বুঝানোর জন্য। স্ট্যাক খুবই ইন্টেরেস্টিং একটা ডাটা স্ট্রাকচার। অনেক কারনেই এটা ব্যাবহার হয়। আগে স্ট্যাক কনসেপ্টটা নিয়ে আলোচনা করা যাক। স্ট্যাক বলতে বোঝায় একটার উপর একটা সাজায়ে রাখা। বিয়ের বাড়িতে আগে মেলামাইনের গ্লাস দেখা যেত একটার ভেতর আরেকটা ঢুকায়ে লম্বা একটা পাইল তৈরি করে একসাথে ক্যারি করা হচ্ছে। এটা একটা স্ট্যাক। প্রোগ্রামিং এর ভাষায় স্ট্যাক এক্সাক্টলি সেইম জিনিসই। তবে একটু ঘষামাজা আছে। আরেকটা উদাহরণ দেয়া যেতে পারে। বয়ামের ভেতর একটার পর আরেকটা বিস্কিট ঢুকিয়ে রাখা হয়। হ্যা, এইটা পারফেক্ট উদাহরণ হয়েছে। এই

ডাটা স্ট্রাকচার- কিউ (Queue)

Programming is all about data manipulation. Data structure is way of storing data for further manipulation. ডাটা স্ট্রাকচার আমাদেরকে বিভিন্ন ডাটা সাজিয়ে রাখার ব্যবস্থা করে দেয়। ডাটা সাজিয়ে রাখার অনেক গুলো "তরিকা" আছে। কোনকিছু আমরা কেন সাজিয়ে রাখি? যেন পরে নির্দিষ্ট একটা ডাটা সহজে খুঁজে পেতে পারি। "তরিকা" গুলোর নাম Array, Stack, Queue, Linked List, Tree, Graph. এগুলা শ খানেক ডাটা স্ট্রাকচারের মধ্যে কিছুর নাম, যেগুলো অনেক বেশি ব্যবহার হয়। এই পোস্টটা কিউ বুঝানোর জন্য। কিউ জিনিসটার সাথে আমরা সবাই পরিচিত। জীবনে আমরা সবাই কখনো না কখনো লাইনে দাঁড়ায়ছি। কিউ এর বেসিক ক্যারেক্টারিস্টিকসের সাথে মিলিয়েই প্রোগ্রামিং এ কিউ এর কনসেপ্ট। বাস্তব জীবনে একটা কিউ তে কি হয়? সবাই লাইন ধরে দাঁড়ায় কিছু একটা কারনে। যে সবার আগে দাঁড়ায় সেই সবার আগে কার্জসিদ্ধি করে। সবার পরের জন সবার পরে। স্ট্যাকের ক্ষেত্রে আমরা পড়েছিলাম Last In First Out (LIFO) or First In Last Out (FILO)। কেমন আনফেয়ার শোনায় না? সবার পরে আসবে, আবার সবার আগে চলে যাবে। অ্যাটলিস্ট আমার আনফেয়ার লেগেছিলো যখন স্ট্যাক শিখছিল

কম্পিউটার বচন

কম্পিউটারের সাথে আমার পরিচয় খুব ছোট বয়সে না। অন্তত চেনাপরিচিত বন্ধুমহলের অনেকের তুলনায় আমি বাচ্চাই বলা চলে। ক্লাস ২ বা ৩ তে পড়ি যখন ফুফাতো ভাই প্রথম কম্পিউটার কেনে। ফুফুর বাসা কাছেই হওয়ায় মাঝে মাঝেই সেটা দেখার সৌভাগ্য হতো। কিন্তু ছুয়ে দেখার সাহস তখনও হয়নি। সেই বছর থেকে আমাদের রেওয়াজ, ঈদের দিন নামাজ পড়েই সোজা ভাইয়ার রুমে। ভাইয়া নতুন একটা গেম ইন্সটল করে রাখতো আর আমরা কাজিনেরা লাইন দিয়ে সেটা খেলতাম (যদিও আমার ভাগে কমই পড়তো :p )  ক্লাস সিক্সে নতুন স্কুলে ভর্তি হলাম। এ এক আজব জায়গা। সবাই বড়লোকের পোলাপাইন। কতজন গাড়িতে করে আসে। তাদের তো কথাই নাই। যাদের গাড়ি নাই তাদেরও রোজকার বাজেট বিশাল। ইচ্ছামত উড়ায়। আর আমি মধ্যবিত্তের ছেলে। যাওয়া আসার ভাড়া বাদে কোনো টাকা পেতামনা। টিফিন বাসা থেকে বানায়ে দিতো। এই যখন আমার অবস্থা তখন ক্লাসমেটরা নিত্যনতুন গ্যাজেটের সাথে শুধু পরিচয়ই না, পকেটে নিয়ে ঘুরে (যদিও স্কুলে নিষিদ্ধ ছিলো )। সবাই এসব নিয়ে কত গল্প চালায় যায়। আর আমার মত বোকা কিছু পাবলিক হা করে এদের গল্প শুনি। কম্পিউটারের কত বচন, কত আড্ডা। এসব আড্ডায় ঢুকতে কত ইচ্ছা করতো, কিন্তু ঢুকবো কি করে? আমার কম্পিউটা