Qt框架黑白棋实现网络对战功能

一、前言

前文介绍了UDP和TCP两种协议的服务器搭建方法,本文将基于UDP协议搭建服务器,实现黑白棋游戏的网络对战功能

二、基本功能

实现网络对战功能,要通过服务器进行两个客户端之间数据的转发。因此,需要绑定服务器IP地址和端口,同时要读取客户端的IP地址和端口进行接收和转发。

服务器功能如下:

IP地址和端口设置:用于设置服务器IP地址和端口号

在线列表:用于显示目前已经连接的客户端名称,IP地址,端口号

消息列表:显示上线,下线和端口绑定信息

删除功能:删除对应客户端

客户端功能如下:

落子吃子:与前文相同

IP地址和端口设定:用于连接服务器IP地址和端口号

用户名和对手用户名:用于指定对手进行网络对战

三、具体原理和详细实现

服务器

1.端口绑定

绑定端口通过QUdpSocket类中的bind()方法实现,该方法的作用是绑定后,每次UDP数据包到达指定地址和端口时,都会发出readyRead信号。

void Widget::on_btn_bind_clicked()
{
    QString myIp = ui->ledit_ip->text();
    QString myPort = ui->ledit_port->text();
    QString msg;
    bool ret = mySocket->bind(QHostAddress(myIp),myPort.toUInt());
    if(!ret)
    {
        msg = "绑定失败";

    }
    else
    {
        msg = "绑定成功";
    }
    ui->tedit_message->append(msg);

}
2.删除功能

删除功能通过currentItem()确定选中项,通过contains()判断列表是否存在该条数据,通过removeAt()进行删除。

void Widget::on_btn_delete_clicked()
{
    QListWidgetItem *cur = ui->lwid_inline->currentItem();
    //先删除在线列表
    for(int i = 0;i<listClient.length();i++)
    {
        if(cur->text().contains(listClient.at(i).name))
        {
            listClient.removeAt(i);
            break;
        }
    }
    int row = ui->lwid_inline->currentRow();
    ui->lwid_inline->takeItem(row);
}
3.数据的接收和转发

数据的接收通过reaDatagram(),参数分别为数据报,最大字节,IP,端口号四个

其中最大字节可以通过pendingDatagramSize()设置,作用是返回上一个数据报的最大字节。

将数据包存储在字符串中,使用contains进行不同功能的筛选,该函数的功能是当字符串含有该关键字时返回1;

当检测到readyRead()信号时,服务器需要进行四种数据的筛选:

1.初始化:设置数据格式为init#from#to#role#initEnd,from表示数据来源,to表示数据发送对象,role表示当前落子,init和initEnd代表数据头和尾

查找在线列表中的用户名,筛选对应的用户进行数据报的发送,发送通过writeDatagarm(),第一个参数代表数据,第二个参数代表IP地址,第三个参数代表端口号,并通过消息列表提示初始化成功

//棋盘初始化 eg:init#from#to#role#initEnd
        else if(msg.contains("init#"))
        {
            QStringList list = msg.split("#");
            QString toname = list.at(2);
            QString role = list.at(3);
            for (int i=0;i<listClient.length();i++ )
            {
                if(listClient.at(i).name == toname)
                {
                    QString buf = QString("init#%1#initEnd").arg(role);
                    mySocket->writeDatagram(buf.toUtf8(),
                              listClient.at(i).addr,
                              listClient.at(i).port);
                    QString msg = QString("通知棋盘初始化");
                    ui->tedit_message->append(msg);
                    break;
                }
            }
        }

2.上线数据:inline#name#inlienEnd,name代表用户名

上线数据主要是加入在线列表和消息的显示

if(msg.contains("inline"))
        {
            QStringList list = msg.split("#");
            member mb;
            mb.addr = addr;
            mb.port = port;
            mb.name = list.at(1);
            listClient.append(mb);

            //记录上线
            QString msg = QString("%1 [%2:%3] 上线!")
                    .arg(mb.name)
                    .arg(mb.addr.toString())
                    .arg(mb.port);
            ui->tedit_message->append(msg);
            //显示到在线列表中
            ui->lwid_inline->clear();
            for(int i=0;i<listClient.length();i++)
            {
                QString label = QString("%1#[%2%3]")
                        .arg(listClient.at(i).name)
                        .arg(listClient.at(i).addr.toString())
                        .arg(listClient.at(i).port);
                ui->lwid_inline->addItem(label);
            }
        }

3.接收棋盘数据:data#from#to#x#y#role#daraEnd,x和y表示落子的坐标

//棋盘数据 eg:data#from#to#x#y#role#dataEnd
        else if(msg.contains("data#"))
        {
            QStringList list = msg.split("#");
            if(list.length()<=0)return;
            QString toName = list.at(2);
            for(int i = 0;i<listClient.length();i++)
            {
                if(listClient.at(i).name == toName)
                {
                    mySocket->writeDatagram(msg.toUtf8(),
                          listClient.at(i).addr,listClient.at(i).port);
                    break;
                }
            }
        }

