具有 AI 和网络支持的 C# TicTacToe






4.54/5 (23投票s)
用 C# 编写的带有 AI 和网络支持的井字游戏
引言
这是一个用C#编写的井字游戏示例,具有AI和网络支持。可以与电脑对战,也可以与另一名玩家对战。与电脑对战时有三个难度级别。与另一名玩家对战时,可以本地对战,也可以通过局域网或互联网对战(它使用TCP套接字进行通信)。控制方式可以是鼠标或键盘,使用数字键盘或QWE、ASD、ZXC键。
AI支持
我尽力以最好的方式实现AI,即使代码很长。它首先尝试赢得游戏,阻止其他玩家获胜,然后尝试进行移动,避免“陷阱”并将两个电脑棋子排成一行。如果以上都不可能,则进行随机移动,首先尝试角落,然后尝试侧面。我通过在难度设置为简单或中等时,并非每次都处理防御移动例程来实现难度级别。
网络支持
使用TcpListener
和TcpClient
类实现网络对战,用于TCP套接字通信。通信协议非常简单,一个两字节的数据包:第一个字节指示行,第二个字节指示列。还有一个控制数据包,第一个字节为'R',用于控制游戏重启。客户端和服务器可以随时重启游戏。
这是服务器端接收数据线程的代码片段
private void ThreadReceivingServer()
{
//________________________________________________________________________
//
// Thread for receiving packets from client
//________________________________________________________________________
try
{
byte[] buf = new byte[512];
IPHostEntry localHostEntry = Dns.GetHostByName(Dns.GetHostName());
int bytesReceived=0;
tcpListener = new TcpListener(localHostEntry.AddressList[0],SERVERPORT);
tcpListener.Start();
//____________________________________________________________________
//
// Thread is blocked until it gets a connection from client
//____________________________________________________________________
soTcpServer = tcpListener.AcceptSocket();
serverSockStream = new NetworkStream(soTcpServer);
objTicTacToe.RestartGame();
objTicTacToe.SetStatusMessage("Connected!");
wReceivingServer=true;
while (wReceivingServer)
{
//________________________________________________________________
//
// Thread is blocked until receives data
//________________________________________________________________
try
{
bytesReceived=serverSockStream.Read(buf,0,2);
}
catch
{
return;
}
//________________________________________________________________
//
// Processes network packet
//________________________________________________________________
if (bytesReceived>0)
{
//____________________________________________________________
//
// Control packet for game restart
//____________________________________________________________
if (buf[0]==byte.Parse(Asc("R").ToString()))
{
objTicTacToe.RestartGame();
continue;
}
//____________________________________________________________
//
// Packet indicating a game move
//____________________________________________________________
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);
}
} //if (bytesReceived>0)
} //while (wReceivingServer)
}
catch (ThreadAbortException) {}
catch (Exception ex)
{
MessageBox.Show("An error ocurred: " + ex.Message + "\n" + ex.StackTrace);
objTicTacToe.mnDisconnect_Click(null,null);
return;
}
}
这是通过网络发送游戏移动的代码片段
public void SendMove(int wRow,int wColumn)
{
//________________________________________________________________________
//
// Sends packet that shows move position
//________________________________________________________________________
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)
{
//_________________________________________________________________________
//
// Sends a packet via TCP
//_________________________________________________________________________
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;
}
}
关注点
我在做这个项目时遇到了一些问题。第一个问题是绘制显示获胜者的简单线条。我尝试在游戏窗体中绘制一条线,但是PictureBox
es在绘制线条后会被重新绘制。我尝试在PictureBox
es中绘制,但是代码太复杂,结果也不好。所以,我有了使用GDI32.dll BitBlt
函数捕获游戏屏幕,将图像放在一个大的PictureBox
中,然后在该PictureBox
上绘制线条的想法。
另一个问题,我花了一些时间才解决。我让客户端和服务器线程在有获胜者时显示大的PictureBox
。但是当我使用picWinner.Visible=True
时,程序变得不稳定并冻结。我进行了大量研究,发现只有窗体的线程才能更新UI,而其他线程不能。因此,我必须使用Control.Invoke
来更新PictureBox
的可见性。
//_____________________________________________________________________________
//
// Show the picure using Invoke because of socket thread
// (locks the game if you set picWinner.Visible=True)
//_____________________________________________________________________________
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)
{
//_________________________________________________________________________
//
// Make changes to UI using Invoke because of socket thread
//_________________________________________________________________________
control.Visible = true;
}