基於Udp的五子棋對戰游戲


引言

本文主要講述在局域網內,使用c#基於Udp協議編寫一個對戰的五子棋游戲。主要從Udp的使用、游戲的繪制、對戰的邏輯這三個部分來講解。

開發環境:vs2013,.Net4.0,在文章的末尾提供源代碼下載的地址。
 

Udp通信


Udp是基於無連接的傳輸協議,特點是資源消耗小、處理速度快、使用方便,不需要與接收方建立連接即可發送消息,但是對方有可能會接受不到發送的消息。在.Net中提供了UdpClient類來實現基於Udp協議的通信,下面就講解一下Udp的基本使用。
 
1、發送信息
首先聲明一個UdpClient對象。
UdpClient udpSend;
然后建立一個方法去發送信息即可。
  public void Send(string sendMsg)
        {
            udpSend = new UdpClient();
            byte[] byteMsg = Encoding.Default.GetBytes(sendMsg);
            udpSend.Send(byteMsg, byteMsg.Length, this.sendIp, sendPort);
            udpSend.Close();
        }
 
2、接受信息
首先也聲明一個UdpClient對象。
 UdpClient  udpReceive;
然后建立一個方法初始化UdpClient對象,開辟一個線程去接受消息以及一個接受消息的方法
 
 public void StartReceive()
        {
            //接受端口5888的消息
            udpReceive = new UdpClient(5888);
            Thread threadReceive = new Thread(ReceiveMessages);
            threadReceive.IsBackground = true;
            threadReceive.Start();
        }
    private  void ReceiveMessages()
        {
            IPEndPoint remoteIPEndPoint = new IPEndPoint(IPAddress.Any, 0);//獲取發送信息方的ip和端口信息
            while (true)
            {
                try
                {
                    //關閉receiveUdpClient時此句會產生異常
 
                    byte[] receiveBytes = udpReceive.Receive(ref remoteIPEndPoint);
 
                    string message = Encoding.Default.GetString(receiveBytes, 0, receiveBytes.Length);
                    MessageParse(message);//去分析消息,並處理
                }
                catch
                {
                    break;
                }
            }
        }
到此,接受信息和發送信息就接受,相比Tcp協議來說,使用這個是更加簡,。是在局域網內對戰游戲通信的最佳選擇。
但是也有值得注意的地方。不能同時運行兩個一樣的程序,在接受信息的時候,不能有兩個upd實例同時去監聽相同的端口號。比如在寫完程序之后需要用在本機上使用“127.0.0.1”這個ip去調試自己變成的程序。那么程序就會是這樣。
程序1:UdpReceive接受的端口號為5888。UdpSend發送的ip和端口號分別為“127.0.0.1”和“5888”。
程序2:UdpReceive接受的端口號也是5888。UdpSend發送的ip和端口號分別為“127.0.0.1”和“5888”。
這樣運行程序2的時候就會出錯,因為程序1和程序2監聽的是同一個端口號,這是不被允許的(udp監聽的端口號不能被其他程序占,用要唯一),所以要進行小小的修改。
程序1:UdpReceive UdpReceive接受的端口號為5888。UdpSend發送的ip和端口號分別為“127.0.0.1”和“5887”。
程序2:UdpReceive接受的端口號也是5887。UdpSend發送的ip和端口號分別為“127.0.0.1”和“5888”。
這樣稍微變動一下就不會出錯了。
 
 

游戲的繪制


玩過五子棋的都知道,主要是繪制棋盤和棋子,稍微再人性化的就是把對方最后一個放置的棋子標注出來,讓我們更加清楚的知道對方的剛下的棋子是哪一個。

首先需要建立一個全局變量,保存棋盤中各個位置的狀態。
  const int BOARDSIZE = 15;
  const int BOARDLENGTH = 800;
 int[,] chessMap = new int[BOARDSIZE, BOARDSIZE];//其中值為0:沒有棋子,1:白棋,2:黑棋
