网络中国象棋小游戏的实现

      开学了,去图书馆借了几本书,没有找到想要的C++网络编程,倒是找到了几本LINUX的书,以及一本《Visual C#经典游戏编程开发》。翻了翻发现里面有个可以联网对弈的中国象棋游戏,一直写ASP.NET的网页,也想试着写写Winform的图形程序了,而且可以了解一下.NET的网络编程相关的内容。

      象棋方面,先从网上找了张棋盘图片,在PS里把棋子一一扣了出来作为素材。编写对应的棋子类和棋盘类,前者实现棋子身份的识别和棋子图片的绘制,后者实现棋盘绘制、走棋规则、棋谱生成等功能。

      网络通信的协议使用的是UDP协议,发送的信息规则是:“命令|参数1|参数2……”,主要命令:join表示用户想要联机,处于接受对方连接的状态;conn表示收到对方联机命令,已准备好开始游戏,对方可以开局了;new表示其中一方提出重新开始游戏;move|id|x|y表示移动棋盘上编号为id的棋子到x,y处;succ|赢方代号 表示此局已分出胜负;exit表示退出游戏。

      游戏开始后创建了一个线程,在read方法中创建UdpClient对象,循环侦听指定端口中由指定IP主机传来的信息。主要代码是:

udpClient = new UdpClient(Convert.ToInt32(txt_port.Text));
int id, x, y;
while(readFlag == true)
{
	try
	{
		byte[] data = udpClient.Receive(ref remote);
		string strData = Encoding.UTF8.GetString(data);
		string[] a = strData.Split('|');		// 分割命令与参数
		switch(a[0])							// 判断命令
		{
			case "join":
			case "conn":
			//......
		}
	}
	catch
	{
		break;
	}
}

      游戏过程中发送数据则是通过send方法,创建UdpClient网络服务对象,向指定IP的主机发送消息到设定的端口号。完成后发送后,关闭UDP网络服务。主要代码:

UdpClient sendUdp = new UdpClient();
UPAddress remoteIP;
try {
	remoteIP = IPAddress.Parse(txt_IP.Text);
}
catch {
	MessageBox.Show("请输入正确的IP地址!", "错误");
	return;
}
remote = new IPEndPoint(remoteIP, Convert.ToInt32(txt_remoteport.Text));
byte[] buffer = Encoding.UTF8.GetBytes(str);
sendUdp.Send(buffer, buffer.Length, remote);
sendUdp.Close();

      原书中的代码有一些小问题,所以做了点改动和简化。不过基本的思路还是和原书上是一样的。

      通信过程方面,每次联机都需要双方填写好对方的IP、端口号以及自己的端口号。而且,由于没有明显的游戏过程标记,所以造成了一些通信问题,导致在不正确的阶段收到对方错误的请求时游戏出现意外的情况。

      于是在完成后,就开始了对原程序的改造。考虑到内网用户连接外网的问题,就将游戏中的UDP协议通信改为了使用Socket通过TCP协议进行游戏通信。添加游戏状态state字段,包括以下几个主要状态:

        public const short WAITING_CLIENT = 1;  // 作为主机,等待客户端连接
        public const short SERVING = 2;         // 作为主机,客户端已连接,游戏中
        public const short WAITING_SERVER = 3;  // 作为从机,等待主机接受连接
        public const short CONNECTING = 4;      // 作为从机,连接到主机,游戏中

      游戏的命令和监听流程也做了对应的改动。

      主机建立游戏等待客户端连接的过程主要包括:监听端口、建立连接、开始监听消息,这三个步骤。首先创建线程,在线程中执行acceptClientConnect监听端口等待连接,检测到连接后建立连接,转入Read监听阶段。

        private void WaitClient()
        {
            // 启动一个线程来接受请求
            thread = new Thread(acceptClientConnect);
            thread.Start();
            SetState(WAITING_CLIENT);
        }

        // 接受请求
        private void acceptClientConnect()
        {
            IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, DEFAULT_PORT);
            listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            toolStripStatusLabel1.Text = "正在等待其他玩家连接……";
            try
            {
                if (timeoutThread != null)
                {
                    timeoutThread.Abort();
                }
                timeoutThread = new Thread(WaiteTimeOut);
                timeoutThread.Start();
                listener.Bind(localEndPoint);
                listener.Listen(1);
                connection = listener.Accept();
                listener.Close();
                listener = null;
                Read();
            }
            catch
            {
                toolStripStatusLabel1.Text = "连接中断,游戏终止。";
                SetState(FREE);
            }
        }

        private void WaiteTimeOut()
        {
            Thread.Sleep(30000);
            if (connection == null || !connection.Connected)
            {
                if (listener != null)
                {
                    SetState(FREE);
                    toolStripStatusLabel1.Text = "连接超时,无玩家连接游戏。";
                }
            }
            timeoutThread = null;
        }

      预定的监听超时时间是30秒,30秒后没有连接时,转到FREE状态并终止监听。终止的实现思路是在主机本身创建一个TCP协议的Socket对象,向自身端口发送终止连接的消息,listener建立连接后在Read中接受到此消息停止阻塞,结束游戏彻底转为FREE状态,线程结束。

        private void SetState(int newState)     // 支线程
        {
            state = newState;
            switch (state)
            {
                case FREE:
                    btnCreate.Text = "创建游戏";
                    btnCreate.Enabled = true;
                    btnJoin.Text = "加入游戏";
                    btnJoin.Enabled = true;
                    if (listener != null)
                    {
                        Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                        socket.Connect(IPAddress.Parse("127.0.0.1"), DEFAULT_PORT);
                        if (socket.Connected)
                        {
                            socket.Send(Encoding.UTF8.GetBytes("end"));
                        }
                    }
                    if (connection != null)
                    {
                        connection.Close();
                        connection = null;
                    }
                    break;
                case WAITING_CLIENT:
                    btnCreate.Text = "关闭游戏";
                    btnJoin.Enabled = false;
                    break;
                case SERVING:
                    break;
                case WAITING_SERVER:
                    btnJoin.Text = "断开游戏";
                    btnCreate.Enabled = false;
                    break;
                case CONNECTING:
                    break;
            }
        }

      接受消息的Read方法和发送消息的Send方法方面,原本的UdpClient收发消息改为通过创建的connection(Socket)连接来通信。

        private void Read()
        {
            int t, x1, y1, x2, y2;
            while (state != FREE)
            {
                try
                {
                    byte[] data = new byte[1024];
                    int len = connection.Receive(data);
                    string msg = Encoding.UTF8.GetString(data);
                    string[] a = msg.Split('|');
                    switch (a[0])
                    {
                        case "end": // 终止listener的监听
                            if (state != WAITING_CLIENT) continue;
                            connection.Close();
                            connection = null;
                            SetState(FREE);
                            return;
                        case "join": // 客户端请求加入游戏
                            break;
                        case "acce": // 服务端接受加入的请求
                            break;
                        case "exit": // 对方方退出游戏
                            break;
                        case "new":  // 对方方提出重新开局
                            break;
                        case "succ": // 一方获胜
                            break;
                        case "lose": // 一方认输
                            break;
                        case "move": // 对方移动棋子
                            break;
                    }
                }
                catch
                {
                    break;
                }
            }
        }

        private void Send(string msg)
        {
            if (connection != null)
            {
                byte[] buffer = Encoding.UTF8.GetBytes(msg);
                connection.Send(buffer);
            }
        }

      具体游戏效果截图:

2013-09-05_214002

2013-09-05_214006

01

02

项目工程下载:

 点我下载整个项目的压缩包(883K)

 点我下载整个项目(UDP版)的压缩包(881K)

posted @ 2013-09-05 21:41  Krime Rex  阅读(1723)  评论(1编辑  收藏  举报