基于TCP的网络游戏黑白棋系列(四):游戏棋盘

上一节我们讨论的游戏大厅的实现,这一节我们来看一下客户端游戏棋盘的处理

关于棋盘的呈现采用了GDI的DrawImage方法,先准备一张400*400的棋盘图片和两个40*40的棋子图片(分别为黑棋和白棋),我们的思路是通过和客户端服务器的数据交互得到游戏大厅某桌的棋子信息,然后客户端直观的呈现该信息。

棋盘同样可以看作一个对象,只不过这个对象我们需要从Form来继承,因为我们需要用到PictureBox控件来呈现棋盘和棋子

 

 1    // 此处选择从Form继承,是考虑需要在Form上利用GDI画图已达到走棋的效果
 2    public   partial   class  FormPlaying : Form
 3  {
 4           private   int  tableIndex;
 5           private   int  side;
 6       // 这里和服务器端的GameTable一样也定义了grid(8,8),目的是在客户端保存棋子的状态
 7       // 每次通讯时只由服务器端通知更改的棋子的位置和颜色,客户端也作相应的修改
 8       // 当然这里也可以不声明grid(8,8),不过那样每次通讯都需要传递所有的棋子位置状态,效率不高
 9           private   int [,] grid  =   new   int [ 8 8 ];
10       // 工具类
11           private  Service service;
12       // FormPlaying和FormRoom类似都需要被负责接受数据的线程操作,需要调用Object.Invoke方法,详见第二节
13       delegate   void  LabelDelegate(Label label,  string  str);
14           delegate   void  ButtonDelegate(Button button,  bool  flag);
15          LabelDelegate labelDelegate;
16          ButtonDelegate buttonDelegate;
17           // 保存棋子位图信息
18       private  Bitmap blackBitmap;
19           private  Bitmap whiteBitmap;
20 
21       // FormPlaying将在玩家落座后呈现,在FormRoom中被初始化
22       // TableIndex 桌数
23       // Side 黑方或者白方
24       // sw 客户端发送的数据流
25           public  FormPlaying( int  TableIndex, int  Side,StreamWriter sw)
26          {
27              InitializeComponent();
28           // 构造工具类,负责打印调试信息及发送数据流到服务器端
29              service  =   new  Service(listBox1, sw);
30               this .tableIndex  =  TableIndex;
31               this .side  =  Side;
32          blackBitmap  =   new  Bitmap( " black.gif " );
33              whiteBitmap  =   new  Bitmap( " white.gif " );
34          }
35  }

 

这里和服务器端的GameTable一样也定义了grid(8,8),目的是在客户端保存棋子的状态,每次通讯时只由服务器端通知更改的棋子的位置和颜色,客户端也作相应的修改。当然也可以不声明grid(8,8),不过那样每次通讯都需要传递所有的棋子位置状态,效率不高

上一节提到当玩家选中Checkbox表明坐下时,触发CheckedChange事件,这一节对该事件进行修改,初始化棋盘FormPlaying并打开

 

 1  void  checkBox_CheckedChanged( object  sender, EventArgs e)
 2  {
 3      CheckBox checkbox  =  (CheckBox)sender;
 4       if  (checkbox.Checked)
 5      {
 6       int  i  =   int .Parse(checkbox.Name.Substring( 5 4 ));
 7       int  j  =   int .Parse(checkbox.Name.Substring( 9 4 ));
 8 
 9      side  =  j;
10 
11      service.SendToServer( string .Format( " SitDown,{0},{1} " , i, j));
12      formPlaying  =   new  FormPlaying(i, j, sw);
13      formPlaying.Show();
14      }
15 
16  }

 

