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