10天开发的两国军旗


两国军旗

一、界面设计

下棋需要有棋盘,程序中通过在窗体激活时在图片框控件qi_pan上显示军旗棋盘图片文件。有了盘还需要有棋子,使用了以下如图5-6,图5-7中的图片表示棋子。另外还有R.bmpG.bmp两个图片表示红方和绿方暗子。使用控件数组Qizi_Pic[]Add方法加载了我们所需要的棋子图片控件并使用相应图片。Qizi_Pic[]中下标i的含义是:如果i小于24,那么说明它属于红方的棋子,否则是绿方的棋子。同时Qizi_Pic[i]tag属性保存了棋子在控件数组的索引号。

5-6  红方军棋棋子图片

5-7  绿方军棋棋子图片

在界面中要求用户输入对方IP、端口(本地及对方远端)。同时按图5-8添加“联机”、“重新开始”、“退出”、“保存布阵”、“读取布阵”、“开始对战”6个按钮以及一些题使用的标签。

   

5-8  网络两人对战军棋设计界面

二、通讯协议设计

网络程序设计的难点在于与对方需要通讯。这里使用了UDP(User Data Protocol)UDP是用户数据文报协议的简称,两台计算机之间的传输类似于传递邮件;两台之间没有明确的连接,使用UDP协议建立对等通信。这里虽然两者两台计算机不分主次,但我们设计时假设一台做主机(红方),等待其他人加入。其他人想加入的时候输入主机的IP。为了区分通信中传送的是“输赢信息”,“下的棋子位置信息”,“重新开始”等,在发送信息的首部加上代号。定义了如下协议:

命令|参数|参数……

1)联机功能

join|

2)对方棋子移动信息

move|x , y , idx, old_x, old_y,old_idx

其中:棋子移动的目标位置坐标(x,y),棋子移动的起始位置坐标(old_x,old_y);

old_idx是被移动棋子的控件数组索引号,

idx是目标位置留下的棋子的控件数组索引号。

3)游戏结束

over|+赢方代号 (赢了此局)

4)表示要重新开始

reset|

5)布阵信息

layout|布阵棋子信息

布阵棋子信息形式为x,y,idx,x,y,idx,x,y,idx……

idx是(x,y)处的棋子的控件数组索引号。

注意本程序在传递布阵信息时,我们默认了是绿方在棋盘上方,红方在下。但实际下棋如果棋手使用绿方,这样看非累死不可。所以这里我们采取了个小技巧,在发送布阵数据的时候我们把坐标在颠倒(把自己的棋盘颠倒)。即(x,y)坐标以(18-x)(18-y) 坐标发给对方。

在下棋过程中,为了保存下过的棋子的位置使用了Map数组,Map数组初值为101,表示此处无棋子。Map数组可以有049值,其中024代表红方棋子;2549代表绿方棋子。

关于024的棋子含义如下在qi_index()规定:

              private void qi_index()

              {

                     Q=new int[25];

                     Q[0]=29; //军旗29

                     Q[1]=30;Q[2]=30;Q[3]=30;//地雷30

                  Q[4]=31;Q[5]=31;//炸弹31

                     Q[6]=32;Q[7]=32;Q[8]=32;//工兵32

                     Q[9]=33;Q[10]=33;Q[11]=33;//排长33

                     Q[12]=34;Q[13]=34;Q[14]=34;//连长34

                     Q[15]=35;Q[16]=35;//营长35

                     Q[17]=36;Q[18]=36;//团长36

                     Q[19]=37;Q[20]=37;//旅长37

                     Q[21]=38;Q[22]=38;//师长38

                     Q[23]=39;//军长39

                     Q[24]=40;//司令40

              }

也即是0军旗,1—3地雷,4—5炸弹,6—8工兵,9—11排长,12—14连长,15—16营长,17—18团长,19-20旅长,21—22师长,23军长,24司令。

Q数组中Q[24]=40而不直接写为“司令”,原因是棋子图片的命名时采用40.bmp

所以为显示图片时的方便,这里Q[24]=40

2549代表绿方棋子。绿方只要减去25(每方25个棋子)即可利用Q数组得知绿方棋子的含义。例如27代表的绿方地雷。

三、走棋规则设计

对于军旗游戏来说,规则非常简单,就是按照先后顺序在棋盘上走棋吃子,直到最先一方将对方的“军旗”挖掉为胜。

但在走棋过程中,需要考虑以下情况:

1)是否为非棋子区;

2)目标处是否是自己的棋子;

3)判断目标是否实行营,如果是则需判断是否有子,如果没有可以如“士”一样斜线走棋。

4)判断起始位置是否是铁道线,如果是则考虑弯道、直道、棋盘正中间的3*3“田子”,否则只能移动一步。

5)“军旗”棋子、地雷棋子不能走动;

6)在“大本营”中的棋子不能走动;

