Components Notebook
Home

Notebooks
C#
C++
Agile Hacker
Hardware
Photos

Book Reviews

Tic Tac Toe in C#

This example is based on the program TicTac from the book Programming Windows 95 with MFC by Jeff Prosise. There is a newer edition called Programming Windows With MFC .

I chose this example to learn how to handle mouse events in C# and .NET.

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

namespace TicTac
{
   public class FTicTac : System.Windows.Forms.Form
   {
      private int[] m_nGameGrid;
      private int m_nNextChar;
      private Rectangle[] m_rcSquares;
      private const int EX = 1;
      private const int OH = 2;
      private bool m_bDoubleClick = false;

      private System.ComponentModel.Container

      components = null;

      public FTicTac()
      {
         InitializeComponent();

         m_nNextChar = EX;
         m_nGameGrid = new int[9];
         m_rcSquares = new Rectangle[9];
         Size size = new Size( 96, 96 );
         for( int j = 0 ; j < 9 ; j++ )
         {
            m_rcSquares[j].Size = size;
         }
         m_rcSquares[0].Offset( 16, 16 );
         m_rcSquares[1].Offset( 128, 16 );
         m_rcSquares[2].Offset( 240, 16 );
         m_rcSquares[3].Offset( 16, 128 );
         m_rcSquares[4].Offset( 128, 128 );
         m_rcSquares[5].Offset( 240, 128 );
         m_rcSquares[6].Offset( 16, 240 );
         m_rcSquares[7].Offset( 128, 240 );
         m_rcSquares[8].Offset( 240, 240 );
      }

      protected override void Dispose( bool disposing )
      {
         if( disposing )
         {
            if (components != null)
            {
               components.Dispose();
            }
         }
         base.Dispose( disposing );
      }

      #region Windows Form Designer generated code
      private void InitializeComponent()
    {
         this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
         this.ClientSize = new System.Drawing.Size(292, 273);
         this.Name = "FTicTac";
         this.Text = "Tic Tac Toe";
      }
      #endregion

      [STAThread] static void Main()
      {
         Application.Run(new FTicTac());
      }

      protected override void OnMouseDown( MouseEventArgs e )
      {
         switch( e.Button )
         {
            case MouseButtons.Left:
               OnLButtonDown( new Point( e.X, e.Y ) );
               break;
            case MouseButtons.Right:
               OnRButtonDown( new Point( e.X, e.Y ) );
               break;
         }
         base.OnMouseDown( e );
      }

      private void OnLButtonDown( Point p )
      {
         if( m_bDoubleClick )
         {
            m_bDoubleClick = false;
            return;
         }
         if( m_nNextChar != EX )
         {
            return;
         }
         int nPos = GetRectID( p );
         if( nPos == -1 )
         {
            return;
         }
         if( m_nGameGrid[nPos] != 0 )
         {
            return;
         }
         m_nNextChar = OH;
         m_nGameGrid[nPos] = EX;
         Graphics g = this.CreateGraphics();
         DrawX( g, nPos );
         g.Dispose();
         CheckForGameOver();
      }

      private void OnRButtonDown( Point p )
      {
         if( m_bDoubleClick )
         {
            m_bDoubleClick = false;
            return;
         }
         if( m_nNextChar != OH )
         {
            return;
         }
         int nPos = GetRectID( p );
         if( nPos == -1 )
         {
            return;
         }
         if( m_nGameGrid[nPos] != 0 )
         {
            return;
         }
         m_nNextChar = EX;
         m_nGameGrid[nPos] = OH;
         Graphics g = this.CreateGraphics();
         DrawO( g, nPos );
         g.Dispose();
         CheckForGameOver();
      }

      protected override void OnDoubleClick( EventArgs e )
      {
         m_bDoubleClick = true;
         ResetGame();
         base.OnDoubleClick( e );
      }

      protected override void OnPaint( PaintEventArgs pe )
      {
         Graphics g = pe.Graphics;
         DrawBoard( g );
      }

      private int GetRectID( Point p )
      {
         for( int i = 0 ; i < 9 ; i++ )
         {
            if( m_rcSquares[i].Contains( p ) )
            {
               return( i );
            }
         }
         return( -1 );
      }

