Tic Tac Toe

//
// Written by Grant Jenks, Copyright 2010 - All rights reserved.
//
// Interesting ideas worth considering:
// 1. Write a nearly identical version of this that uses a loop instead of
//    recursion. Add comments to that and put it on the wiki.
// 2. Make the loop-based solution generic such that separate games may be
//    made for Mancala and Othello. I think this sounds the most interesting.
//    It would probably require separate classes: Graphics, Board, Player,
//    Game... ?
//

using System;
using System.Drawing;
using System.Windows.Forms;

enum Square { X = -1, Empty = 0, O = 1 };
enum Turn { X = -1, O = 1 };
enum Outcome { Lose = -1, Tie = 0, Win = 1 };

class TicTacToeForm : Form
{
  bool HumanFirst = true;
  Turn CurrentTurn = Turn.X;
  Square[,] Squares = new Square[3, 3];
  Pen BlackPen = new Pen(Color.Black, 5);
  Pen RedPen = new Pen(Color.Red, 3);
  Pen GreenPen = new Pen(Color.Green, 3);
  Random random = new Random();

  void TicTacToeForm_MouseClick(object sender, MouseEventArgs e)
  {
    if (CountEmptySquares(Squares) == 0 || (int)Winner(Squares) != 0)
      {
	Clear();
	HumanFirst = !HumanFirst;
	if (!HumanFirst)
	    GoComputer();
	this.Refresh();
      }
    else if (Squares[e.X/100, e.Y/100] == Square.Empty)
      {
	Squares[e.X/100, e.Y/100] = (Square)(int)CurrentTurn;
	CurrentTurn = (Turn)(-1 * (int)CurrentTurn);
	this.Refresh();
	GoComputer();
	this.Refresh();
      }
  }

  void GoComputer()
  {
    int row = -1, col = -1;
    if (CountEmptySquares(Squares) == 9)
      {
	row = random.Next(3);
	col = random.Next(3);
      }
    else
      FindBestMove(CurrentTurn, Squares, ref row, ref col);
    if (row != -1 && col != -1)
	Squares[row,col] = (Square)(int)CurrentTurn;
    CurrentTurn = (Turn)(-1 * (int)CurrentTurn);
  }

  Outcome FindBestMove(Turn cur, Square[,] squares, ref int row,
			       ref int col)
  {
    int winner = (int)Winner(squares);
    if (winner != 0)
      return (Outcome)(winner * (int)cur);
    if (CountEmptySquares(squares) == 0)
      return Outcome.Tie;

    Outcome bestOutcome = Outcome.Lose;
    int bestRow = -1, bestCol = -1;

    for (int i = 0; i < 3; i++)
      for (int j = 0; j < 3; j++)
	if (squares[i,j] == Square.Empty)
	  {
	    Square[,] local = new Square[3,3];
	    int dummyRow = -1, dummyCol = -1;
	    Array.Copy(squares, local, squares.Length);
	    local[i,j] = (Square)(int)cur;
	    Outcome counterMove = FindBestMove((Turn)(-1*(int)cur), local,
					       ref dummyRow, ref dummyCol);
	    if (-1*(int)counterMove >= (int)bestOutcome)
	      {
		bestRow = i;
		bestCol = j;
		bestOutcome = (Outcome)(-1*(int)counterMove);
	      }
	  }

    row = bestRow;
    col = bestCol;
    return bestOutcome;
  }

  void TicTacToeForm_Paint(object sender, PaintEventArgs e)
  {
    Graphics g = e.Graphics;

    g.DrawLine(BlackPen, 5, 100, 295, 100);
    g.DrawLine(BlackPen, 5, 200, 295, 200);
    g.DrawLine(BlackPen, 100, 5, 100, 295);
    g.DrawLine(BlackPen, 200, 5, 200, 295);

    for (int i = 0; i < 3; i++)
      for (int j = 0; j < 3; j++)
	if (Squares[i, j] == Square.X)
	  {
	    g.DrawLine(RedPen, i*100+10, j*100+10, (i+1)*100-10, (j+1)*100-10);
	    g.DrawLine(RedPen, i*100+10, (j+1)*100-10, (i+1)*100-10, j*100+10);
	  }
	else if (Squares[i, j] == Square.O)
	  g.DrawEllipse(GreenPen, i*100+10, j*100+10, 80, 80);
  }

  Square Winner(Square[,] squares)
  {
    for (int i = 0; i < 3; i++)
      if (squares[i,0] == squares[i,1] && squares[i,1] == squares[i,2])
	return squares[i,0];
      else if (squares[0,i] == squares[1,i] && squares[1,i] == squares[2,i])
	return squares[0,i];
    if (squares[0,0] == squares[1,1] && squares[1,1] == squares[2,2])
      return squares[1,1];
    if (squares[2,0] == squares[1,1] && squares[1,1] == squares[0,2])
      return squares[1,1];
    return Square.Empty;
  }

  void InitializeComponent()
  {
    this.SuspendLayout();
    this.MaximizeBox = false;
    this.MinimizeBox = false;
    this.Text = "Tic Tac Toe";
    this.BackColor = Color.White;
    this.ClientSize = new Size(300, 300);
    this.FormBorderStyle = FormBorderStyle.FixedSingle;
    this.Paint += new PaintEventHandler(this.TicTacToeForm_Paint);
    this.MouseClick += new MouseEventHandler(this.TicTacToeForm_MouseClick);
    this.ResumeLayout(false);
  }

  int CountEmptySquares(Square[,] squares)
  {
    int count = 0;
    for (int i = 0; i < 3; i++)
      for (int j = 0; j < 3; j++)
	if (squares[i,j] == Square.Empty)
	  count++;
    return count;
  }

  void Clear()
  {
    for (int i = 0; i < 3; i++)
      for (int j = 0; j < 3; j++)
	Squares[i, j] = Square.Empty;
  }

  TicTacToeForm()
  {
    Clear();
    InitializeComponent();
  }

  [STAThread]
  static void Main()
  {
    Application.Run(new TicTacToeForm());
  }
}
projects/tic_tac_toe.txt · Last modified: 2012/04/30 23:40 by grant