可以看出走棋规则实行比较复杂。在设计时Go_Juge(int old_x,int old_y,int x,int y)判断走棋的位置是否适当。

       private bool Go_Juge(int old_x,int old_y,int x,int y)

       {

                     //是否是棋子区域

                     if( (x<=6 && y>=1 && y<=6) || (x>=12 && y>=1 && y<=6) ||

                            (x<=6 && y>=12 && y<=17) || (x>=12 && y>=12 && y<=17)|| y>17 )

                            return false;

                     //目标位置是自己方的棋子

                     if(IsmyChess(x,y)) return false;

                     //到行营,行营是否有子

                     if(Is_Home( x, y)&& Map[x,y]!=101)return false;

                     //如“士”斜线从行营中出来**********

                     if(Is_Home( old_x, old_y)&& Map[x,y]==101&& Math.Abs(x-old_x)*Math.Abs(y-old_y)==1 )return true;

                     //如“士”斜线走入行营************

                     if(Is_Home( x, y)&& Map[x,y]==101 && Math.Abs(x-old_x)*Math.Abs(y-old_y)==1 )return true;

                     //移动一步

                     if( Math.Abs(x-old_x)==1 && y==old_y ||Math.Abs(y-old_y)==1 && x==old_x) return  true;

                     //铁道线

                     if(T_Juge(old_x,old_y,x,y))   return true;                  

                     return false;          

              }

本程序中go_chess(int old_x,int old_y,int x,int y,int idx)完成走棋吃子功能,其中判断以下几种情况:

1)双发均为工兵到司令的棋子,则按大小比较决定保留那方棋子;

2)其中一方式炸弹(31),则同时去掉。

3)其中一方是地雷(30),对方为工兵,则留兵,否则留雷。

4)其中一方是军旗(29),则可以判断输赢。

注意:在发送走棋数据的时候我们仍然需要把坐标在颠倒(把自己的棋盘颠倒)。即(x,y)坐标以(18-x)(18-y) 坐标发给对方。

四、布阵规则设计

注意布阵是采用两次单击不同棋子来决定对调的,所以(old_x, old_y)是第一次单击的棋子坐标,(x1y1)是第二次单击的棋子坐标。

判断布局棋子的位置是否适当是使用Layout_Juge(int old_x,int old_y,int x1,int y1)实现以下情况判断:

1)第一排不允许放置炸弹;由于布阵均在南方,所以第一排即(y1=12)

2)自己的军旗只能放置在大本营;由于布阵均在南方,所以大本营即x1==8&&y1==17||x1==10&&y1==17

3)后两排允许放置地雷。由于布阵均在南方,后两排即y1==16||y1==17

具体实现如下:

       //炸弹控件编号45,第一排(y1=12)不允许放置炸弹

       if(Q[Map[old_x,old_y]%25]==31 && y1==12)   return false;

       if(Q[Map[x1,y1]%25]==31 && old_y==12)      return false;                  if(Q[Map[old_x,old_y]%25]==29 && !(x1==8&&y1==17||x1==10&&y1==17))

              return false;//自己的军旗控件,只能放置在大本营

       if(Q[Map[x1,y1]%25]==29 && !(old_x==8&&old_y==17||old_x==10&&old_y==17))

              return false;//自己的军旗控件,只能放置在大本营

       if(Q[Map[x1,y1]%25]==30 && !(old_y==16||old_y==17))

              return false;//1234排不允许放置地雷,

       if(Q[Map[old_x,old_y]%25]==30 && !(y1==16||y1==17))

              return false;//1234排不允许放置地雷,      

       return true;//其余情况均可以

 

五、通讯过程
       游戏开始后,创建一个线程th:
       th = new Thread ( new ThreadStart ( read ) ) ;
       th_flag=true;
       th.Start ( )启动线程后,通过read ( )实现不断侦听本机设定的端口,得到对方发送来的信息,根据自己定义的通信协议通信中传送的是“输赢信息”,“下的棋子位置信息”,“重新开始”等信息而分别处理。
              private void read ( )
              {
                     //侦听本地的端口号
                     udpclient = new UdpClient ( Convert.ToInt32(txt_port.Text) ) ;
                     remote = null ;
                     //设定编码类型
                     Encoding enc = Encoding.Unicode ;
                     int x,y,old_x,old_y,idx,old_idx;
                     while ( ReadFlag == true )
            //一个While(ture)循环,不断判断是否有信息流入,有就接收
                     {
                            try{
                            Byte[] data = udpclient.Receive ( ref remote ) ;
                            //得到对方发送来的信息
                            String strData = enc.GetString ( data ) ;
                            string []a=new string[5];
                            a=strData.Split(‘|’);
                            switch(a[0])
                            {
                         case “join”:
                         case “begin_layout”://允许对方布阵
                         case “layout”://布阵信息
                         case “move”://对方棋子移动信息
                         case “over”:
                         case “reset”:
                            }
                            }
                            catch{
                                   //退出循环,结束线程
                                   break;
                            }
                     }
              }
发送信息send(string info)较为简单,主要实现创建UDP网络服务,传送信息到指定计算机的txt_remoteport端口号后,关闭UDP网络服务。
              private void send(string info)
              {
                     //创建UDP网络服务
                     UdpClient SendUdp = new UdpClient ( ) ;
                     IPAddress remoteIP ;
                     //判断IP地址的正确性
                     try{
                            remoteIP = IPAddress.Parse ( txt_IP.Text );
                     }
                     catch{
                            MessageBox.Show ( “请输入正确的IP地址!” , “错误” ) ;
                            return ;
                     }
                     IPEndPoint remoteep = new
IPEndPoint ( remoteIP , Convert.ToInt32 (txt_remoteport.Text )) ;
                     Byte [] buffer = null ;
                     Encoding enc = Encoding.Unicode ;
                     string str = info ;
                     buffer = enc.GetBytes ( str.ToCharArray ( ) ) ;
                     //传送信息到指定计算机的txt_remoteport端口号
                     SendUdp.Send ( buffer , buffer.Length , remoteep ) ;
                     //关闭UDP网络服务
                     SendUdp.Close ( ) ;
              }