描述:Qt学习结束后的一个总结性的练习项目,实现两个客户端(邀请和接受方)联机游戏,其它的客户端观看的功能。总代码量1200行。服务器为linux系统编程实现共450行,客户端Qt实现共740行。主要用到的知识点:线程和网络通信, Qt布局,信号和槽,C++线程类。总花费时间5天。关于网络通信部分均为Linux系统编程部分代码,完全可以改为Qt版的。
1.客户端布局
分左右两边,比例为1:3,左边为聊天框TextBrowser和TextEdit,列表选择框按键和3个label,分别用于计时,显示玩家信息和显示图片,布局方式为竖直VBoxLayout,比例为3:1:1:1:1:3;右边为30*30的棋盘布局方式为网格GridLayout,整体为HBoxLayout。
2.添加棋盘类chessindividual,继承于QWidget,在该类型中创建900个按钮ChessBtn和一个按钮组BtnGroup,并声明网格布局。再把所有的按钮添加到网格布局和按钮组中,之后可以用btnGroup的ButtonPressed信号可以传递一个整形的按钮id,可以用该id来确定出点击的棋盘的坐标。
3.添加thread类,继承于QThread类,该类为客户端分离出来的一个子线程,在客户端初始化的时候连接服务器,获取套接字描述符,并实时接收服务器发送回来的数据,执行相应的信号发送操作。
4.添加MyTextEdit类,继承于QTextEdit类。重写该类的KeyPressEvent方法,使每次在TextEdit中按下回车键并有内容输入的时候发送信号,把内容以QString类型的信号方式发给主线程。主线程接受的槽函数拿到信息并把它写给服务器。
5.添加myLabel类,继承于QLabel类。声明两个Qpixmap,创建角色标记falg,表示自己和对方的图标。再重写该类的paintEvent方法,使图片在客户端的窗口任意变化的时候能铺满整个label组件。
分析:
此时,客户端已基本成型,能发送给服务器数据(聊天的),服务器不知道客户端发的是什么数据,所以在客户端需要定义简单的协议对数据进行区分。之后定义槽函数绑定相应的信号来发送这些信息,所有的步骤流程和聊天模块都是相同的。
一、客户端的发出数据有:
1.开始按键点击的邀请信息;2.聊天的信息;3.棋盘点击后具体的坐标信息;4.同意邀请的信息;5.拒绝邀请的信息。
给所有的要发送的数据加上协议让服务器区分它们1."#Command:";2."#Chat:";3."#Pos:";4."#Agree:"5."#disAgree:"
二、服务器的工作:
1 死循环阻塞接受,接受得到客户端的套节字,然后把套节字放到顺序表中。
2 用接受得到客户端的套接字描述符创建一个线程去收此描述符的数据,做分析转发。
转发方式:对"#Command"发给另外一个客户端邀请消息"#Invite:加自己和对方的fd";
对"#Chat:","#Pos:"和"#Agree"转发给顺序表中所有的客户端;对"disAgree:"转发给邀请它的客户端。
三、客户端thread子线程收到的来自服务器的数据:
1).收到同意开始游戏数据"#Agree"带fd的发给被邀请者,没有带fd的发给其他观看者2
2).收到邀请数据"#Invite:"包含对方和自己的fd,发开始游戏的信号。
3).收到了拒绝的数据"#disAgree",发送被拒绝的信号。
4).收到了坐标数据"#Pos:",发送棋子坐标数据信号。
5).收到了赢家的数据"#Winner",发送赢家fd的信号。
6.为了区分玩家和观看者,在主线程中设置了MyfdonServer默认值为2,该客户端在服务器上的fd,如果是邀请者,邀请被对方同意,自己必然是玩家,如果是被邀请者,同意对方的邀请,自己必然是玩家。根据这两个条件设置MyfdonServer,修改默认值。从而区分开玩家的客户端和观看者的客户端,并做相应的操作限制。
7.为了实现轮流落子,思路可以在服务器上面设置标志位,每一次落子后修改标志位。也可以把自己的Fd和棋子的坐标同时发送给服务器,服务器解析后判断当前是哪一个客户端的落子操作,从而做转发,若当前和上一次是同一个客户端发的坐标信息,则不作转发。
8.对于已有棋子的位置重复点击可以重新设置的问题,可以设置chessBtn的Checkable属性,客户端每一次收到了服务器的"#Pos:"位置信息,设置该位置按键的Checkable属性为false,再对客户端发送棋子坐标的地方做判断,若该棋子的Checkable属性为false则不发送。
9.输赢由服务器做判断,服务器保存一张虚拟的二维表,存储两方落子的位置信息,并标记是谁的棋子,然后判断四个方向的自己的棋子数量是否为5,若有一方到5,虚拟棋盘清空,并向所有客户端发送"#Winner:"信息。
10.最后一颗棋子的闪烁效果:重写Widget的timerEvent,保存timerId,设置blinkflag标志位实现交替闪烁。在设置图标的槽函数中杀掉上次的timerId,设置上次位置的图标,保存本次的图标位置,开始本次的计时器。
11.客户端对于"#Winner:"信息做不同反应,弹出不同的对话框,timer计时器停止,startBtn复原。
12.重新点击StartBtn按钮,邀请被同意后 startBtn按钮Enable = false,计时器重新开始工作,棋盘清空,重新开始第二轮游戏。
细节部分设计和修饰在网络五子棋源代码中呈现。
客户端界面:
.