Click here to Skip to main content
15,867,308 members
Articles / Programming Languages / C#
Article

TicTacToe in C#

Rate me:
Please Sign up or sign in to vote.
3.12/5 (7 votes)
6 Mar 20063 min read 90.5K   3.3K   21   10
A C# implementation of Tic-Tac-Toe using Minimax look-ahead.

Sample Image

Introduction

This is an implementation of the game Tic-Tac-Toe, written in C#.

Background

I wrote this program as a basic exercise to improve my coding skills. It is admittedly quite simple, but since I am a novice programmer, it was adequately challenging. This article is intended for other novices who are interested in board games and/or Windows Forms, but looking for a simple example to get them started.

If you are not a novice, but you'd like to look at my code and provide constructive criticism, please do! I am not receiving any formal instruction, so any feedback you provide will be greatly appreciated.

Using the code

Simply compile the source files and run the resulting executable. Try resizing the window.

Points of Interest

Program Structure

The program is divided into three classes: Board, SquareControl, and TicTacToe.

The Board class

The game board is represented by a two-dimensional integer array. The public field BoardState denotes whether the game is in progress, and if not, what the previous outcome was. The possible values for BoardState are defined by the GameState enumeration. The public field iEmptySquares keeps count of the empty squares on the board to facilitate the detection of a draw.

The Board class provides two public constructors. One creates a new, empty board, while the other creates a new board by copying an existing one. The second constructor is used during the game by the AI.

The CheckBoard() method is used to check the board for a winner after every call to MakeMove().

The SquareControl class

This class inherits from UserControl, and is used to represent each square in the UI. The OnPaint() method is overridden.

Initially, I had a flickering problem with SquareControl objects. To resolve this, I set up double-buffering in the OnPaint() method. The most commonly prescribed method for double-buffering in Windows Forms involves several calls to the SetStyles() method to set up automatic double-buffering. However, this didn't work for me, so I did it this way:

C#
//Use a back-buffer to reduce flicker
Graphics buffer = Graphics.FromImage(bmpBackBuffer);
buffer.SmoothingMode = SmoothingMode.AntiAlias;

//....
//....draw onto buffer as needed
 //....

//draw the contents of buffer to the screen
Graphics viewable = pea.Graphics;
viewable.DrawImageUnscaled(bmpBackBuffer, 0, 0);

The TicTacToe class

This class represents the main (and only) form of the program. TicTacToe contains several methods for controlling the game play. For example, the MakeMove() method is responsible for applying a given move to the Board object and the appropriate SquareControl. The Reset() method is used to reset the Board and SquareControl objects in preparation for a new game. TicTacToe also contains the AI code.

Game AI

The GetBestMove() method employs the Minimax look-ahead algorithm to determine the best possible move for the computer player. Alpha-beta pruning is used to increase performance. A Google search will turn up plenty of information on this topic.

The look-ahead is executed in a worker thread in order to avoid hanging the UI during the search. When the search is completed, the worker thread makes a callback to the main thread to apply the new move. Check the CalculateComputerMove() and MakeComputerMove() methods to see how this is done.

Because the search space for a 3x3 Tic-Tac-Toe game is relatively small, I decided not to limit the depth of the search. This simplified the scoring system used during the look-ahead. In order to adapt the algorithm to boards any larger, a depth limit and a new evaluation function would need to be implemented. You may notice that the computer player occasionally chooses to forgo an immediate win, but only to fork you so that you lose on the computer's next move anyway. I thought about adding a preference to win as soon as possible, but I decided that this behavior was cute.

History

No updates so far.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
General[Message Deleted] Pin
Danny Rodriguez27-Jan-08 9:19
Danny Rodriguez27-Jan-08 9:19 
Generalminimize crash Pin
The_Grand_Inquisitor6-Mar-06 6:55
The_Grand_Inquisitor6-Mar-06 6:55 
GeneralRe: minimize crash Pin
Ikado12-Aug-08 21:38
Ikado12-Aug-08 21:38 
GeneralRe: minimize crash Pin
Chris Congdon13-Feb-10 6:54
Chris Congdon13-Feb-10 6:54 
GeneralRe: minimize crash Pin
Chris Congdon13-Feb-10 7:02
Chris Congdon13-Feb-10 7:02 
GeneralSome Suggestions On C# Coding Pin
Marc Clifton6-Mar-06 6:21
mvaMarc Clifton6-Mar-06 6:21 
GeneralRe: Some Suggestions On C# Coding Pin
The_Grand_Inquisitor6-Mar-06 6:43
The_Grand_Inquisitor6-Mar-06 6:43 
GeneralRe: Some Suggestions On C# Coding Pin
Ray Cassick6-Mar-06 7:13
Ray Cassick6-Mar-06 7:13 
GeneralRe: Some Suggestions On C# Coding Pin
Chris Congdon13-Feb-10 6:22
Chris Congdon13-Feb-10 6:22 
I was looking for a simple tutorial about C# games and found yours. Going through OP's comments about the using blocks, I found there were several cases where a using construct would work (basically, anyplace you need to call Dispose()) I also optimized this section a little bit. Didn't really appear to need an if..else..else construct right before a switch when they both seem to be looking for the same conditions. Also, a slight alteration under board, turning X, O, and Empty into Consts....

Board.cs (replacing the public static readonly with....
public const int X = -1;
public const int O = 1;
public const int Empty = 0;


SquareControl.cs
protected override void OnPaint(PaintEventArgs pea)
       {
           base.OnPaint(pea);

           //Use a back-buffer to reduce flicker
           using (Graphics buffer = Graphics.FromImage(bmpBackBuffer))
           {
               buffer.SmoothingMode = SmoothingMode.AntiAlias;

               switch (iContents)
               {
                   case Board.Empty:
                       //empty square
                       buffer.Clear(BackColor);
                       break;
                   case Board.X:
                       //draw an X
                       using (Pen pen = new Pen(new SolidBrush(Color.Red), 3))
                       {
                           buffer.Clear(BackColor);
                           buffer.DrawLine(pen, 0, 0, ClientSize.Width, ClientSize.Height);
                           buffer.DrawLine(pen, 0, ClientSize.Height, ClientSize.Width, 0);
                       }
                       break;
                   case Board.O:
                       //draw an O
                       using (Pen pen = new Pen(new SolidBrush(Color.Blue), 3))
                       {
                           buffer.Clear(BackColor);
                           Rectangle rect = new Rectangle(2, 2, (ClientRectangle.Width - 5), (ClientRectangle.Height - 5));
                           buffer.DrawEllipse(pen, rect);
                       }
                       break;
               }

               //Copy the back-buffer to the screen
               using (Graphics viewable = pea.Graphics)
               {
                   viewable.DrawImageUnscaled(bmpBackBuffer, 0, 0);
               }
           }
       }

GeneralRe: Some Suggestions On C# Coding [modified] Pin
Chris Congdon13-Feb-10 7:16
Chris Congdon13-Feb-10 7:16 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.