Simple, Quality, Awesome Software

Connect Four

Below is the complete code for the Connect Four Try It Out! problem.

Program Class

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

namespace CSharpBook.Examples.ConnectFour
{
    class Program
    {
        /// <summary>
        /// Allows two players to play a game of Connect Four against each
        /// other.
        /// </summary>
        static void Main(string[] args)
        {
            Board board = new Board();

            int currentColumn = 3;
            TokenType type = TokenType.Red;

            // Loop until one player or the other has won, or the entire
            // board is full.
            while (!board.HasWon(TokenType.Red) &&
                !board.HasWon(TokenType.Black) &&
                !board.IsFull())
            {
                // Draw the current state of the board.
                Console.Clear();
                DrawHeader(board, currentColumn, type);
                board.DrawBoard();

                // Get input from the user.
                // Pressing a number 1-7 will select that column.
                // Left and right arrow keys can be used to move over one
                // column at a time.
                ConsoleKeyInfo key = Console.ReadKey();
                if (key.KeyChar == '1') { currentColumn = 0; }
                else if (key.KeyChar == '2') { currentColumn = 1; }
                else if (key.KeyChar == '3') { currentColumn = 2; }
                else if (key.KeyChar == '4') { currentColumn = 3; }
                else if (key.KeyChar == '5') { currentColumn = 4; }
                else if (key.KeyChar == '6') { currentColumn = 5; }
                else if (key.KeyChar == '7') { currentColumn = 6; }
                else if (key.Key == ConsoleKey.LeftArrow && currentColumn > 0)
                {
                    currentColumn--;
                }
                else if (key.Key == ConsoleKey.RightArrow && currentColumn < 6)
                {
                    currentColumn++;
                }
                else if (key.Key == ConsoleKey.Enter)
                {
                    // If they press <Enter>, we're going to try to drop the
                    // token at the current spot.
                    if (board.CanAdd(currentColumn))
                    {
                        board.Add(currentColumn, type);
                        if (type == TokenType.Red) { type = TokenType.Black; }
                        else { type = TokenType.Red; }
                    }
                }
            }

            // Wrapping up the game... print out the final board.
            Console.Clear();
            DrawHeader(board, currentColumn, type);
            board.DrawBoard();

            PrintWinConditions(board);

            Console.ReadKey();
        }

        /// <summary>
        /// Prints a message about who won when the game ends.
        /// </summary>
        /// <param name="board"></param>
        private static void PrintWinConditions(Board board)
        {
            if (board.HasWon(TokenType.Red))
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("Red Won!");
            }
            else if (board.HasWon(TokenType.Black))
            {
                Console.ForegroundColor = ConsoleColor.DarkGray;
                Console.WriteLine("Black Won!");
            }
            else
            {
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.WriteLine("The game was a draw.");
            }
        }

        /// <summary>
        /// Draws the top line, above the board. This shows what column
        /// the token is currently above, so that the player can know where to
        /// drop the token.
        /// </summary>
        private static void DrawHeader(Board board, int currentColumn,
                        TokenType currentType)
        {
            string symbol = board.CanAdd(currentColumn) ? "O" : "×";
            Console.ForegroundColor = currentType == TokenType.Red ?
                        ConsoleColor.Red : ConsoleColor.DarkGray;
            Console.WriteLine("   {0} {1} {2} {3} {4} {5} {6}",
                currentColumn == 0 ? symbol : " ",
                currentColumn == 1 ? symbol : " ",
                currentColumn == 2 ? symbol : " ",
                currentColumn == 3 ? symbol : " ",
                currentColumn == 4 ? symbol : " ",
                currentColumn == 5 ? symbol : " ",
                currentColumn == 6 ? symbol : " ");
        }
    }
}

The TokenType Enumeration

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

namespace CSharpBook.Examples.ConnectFour
{
    /// <summary>
    /// Represents the possible types of tokens that can
    /// be used in the game.
    /// </summary>
    public enum TokenType
    {
        /// <summary>
        /// The red player's tokens.
        /// </summary>
        Red,

        /// <summary>
        /// The black player's tokens.
        /// </summary>
        Black,

        /// <summary>
        /// Represents an empty square. Neither player's tokens.
        /// </summary>
        Empty
    }
}

The Board Class

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

namespace CSharpBook.Examples.ConnectFour
{
    /// <summary>
    /// Keeps track of the state of a board in a game of Connect Four,
    /// and additionally provides methods for modifying the contents of
    /// the board and determining if a player has won.
    /// </summary>
    public class Board
    {
        /// <summary>
        /// The number of rows in the grid.
        /// </summary>
        public static readonly int Rows = 6;

        /// <summary>
        /// The number of columns in the grid.
        /// </summary>
        public static readonly int Columns = 7;

        /// <summary>
        /// Stores the actual contents of the grid in the board.
        /// </summary>
        private TokenType[,] grid;

        /// <summary>
        /// Creates a new board that is empty.
        /// </summary>
        public Board()
        {
            grid = new TokenType[Rows, Columns];

            PopulateEmptyGrid();
        }