4.下线数据:unline#name#unlineEnd:name代表用户名

//3.下线数据 unline#name#unlineEnd
        else if(msg.contains("unline"))
        {
            QStringList list = msg.split("#");
            QString name = list.at(1);
            int i = 0;
            for (; i < list.length(); i++)
            {
                if(name == listClient.at(i).name)
                {
                    listClient.removeAt(i);
                    break;
                }
            }

            //记录下线
            QString msg = QString("%1 [%2:%3] 下线!")
                    .arg(listClient.at(i).name)
                    .arg(listClient.at(i).addr.toString())
                    .arg(listClient.at(i).port);
            ui->tedit_message->append(msg);
            //更新在线列表
            ui->lwid_inline->clear();
            for(int i = 0;i<listClient.length();i++)
            {
                QString label = QString("%1#[%2%3]")
                        .arg(listClient.at(i).name)
                        .arg(listClient.at(i).addr.toString())
                        .arg(listClient.at(i).port);
                ui->lwid_inline->addItem(label);
            }
        }

客户端

1.连接服务器

向服务器发送上线数据,并将连接按钮设置为不可用

void ChessForm::on_btn_connect_clicked()
{
    QString myIp = ui->ledit_ip->text();
    QString myPort = ui->ledit_port->text();
    QString myName = ui->ledit_name->text();
    //1.上线数据 inline#name#inlineEnd
    QString msg =QString("inline#%1#inlineEnd")
            .arg(myName);
    mySocket->writeDatagram(msg.toUtf8(),
              QHostAddress(myIp),myPort.toUInt());

    ui->btn_connect->setEnabled(false);
}
2.模式选择

当点击网络对战按钮时,将模式变量设置为NVN,将界面进行初始化,并向服务器发送上线数据报通知对方界面初始化。

void ChessForm::on_btn_nvn_clicked()
{
    isDown = true;
    currentPK = NVN;
    //把界面初始化
    if(ui->cbox_item->currentText().contains("白"))
    {
        setRole(chess::White);
    }
    else
    {
        setRole(chess::Black);
    }
    //把棋盘初始化
    setChessInit();
    //通知对方棋盘初始化 eg:init#from#to#role#initEnd
    QString myIp = ui->ledit_ip->text();
    QString myPort = ui->ledit_port->text();
    QString myName = ui->ledit_name->text();
    QString toname = ui->ledit_toname->text();
    QString msg = QString("init#%1#%2#%3#initEnd")
            .arg(myName).arg(toname).arg(currentRole);
    mySocket->writeDatagram(msg.toUtf8(),QHostAddress(myIp),
                            myPort.toUInt());
}
3.数据接收

当服务器向界面转发数据的时候,进行数据报判断是初始化界面还是落子数据

初始化棋盘:调用写好的棋盘初始化方法

落子数据:接收对方落子位置,通过吃子方法判定,并更新数据。

//棋盘初始化数据 eg:init#role#initEnd
        if(msg.contains("init#"))
        {
            QStringList list = msg.split("#");
            if(QString(list.at(1)).toInt() == chess::White)
            {
                currentRole = chess::Black;
            }
            else if(QString(list.at(1)).toInt() == chess::Black)
            {
                currentRole = chess::White;
            }
            currentPK = NVN;
            //把界面初始化
            setRole(currentRole);
            //把棋盘初始化
            setChessInit();
        }
        //下棋数据 eg:data#from#to#x#y#role#dataEnd
        else if(msg.contains("data#"))
        {
            QStringList list = msg.split("#");
            int x = QString(list.at(3)).toUInt();
            int y = QString(list.at(4)).toUInt();
            int role = QString(list.at(5)).toUInt();

            //更新界面数据
            int ret = judegRule(x, y,formChessData,(chess::ChessType)role,true);
            if(ret)
            {
                //把数据传送给棋盘类,用于更新界面
                mychess->setChessStatus(formChessData);
                //数据统计
                ChessShow();
                isDown = true;
            }
        }
4.落子判定

当选择网络对战模式时,每次落子需要向服务器发送落子信息,并进行界面更新

else
    {
        if(isDown)
        {
            int ret = judegRule(i,j,formChessData,currentRole,true);
            if(ret)
            {
                //把数据传送给棋盘类,用于更新界面
                mychess->setChessStatus(formChessData);
                QString myIp = ui->ledit_ip->text();
                QString myPort = ui->ledit_port->text();
                QString myName = ui->ledit_name->text();
                QString toName =ui->ledit_toname->text();
                //通知对方客户端更新界面 eg:data#from#to#x#y#role#dataEnd
                QString msg = QString("data#%1#%2#%3#%4#%5#dataEnd")
                        .arg(myName).arg(toName)
                        .arg(i).arg(j).arg(currentRole);

                mySocket->writeDatagram(msg.toUtf8(),QHostAddress(myIp),myPort.toUInt());
                isDown = false;
                //数据统计
                ChessShow();
            }
        }
    }

四、效果展示

上线:

落子:

下线:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值