调用GDI画棋盘及棋子信息(初始化及任一方落子都调用该事件)

 

 1    private   void  pictureBox1_Paint( object  sender, PaintEventArgs e)
 2  {
 3      Graphics g  =  e.Graphics;
 4       for  ( int  i  =   0 ; i  <=  grid.GetUpperBound( 0 ); i ++ )
 5      {
 6       for  ( int  j  =   0 ; j  <=  grid.GetUpperBound( 1 ); j ++ )
 7      {
 8           if  (grid[i, j]  !=  DotColor.None)
 9          {
10           if  (grid[i, j]  ==  DotColor.Black)
11          {
12               // i代表行j代表列 y轴应为列 x轴应为行 
13              g.DrawImage(blackBitmap, (i  +   1 *   40 ,(j  +   1 *   40 );
14          }
15           else
16          {
17              g.DrawImage(whiteBitmap, (i  +   1 *   40 , (j  +   1 *   40 );
18          }
19          }
20      }
21      }
22  }

 

当玩家落子的时候实际上是触发了PictureBox的MouseDown事件,程序判断是否可在该处下子,如果可以则调用GDI的Graphics.DrawImage将棋子“画”到棋盘上。

 

 1  private   void  pictureBox1_MouseDown( object  sender, MouseEventArgs e)
 2  {
 3       // i代表行j代表列 y轴应为列 x轴应为行 
 4       // 棋子的图片大小为40*40,所以这里根据坐标信息得到棋子在对应的grid数组中的位置
 5       int  x  =  e.X  /   40 ;
 6       int  y  =  e.Y  /   40 ;
 7       // 黑白棋逻辑
 8       // 保证鼠标点击位置在棋盘内部
 9       if  ( ! (x  <   1   ||  x  >   8   ||  y  <   1   ||  y  >   8 ))
10      {
11       if  (grid[x  -   1 ,y  -   1 ==  DotColor.None)
12      {
13           // int color = grid[x - 1, y - 1];
14           // 格式 SetDot,参数1,参数2,参数3,参数4
15           // 功能:由服务器端判断此处是否允许落子,
16           //  不允许落子的情况
17           //  1 在不合法的位置落子
18           //  2 轮到黑方落子,但白方在点击棋盘,则对白方不允许落子
19           // 如果允许同时计算落子后双方棋子的变化情况,
20           // 参数1 桌号
21           // 参数2 棋盘行
22           // 参数3 棋盘列
23           // 参数4 黑方或白方
24          service.SendToServer( string .Format( " SetDot,{0},{1},{2},{3} " , tableIndex, x  -   1 ,y - 1 , side));
25      }
26      }
27  }

 

此处的实现效率上有些问题,把判断能否落子的任务交给服务器端,实际上我们已经在客户端利用grid数组保存了棋子的状态,完全可以放到客户端去判断,放到服务器端会造成很多无用的通讯数据(写文章的时候代码已经完成,不打算改了)

列一下服务器端处理SetDot命令的逻辑

1 如果没有轮到该方落子,则丢弃此次命令

2 将落子的位置向八个方向进行比较(上、下、左、右、左上、左下、右上、右下)

   考虑落子的同一列的上下两个方向的比较情况
   (1)单列中小于目标行的情况,目标行最小为第1行(下标为0)
      假设目标行为第5行(下标为4)则比较行k1依次为3,2,1,0
      如果目标行为第1行(下标为0)则不需要比较小于目标行的情况
   (2)单列中大于目标行的情况,目标行最大为第8行(下标为7)
      假设目标行为第5行(下标为4)则比较行k2依次为5,6,7
      如果目标行为第8行(下标为7)则不需要比较大于目标行的情况

3 准备八个List<string>,将八个方向需要反转的棋子分别存入代表各自方向的List<string>中,以向上方向为例

   (1)从最靠近目标行的行数开始,逐个检验同列中棋子颜色,遇到相同颜色或者无子即退出,连续不同存入;如果检测位置无子,则晴空该方向List<string>,即无法改变该方向棋子颜色
   (2)如果有同颜色的子,则退出,List<string>中的值即为该方向需要更改的值
   (3)如果有不同颜色的子且不为第1行,则记录,否则清空列表。假设在第1列第1-4行为黑子,白方在第1列第5行打算落下白子,从向上的方向比较,当检测到第1行时,该方向列表中已有3个子,但第一行为黑子,无法反转1-4行黑子,所以清空已有列表。

4 如果八个方向List<string>均为空,则表明此处无法下子,丢弃此命令

5 通知客户端修改 落子的信息及需要反转的棋子的所有信息

此处代码较多,不列了,请看源码

本节完成了棋盘的呈现及简单的落子算法,但未考虑游戏中一方无法落子而由另一方继续落子的情况,下一节将对代码进行简单的重构以添加该功能,最终完成一个完整的网络黑白棋对弈。

源代码下载

你可能感兴趣的:(基于TCP的网络游戏黑白棋系列(四):游戏棋盘)