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

Filed under: dotnet | No Comments »
Posted on

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

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

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

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

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

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

?View Code CSHARP
void checkBox_CheckedChanged(object sender, EventArgs e)
{
    CheckBox checkbox = (CheckBox)sender;
    if (checkbox.Checked)
    {
	int i = int.Parse(checkbox.Name.Substring(5, 4));
	int j = int.Parse(checkbox.Name.Substring(9, 4));
 
	side = j;
 
	service.SendToServer(string.Format("SitDown,{0},{1}", i, j));
        //初始化棋盘
	formPlaying = new FormPlaying(i, j, sw);
	formPlaying.Show();
    }
 
}

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

?View Code CSHARP
 private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
    Graphics g = e.Graphics;
    for (int i = 0; i <= grid.GetUpperBound(0); i++)
    {
	for (int j = 0; j <= grid.GetUpperBound(1); j++)
	{
	    if (grid[i, j] != DotColor.None)
	    {
		if (grid[i, j] == DotColor.Black)
		{
		    //i代表行j代表列 y轴应为列 x轴应为行
		    g.DrawImage(blackBitmap, (i + 1) * 40,(j + 1) * 40);
		}
		else
		{
		    g.DrawImage(whiteBitmap, (i + 1) * 40, (j + 1) * 40);
		}
	    }
	}
    }
}

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

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

此处的实现效率上有些问题,把判断能否落子的任务交给服务器端,实际上我们已经在客户端利用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 通知客户端修改 落子的信息及需要反转的棋子的所有信息

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

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

源代码下载