在棋盤控件PictureBox控件的Paint方法寫如下代碼:
 //paint方法
   private void picChessboard_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            //繪制棋盤
            chessBoard.DrawBoard(g);
            //繪制棋子
            chessBoard.DrawChess(g);
        }
 
  //初始化全局變量
        public void InitialChess()
        {
            for (int i = 0; i < BOARDSIZE; i++)
            {
                for (int j = 0; j < BOARDSIZE; j++)
                {
                    chessMap[i, j] = 0;
                }
            }
            this.picChessboard.Invalidate();
        }
        //繪制棋盤
        public  void DrawBoard(Graphics g)
        {
            Pen p = new Pen(Brushes.Black, 3.0f);
            //  p.Width = 2f;
            //橫線
            for (int i = 0; i < BOARDSIZE; i++)
            {
                g.DrawLine(p, new Point(0, (i + 1) * 50), new Point(BOARDLENGTH, (i + 1) * 50));
            }
            //豎線
            for (int i = 0; i < BOARDSIZE; i++)
            {
                g.DrawLine(p, new Point((i + 1) * 50, 0), new Point((i + 1) * 50, BOARDLENGTH));
            }
        }
 
        //繪制棋子
        public void DrawChess(Graphics g)
        {
 
            for (int i = 0; i < BOARDSIZE; i++)
            {
                for (int j = 0; j < BOARDSIZE; j++)
                {
                    if (chessMap[i, j] == 1)
                    {
                        g.DrawImage(Properties.Resources.whitechess, new Point(50 * (i + 1) - 20, 50 * (j + 1) - 20));
                    }
                    if (chessMap[i, j] == 2)
                    {
                        g.DrawImage(Properties.Resources.blackchess, new Point(50 * (i + 1) - 20, 50 * (j + 1) - 20));
                    }
                }
            }
            if (pCurrent.X !=-1)
            {
                //繪制最后落下棋子上的紅色標注
                g.FillEllipse(Brushes.Red, new Rectangle((pCurrent.X + 1) * 50-5, (pCurrent.Y + 1) * 50-5, 10, 10));
            }
        }
 
到此游戲的界面完成了,效果如下。
 
 

對戰邏輯


對戰的邏輯簡單的說就是兩部分。1、自己下棋,然后設置全局變量chessMap 通知界面繪制棋子;接受對方下棋的消息,然后設置全局變量chessMap 通知界面繪制棋子;2、下棋之后檢測輸贏情況。

在這里只貼出關鍵的代碼,具體細節可以把我的源碼下載下來,里面的注釋寫的還算詳細。
 

1、下棋的代碼

   
 /// <summary>
        /// 下棋
        /// </summary>
        /// <param name="flag">設置全局變量chessMap的標志</param>
        /// <param name="x">棋盤x坐標</param>
        /// <param name="y">棋盤y坐標</param>
        public void PutOneChess(int flag, int x, int y)
        {
            if (isPut||myFlag !=flag)//判斷是否是自己下棋,或者是別人下棋
            {
                //計算鼠標點擊位置在棋盤的中的行、列的位置
                int tolerance = 8;
                int row = y / 50;
                int rows = y % 50;
                int col = x / 50;
                int cols = x % 50;
 
                if (rows + tolerance >= 50)
                {
                    row++;
                }
                else if (rows - tolerance <= 0)
                {
                }
                else
                {
                    return;//沒有選中
                }
 
                if (cols + tolerance >= 50)
                {
                    col++;
                }
                else if (cols - tolerance <= 0)
                {
                }
                else
                {
                    return;
                }
 
                col--;
                row--;
 
                if (col >= 0 && col < BOARDSIZE && row >= 0 && row < BOARDSIZE)
                {
                    this.chessMap[col, row] = flag;
                    pCurrent = new Point(col, row);//保存最新放置棋子的位置,以便標注和悔棋
                    this.picChessboard.Invalidate();
                    if (myFlag == flag)//如果是自己下棋
                    {
                        this.isPut = false;//輪到對方走棋
                        UpdateRemoteChessBoardDelegate(flag, x, y);//將更新對方的棋盤
 
                        if (IsWin(col, row))//自己贏了
                        {
                            InformRemoteResultDelegate();
                        }
                    }
                    else
                    {
                        this.isPut = true;//輪到自己下棋了。
                    }
                }
            }
        }

 

