網絡中國象棋小遊戲的實現

      開學了,去圖書館借了幾本書,沒有找到想要的C++網絡編程,卻是找到了幾本LINUX的書,以及一本《Visual C#經典遊戲編程開發》。翻了翻發現裏面有個能夠聯網對弈的中國象棋遊戲,一直寫ASP.NET的網頁,也想試着寫寫Winform的圖形程序了,並且能夠了解一下.NET的網絡編程相關的內容。編程

      象棋方面,先從網上找了張棋盤圖片,在PS裏把棋子一一扣了出來做爲素材。編寫對應的棋子類和棋盤類,前者實現棋子身份的識別和棋子圖片的繪製,後者實現棋盤繪製、走棋規則、棋譜生成等功能。c#

      網絡通訊的協議使用的是UDP協議,發送的信息規則是:「命令|參數1|參數2……」,主要命令:join表示用戶想要聯機,處於接受對方鏈接的狀態;conn表示收到對方聯機命令,已準備好開始遊戲,對方能夠開局了;new表示其中一方提出從新開始遊戲;move|id|x|y表示移動棋盤上編號爲id的棋子到x,y處;succ|贏方代號 表示此局已分出勝負;exit表示退出遊戲。網絡

      遊戲開始後建立了一個線程,在read方法中建立UdpClient對象,循環偵聽指定端口中由指定IP主機傳來的信息。主要代碼是:socket

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網絡服務。主要代碼:spa

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、端口號以及本身的端口號。並且,因爲沒有明顯的遊戲過程標記,因此形成了一些通訊問題,致使在不正確的階段收到對方錯誤的請求時遊戲出現意外的狀況。3d

      因而在完成後,就開始了對原程序的改造。考慮到內網用戶鏈接外網的問題,就將遊戲中的UDP協議通訊改成了使用Socket經過TCP協議進行遊戲通訊。添加遊戲狀態state字段,包括如下幾個主要狀態:code

        public const short WAITING_CLIENT = 1;  // 做爲主機,等待客戶端鏈接
        public const short SERVING = 2;         // 做爲主機,客戶端已鏈接,遊戲中
        public const short WAITING_SERVER = 3;  // 做爲從機,等待主機接受鏈接
        public const short CONNECTING = 4;      // 做爲從機,鏈接到主機,遊戲中

      遊戲的命令和監聽流程也作了對應的改動。orm

      主機創建遊戲等待客戶端鏈接的過程主要包括:監聽端口、創建鏈接、開始監聽消息,這三個步驟。首先建立線程,在線程中執行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)

相關文章
相關標籤/搜索