- 清小C -
C++大作业赏析
——如何完成一篇优秀的C++大作业
苦恼C++大作业的同学快点进来看看!(小声)
五子棋设计
五子棋是大家最为熟悉双人对弈的棋类游戏之一,
只要任意行、列、斜线连成五子即可获胜。
上个学期中,陈昊柯同学的大作业实现了五子棋,
不但能够双人对战,而且实现了五子棋AI,能人机对战。
我们一起来欣赏陈同学的大作业吧。
在开始之前,
我们先来看下运行实例吧!
1. 首先运行程序,进入主页
2. 输入1,则进入了人-人对战界面
3. 输入2,则进入了人-机对战界面
4. 如果五子棋太难,我们可以选择输入4,玩更简单的井字棋
类的设计
- PART ONE -
(1) 五子棋类:trial
我们可以看到,
整个类的设计稍显复杂,
所有函数都被封装在一个类中。
在此基础上,小编稍作修改,
把一个类拆成多个类。
小编觉得这种风格,代码可读性或可更好:
/ / / / / / /
/ / / / / / /
分成了五个类:
判断胜负:judge,打印界面:print,控制落子:play
人-人对战:pp_play,人-机对战:pc_play
其中pp_play类和pc_play类又由play类继承得到
——————————————————
(2) 类数据成员说明
Public:
Over: 用于判断游戏的结束(1为结束,0为继续);
Mode:用来选择游戏模式(1为人-人对战,2为人机对战,3为载入上次游戏);
Again:用来选择是否重新游戏(1为返回主页并选择是否继续游戏,2为直接退出游戏);
Zong,heng:分别用来传递横坐标与纵坐标。
Private:
char chessmove[15][15]:决定网格中是填补“X”、“O”还是“ ”,也就是控制落子。
int flag:用来确定该哪个玩家落子了(每次使用flag后flag都会加1,然后每次通过判断flag%2是0还是1来确定玩家)。
int player:用来标记玩家。
int victory:用来确定游戏是否有人实现了“五子”连续(0为无人胜利,1为玩家1胜利,2为玩家2胜利)。
int point_chess[15][15]:在人机对战的时候负责给棋盘里的格子计分(每个点的默认得分都是0分)。
(3)类函数成员说明
1)printboard函数
printboard函数的主要功能是打印棋盘。当游戏没有分出胜者的时候,每次printboard函数被调用的时候都会借助for循环把棋盘打印出来,最重要的是把新落的子打印在棋盘上,起到一个刷新的作用。
2)clearscreen函数
借用C++标准库头文件stdlib.h中的system("cls")函数进行清屏。在每次玩家或者电脑确认落子之后,通过clearscreen将控制台上面所有信息清除,方便之后的打印。
3)homepage函数
homepage函数的只要作用就是显示程序的主界面,用于选择游戏模式。并且,当一局游戏结束之后,如果选择“继续游戏”,那么程序会再次调用homepage函数,并且让玩家再自由选择游戏类型。
4)restart函数
restart函数可以用来选择是返回首页继续游戏,还是直接退出程序。
5)rowvic,columevic,diagonal函数
可以分别判断行、列和对角线(与副对角线)上是否有五个连续相连的棋子。
6)pp_play函数
pp_play函数负责人-人对战的落子,两位玩家交替落子。
7)pc_play函数
pc_play函数负责人-机对战中的落子,在后面的算法设计中会详细介绍。
8)point_initialize函数
point_initialize函数的主要作用就是在每次落子之后将棋盘上每一个点的分值清零,方便再次落子时的打分。
9)chess_initialize函数
chess_initialize函数用于同时给chessmove[ ][ ],point_chess[ ][ ]矩阵进行初始化,这个函数只在游戏最开始的时候使用一次。
10)save函数
save函数可以把即时的落子情况(chessmove[ ][ ]),以及当时的游戏模式(mode)和玩家(flag)存储起来(三者分别存放在三个文件中)。在每次有人落子之后都会调用save函数,这样当游戏突然非正常关闭的时候可以保证游戏数据不会丢失。
11)read函数
read函数可以把save函数存储的数据提取出来再分别赋予他们对应的数据成员,这样游戏可以从上次退出的地方完全相同的继续进行。
关键点说明
- PART TWO -
(1) char chessmove[15][15]
五子棋需要什么?
首先当然是一个空白的棋盘,不同的两种棋子
棋盘的输出是简单的,简单的for循环嵌套就能实现这点
但关键的就是如何实现放置棋子这一操作,
当然我们要求这一操作尽可能的简洁
陈同学在他的大作业中很巧妙地实现了这一步,
他注意到我们改变的其实是棋盘格子的状态,
格子有几种状态呢?
其实就是三种:“空白”,“A类棋子”,“B类棋子”。
所以他定义了一个15*15的字符数组char chessmove[15][15],来表示棋盘格子的状态。(选的棋盘是15*15)
因此下棋操作就是改变数组中相应元素的状态。
下图分别是:
字符数组的初始化,
棋盘的输出以及输出棋盘实例
/ / / / / / /
/ / / / / / /
——————————————————
(2)人-机对战的设计
在设计如何让电脑合理落子的时候,这位同学主要运用到的就是打分的算法,而设计的只要思路是“稳住防守,相互靠近,伺机反击”。
“所谓打分的算法就是借助矩阵point_chess[i][j]给棋盘上每一个点赋予其一个特定的分数,这个分数由其周围黑白子的分布形势确定,通过筛选出分值最高的点即可在一定程度上比较合理的为计算机选择一个落子点。”
1. 防守“打分”
(1)每个已经被“X”或者“O”占据的点都直接赋为-10000000分,确保这个点永远不可能被选为最大值点;
(2)每一个“X”周围紧贴的8个点都+100分;
(3)每个方向“X”的“活二”形式的两端的点都+1000分;
(4)每个方向的“X_X”形式,其中间的点都会+2000分。这个点的分数比上面的“活二”分数高的原因是因为”X_X”形式比较容易被用来构建“三三”所以需要重点防守;
(5)每个方向上的“XXX”形式,其两端的端点都+8000分。但是,由于“XXX”可能是“活三”也可能是“死三”,所以仅仅这样赋值对于棋型的考虑其实是不充分的;
(6)每个方向上的“OXXX”或者“XXXO”其另一端都会-8000分,这是因为这就构成了“死三”,死三的价值就比“活三”差了很多;
(7)每个方向上的“X_XX”或者“XX_X”,其中间的空点都会被赋予+6000分,但是这个情况也没有考虑一端被封堵的情况;
(8)为了进一步完善上述的棋型,每个方向上的“OX_XX”或者“XX_XO”,其中间的字都会被-7000分,这是因为这个点除了完成“冲四”之外,没有别的作用,比较容易为计算机落子带来迷惑性;
(9)每一个方向上的“XXXX”其两端都会+10000分,此点的赋值很高是因为这个点是无论如何都要封堵的,不然就输了。
2. 进攻打分
(1)每一个“O”周围紧挨的8个点都会被+500分,这就在一定程度上保证了这些“O”在有条件的时候会尽可能紧贴,事实上将“O”周围几周的点都可以赋予加分,但是工作量实在是太大了;
(2)每一个方向上的“OO”两端都会被+1000分;
(3)每一个方向上的“O_O”其中间的点都+5000分;
(4)每一个方向上的“OOO”,其两端都会被+6000分;
(5)每一个方向上的“XOOO”,即“死三”,其两端都会被-5000分;
(6)每一个方向上的“OOOO”都会被+100000分,因为落这个子就赢了。
下图是部分源代码实现:
/ / / / / / /
小结
- PART THREE -
陈昊柯同学的C++大作业很好地体现了C++支持面向对象的方法这一特点,其中充分运用了类、对象、数组、指针、字符串等知识要点,这也反映着陈同学对C++各个知识点的牢固掌握。希望所有完成C++大作业,或者正在编写的同学们,在将来能够如同陈同学所说:“真真正正的通过编程解决一些在学习专业知识时遇到的实际问题!”。
PS:写完代码不要忘了
程序设计说明书、用户手册和总结报告哦!
E.N.D
文字|张帅
排版|张帅
大作业作者|陈昊柯 2020春季学期
校阅|刘政宁 姚炫熔
指导老师|李超