Introduction
This is an example of a Tic Tac Toe game with AI and network support, written in C#. It is possible to play against the computer and against another player. There are three levels of difficulty when playing against the computer. When playing against another player, it is possible to play local or over a LAN or Internet (it uses TCP sockets for communication). The control can be either by mouse or keyboard, using the NumPad or QWE, ASD, ZXC keys.
AI support
I tried to implement AI the best way I could, even with a long code. It tries to win the game at first, preventing the other player to win, and then tries to make a move avoiding "traps" and putting two computer pieces in a row. If none of this is possible, a random move is made, first trying on the corners and then on the sides. I implemented the level of difficulty by not processing the defensive move routine every time if the difficulty was set to easy or average.
Network support
Network support for a two-player game over a network was implemented using TcpListener
and TcpClient
classes for TCP sockets communication. The protocol for communication is really simple, a two-bytes packet: first byte indicating the row and second byte indicating the column. There is also a control packet with the first byte 'R' that controls game restart. Both client and server can restart the game anytime.
Here is a code snippet of the thread for receiving data in the server:
private void ThreadReceivingServer()
{
try
{
byte[] buf = new byte[512];
IPHostEntry localHostEntry = Dns.GetHostByName(Dns.GetHostName());
int bytesReceived=0;
tcpListener = new TcpListener(localHostEntry.AddressList[0],SERVERPORT);
tcpListener.Start();
soTcpServer = tcpListener.AcceptSocket();
serverSockStream = new NetworkStream(soTcpServer);
objTicTacToe.RestartGame();
objTicTacToe.SetStatusMessage("Connected!");
wReceivingServer=true;
while (wReceivingServer)
{
try
{
bytesReceived=serverSockStream.Read(buf,0,2);
}
catch
{
return;
}
if (bytesReceived>0)
{
if (buf[0]==byte.Parse(Asc("R").ToString()))
{
objTicTacToe.RestartGame();
continue;
}
int wRow=int.Parse(Convert.ToChar(buf[0]).ToString());
int wColumn=int.Parse(Convert.ToChar(buf[1]).ToString());
if ((wRow>0 && wRow<4) && (wColumn>0 && wColumn<4))
{
objTicTacToe.wNetworkPlay=true;
objTicTacToe.MakeMove(wRow,wColumn);
}
}
} }
catch (ThreadAbortException) {}
catch (Exception ex)
{
MessageBox.Show("An error ocurred: " + ex.Message + "\n" + ex.StackTrace);
objTicTacToe.mnDisconnect_Click(null,null);
return;
}
}
Here is the code snippet for sending a game move over the network:
public void SendMove(int wRow,int wColumn)
{
byte[] buf = new byte[2];
buf[0]=byte.Parse(Asc(wRow.ToString()).ToString());
buf[1]=byte.Parse(Asc(wColumn.ToString()).ToString());
SendPacketTCP(buf);
}
public void SendPacketTCP(Byte[] pDados)
{
try
{
if (objTicTacToe.wClient==true)
{
if (clientSockStream==null)
return;
if (clientSockStream.CanWrite)
{
clientSockStream.Write(pDados, 0, 2);
clientSockStream.Flush();
}
}
else
{
if (serverSockStream==null)
return;
if (serverSockStream.CanWrite)
{
serverSockStream.Write(pDados,0, 2);
serverSockStream.Flush();
}
}
}
catch (Exception ex)
{
MessageBox.Show("An error ocurred: " + ex.Message + "\n" + ex.StackTrace);
objTicTacToe.mnDisconnect_Click(null,null);
return;
}
}
Points of Interest
I had some problems while doing this project. The first one was doing the simple line that shows the winner. I tried drawing a line in the game form, but the PictureBox
es were redrawn after I draw the line. I tried drawing in the PictureBox
es but the code was too extensive and the result wasn't good. So I had the idea of capturing game screen with GDI32.dll BitBlt
function, putting the image in a big PictureBox
and simply drawing the line over this PictureBox
.
The other problem, I had took some time to solve. I had the client and server thread to show the big PictureBox
when there was a winner. But when I used picWinner.Visible=True
the program got unstable and froze. I researched a lot and found out that only the form's thread can update de UI and no other thread. So I had to use Control.Invoke
to update the visibility of the PictureBox
:
object[] p = new object[1];
p[0] = picWinner;
picWinner.Invoke(new MakeVisibleHandler(MakeVisible), p);
public delegate void MakeVisibleHandler(Control control);
public void MakeVisible(Control control)
{
control.Visible = true;
}