        /// <summary>
        /// Fills the grid with empty values.
        /// </summary>
        private void PopulateEmptyGrid()
        {
            for (int row = 0; row < Rows; row++)
            {
                for (int column = 0; column < Columns; column++)
                {
                    grid[row, column] = TokenType.Empty;
                }
            }
        }

        /// <summary>
        /// Figures out if a particular player, identified by that
        /// player's token type, has won the game yet.
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public bool HasWon(TokenType type)
        {
            // The basic process I'm using here is to go across every
            // row and every column in the board, and check to see if it is
            // the starting point for four in a row in each direction.
            for (int row = 0; row < Rows; row++)
            {
                for (int column = 0; column < Columns; column++)
                {
                    if (CheckVertically(row, column, type)) { return true; }  
                    if (CheckHorizontally(row, column, type)) { return true; }
                    if (CheckDiagonallyDown(row, column, type)) { return true; }
                    if (CheckDiagonallyUp(row, column, type)) { return true; }
                }
            }

            return false;
        }

        /// <summary>
        /// Looks to see if the given row and column is the starting point for
        /// four in a row, horizontally.
        /// </summary>
        private bool CheckHorizontally(int row, int column, TokenType type)
        {
            // If there aren't even four more spots before leaving the grid,
            // we know it can't be.
            if (column + 3 >= Columns) { return false; }

            for (int distance = 0; distance < 4; distance++)
            {
                if (grid[row, column + distance] != type) { return false; }
            }

            return true;
        }

        /// <summary>
        /// Looks to see if the given row and column is the starting point for
        /// four in a row, vertically.
        /// </summary>
        private bool CheckVertically(int row, int column, TokenType type)
        {            
            // If there aren't even four more spots before leaving the grid,
            // we know it can't be.
            if (row + 3 >= Rows) { return false; }

            for (int distance = 0; distance < 4; distance++)
            {
                if (grid[row + distance, column] != type) { return false; }
            }

            return true;
        }

        /// <summary>
        /// Looks to see if the given row and column is the starting point for
        /// four in a row, diagonally down.
        /// </summary>
        private bool CheckDiagonallyDown(int row, int column, TokenType type)
        {
            // If there aren't even four more spots before leaving the grid,
            // we know it can't be.
            if (row + 3 >= Rows) { return false; }
            if (column + 3 >= Columns) { return false; }

            for (int distance = 0; distance < 4; distance++)
            {
                if (grid[row + distance, column + distance] != type) { return false; }
            }

            return true;
        }

        /// <summary>
        /// Looks to see if the given row and column is the starting point for
        /// four in a row, diagonally up.
        /// </summary>
        private bool CheckDiagonallyUp(int row, int column, TokenType type)
        {
            // If there aren't even four more spots before leaving the grid,
            // we know it can't be.
            if (row - 3 < 0) { return false; }
            if (column + 3 >= Columns) { return false; }

            for (int distance = 0; distance < 4; distance++)
            {
                if (grid[row - distance, column + distance] != type) { return false; }
            }

            return true;
        }

        /// <summary>
        /// Determines if the board is full. Note that just because a board is
        /// full does not mean that the game ended in a draw. The last token
        /// placed may have completed four in a row for one player or another.
        /// </summary>
        /// <returns></returns>
        public bool IsFull()
        {
            for (int column = 0; column < Columns; column++)
            {
                if (CanAdd(column)) { return false; }
            }

            return true;
        }

        /// <summary>
        /// Adds the given token to the column given.
        /// </summary>
        public void Add(int column, TokenType type)
        {
            // If it is full, give up.
            if (!CanAdd(column)) { return; }

            // Starting at the top, look down to see how far down gravity will
            // pull it.
            int currentRow = 0;
            while (currentRow < Rows - 1 &&
                grid[currentRow + 1, column] == TokenType.Empty)
            {
                currentRow++;
            }

            // When we either get to the bottom or to another token, we can
            // go ahead and place the current token.
            grid[currentRow, column] = type;
        }

        /// <summary>
        /// Determines if a token can be added to the top of the given row.
        /// </summary>
        public bool CanAdd(int column)
        {
            if (grid[0, column] == TokenType.Empty) { return true; }
            else { return false; }
        }

        /// <summary>
        /// Does the work of of drawing the complete board to the console
        /// window.
        /// </summary>
        public void DrawBoard()
        {
            for (int row = 0; row < Rows; row++)
            {
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write("■■ ");
                for (int column = 0; column < Columns; column++)
                {
                    TokenType type = grid[row, column];
                    switch(type)
                    {
                        case TokenType.Empty:
                            Console.ForegroundColor = ConsoleColor.Gray;
                            Console.Write(". ");
                            break;
                        case TokenType.Red:
                            Console.ForegroundColor = ConsoleColor.Red;
                            Console.Write("O ");
                            break;
                        case TokenType.Black:
                            Console.ForegroundColor = ConsoleColor.DarkGray;
                            Console.Write("O ");
                            break;
                    }
                }

                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.WriteLine("■■");
            }

            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine("■■■■■■■■■■■■■■■■■■■");
        }
    }
}