一、程序介绍

1.1 现实背景

国际象棋是世界上一个古老的棋种。据现有史料记载,国际象棋的发展历史已将近2000年。关于它的起源,有多种不同的说法,诸如起源于古印度、中国、阿拉伯国家等。

国际象棋分为黑白两方共32枚,每方各16枚;棋盘为正方形,由64个黑白(深色与浅色)相间的格子组成。每方有王、后、象、车、马、兵六种棋子,不同棋子走子和吃子的方法不同,不再赘述。

1.2 设计目的

使用Qt自带的Socket编程,实现客户端和服务端一对一的网络对战,正确复现国际象棋的规则。在基本的走子和吃子回合制下,要求有:

对局中,出现下列情况之一,本方算输,对方赢:

己方王被将死(不允许送吃); 2)己方发出认输请求; 3)己方走棋超出步时限制;实现兵生变,用户可选择生变为的棋子。

加入逼和和车马易位功能。车马易位不考虑车和马是否移动过,只要在固定位置,满足车马易位的其他条件即可进行易位。

一局结束时(胜负,逼和)在两端弹出对话框告知结果。

加入残局载入和保存功能。

1.3 程序使用方法

程序初始界面如下

本程序采用客户端和服务端集成的设计,点击初始化—连接,用户自主选择创建客户端抑或服务端,以及ip地址和端口号。

选择创建服务端,点击OK,处于等待连接状态;可随时取消等待。

再开一个程序,选择创建客户端,设置对应端口号,点击OK连接服务端。这时客户端和服务端的创建连接窗口销毁,代表二者成功建立连接。这时两端的界面分别变为

服务端

客户端

本程序的特色之一是客户端和服务端的不对称权限。一个不成文的规定是,数据的运算和主动权握在服务端手里,于是只有服务端能点击开始游戏,能够载入残局等。在服务端点击开始游戏,双方同时开始


双方均可正常走子。此时黑方不走,倒计时结束后,双方均弹出消息框

认输和将死效果相同,不再演示。载入兵升变残局,将兵走到底线,出现如下界面

若选择后,则将死对方

若选择象,则逼和

最后演示车马易位。载入残局

此时由于王受黑车威胁,无法进行车马易位

将局势走成上图所示,此时王以及王经过的路径均不受威胁,可进行长短易位。长易位后效果如下

二、程序设计

技术基础本程序在Qt Creator中编写测试,共约1400行代码,使用自带编译器(mingW)编译。

使用了Qt自带的QString文本库,QFile文件库,QMap、QSet、QVector、QList等容器库存储数据,QTimer进行计时,QPaintEvent、QMouseEvent处理鼠标和绘图事件,QMessageBox、

QFileDialog标准消息框和文件对话框,QPainter绘图方法;没有使用QThread进行多线程并发操作;网络通信部分使用的是Qt Socket部分的QTcpServer和QTcpSocket类,以及数据打包和处理时的QDataStream,QByteArray等。

类的职责和关系

utils.h中首先定义了方便数据传输和运算的结构体包装,并重载了 QDataStream 的流运算符用于结构 体的序列化、二进制化,编码和解码的过程

struct Pos{//坐标二元组int x,y;bool operator == (const Pos & value) const{//用于比较以及取得作QSet键值的资格return (value.x==x&&value.y==y);}bool operator < (const Pos & value) const{//用于比较以及取得作QMap键的资格if(x<value.x)return true;if(x>value.x)return false;if(y<value.y)return true;if(y>value.y)return false;return false;}friend QDataStream& operator>>(QDataStream& in, Pos& s);friend QDataStream& operator<<(QDataStream& out,const Pos& s);
};
struct Piece{//棋子bool white;//棋子属于白方与否int type;//棋子类型,0~5 queen bishop rook knight pawn kingfriend QDataStream& operator>>(QDataStream& in, Piece& s);friend QDataStream& operator<<(QDataStream& out,const Piece& s);
};
struct Walk{//棋子的移动,将王车易位也看做一种移动,在下面的move()函数中一视同仁进行充分处理Pos pos;//目标位置int attack;//移动的附加属性 0:走 1:攻击 2:王车易位
};
struct Situation{//对局面的快照//单独标记王的位置Pos black_king;Pos white_king;QMap<Pos,Piece> pieces;//记录全局所有位置-棋子映射friend QDataStream& operator>>(QDataStream& in, Situation& s);friend QDataStream& operator<<(QDataStream& out,const Situation& s);
};
inline uint qHash(const Pos key){//用于结构体契合QSet的key,与大作业一的功能相同,不再赘述return key.x + key.y;
}

需要将死、逼和、王车易位功能,在得到棋子可走范围时需要进行虚拟的模拟,于是有了两个步骤的处理。这样的处理也使得将死和逼和的判断轻而易举:当某方所有子的FinalRange的并集为空集时,若此时此方被将军,则是将死;否则为逼和。

updialog.h中的UpDialog继承自QDialog,是进行兵生变选择的对话框,调用其int getSetting()函数将执行exec()并阻塞,窗口销毁后返回选择的生变类型。 connectiondialog.h提供了一开始选择服务端/客户端并创建连接的窗口。

public:
explicit ConnectionDialog(QWidget *parent); signals:
void serverdone(QTcpSocket*);
void clientdone(QTcpSocket*);

父窗口只需新建ConnectionDialog对象进行初始化并进行信号的连接即可。当用户选择建立服务器并成功创建连接时触发携带socket的void serverdone(QTcpSocket*)信号,当用户选择建立客户端并成功创建连接时触发携带socket的void clientdone(QTcpSocket*)信号。

chessboard.h是国际象棋棋盘的显示控件,内部将处理鼠标的点击事件、可移动区域的计算和显示等。对外层来说重要的只有

public:
explicit ChessBoard(QWidget *parent);
void setSituation(Situation s);//设置局面,立刻repaint()生效
void setStatus(int id);//设置棋盘显示状态 //0:empty 1:loaded 2:selecting
//下面两个函数控制着某棋子能否被点击等
void setSide(bool iw);//设置本端是白方还是黑方     bool isnowwhite;//设置当前是哪一方走棋 signals:
void move(Pos pos,Pos pos2);//当用户点击鼠标进行棋子的移动时触发信号,携带棋子位置和目标位置

mainwindow.h包含了主窗口MainWindow。在本次设计中主窗口负责的功能较少,它主要是起到调整界面的作用。它在一开始创建了ChessBoard*,但当客户端或服务端创建完成时,便对应创建了 chessclient.h中的ChessClient或chessserver.h中的ChessServer,并将chessboard*交予管理。可以认为ChessClient和ChessServer起到的是proxy的作用,它们负责客户端和服务端间的网络通信,在一端构成了ChessBoardProxyMainWindow的沟通模式。客户端、服务端工作流程下面列出了客户端/服务端单方面ChessBoardProxyMainWindow可能出现的沟通类型。

在程序中,这些可能的情况均使用信号槽或者公共方法来进行,不再详细叙述。mainwindow主要负责

主界面的调整(按钮的disable等),proxy负责网络通信和模块间通信,board则进行棋盘的更新显示。

connect(socket,SIGNAL(readyRead()),this,SLOT(received()));

即托管了socket*的数据接收,处理后再以void newPacket(QByteArray data)的signal发送出去。

Packet的出现是为了规避TCP/IP协议可能出现的粘包/拆包问题(这在此程序中是必要的,由于此程序设计的思想所致,客户端和服务端的通信来回次数较多,程序中具有连续发送两个包的情况,因此必须

可以看到处理方式是在数据的头部加入数据的长度信息,再发送出去。而对接收到信息的处理为

当接收数据的长度不足以读取头部的长度信息,或接收的数据量小于应接收的数据长度时,程序将等待下一次接收,这样就解决了拆包。而数据量足够时,程序读取头部的数据长度信息并读取对应长度的数据,这样便完成了一个独立的数据包的提取;而每成功提取一个包,就发送携带它的信号,这样就解决了粘包。经过这一个中间层的包装,数据的收发轻易很多,如在chessclient中,开始时进行信号连接

2.1 通信协议

之前提到过,本程序采用不对称通信设计。这个构想的初衷是实现一定程度的反作弊功能,这个框架下若期望拓展到游戏平台的构建(而非双人对战),修改会减少很多。对于游戏平台来说,要不惮以最坏的恶意来揣测玩家。因此应该将一切的计算、控制权限交予服务端,否则,举例说客户端采用强行修改内存等方法像服务端发出了非法、不合理的动作(如开局直接用兵将对方王吃掉),服务端若想避免惨剧只能加入繁琐的检测机制(实际上很多即时性的网游就是这样干的,不过这只是因为射击游戏等对实时性的刚需,因为网络延迟的原因全部交予服务器处理数据不成立)。而国际象棋这样的对战不需要很高的即时性,所以完全可以采取客户端只向服务端发送可行的动作,服务端处理局面后将结果返回客户端(同时也更新服务端的界面和变量等),客户端进行显示的方法。

下面是客户端和服务端的通信协议。

//server to client //0:发送局面 //1:发送双方计时 //2:发送胜/负/和 0胜1负2和 //3:换为X边 0白1黑 //4:

请求兵升变 0~3 queen bishop rook knight //5.开始 白方先走 只需调整变量 紧接着还要发换边信息

//client to server //0:走棋 //1:升变 //3:认输

从上一个部分的代码中可以看到,每个数据包的头部都是一个整数,代表的就是信息类型;之后是具体数据。对于客户端的动作,客户端向服务端发送请求,服务端处理后更新自身界面、变量并发送使客户端更新界面、变量的数据包,客户端接收后进行更新。计时的变动、胜负和的消息、换边的消息、改变局面棋子排布、弹出生变对话框、开始一局的动作都是服务端发起,客户端只能接收并显示。

:升变 //3:认输


从上一个部分的代码中可以看到,每个数据包的头部都是一个整数,代表的就是信息类型;之后是具体数据。对于客户端的动作,客户端向服务端发送请求,服务端处理后更新自身界面、变量并发送使客户端更新界面、变量的数据包,客户端接收后进行更新。计时的变动、胜负和的消息、换边的消息、改变局面棋子排布、弹出生变对话框、开始一局的动作都是服务端发起,客户端只能接收并显示。

基于C++的简易的国际象棋双人对战程序设计相关推荐

  1. 基于FPGA(basys3)的双人对战人机对战五子棋(vivado)课程设计项目

    目录 主界面显示与选择模式 双人对战 人机对战 胜利界面显示 部分源码 主界面显示与选择模式 VGA显示器显示图片,显示图片利用Block Memory Generator将图片像素点储存在RAM里面 ...

  2. socketserver模块用法,多道技术、 基于UDP的简易版QQ

    复习 1.OSI七层2.以太网协议3.ip协议(arp协议)4.TCP5.UDP OSI七层 应表会 # 应用层 (HTTP协议, FTP协议)传输层 # 端口协议 在此层发挥作用网络层 # IP协议 ...

  3. [原创][连载].基于SOPC的简易数码相框 - Nios II SBTE部分(软件部分) - 从SD卡内读取图片文件,然后显示在TFT-LCD上...

    实在很抱歉,时间紧张,我只讲怎样从SD卡内读取bin文件(二进制文件),然后现在TFT-LCD上. 准备工具 1. Image2Lcd.zip 操作步骤 步骤1 寻找或制作240x320的图片 简单起 ...

  4. 简单计算器的设计java_(基于java的简易计算器的设计.doc

    (基于java的简易计算器的设计 基于java的简易计算器的设计 摘要 自从java语言诞生以来,java语言就以不可抵挡的趋势很快成为国际上广泛流行的面向对象编程语言,它既具有高级语言的特点,又少了 ...

  5. 用python做双人五子棋_基于python的socket实现单机五子棋到双人对战

    基于python的socket实现单机五子棋到双人对战,供大家参考,具体内容如下 本次实验使用python语言.通过socket进行不同机器见的通信,具体可以分为以下四步:1.创建ServerSock ...

  6. 基于stm32简易计算机电路图,基于STM32的简易电子计算器设计与实现(DOC).doc

    嵌入式系统设计实验综合设计报告 PAGE 四川师范大学成都学院通信工程学院 基于STM32的简易电子计算器设计与实现 实验综合设计报告 学生姓名 陶龑 学 号 2016301033 所在学院 通信工程 ...

  7. python实现简易聊天需要登录博客园zip下载_Python基于Socket实现简易多人聊天室的示例代码...

    前言 套接字(Sockets)是双向通信信道的端点. 套接字可以在一个进程内,在同一机器上的进程之间,或者在不同主机的进程之间进行通信,主机可以是任何一台有连接互联网的机器. 套接字可以通过多种不同的 ...

  8. 基于Zookeeper实现简易版服务的注册与发现机制

    一.功能要求 基于Zookeeper实现简易版服务的注册与发现机制 启动2个服务端 将服务端IP和端口信息注册到Zookeeper上 启动1个客户端 从Zookeeper中获取2个服务端节点信息 客户 ...

  9. 基于redis的简易分布式爬虫框架

    代码地址如下: http://www.demodashi.com/demo/13338.html 开发环境 Python 3.6 Requests Redis 3.2.100 Pycharm(非必需, ...

  10. 基于FPGA的简易DDS信号发生器的设计与验证

    基于FPGA的简易DDS信号发生器的设计与验证 一,理论介绍 补充:举例理解 二,代码实现 1,实验目标 2,MATLAB代码 3,verilog代码及实现思路 一,理论介绍 DDS 是直接数字式频率 ...

最新文章

  1. 用户稿件 | 好家伙,到底谁在用TBtools?
  2. Windows Home Server 2011 RC 安装体验
  3. inet_ntoa()返回字符串的生命周期
  4. 将用户名保存至cookie中
  5. 【C 语言】C 语言 函数 详解 ( 函数本质 | 顺序点 | 可变参数 | 函数调用 | 函数活动记录 | 函数设计 ) [ C语言核心概念 ]
  6. 神经网络贷款风险评估(base on keras and python )
  7. 萌新的Linux的学习之路(十) --ip设置管理
  8. poe交换机标准与非标准的区别介绍
  9. 史上最全的技术手册整理总结,编程小白都从这篇文章迅速成为大牛
  10. html-表单的应用
  11. 导入train_test_split时,ModuleNotFoundError: No module named 'sklearn.cross_validation'
  12. Codeforces 448 D. Multiplication Table
  13. 广义pareto分布_Generalized Pareto Distribution (GPD)
  14. CSDN博客成长记录
  15. U盘启动晨枫U盘维护工具V2.0版
  16. 反软件盗版的最佳实践
  17. 华为手表 GT3训练计划怎么用?
  18. map contract violation
  19. Swift对接C++库
  20. 031 Rust死灵书之Vec实现insert和remove

热门文章

  1. 解决局域网共享文件时提示“没有权限访问,请与网络管理员联系请求访问权限“
  2. Oracle实现网吧计费系统,毕业设计(论文)-网吧计费管理系统设计.doc
  3. 『vulnhub系列』dpwwn-1—Linux计划任务提权
  4. 根据人脸关键点做人脸对齐face alignment----C++实现
  5. waves效果器_学会EQ效果器,浑厚亮嗓又大气
  6. windows service (三)打包安装服务
  7. 风云java_风云烈传-执掌风云
  8. SharePoint开发环境配置
  9. e的n次方要怎么用计算机计算,Excel函数公式大全,使用EXP函数计算常数e的n次方....
  10. 梦幻西游易语言辅助教程