2、檢測輸贏的代碼 

 //橫向
        private bool Is1(int c, int r)
        {
            int count = 1;
            for (int i = c+1; i <c+5; i++)
            {
                if(i<BOARDSIZE)
                {
                    if (chessMap[i, r] == myFlag)
                    {
                        count++;
                    }
                    else
                    {
                        break;
                    }
                }
                else
                {
                    break ;
                }
            }
            for (int i = c - 1; i > c - 5; i--)
            {
                if (i >= 0)
                {
                    if (chessMap[i, r] == myFlag)
                    {
                        count++;
                    }
                    else
                    {
                        break;
                    }
                }
                else
                {
                    break;
                }
            }
            if (count > 4)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
 
        //縱向
        private bool Is2(int c, int r)
        {
            int count = 1;
            for (int i = r + 1; i < r + 5; i++)
            {
                if (i < BOARDSIZE)
                {
                    if (chessMap[c, i] == myFlag)
                    {
                        count++;
                    }
                    else
                    {
                        break;
                    }
                }
                else
                {
                    break;
                }
            }
            for (int i = r - 1; i > r - 5; i--)
            {
                if (i >= 0)
                {
                    if (chessMap[c, i] == myFlag)
                    {
                        count++;
                    }
                    else
                    {
                        break;
                    }
                }
                else
                {
                    break;
                }
            }
            if (count > 4)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
 
        //左上-右下
        private bool  Is3(int c,int r)
        {
            int count = 1;
            for (int i = 1; i < 5;i++ )
            {
                if ((c - i) >= 0 && (r - i)>=0)
                {
                    if (chessMap[c-i,r-i] == myFlag)
                    {
                        count++;
                    }
                    else
                    {
                        break;
                    }
                }
                else
                {
                    break;
                }
            }
            for (int i = 1; i < 5; i++)
            {
                if ((c + i) < BOARDSIZE && (r + i) < BOARDSIZE)
                {
                    if (chessMap[c + i, r +i] == myFlag)
                    {
                        count++;
                    }
                    else
                    {
                        break;
                    }
                }
                else
                {
                    break;
                }
            }
            if (count > 4)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
 
 
        //右下-左上
        private bool Is4(int c, int r)
        {
            int count = 1;
            for (int i = 1; i < 5; i++)
            {
                if ((c - i) >=0&&(r+i)<BOARDSIZE)
                {
                    if (chessMap[c - i, r + i] == myFlag)
                    {
                        count++;
                    }
                    else
                    {
                        break;
                    }
                }
                else
                {
                    break;
                }
            }
            for (int i = 1; i < 5; i++)
            {
                if ((c+i) < BOARDSIZE&&(r-i)>=0)
                {
                    if (chessMap[c + i, r - i] == myFlag)
                    {
                        count++;
                    }
                    else
                    {
                        break;
                    }
                }
                else
                {
                    break;
                }
            }
            if (count > 4)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
其中如何通知對方自己下棋的位置信息、接收對方的信息更新自己棋盤、悔棋、聊天的代碼都省略啦,可以自行看源碼。
 
3、游戲最終界面效果
 
4、注意
通信都是以字符串的形式發送的,以字符“|”分割的。信息分為信息頭和信息內容。
  • 發送消息:Talk|"hello"
  • 通知對方下棋:Put|1|230|400
  • 通知對方輸棋:Lose|
  • 申請悔棋:ReSet|
  • 對方同意:OkSet|
  • 對方退出游戲:Exit|
 

總結


通過寫這一個小游戲,讓我學會了Udp的具體用法以及體會到了委托在window程序設計中的方便,以往寫的程序委托都沒有用武之地,我們只是知道委托的的語法如何怎么使用,但是卻不知道什么情況使用,在哪里是用。

好了分享的就這么多了。大家要是對着這個程序有什么疑問的可以私信我;對這個程序有建議的也可以私信我。嘿嘿。
 下載鏈接:http://download.csdn.net/detail/mingge38/9387603
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM