I just started learning coding about 4 months ago, I think Tic-tac-toe was the first application that I've ever written in C# not counting the mandatory "Hello, World!". 

So for me, I really wanted to revisit this game and code a new one, unfortunately I don't have the original code anymore, but I can assure you that it was a lot worse than whatever mess I've thrown together now ;). 

Anyhow, as always both positive and negative feedback is welcome. My design goal was to try and make a "clean" and well designed Tic-tac-toe game but also to try and use concepts and programming constructs that I'm new to. 

**Bonus question:** I'm planning to implement an "AI Player" class later on, I know that Tic-tac-toe is a solved game so there are more effective approaches for AI logic but I've never programmed an AI before so my idea is to at runtime generate a "database" of all possible gamestates, and then have the AI rank each "intermediate gamestate" by the amount of positive outcomes (that is where the AI wins) - negative outcomes when it decides where to place it's marker. Would this be a good idea? / Work at all?

**Game.cs**

    public partial class Game : Form
    {
        #region Fields & Properties

        internal static VisualCell[,] vGrid;
        internal static Player[] players;

        #endregion

        #region Constructors

        public Game()
        {
            InitializeComponent();
        }

        #endregion

        #region Instanced Methods

        private void Game_Load(object sender, EventArgs e)
        {
            players = new Player[2];
            players[0] = new Player(); // Set to human player or AI player
            players[1] = new Player(); // Always human
            GameRound.Complete += ShowCompletionDialog;
            GenerateGridBtns();
            Task roundTask = Task.Factory.StartNew(() => new GameRound().Start());
        }

        private void ShowCompletionDialog(OutcomeType gameOutcome)
        {
            long pID = (long)gameOutcome + 1;
            string WonText = "Player " + pID + " won!";

            if (gameOutcome == OutcomeType.Draw)
            { //*HACK*: If the marker is empty it means that the passed player was a dummy and game was a draw
                WonText = "The game ended in a draw!";
            }

            DialogResult dialogResult = MessageBox.Show("Play again?", WonText, MessageBoxButtons.YesNo);
            
            if (dialogResult == DialogResult.Yes)
            {
                RestartGame();
                // TODO: Add a bunch of resets here...
            } else if (dialogResult == DialogResult.No)
            {
                //do something else
            }
        }

        private void GenerateGridBtns()
        {
            vGrid = new VisualCell[3, 3];

            for (int x = 0; x < 3; x++)
            {
                for (int y = 0; y < 3; y++)
                {
                    vGrid[x, y] = new VisualCell() 
                    { 
                        AutoSize = true, 
                        Location = new Point(x*110, y*110), 
                        Size = new Size(124, 124),
                        Enabled = false,
                        Font = new Font(Font.FontFamily, 40),
                        CellPos = new Tuple<int,int>(x, y)
                    };
                    Controls.Add(vGrid[x, y]);
                }
            }
        }

        private void RestartGame() 
        {
            for (int x = 0; x < 3; x++)
            {
                for (int y = 0; y < 3; y++)
                {
                    vGrid[x, y].Invoke(new Action(() => vGrid[x, y].Text = String.Empty));
                    vGrid[x, y].Invoke(new Action(() => vGrid[x, y].Enabled = false));
                }
            }
            var round = new GameRound();
            Task roundTask = Task.Factory.StartNew(() => round.Start());
        }

        #endregion 
    }

**VCell.cs**

    class VisualCell : Button
    {
        #region Fields & Properties

        public Tuple<int, int> CellPos { get; set; }


        protected override bool ShowFocusCues
        {
            get
            {
                return false;
            }
        }

        #endregion

        #region Constructors

        public VisualCell(): base()
        {

            this.SetStyle(ControlStyles.Selectable, false);
        }

        #endregion
    }

**Cell.cs**

    class Cell
    {
        #region Properties & Fields

        private Mark markType = Mark.Empty; /* Have to use old way of defining properties here unfortunately due to lack of support for,
                                               default values in auto-implemented properties. */
        public Mark MarkType { 
            get { return markType; } 
            set 
            {   //Only allow the type of the cell to change if it it's empty
                if (MarkType == Mark.Empty)
                {
                    markType = value;
                }
            } 
        }

        #endregion

        #region Constructors

        public Cell()
        {
            MarkType = Mark.Empty;
        }

        #endregion
    }

**Grid.cs**

    enum OutcomeType
    {
        None = -1, CrossWin, NoughtWin, Draw
    }

    class Grid
    {
        const int MAX_CELLS = 3;
        
        #region Properties & Fields
        
        public Cell[,] cells { get; set; }
        public OutcomeType Outcome { get; private set; }
        
        #endregion

        #region Constructors

        public Grid()
        {
            Outcome = OutcomeType.None;
            cells = new Cell[MAX_CELLS, MAX_CELLS];

            for (int x = 0; x < MAX_CELLS; x++)
            {
                for (int y = 0; y < MAX_CELLS; y++)
                {
                    cells[x, y] = new Cell();
                }
            }
        }

        #endregion

        #region Instanced Methods

        public bool CheckOutcome(Tuple<int, int> coords, Player player)
        {
            //Check for draws first
            if (cells.GetEmptyCells().Length == 0)
            {
                Outcome = OutcomeType.Draw;   
                return true;
            }

            //Now check for player wins
            var corners = new Tuple<int, int>[] { Tuple.Create(0,0), Tuple.Create(2,0), Tuple.Create(0,2), Tuple.Create(2,2) };
            var sides = new Tuple<int, int>[] { Tuple.Create(1,0), Tuple.Create(0,1), Tuple.Create(1,2), Tuple.Create(2,1) };
            var middle = new Tuple<int, int>(1,1);
            var checkDiagonals = false;
            
            //If the cell is at the corner or the middle we have to check for diagonal wins too
            if (corners.Any(e => e.Equals(coords)) || middle.Equals(coords)) { checkDiagonals = true; }

            if (player.PlayerWon(cells[coords.Item1, coords.Item2], this, checkDiagonals))
            {
                switch (player.marker)
                {
                    case Mark.Cross:
                        Outcome = OutcomeType.CrossWin;
                        break;
                    case Mark.Nought:
                        Outcome = OutcomeType.NoughtWin;
                        break;
                }

                return true;
            }

            //If execution reaches this point then no one has won, return false
            return false;
        }

       #endregion
    }

**Player.cs**

    class Player
    {
        #region Fields & Properties

        private static int ctr;
        private static Game form;
        public Mark marker;
        public readonly int ID;
        public int Score { get; set; }

        #endregion 

        #region Constructors

        public Player()
        {
            ID = ctr++;
            
            if (ID == 0) { // X is the starting player
                marker = Mark.Cross; } 
            else { 
                marker = Mark.Nought; }
        }

        #endregion

        #region Instanced Methods

        public virtual Tuple<int, int> GetPlayerChoice(Grid grid, Player player) // Virtual because we want the option to override this for AIPlayer later.
        {
            var pMark = (player.ID == 0) ? "X" : "O";
            var choice = new Tuple<int,int>(-1, -1);
            var waitBtns = new List<Task>() { new Task(() => new SpinWait().SpinOnce()) };

            //Enable eligble buttons for player
            for (int x = 0; x < grid.cells.GetLength(0); x++)
            {
                for (int y = 0; y < grid.cells.GetLength(1); y++)
                {
                    if (grid.cells[x, y].MarkType == Mark.Empty)
                    {
                        Game.vGrid[x, y].Invoke(new Action(() => Game.vGrid[x, y].Enabled = true));                                
                        Game.vGrid[x, y].Click += new EventHandler(
                            (a,b) => 
                            {
                                var pos = new Tuple<int,int>((a as VisualCell).CellPos.Item1, (a as VisualCell).CellPos.Item2);
                                choice = Tuple.Create(pos.Item1, pos.Item2);
                                Game.vGrid[pos.Item1, pos.Item2].Invoke(new Action(() => Game.vGrid[pos.Item1, pos.Item2].Text = pMark));
                                Game.vGrid[pos.Item1, pos.Item2].Invoke(new Action(() => Game.vGrid[pos.Item1, pos.Item2].Enabled = false));
                                waitBtns.Add(new Task(() => new SpinWait().SpinOnce()));
                                waitBtns[waitBtns.FindIndex(t => t.Status == TaskStatus.Created)].Start();
                            });
                    }
                }                
            }
            Task.WaitAny(waitBtns.ToArray());
            return choice;
        }

        public bool PlayerWon(Cell cell, Grid grid, bool checkDiagonals) 
        {
            if (cell.HorizontalRelatives(grid) == 2 || cell.VerticalRelatives(grid) == 2)
            {
                return true;
            }
            
            if (checkDiagonals)
            {
                if (cell.DiagonalRelatives(grid) == 2) { return true; } 
                else if (cell.DiagonalRelatives2(grid) == 2) { return true; }
            }

            return false;
        }
  
        #endregion 
    }

**Gameround.cs**

    class GameRound
    {
        public delegate void RoundEndHandler(OutcomeType gameOutcome);

        #region Fields & Properties

        private static int ctr;
        private Grid grid;
        public readonly int ID;
        public static event RoundEndHandler Complete;
        public Player CurrentPlayer { get; private set; }

        #endregion

        #region Constructors

        public GameRound()
        {
            ID = ctr++;
            grid = new Grid();
        }

        #endregion

        #region Instanced Methods

        public void Start()
        {
            bool doLoop = true;

            while (doLoop)
            {
                for (int i = 0; i < Game.players.Length; i++)
                {
                    CurrentPlayer = Game.players[i];
                    var coord = Game.players[i].GetPlayerChoice(grid, CurrentPlayer);
                    grid.cells[coord.Item1, coord.Item2].MarkType = CurrentPlayer.marker;

                    //check if game is over
                    if (grid.CheckOutcome(coord, CurrentPlayer))  
                    {
                        doLoop = false;
                        break; 
                    }
                }
            }

            Complete(grid.Outcome); 
        }

        #endregion
    }

**ExtensionMethods.cs**

    static class ExtensionMethods
    {
        #region Cell Extension Methods

        public static int DiagonalRelatives(this Cell cell, Grid grid) 
        {
            int relatives = new int();

            for (int x = 0; x < 3; x++)
            {
                if (grid.cells[x, x].MarkType.Equals(cell.MarkType)) { relatives++; }
            }

            return relatives - 1;
        }
        
        public static int DiagonalRelatives2(this Cell cell, Grid grid)
        {
            int relatives = new int();

            for (int x = 0; x < 3; x++)
            {
                if (grid.cells[x, 2 - x].MarkType.Equals(cell.MarkType)) { relatives++; }
            }

            return relatives - 1;
        }

        public static int HorizontalRelatives(this Cell cell, Grid grid)
        {
            int relatives = new int();
            int rowNum = grid.cells.IndexOf(cell).Item2;

            for (int x = 0; x < 3; x++)
            {
                //Find row of cell
                if (grid.cells[x, rowNum].MarkType.Equals(cell.MarkType)) { relatives++; }
            }

            return relatives - 1;
        }
        public static int VerticalRelatives(this Cell cell, Grid grid)
        {
            int relatives = new int();
            int colNum = grid.cells.IndexOf(cell).Item1;

            for (int y = 0; y < 3; y++)
            {
                //Find row of cell
                if (grid.cells[colNum, y].MarkType.Equals(cell.MarkType)) { relatives++; }
            }

            return relatives - 1;
        }

        #endregion

        #region Cell[] Extension Methods

        public static Cell[] GetEmptyCells(this Cell[,] cells)
        {
            List<Cell> emptyCells = new List<Cell>();

            foreach (Cell cell in cells)
            {
                if (cell.MarkType == Mark.Empty)
                {
                    emptyCells.Add(cell);
                }
            }

            return emptyCells.ToArray();
        }

        public static Tuple<int, int> IndexOf(this Cell[,] cells, Cell cell) 
        {
            for (int x = 0; x < cells.GetLength(0); x++)
            {
                for (int y = 0; y < cells.GetLength(1); y++)
                {
                    if (cells[x, y].Equals(cell))
                    {
                        return new Tuple<int,int>(x, y);
                    }
                }
            }

            //If code reaches this point, then it didn't find anything, return -1
            return new Tuple<int,int>(-1, -1);
        }

        #endregion
    }