      private void DrawBoard( Graphics g )
      {
         Pen myPen = new Pen( Color.Black, 16 );
         g.DrawLine( myPen, 120, 16, 120, 336 );
         g.DrawLine( myPen, 232, 16, 232, 336 );
         g.DrawLine( myPen, 16, 120, 336, 120 );
         g.DrawLine( myPen, 16, 232, 336, 232 );
         for( int i = 0 ; i < 9 ; i++ )
         {
            if( m_nGameGrid[i] == EX )
            {
               DrawX( g, i );
            }
            else if( m_nGameGrid[i] == OH )
            {
               DrawO( g, i );
            }
         }
         myPen.Dispose();
      }

      private void DrawX( Graphics g, int nPos )
      {
         Pen myPen = new Pen( Color.Red, 16 );
         g.DrawLine( myPen, m_rcSquares[nPos].Left+16, m_rcSquares[nPos].Top+16,
            m_rcSquares[nPos].Right-16, m_rcSquares[nPos].Bottom-16 );
         g.DrawLine( myPen, m_rcSquares[nPos].Left+16, m_rcSquares[nPos].Bottom-16,
            m_rcSquares[nPos].Right-16, m_rcSquares[nPos].Top+16 );
         myPen.Dispose();
      }

      private void DrawO( Graphics g, int nPos )
      {
         Pen myPen = new Pen( Color.Blue, 16 );
         Rectangle rect = new Rectangle();
         rect = m_rcSquares[nPos];
         rect.Inflate( -16, -16 );
         g.DrawEllipse( myPen, rect );
         myPen.Dispose();
      }

      private void ResetGame()
      {
         m_nNextChar = EX;
         for( int j = 0 ; j < m_nGameGrid.Length ; j++ )
         {
            m_nGameGrid[j] = 0;
         }
         this.Invalidate();
      }

      private void CheckForGameOver()
      {
         int nWinner = IsWinner();
         switch( nWinner )
         {
            case 0:
               if( IsDraw() )
               {
                  MessageBox.Show( "It's a draw!", "Tic Tac Toe" );
                  ResetGame();
               }
               break;
            case EX:
               MessageBox.Show( "X Wins!", "Tic Tac Toe" );
               ResetGame();
               break;
            case OH:
               MessageBox.Show( "O Wins!", "Tic Tac Toe" );
               ResetGame();
               break;
         }
      }

      private int IsWinner()
      {
         int[,] nPattern =
         {
            { 0, 1, 2 },
            { 3, 4, 5 },
            { 6, 7, 8 },
            { 0, 3, 6 },
            { 1, 4, 7 },
            { 2, 5, 8 },
            { 0, 4, 8 },
            { 2, 4, 6 }
         };

         for( int i = 0 ; i < 8 ; i++ )
         {
             if( ( m_nGameGrid[nPattern[i,0]] == EX )
                && ( m_nGameGrid[nPattern[i,1]] == EX )
                && ( m_nGameGrid[nPattern[i,2]] == EX ) )
             {
                return( EX );
             }
             if( ( m_nGameGrid[nPattern[i,0]] == OH )
                && ( m_nGameGrid[nPattern[i,1]] == OH )
                && ( m_nGameGrid[nPattern[i,2]] == OH ) )
             {
                return( OH );
             }
          }
          return( 0 );
      }

      private bool IsDraw()
      {
         for( int i = 0 ; i < 9 ; i++ )
         {
            if( m_nGameGrid[i] == 0 )
            {
               return( false );
            }
         }
         return( true );
      }
   }
} 

The properties for the Form which appear in InitializeComponent() were set using the Properties Window. When modifying these, or other properties, use the Properties Window instead of editing the code.

The OnMouseDown() event occurs twice during a double click, in addition to OnDoubleClick(). The second OnMouseDown() occurs after OnDoubleClick(), so I set a flag to cause the OnLButtonDown() and OnRButtonDown() functions to return immediately. This code could be moved to OnMouseDown(), but needs to be done so as not to skip the base.OnMouseDown( e ) call.

To zero my array in the ResetGame() function, I tried m_nGameGrid.Initialize, but this did not work. Checking the documentation, I found this:

CAUTION This method can only be used on value types that have constructors, and value types that are native to C# do not have constructors.

If you want to trigger a call to OnPaint() use this.Invalidate(). To draw outside the OnPaint() function, obtain a graphics object using Graphics g = this.CreateGraphics().

C# Notebook


wburris at telusplanet dot net