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("■■■■■■■■■■■■■■■■■■■");
}
}
}