客户端:
1.客户端布局组件
左右两边,比例为1:3,左边为聊天框TextBrowser和TextEdit,列表选择框按键和3个label,分别用于计时,显示玩家信息和显示图片,布局方式为竖直VBoxLayout,比例为3:1:1:1:1:3;右边为30*30的棋盘布局方式为网格GridLayout,整体为HBoxLayout。
widget.h#ifndef WIDGET_H
#define WIDGET_H
#include <QtGui/QWidget>
#include <QTextBrowser>
#include <QTextEdit>
#include <QListWidget>
#include <QPushButton>
#include <QLabel>
#include <QButtonGroup>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QPaintEvent>
#include <QImage>
#include <QPainter>
#include <QMessageBox>
#include <QTimerEvent>
#include <QTimer>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include "chessindividual.h"
#include "mylabel.h"
#include "thread.h"
#include <QDebug>
class MyTextEdit;
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
Thread *th;//添加线程成员
private:
//聊天信息显示
QTextBrowser *chatBrowser;
//聊天输入
MyTextEdit *messageSend;
//棋子个数列表
QListWidget *chessNumber;
//开始按钮
QPushButton *startBtn;
//计算时间
QLabel *timeLabel;
//玩家提示信息
QLabel *playerLable;
//玩家图片
MyLabel *imageLabel;
//右边的棋盘部分
ChessIndividual *chessArea;
//计时器
QTimer *timer;
//时间
int sec;
int min;
//黑白子的标记
int flag;
//闪烁的flag
int blinkflag;
//上次落子的位置
int lastTimePos;
//timer的id
int timerID;
//闪烁 是否为第一个落子的标志
int count;
//自己在服务器上的fd 区别玩家和观看者
//为了区分玩家和观看者,在主线程中设置了MyfdonServer默认值为2,
//该客户端在服务器上的fd,如果是邀请者,邀请被对方同意,自己必然是玩家,
//如果是被邀请者,同意对方的邀请,自己必然是玩家。根据这两个条件设置MyfdonServer,修改默认值。
//从而区分开玩家的客户端和观看者的客户端,并做相应的操作限制。
int myfdOnServer;
//如果发送了#Agree信号的必然是玩家
//如果发送了#command 信号,并且收到#Agree信号的必然是玩家;
//如果发送了#command 信号又收到了#disAgree信号必然不是玩家
protected:
void timerEvent(QTimerEvent *);
public slots:
//聊天框的处理:向服务器发送聊天信息
void sendMsgToServer(QString);
//start按钮的处理:向服务器发送信息#Command:
void sendStartMsgToServer();
//接受到了开始的邀请命令,开启对话框
void JoinOrNot(int,int);
//接收到了拒绝游戏的命令
void refuse();
//刷新计时器
void flushTime();
//开始游戏
void startGame(int);
//向服务器发送坐标信息
void sendMyposition(int);
//收到坐标信号在相应的位置画图
void getPos_and_paintImg(int);
//收到赢家的fd
void getWinnerInfo(int);
};
//添加MyTextEdit类,继承于QTextEdit类。
//重写该类的KeyPressEvent方法,
//使每次在TextEdit中按下回车键并有内容输入的时候发送信号,
//把内容以QString类型的信号(sendChatMsg(QString))方式发给主线程。
//主线程接受的槽函数拿到信息并把它写给服务器。
class MyTextEdit: public QTextEdit{
Q_OBJECT//信号和槽的类都是必须的宏名
public:
MyTextEdit(QWidget *parent = 0);
protected:
//重写键盘处理函数
void keyPressEvent(QKeyEvent *e);
signals:
void sendChatMsg(QString);
//public slots:
};
#endif // WIDGET_H
2.重写MyTextEdit类的KeyPressEvent方法,使每次在TextEdit中按下回车键并有内容输入的时候发送信号,把内容以QString类型的信号方式发给主线程。主线程接受的槽函数拿到信息并把它写给服务器。
MyTextEdit.cpp
#include "widget.h"
#include <QKeyEvent>
/*MyTextEdit类继承了QTextEdit类,并重写keyPressEvent方法,
然后聊天的textEdit实例化为他的对象,就有了keyPressEvent的方法
当ENTER键按下的时候发送信号sendChatMeg(Qstring) ,
chatBrowser绑定该信号执行槽函数append()*/
MyTextEdit::MyTextEdit(QWidget *parent):QTextEdit(parent)
{
// w = (Widget *)parent;
}
void MyTextEdit::keyPressEvent(QKeyEvent *e){
if(e->key() == Qt::Key_Return){
//截除开始和结尾处的空白字符,过去掉
if(toPlainText().trimmed() == "")
return;
//把自己中的内容以信号的方式发送出去
emit sendChatMsg(this->toPlainText());
// toPlainText().toStdString().c_str();
// 将QString转换为const char *
this->clear();
}else{
QTextEdit::keyPressEvent(e);
}
}
3.添加棋盘类chessindividual,继承于QWidget,在该类型中创建900个按钮ChessBtn和一个按钮组BtnGroup,并声明网格布局。再把所有的按钮添加到网格布局和按钮组中,之后可以用btnGroup的ButtonPressed信号可以传递一个整形的按钮id,可以用该id来确定出点击的棋盘的坐标。
chessindividual.h
#ifndef CHESSINDIVIDUAL_H
#define CHESSINDIVIDUAL_H
#include <QWidget>
#include <QPushButton>
#include <QStackedLayout>
#include <QButtonGroup>
#include <QPixmap>
class ChessIndividual : public QWidget
{
Q_OBJECT
public:
explicit ChessIndividual(QWidget *parent = 0);
//按键组
QButtonGroup *BtnGroup;
//900个按键
QPushButton *chessBtn[30][30];
};
#endif // CHESSINDIVIDUAL_H
棋盘实现文件
chessindividual.cpp
#include "chessindividual.h"
ChessIndividual::ChessIndividual(QWidget *parent) :
QWidget(parent)
{
QGridLayout *glayout = new QGridLayout;
glayout->setSpacing(0);
glayout->setMargin(5);
//右半边的900按钮
//添加按键组
BtnGroup = new QButtonGroup(this);
int i,j;
for(i=0;i<30;i++){
for(j=0;j<30;j++){
chessBtn[i][j]= new QPushButton(this);
//通过判断这个checkable的属性,来确定这个键是不是已经设置了图标,即:是不是已落子
//它的默认值为false,现在把它改为true
//用CSS修改了按钮的样子,用每个按钮的边画出虚线网格
chessBtn[i][j]->setCheckable(true);
chessBtn[i][j]->setStyleSheet("border-radius:15px;border-top:1px dashed #000000;border-left:1px dashed #000000;background-color:#F5F5DC;");
if(i == 29){
chessBtn[i][j]->setStyleSheet("border-radius:15px;border-bottom:1px dashed #000000;border-top:1px dashed #000000;border-left:1px dashed #000000;background-color:#F5F5DC;");
}else if(j == 29){
chessBtn[i][j]->setStyleSheet("border-radius:15px;border-right:1px dashed #000000;border-top:1px dashed #000000;border-left:1px dashed #000000;background-color:#F5F5DC;");
}
BtnGroup->addButton(chessBtn[i][j],i*30+j);
chessBtn[i][j]->setSizePolicy(QSizePolicy::Ignored,QSizePolicy::Ignored);
chessBtn[i][j]->setCursor(Qt::PointingHandCursor);
glayout->addWidget(chessBtn[i][j],i,j);
}
}
chessBtn[29][29]->setStyleSheet("border-radius:15px;border:1px dashed #000000;background-color:#F5F5DC;");
setLayout(glayout);
}
4.添加myLabel类,继承于QLabel类。声明两个Qpixmap,创建角色标记falg,表示自己和对方的图标。再重写该类的paintEvent方法,使图片在客户端的窗口任意变化的时候能铺满整个label组件。
myLabel,h
#ifndef MYLABEL_H
#define MYLABEL_H
#include <QPaintEvent>
#include <QPainter>
#include <QLabel>
class MyLabel : public QLabel
{
Q_OBJECT
public:
explicit MyLabel(QWidget *parent = 0);
QPixmap map1,map2;
int flag;//角色标记
signals:
public slots:
private:
protected:
void paintEvent(QPaintEvent *);
};
#endif // MYLABEL_H
mylabel.cpp
#include "mylabel.h"
#include <QDebug>
MyLabel::MyLabel(QWidget *parent) :
QLabel(parent),flag(0)
{
map1.load(":/mark.jpg");
map2.load(":/andy.jpg");
}
void MyLabel::paintEvent(QPaintEvent *){
QPainter painter(this);//指定父控件
qDebug()<<"in Mylabel paintEvent!";
//将Map铺满整个组建
if(flag == 0){
painter.drawPixmap(0,0,this->width(),this->height(),map1);
}else{
painter.drawPixmap(0,0,this->width(),this->height(),map2);
}
}
5.实现客户端布局
widget.cpp 客户端构造函数
#include "widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent),sec(0),min(0),flag(0),myfdOnServer(2),blinkflag(0),count(0)
{
resize(800,600);
//左半边所有组件
chatBrowser = new QTextBrowser(this);
chatBrowser->setSizePolicy(QSizePolicy::Ignored,QSizePolicy::Ignored);
chatBrowser->setStyleSheet("background-image:url(:/guaniu.png);background-position:left bottom;background-attachment:fixed;background-repeat:none;color:#0000FF;font-size:12px;border-radius:3px;padding-bottom:5px;border:1px dashed #008000");
messageSend = new MyTextEdit(this);
messageSend->setStyleSheet("color:#008000;border-radius:5px;border:3px solid #FFFFFF;font-size:14px;");
messageSend->setSizePolicy(QSizePolicy::Ignored,QSizePolicy::Ignored);
chessNumber = new QListWidget(this);
chessNumber->setStyleSheet("color:#FFD700;font-size:14px;font-weight:normal;");
chessNumber->addItem("5 Chess");
chessNumber->addItem("6 Chess");
chessNumber->addItem("7 Chess");
//chessNumber->currentRow();返回当前的列值几子棋?从0开始默认不选的是-1 点了start按钮把这个值传给服务器
chessNumber->setSizePolicy(QSizePolicy::Ignored,QSizePolicy::Ignored);
startBtn = new QPushButton("Start",this);
startBtn->setFont(QFont("Monospace",18,QFont::Bold,false));
startBtn->setStyleSheet("*:enabled{background-color:#FFA500;border-radius:6px;color:#F5F5F5;border:3px solid #FFA500;padding:5px 8px;} *:hover{background-color:#FFD700;border-radius:6px;color:#FFFFFF;border:3px solid #FFD700;padding:5px 8px;} *:!enabled{background-color:#A9A9A9;border-radius:6px;color:#FFFFFF;border:3px solid #A9A9A9;padding:5px 8px;}");
startBtn->setCursor(Qt::PointingHandCursor);
timeLabel = new QLabel(this);
timeLabel->setText("TIME: 00:00");
timeLabel->setFont(QFont("Monospace",18,QFont::Bold,false));
timeLabel->setStyleSheet("color:#FFD700;border:1px solid #FFFFFF;border-radius:6px;background-color:#FFFFFF");
timeLabel->setSizePolicy(QSizePolicy::Ignored,QSizePolicy::Ignored);
playerLable = new QLabel(this);
playerLable->setText("<B style='color:#A9A9A9;font-size:13px;white-space:pre;'> Attention:</B><p style='color:#FFFF00;text-decoration:underline; text-indent:40px;'>who's turn</p>");
playerLable->setSizePolicy(QSizePolicy::Ignored,QSizePolicy::Ignored);
imageLabel = new MyLabel(this);
imageLabel->setSizePolicy(QSizePolicy::Ignored,QSizePolicy::Ignored);
QHBoxLayout *list_stBtn = new QHBoxLayout;
list_stBtn->addWidget(chessNumber,2);
list_stBtn->addWidget(startBtn,1);
QVBoxLayout *lefthalf = new QVBoxLayout;
lefthalf->addWidget(chatBrowser,3);
lefthalf->addWidget(messageSend,1);
lefthalf->addLayout(list_stBtn,1);
lefthalf->addWidget(timeLabel,1);
lefthalf->addWidget(playerLable,1);
lefthalf->addWidget(imageLabel,3);
//右半边棋盘的类 900个封装成为一个类 初始化为Widget组件
chessArea = new ChessIndividual(this);
QHBoxLayout *mainlayout = new QHBoxLayout;
mainlayout->addLayout(lefthalf,1);
mainlayout->addWidget(chessArea,3);
setLayout(mainlayout);
.....未完
6.添加thread类,继承于QThread类,该类为客户端分离出来的一个子线程,在客户端初始化的时候连接服务器,获取套接字描述符,并实时接收服务器发送回来的数据,执行相应的信号发送操作。
需要的信号有6个:
1.//发送接收到的信息信号给主线程
void thread_copy_message(QString);
2.//收到了其他玩家开始游戏的邀请
void thread_gameStart_Invite(int,int);
3. //接收到其他玩家同意加入游戏的消息
//现在正式进入游戏
void agree_ToStrat_Game(int);
4.//接收到对方的拒绝信息
void thread_gameDisagree();
5.//接受到对方的坐标信息
void thread_position(int);
6.//接受到赢家的消息
void thread_winner(int);
thread,h文件
#ifndef THREAD_H
#define THREAD_H
#include <QThread>
#include<QTextCodec>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <stdio.h>
#include <errno.h>
#include <arpa/inet.h>//inet_ntop()
#include <unistd.h>
class Thread : public QThread
{
Q_OBJECT
public:
Thread();
int sockfd;//套节字描述符
//执行客户端连到服务器上的操作返回一个int的sockfd
int conn_to_server();
//protected:
void run();
signals:
//发送接收到的信息信号给主线程
void thread_copy_message(QString);
//收到了其他玩家开始游戏的邀请
void thread_gameStart_Invite(int,int);
//接收到其他玩家同意加入游戏的消息
//现在正式进入游戏
void agree_ToStrat_Game(int);
//接收到对方的拒绝信息
void thread_gameDisagree();
//接受到对方的坐标信息
void thread_position(int);
//接受到赢家的消息
void thread_winner(int);
};
#endif // THREAD_H
thread.cpp文件
#include "thread.h"
#include <QDebug>
#define BUF_SIZE 1024
Thread::Thread()
{
QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));
}
//客户端链接到服务器上面并返回一个int 的socketfd
int Thread::conn_to_server(){
int client_socked_fd = socket(AF_INET,SOCK_STREAM,0);
if(client_socked_fd < 0){
fprintf(stderr,"client_socked_fd %s\n",strerror(errno));
exit(1);
}
//填写主机的信息
struct sockaddr_in addr;
memset(&addr,0,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);/********************port *********************/
inet_pton(client_socked_fd,"172.16.3.143",&addr.sin_addr.s_addr);
//connect
if(::connect(client_socked_fd,(struct sockaddr*)&addr,sizeof(addr)) < 0){
fprintf(stderr,"client_socked_fd %s\n",strerror(errno));
exit(1);
}
return client_socked_fd;
}
void Thread::run(){
/*客户端:1发送聊天信息,2实时接受聊天信息
客户端需要分离出来一个子线程,专门用来读取数据,
客户端需要:读取服务器发送到的数据,可以以信号的形式将数据发送给主线程,然后由主线程对相关的组建作出修改!*/
//建立网络链接,拿到套节字描述符
sockfd = conn_to_server();
qDebug()<<"socketfd:"<<sockfd;
//接受服务器返回来的数据,并以信号的形式发给主线程,实现相关的功能
int nread;
char buffer[BUF_SIZE];
while(1){
memset(buffer,0,sizeof(buffer));
nread = read(sockfd,buffer,sizeof(buffer));
if(nread < 0){
//error
fprintf(stderr,"client read from server:%s\b",strerror(errno));
sleep(1);
exit(1);
}else if(nread == 0){
printf("server connection closed!\n");
exit(1);
}else{
//读到了服务器发送回来的数据,
//分析
if(strstr(buffer,"#Agree") != NULL){
//对方同意了我的请求
if(strlen(buffer) >= 8){
QString agreeBuf(buffer);
int myfd = agreeBuf.remove(0,7).toInt();
qDebug()<<"received myfd:"<<myfd;
emit agree_ToStrat_Game(myfd);
}else{
emit agree_ToStrat_Game(2);
}
}else if(strstr(buffer,"#Invite:") != NULL){
//服务器发给了我开始命令 "#Invite:%d,%d"
//第一个是对方的fd,第二个是自己的
QString temp(buffer);
temp.remove(0,8);//?,?
QString teammatefd(temp);
teammatefd.remove(1,2);
QString myfd(temp);
myfd.remove(0,2);
//得到对方和自己的fd
emit thread_gameStart_Invite(teammatefd.toInt(),myfd.toInt());
}else if(strstr(buffer,"#disAgree") != NULL){
//收到了拒绝的消息
emit thread_gameDisagree();
qDebug()<<"对方拒绝与我玩游戏";
}else if(strstr(buffer,"#Pos:") != NULL){
//收到了坐标信息解析为int型
QString positon(buffer);
positon.remove(0,5);
emit thread_position(positon.toInt());
//qDebug()<<"收到坐标 x:"<<positon.toInt()/30<<"y:"<<positon.toInt()%30;
}else if(strstr(buffer,"#Winner") != NULL){
//服务器发回了赢家的消息
QString winner(buffer);
QString x("#");
int index = winner.indexOf(x);
winner.remove(index,strlen(winner.toStdString().c_str()));
qDebug()<< "winner is:"<<winner.toInt();
emit thread_winner(winner.toInt());
}else{
//服务器发给我的是聊天信息
emit thread_copy_message(buffer);
}
}
}
}
7.主线程对线程thread的五个信号的槽函数和自己的4个槽函数:
定义:
对线程thread的五个信号的槽函数
//接受到了开始的邀请命令,开启对话框
void JoinOrNot(int,int);
//接收到了拒绝游戏的命令
void refuse();
//开始游戏
void startGame(int);
//收到坐标信号在相应的位置画图
void getPos_and_paintImg(int);
//收到赢家的fd
void getWinnerInfo(int);
主线程自己的一些槽函数:
//聊天框的处理:向服务器发送聊天信息
void sendMsgToServer(QString);
//start按钮的处理:向服务器发送信息#Command:
void sendStartMsgToServer();
//刷新计时器
void flushTime();
//向服务器发送坐标信息
void sendMyposition(int);
实现:
所有槽函数的实现:
//向服务器发送文字消息槽函数,meg来自与MyTextEdit的自定义信号
void Widget::sendMsgToServer(QString msg){
//格式化输入,让服务器来判断发送的是聊天信息还是开始游戏的指令
char message[1024] = {0};
//发送的信息message
sprintf(message,"#Chat:%s",msg.toStdString().c_str());
ssize_t len = strlen(message);
qDebug()<<"message len :"<<len;
if(write(th->sockfd,message,len)!=len){
//发送失败
fprintf(stderr,"snedtoserver %s\n",strerror(errno));
exit(1);
}
}
//开始按键的槽函数,向服务器发送命令
void Widget::sendStartMsgToServer(){
char StartMsg[10] = "#Command:";
ssize_t len = strlen(StartMsg);
if(write(th->sockfd,StartMsg,len) != len){
fprintf(stderr,"send Start Command: %s",strerror(errno));
}
}
//是不是开始游戏的槽函数,是否要开始都给对方发送消息
void Widget::JoinOrNot(int parterfd,int myfd){
char Content[256] = {0};
sprintf(Content,"你同意与 玩家%d 玩一局不?",parterfd);
int ret = QMessageBox::question(this,"有人邀请你加入游戏",Content,\
QMessageBox::No,QMessageBox::Yes);
if(ret == QMessageBox::Yes){
//我同意了对方的邀请,现在给对方发送同意的消息 -->服务器 --> 对方客户端
//我的start按键Enable false 计时器start 棋盘可以点击
//qDebug()<<"I agree the invite";
myfdOnServer = myfd;
qDebug()<<"被邀请者:onJoinOrOnt myfdOnServer:"<<myfdOnServer;
char AgreeMsg[256] = {0};
sprintf(AgreeMsg,"#Agree:%d",parterfd);
ssize_t len = strlen(AgreeMsg);
if(write(th->sockfd,AgreeMsg,strlen(AgreeMsg)) != len){
fprintf(stderr,"send AgreeMsg to server:%s\n",strerror(errno));
}
}else if(ret == QMessageBox::No){
//我拒绝了对方的邀请,给对方发送拒绝的消息
char disAgreeMsg[256] = {0};
//附带上对方的fd
sprintf(disAgreeMsg,"#disAgree:%d",parterfd);
ssize_t len = strlen(disAgreeMsg);
if(write(th->sockfd,disAgreeMsg,len) != len){
fprintf(stderr,"send disagree command:%s\n",strerror(errno));
exit(1);
}
}
}
//刷新时间计时器
void Widget::flushTime(){
sec++;
if(sec == 60){
sec=0;
min++;
}
if(min ==60){
min=0;
}
QString timerstr;
timerstr.sprintf("TIME: %02d:%02d",min,sec);
timeLabel->setText(timerstr);
}
//对方同意了我的邀请,开始游戏
void Widget::startGame(int myfd){
//还原时间
min = 0;
sec = 0;
//时间计时器开启
timer->start();
//count 清理
count = 0;
//开始键不可用
startBtn->setEnabled(false);
//停止闪烁
killTimer(timerID);
//清空上局棋子
if(myfd > 3)myfdOnServer = myfd;
for(int i=0;i<900;i++){
chessArea->chessBtn[i/30][i%30]->setIcon(QIcon(""));
chessArea->chessBtn[i/30][i%30]->setCheckable(true);
//chessArea->chessBtn[i/30][i%30]->setEnabled(true);
}
}
//收到了对方拒绝我邀请的命令
void Widget::refuse(){
QMessageBox::information(this,"对不起!","对方拒绝了你的邀请。",\
QMessageBox::Ok);
}
//向服务器发送自己的坐标点
void Widget::sendMyposition(int pos){
//在发送端切断已设置过的图标的发送,以免被重新设置ICON 如:对手的棋子又被重设为自己的
if(chessArea->chessBtn[pos/30][pos%30]->isCheckable() == true){
char strpos[10] = {0};
sprintf(strpos,"%d#Pos:%d",myfdOnServer,pos);
qDebug()<<"on SendMyPosition:"<<myfdonserver int="" len="strlen(strpos);" if="" myfdonserver="">= 4){
if(write(th->sockfd,strpos,len) != len){
fprintf(stderr,"send positon msg to server:%s\n",strerror(errno));
exit(1);
}
}
}
}
//收到坐标点 并开始在相应的位置画图
void Widget::getPos_and_paintImg(int pos){
//保存落子上一次的位置
//本次的闪烁,上一次的设置图标
count++;
if(count > 1){
killTimer(timerID);
}
if(count > 1){
int i = lastTimePos/30;
int j = lastTimePos%30;
if(flag == 0){
//对方的图标 paintingEvent
imageLabel->flag = 1;
this->update();
playerLable->setStyleSheet("*{background-image:url(:/chess2.png);background-position: left top;background-repeat:none; color:#00ff00}");
chessArea->chessBtn[i][j]->setIcon(QIcon(":/chess2.png"));
flag = 1;
}else{
//我的图标 paintingEvent
imageLabel->flag = 0;
this->update();
playerLable->setStyleSheet("*{background-image:url(:/chess4.png);background-position: left top;background-repeat:none; color:#00ff00}");
chessArea->chessBtn[i][j]->setIcon(QIcon(":/chess4.png"));
flag = 0;
}
}
//保留本次的位置
lastTimePos = pos;
startTimer(500);
//设置该按键的可选中属性为false 防止统一个坐标重复设置
chessArea->chessBtn[pos/30][pos%30]->setCheckable(false);
}
//闪烁
void Widget::timerEvent(QTimerEvent *event){
timerID = event->timerId();
if(blinkflag == 0){
chessArea->chessBtn[lastTimePos/30][lastTimePos%30]\
->setIcon(QIcon(":/chess1.png"));
blinkflag = 1;
}else{
if(flag == 0)
chessArea->chessBtn[lastTimePos/30][lastTimePos%30]\
->setIcon(QIcon(":/chess2.png"));
if(flag > 0)
chessArea->chessBtn[lastTimePos/30][lastTimePos%30]\
->setIcon(QIcon(":/chess4.png"));
blinkflag = 0;
}
}
void Widget::getWinnerInfo(int winnerFd){
//按键变亮
startBtn->setEnabled(true);
//停止计时器
timer->stop();
if(winnerFd == myfdOnServer){
//自己赢了游戏
QMessageBox::information(this,"恭喜你!","赢得了本局游戏。",\
QMessageBox::Ok);
}else{
if(myfdOnServer == winnerFd-1||myfdOnServer == winnerFd+1){
//自己输了游戏
QMessageBox::information(this,"很遗憾!","你输了,再玩一局吧!",\
QMessageBox::Ok);
}else{
char winnerInfo[20]={0};
sprintf(winnerInfo,"本局的赢家是:%d",winnerFd);
QMessageBox::information(this,"玩家报告!",winnerInfo,\
QMessageBox::Ok);
}
}
}</myfdonserver>
8.对widget.cpp中构造函数的添加:信号和槽函数的绑定
1,线程Thread的信号和自己的槽函数绑定
//线程Thread的对象 收到消息的信号和槽函数的绑定
th = new Thread;//900:1发送按键组坐标点的信号和槽函数,pos = i*30+j
connect(chessArea->BtnGroup,SIGNAL(buttonPressed(int)),this,SLOT(sendMyposition(int)));
//客户端接收到的(read())从服务器发的【文字】消息,并把它追加到chatVrowser中
connect(th,SIGNAL(thread_copy_message(QString)),chatBrowser,SLOT(append(QString)));
//客户端收到从服务器发过来的开始游戏的邀请命令
connect(th,SIGNAL(thread_gameStart_Invite(int,int)),this,SLOT(JoinOrNot(int,int)));
//客户端收到对方发过来的同意游戏的消息 开始游戏
connect(th,SIGNAL(agree_ToStrat_Game(int)),this,SLOT(startGame(int)));
//客户端接收到对方发来的拒绝游戏的消息 弹出拒绝消息
connect(th,SIGNAL(thread_gameDisagree()),this,SLOT(refuse()));
//接受到位置的坐标信息并开始画图的信号函数和槽函数绑定
connect(th,SIGNAL(thread_position(int)),this,SLOT(getPos_and_paintImg(int)));
//接受到赢家的消息
connect(th,SIGNAL(thread_winner(int)),this,SLOT(getWinnerInfo(int)));
th->start();
2.自己的信号和槽函数绑定
connect(messageSend,SIGNAL(sendChatMsg(QString)),this,SLOT(sendMsgToServer(QString)));
//开始按键的信号和槽函数
connect(startBtn,SIGNAL(clicked()),this,SLOT(sendStartMsgToServer()));
//900:1发送按键组坐标点的信号和槽函数,pos = i*30+j
connect(chessArea->BtnGroup,SIGNAL(buttonPressed(int)),this,SLOT(sendMyposition(int)));
//计时器的初始化 一秒为最小计时单位
timer = new QTimer(this);
timer->setInterval(1000);
//刷新时间 设置timelabel上面的时间
connect(timer,SIGNAL(timeout()),this,SLOT(flushTime()));
9.其他的一些各问题说明,【已经添加到代码注释中】
- 为了区分玩家和观看者,在主线程中设置了MyfdonServer默认值为2,该客户端在服务器上的fd,如果是邀请者,邀请被对方同意,自己必然是玩家,如果是被邀请者,同意对方的邀请,自己必然是玩家。根据这两个条件设置MyfdonServer,修改默认值。从而区分开玩家的客户端和观看者的客户端,并做相应的操作限制。
- 为了实现轮流落子,思路可以在服务器上面设置标志位,每一次落子后修改标志位。也可以把自己的Fd和棋子的坐标同时发送给服务器,服务器解析后判断当前是哪一个客户端的落子操作,从而做转发,若当前和上一次是同一个客户端发的坐标信息,则不作转发。
- 对于已有棋子的位置重复点击可以重新设置的问题,可以设置chessBtn的Checkable属性,客户端每一次收到了服务器的"#Pos:"位置信息,设置该位置按键的Checkable属性为false,再对客户端发送棋子坐标的地方做判断,若该棋子的Checkable属性为false则不发送。
- 输赢由服务器做判断,服务器保存一张虚拟的二维表,存储两方落子的位置信息,并标记是谁的棋子,然后判断四个方向的自己的棋子数量是否为5,若有一方到5,就发送"#Winner:"信息。
- 客户端对于"#Winner:"信息做不同反应,弹出不同的对话框,timer计时器停止,startBtn复原。
- 最后一颗棋子的闪烁效果:重写Widget的timerEvent,保存timerId,设置blinkflag标志位实现交替闪烁。在设置图标的槽函数中杀掉上次的timerId,设置上次位置的图标,保存本次的图标位置,开始本次的计时器。