上一节我们讲到了客户端发送Login命令后,服务器返回欢迎信息,完成了一个简单的数据传输。这一节我们来完成游戏大厅的基本功能,我们首先思考一下游戏大厅的基本功能:
1 提供可供对弈的游戏桌,游戏大厅可供多桌玩家同时游戏,为了考虑游戏大厅服务器的负载能力,应该设置一个人数的上限和桌数的上限。实际上前面提到的功能抽象出来就是一些数据的状态集合。
2 当玩家登入大厅,应该直观的显示当前大厅的就座情况,方便玩家选择。此处应该考虑大厅的直观显示。
3 当玩家选择某一位置就坐,游戏大厅的相应状态数据应发生更改,任何玩家都能看到大厅的就座情况的变化,方便做出选择。比如a选择坐在第一桌的黑方位置,则b应该看到该位置不可落坐,只能选择其他位置就坐。
尽量从面向对象的角度考虑,我们应当把游戏大厅,游戏桌,玩家看做对象。建议大家使用面向对象的方法去思考,个人感觉服务器客户端通信的网络程序主要涉及通信协议(就是我们前面提到的命令,参数1,参数2等等)的分析,设计不好的话到最后你会发现逻辑复杂到难以控制的程度。
下面我们分别看一下这几个对象(有删减,具体请看源代码)
2 public FormServer
3 {
4 // basilwang 2008-09-06
5 // new to this version myGame2
6 // 游戏桌集合
7 private GameTable[] gameTable;
8 // 玩家集合
9 List < User > userList = new List < User > ();
10 // 游戏桌上限
11 private int maxTables;
12 // 玩家上限
13 private int maxUsers;
14 // 上一节列出的帮助类
15 private Service service;
16 }
17 // 游戏桌
18 class GameTable
19 {
20 private const int None = - 1 ; // 无棋子
21 private const int Black = 0 ; // 黑白棋子
22 private const int White = 1 ; // 白色棋子
23 private int [,] grid = new int [ 8 , 8 ]; // 8*8的方格
24 public Player[] gamePlayer;
25 public GameTable()
26 {
27 gamePlayer = new Player[ 2 ];
28 gamePlayer[ 0 ] = new Player();
29 gamePlayer[ 1 ] = new Player();
30 }
31
32 }
33 class Player
34 {
35 // 是否开始
36 public bool started;
37 // 己方棋子个数
38 public int grade;
39 // 是否落座
40 public bool someone;
41 public Player()
42 {
43 someone = false ;
44 started = false ;
45 grade = 0 ;
46 }
47 }
当服务器程序启动的时候,需要初始化游戏大厅的数据;而当客户端登录到游戏大厅后,按照前面列出的逻辑,我们需要把游戏大厅的状态在客户端直观的显示出来。那怎么才能得到游戏大厅的状态数据呢?没错,也是利用的客户端服务器之间的通讯。
当客户端向服务器发送Login命令后,服务器处理完毕后,返回Tables命令返回状态数据
2 {
3 // 省略接受客户端协议代码
4
5 // 拆分接受到的协议 格式: 命令,参数1,参数2 .
6 string [] splitString = receiveString.Split( ' , ' );
7 string sendString = "" ;
8 switch (splitString[ 0 ])
9 {
10 case " Login " :
11 user.userName = string .Format( " [{0}--{1}] " , splitString[ 1 ], client.Client.RemoteEndPoint);
12 // 向客户端发送协议
13 // 格式 : Tables,参数1
14 // 参数1为游戏大厅的游戏桌就座情况
15 sendString = " Tables, " + this .GetOnlineString();
16 service.SendToOne(user, sendString);
17 break ;
18 default :
19 break ;
20 }
21 }
22 // 返回如0100010101的字符串 奇数位表示游戏桌黑方的就座情况,偶数位相反,游戏桌按序号排列连接
23 private string GetOnlineString()
24 {
25 string str = "" ;
26 for ( int i = 0 ; i < gameTable.Length; i ++ )
27 {
28 for ( int j = 0 ; j < 2 ; j ++ )
29 {
30 str += gameTable[i].gamePlayer[j].someone == true ? " 1 " : " 0 " ;
31 }
32 }
33 return str;
34 }
客户端接受到Tables命令协议进行分析,直观显示游戏大厅的就座情况,这里为了简单,采用了动态生成若干组checkbox控件添加到Panel的方法,比较简单但能够说明问题。checkbox选中表明已有玩家就座,如果未选中表明可以在此处落座。
这部分代码就不列出来了,可以看一下原程序。
如果玩家选择在某一位置落座,将出发CheckBox的CheckedChanged事件,并向服务器发送SitDown命令
2 {
3 CheckBox checkbox = (CheckBox)sender;
4 if (checkbox.Checked)
5 {
6 // 动态生成的CheckBox命名规则为checkXXXXYYYY, 第5-8位为桌号,不足0补齐;第9-12位为黑方或白方,不足0补齐
7 int i = int .Parse(checkbox.Name.Substring( 5 , 4 ));
8 int j = int .Parse(checkbox.Name.Substring( 9 , 4 ));
9 side = j;
10 // 格式 SitDown,参数1,参数2
11 // 参数1 桌号
12 // 参数2 黑方或白方
13 service.SendToServer( string .Format( " SitDown,{0},{1} " , i, j));
14 }
15 }
服务器分析SitDown命令
2 {
3 // 省略接受客户端协议代码
4
5 // 拆分接受到的协议 格式: 命令,参数1,参数2 .
6 string [] splitString = receiveString.Split( ' , ' );
7 string sendString = "" ;
8 int tableIndex = - 1 ; // 桌号
9 int side = - 1 ; // 座位号
10 int anotherSide = - 1 ; // 对方座位号
11
12 switch (splitString[ 0 ])
13 {
14 case " Login " :
15 // 省略部分
16 break ;
17 case " SitDown " :
18 tableIndex = int .Parse(splitString[ 1 ]);
19 side = int .Parse(splitString[ 2 ]);
20 gameTable[tableIndex].gamePlayer[side].user = user;
21 gameTable[tableIndex].gamePlayer[side].someone = true ;
22 service.SetListBox( string .Format(
23 " {0}在第{1}桌第{2}座入座 " , user.userName, tableIndex + 1 , side + 1 ));
24 // 得到对家座位号
25 anotherSide = (side + 1 ) % 2 ;
26 // 判断对方是否有人
27 if (gameTable[tableIndex].gamePlayer[anotherSide].someone)
28 {
29 // 先告诉该用户对家已经入座
30 // 发送格式:SitDown,座位号,用户名
31 sendString = string .Format( " SitDown,{0},{1} " , anotherSide,
32 gameTable[tableIndex].gamePlayer[anotherSide].user.userName);
33 service.SendToOne(user, sendString);
34 }
35 // 同时告诉两个用户该用户入座(也可能对方无人)
36 // 发送格式:SitDown,座位号,用户名
37 sendString = string .Format( " SitDown,{0},{1} " , side, user.userName);
38 service.SendToBoth(gameTable[tableIndex], sendString);
39 // 重新将游戏室各桌情况发送给所有用户
40 service.SendToAll(userList, " Tables, " + this .GetOnlineString());
41 break ;
42 default :
43 break ;
44 }
45 }
46
到这里,我们把游戏大厅的简单逻辑都处理了,下一节将介绍客户端棋盘的呈现。
源代码下载