两国军旗
一、界面设计
下棋需要有棋盘,程序中通过在窗体激活时在图片框控件qi_pan上显示军旗棋盘图片文件。有了盘还需要有棋子,使用了以下如图5-6,图5-7中的图片表示棋子。另外还有R.bmp和G.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数组可以有0到49值,其中0到24代表红方棋子;25到49代表绿方棋子。
关于0到24的棋子含义如下在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。
25到49代表绿方棋子。绿方只要减去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)是第一次单击的棋子坐标,(x1,y1)是第二次单击的棋子坐标。
判断布局棋子的位置是否适当是使用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。
具体实现如下:
//炸弹控件编号4,5,第一排(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;//第1,2,3,4排不允许放置地雷,
if(Q[Map[old_x,old_y]%25]==30 && !(y1==16||y1==17))
return false;//第1,2,3,4排不允许放置地雷,
return true;//其余情况均可以