Code:
namespace Nac
{
using System;
using System.Collections.Generic;
internal static class Program
{
private class Position
{
public int X { get; private set; }
public int Y { get; private set; }
public Position(int x, int y)
{
this.X = x;
this.Y = y;
}
public void Right()
{
this.Y++;
}
public void Left()
{
this.Y--;
}
public void Up()
{
this.X--;
}
public void Down()
{
this.X++;
}
}
private const int Height = 3;
private const int Width = 3;
private const char Blank = ' ';
private const char HumanPlayerSymbol = 'X';
private const char AiPlayerSymbol = 'O';
private static readonly Random Random = new Random();
private static Position _cursorPosition = new Position(0, 0);
private static readonly char[,] Field = new char[Program.Height, Program.Width];
private static void Main()
{
Console.Title = "Tic-tac-toe";
Console.CursorVisible = false;
do
{
Program.ResetField();
while (Program.HandleInput())
{
Program.Render();
}
} while (Console.ReadKey(false).Key == ConsoleKey.R);
}
private static void ResetField()
{
for (int x = 0; x < Program.Height; x++)
{
for (int y = 0; y < Program.Width; y++)
{
Program.Field[x, y] = Program.Blank;
}
}
Program.Render();
}
private static bool HandleInput()
{
switch (Console.ReadKey(true).Key)
{
case ConsoleKey.Escape:
case ConsoleKey.Q:
return false;
case ConsoleKey.RightArrow:
if (Program._cursorPosition.Y < Program.Width - 1)
{
Program._cursorPosition.Right();
}
break;
case ConsoleKey.LeftArrow:
if (Program._cursorPosition.Y > 0)
{
Program._cursorPosition.Left();
}
break;
case ConsoleKey.DownArrow:
if (Program._cursorPosition.X < Program.Height - 1)
{
Program._cursorPosition.Down();
}
break;
case ConsoleKey.UpArrow:
if (Program._cursorPosition.X > 0)
{
Program._cursorPosition.Up();
}
break;
case ConsoleKey.Spacebar:
if (Program.SetCell(Program._cursorPosition.X, Program._cursorPosition.Y, Program.HumanPlayerSymbol) == false)
{
break;
}
if (Program.PrintWinner())
{
return false;
}
if (Program.MakeAiMove() == false)
{
Program.PrintMessage("Uh oh.. nobody wins. Press 'R' to play again.", ConsoleColor.Red);
return false;
}
if (Program.PrintWinner())
{
return false;
}
break;
case ConsoleKey.R:
Program.ResetField();
break;
}
return true;
}
private static bool PrintWinner()
{
char winner = Program.DetermineWinner();
if (winner != Program.Blank)
{
Program.Render();
Program.PrintMessage($"Player {winner} won. Press 'R' to play again.", ConsoleColor.Green);
return true;
}
return false;
}
private static void PrintMessage(string message, ConsoleColor color)
{
Console.ForegroundColor = color;
Console.SetCursorPosition(0, Program.Height * 2);
Console.WriteLine(message);
Console.ResetColor();
Program.RenderCursor();
}
private static IList<Position> GetAvailableCells()
{
List<Position> available = new List<Position>();
for (int x = 0; x < Program.Height; x++)
{
for (int y = 0; y < Program.Width; y++)
{
if (Program.Field[x, y] == Program.Blank)
{
available.Add(new Position(x, y));
}
}
}
return available;
}
private static char DetermineWinner()
{
// Horizontally
for (int x = 0; x < Program.Height; x++)
{
bool won = true;
for (int y = 0; y < Program.Width - 1; y++)
{
won = won && Program.Field[x, y] != Program.Blank && Program.Field[x, y] == Program.Field[x, y + 1];
}
if (won)
{
return Program.Field[x, 0];
}
}
// Vertically
for (int y = 0; y < Program.Width; y++)
{
bool won = true;
for (int x = 0; x < Program.Height - 1; x++)
{
won = won && Program.Field[x, y] != Program.Blank && Program.Field[x, y] == Program.Field[x + 1, y];
}
if (won)
{
return Program.Field[0, y];
}
}
// Diagonal left to right
{
bool won = true;
for (int i = 0; i < Program.Height - 1; i++)
{
won = won && Program.Field[i, i] != Program.Blank &&
Program.Field[i, i] == Program.Field[i + 1, i + 1];
}
if (won)
{
return Program.Field[0, 0];
}
}
// Diagonal right to left
{
bool won = true;
for (int i = 0; i < Program.Height - 1; i++)
{
won = won && Program.Field[i, Program.Width - i - 1] != Program.Blank &&
Program.Field[i, Program.Width - i - 1] == Program.Field[i + 1, Program.Width - i - 2];
}
if (won)
{
return Program.Field[0, Program.Width - 1];
}
}
return Program.Blank;
}
private static bool MakeAiMove()
{
IList<Position> availableCells = Program.GetAvailableCells();
if (availableCells.Count == 0)
{
return false;
}
int pick = Program.Random.Next(0, availableCells.Count);
Program.SetCell(availableCells[pick].X, availableCells[pick].Y, Program.AiPlayerSymbol);
return true;
}
private static bool SetCell(int x, int y, char symbol)
{
if (Program.Field[x, y] != Program.Blank)
{
return false;
}
if (x > Program.Height || x < 0 || y > Program.Width || y < 0)
{
return false;
}
Program.Field[x, y] = symbol;
return true;
}
private static void Render()
{
Console.SetCursorPosition(0, 0);
for (int x = 0; x < Program.Height; x++)
{
for (int y = 0; y < Program.Width - 1; y++)
{
Console.Write($" {Program.Field[x, y]} |");
}
Console.Write($" {Program.Field[x, Program.Width - 1]} ");
Console.WriteLine();
if (x != Program.Height - 1)
{
const int numberOfDashes = (Program.Width - 1) * 4 + 3;
Console.WriteLine(new string('-', numberOfDashes));
}
}
// Clear printed message
Console.WriteLine();
Console.WriteLine(new string('\0', Console.BufferWidth));
Program.RenderCursor();
}
private static void RenderCursor()
{
Console.SetCursorPosition(Program._cursorPosition.Y * 4 + 1, Program._cursorPosition.X * 2);
Console.BackgroundColor = ConsoleColor.Cyan;
Console.ForegroundColor = ConsoleColor.Black;
Console.Write(Program.Field[Program._cursorPosition.X, Program._cursorPosition.Y]);
Console.ResetColor();
}
}
}