核心内容摘要
探寻“刘玥外网淫秽June”:真相、争议与用户声音
贪吃蛇小游戏
实验内容本次实验的主要内容为使用 C 编程语言使用类的相关知识构建出一个贪吃蛇小游戏该小游戏应当具备有三种基础功能并可根据 OJ 的提示添加更多的加分项。
设计思路与功能描述
1 设计思路说明
2.
1 贪吃蛇游戏设计思路说明由于本次实验希望采用面向对象的思想来进行程序的编写而贪吃蛇游戏本身作为程序的出题故应当先绘制出该程序的类图再在该类图的基础上进行进一步程序的编写。
由于不同玩法的贪吃蛇之间略有不同故在此我们首先对“入门版”的贪吃蛇类图进行说明。
在本程序中一场贪吃蛇游戏的进行应当是基于 Game 的某一子类展开的在“入门版”中这个子类为 Game1。
通过生成一个 Game1 实体依次调用该实体的 init_game()方法和 init2()方法可以在画布上画出初始的地图并生成一个 snake 实体同时藉由 refresh()方式生成一批 fruit 实体。
在初始化结束后游戏可以正式开始在游戏进行的过程中通过 listen()方法对键盘进行实时监听一旦用户执行了某个操作就将操作值进行记录在下一次调用 play()方法时判断该值对下一步结果的影响最后调用 draw()方法在画布上更新界面。
而当 play()方法发现下一步蛇会死亡时会弹出“游戏结束”字体最后调用 back()方法回到初始菜单。
基于上述类图与大体流程描述一下对各个类进行逐一详细描述①Game 类该类是所有游戏类的父类其主要功能是为各个子类提供一个统一的初始与结束模板使得各个游戏类的调用、结束显得较为统一。
该类中的属性有1 widthint 类型默认值为 800。
该属性是新建画布时所建立的画布宽度在本次程序设计中这一初始值没有进行过改变。
2lengthint 类型默认值为 600。
该属性与 width 类似是初始新建立画布的高度在本次程序设计中也未进行过改变。
该类中的方法有1init_game()void 返回类型。
该方法是在初始化地图前必须进行的调用作用是生成指定规格的画布。
2back()void 返回类型。
该方法是在游戏结束包括按 Q 键退出、游戏失败录入姓名后退出等后生成了一个 Menu 类实体重新回到主页面。
3init2()、listen()Game 类作为一个大的父类还额外定义了 init2()和 listen()这两个虚函数用于在编写子类时记得实现这两个重要的方法。
②Game1 类该类是 Game 类的子类也是其他 Game 类用以修改功能的基础如何利用一个 Game1 类实体进行一场游戏已在前文叙述得较为清晰故不在此赘述这里将重点对 Game1 类中的众多属性和方法进行详细说明。
该类中的属性有1mapchar 型二维数组规格为[40][30]。
这一大小对应了整个贪吃蛇游戏界面的 40×30 的规格这一属性也是 Game1 类中众多方法实现的基础。
map 数组中存放的字符是游戏界面中对应位置方格.jpg 图片文件名的第二个字符。
例如0表示这里是空草坪7表示这里是食物等该数组中可存放的字符会随着后续功能的拓展而发生变化。
2meschar 型字符默认值为u。
mes 用于记录用户上一次对贪吃蛇发出的控制指令其中u表示向上d表示向下l表示向左r表示向右。
3headsnake 型指针。
由于贪吃蛇在本游戏中是以链表的形式存放的即将每一个蛇段看作一个实体所以为了能够对蛇进行控制就需要掌握链表的表头——即蛇头的相关信息。
4areaint 类型初始值为 28×38。
该值用于记录当前地图上还能放置水果的位置总数以便随机生成新水果并在后续功能中辅助判定地图是否已满。
5lengthint 类型初始值为 3。
该值用于记录当前蛇的长度以便在 UI 界面进行显示。
6lifeint 类型初始值为 1。
该值用于记录蛇的生命数并在 UI 上显示在 Game1 的游戏中这个值没有意义但在后续加入的其他功能中该值可以记录蛇的最多死亡次数。
同时当 life 变为 0 时默认是游戏结束的标志在这种情况下play()方法将被禁用。
7scoreint 类型初始值为 0。
该值用于记录当前的得分并在 UI 上显示同时该值也会在排行榜记录时被录入文件。
8highscoreint 类型。
该值在 init2()方法下的 getrecord()方法中被赋值用于记录当前所完游戏种类在排行榜中的最高分当 score 高于 highscore 时highscore 在么一个游戏刻被刷新为 score 值。
该类中的方法有1refresh()void 返回类型。
该方法在初始化地图与 fruit_num 为 0 时被调用该方法可以在当前为空草坪即 map 值为0的位置随机生成 1~5 个 fruit 实体并同时刷新 fruit_num 值与 area 值。
2draw()void 返回类型。
该方法在每一次操作执行结束后进行调用该方法会逐一遍历 map 数组使用 easyX 自带的图形辅助函数将每一个数组元素所代表的图片样式绘制在画布的指定位置。
3getrecord()void 返回类型。
该方法在地图初始化时会被调用。
该方法会通过遍历的方式逐行从 record.txt 中读取游戏记录在游戏版本编号和当前游戏一致的记录中找寻得分最高的成绩赋值给 highscore 属性。
4init2()void 返回类型。
该方法是重写了父类 Game 的方法所得会在新建好画布之后进行调用。
该方法会对 map 属性进行赋值将整个地图的边界都赋值为“硬墙”map 编号为8同时调用 refresh()方法生成一批食物并将食物所在位置赋值为“食物”map 编号为7。
同时该方法还会新建一个 snake 实体其长度为 3蛇头在[20][14]位置蛇尾在[20][16]位置并在 map 中对对应位置赋值。
在完成了对 map 的初始化赋值后调用 draw()方法绘制初始界面。
5play()bool 返回类型。
该方法用于处理在下一个游戏刻时游戏发生变化的逻辑其过程是先通过 snake 类的 next_pos()方法获取到下一个游戏刻蛇头的位置然后对该位置进行判定。
如果该位置是蛇尾以外的蛇身、硬墙则游戏结束弹出提示语同时将 life 归零禁用 play()方法恒返回 false若该位置是果实则调用该 snake 的 eat()方法同时让 length 属性自增 1返回 true若该位置是空草坪则 调用 snake 的 move()方法返回 true返回值适用于标识游戏是否能够进行下去的判断依据。
6record()void 返回类型。
该方法用于在游戏结束或用户自行退出时调用弹出提示语要求用户输入姓名同时对用户输入的内容进行实时显示。
当用户输入完毕后在 record.txt 的末尾追加本次游戏的版本编号、用户名、成绩。
7drawui()void 返回类型。
该方法用于在每一次 draw 方法调用后紧接着调用该方法绘制页面下部的所有 UI 提示交互。
其实现逻辑是使用 int2char 函数将蛇的长度、生命数、分数等存储为 int 类型的信息与提示语一并合成为一个 char 数组。
然后再在指定的位置输出这些提示字符串。
8listen()void 返回类型。
该方式重写了父类 Game 的 listen 方法。
该方法可以实时对键盘内容进行实时监听当有按键按下时采用_getch()函数获取按键值并根据按键值对 mes 属性赋值。
当没有按键按下时则按照系统的内部时钟每隔一秒调用一次 play()方法与 draw()方法重新绘制一遍界面。
值得一提的是当游戏结束后play()被禁止则同时停止调用 draw()方法只调用 record 方法用于记录用户姓名。
③snake 类由于在本程序中采用链表的方式来存储贪吃蛇中的蛇所以这里的 snake 类的实体其实是每一个蛇段每个蛇段之间采用指针的方式前后相连形成一条完整的蛇。
以下对 sanke 类中的属性和方法进行详细说明。
该类中的属性有1addpos 类型。
add 用于存储当前蛇段在地图中的横纵坐标便于后续绘制、判断等操作。
2nextsnake*类型默认值为 NULL。
next 即蛇链表的后继指针用于指向当前蛇段的下一个蛇段。
蛇尾的 next 值为 NULL。
3headbool 类型默认值为 false。
head 属性用作一个标志位用于表示当前这个蛇段是否是蛇头如果是蛇头则为 true其余均为 false。
4towchar 类型默认值为u。
tow 用于记录该蛇段蛇头方向的指向该值与其后继蛇段的 tow 值可以一同决定当前蛇段的形状、扭曲方向。
5lengthint 类型默认值为 0。
该值用于记录当前蛇的长度。
值得一提的是只有蛇头的该属性才是正确的蛇段的 length 属性值无意义。
该类中的方法有1cut(snake *p, int n)snake返回类型。
该方法用于将从p 所指向的蛇段开始向后数连续 n 个蛇段进行删去主要用于配合“高级版”的游戏实现。
该方法是基于递归实现的当 n 不为 0 时则删除当前节点并递归 cut(p-next,n-
。
同时为了便于操作该方法还将返回删减后的头结点用于替换原有的 snake。
2eat(char tow)snake返回类型。
该方法用于在判定到下一个游戏刻蛇头到达食物时调用其方法是在 tow 方向上新建一个 snake 实体并将该实体与原来的头部相连接取而代之为新的头部并将该头部返回为新的head。
3move(char tow)snake*返回类型。
该方法用于在判定到下一个游戏刻蛇头到达位置为草坪时调用。
其流程为先删去蛇尾并将倒数第二个蛇段的 next 置空之后再调用 eat 方法在 tow 方向上生成一个新的蛇头并返回该蛇头。
4next_pos(char tow)pos 返回类型。
该方法返回如果沿 tow 方向走蛇头在下一个游戏刻将要到达的位置并返回该位置。
该方法用于在 play 方法中辅助进行碰撞的判定。
5tail_pos()pos 返回类型。
该方法将返回当前蛇的蛇尾坐标。
6judge(snake* s,pos p)bool 返回类型。
该方法用于判定位置 p 是否在是蛇身上的某点其实现采用了递归的方式来实现遍历了蛇身上的每一个蛇段依次判断该点是否在蛇的身上。
7draw(char a[][])void 返回类型。
该方法用于在 a即 Game 子类中的 map 属性中在对应位置赋值上对应蛇段的形状标号。
例如下拐左、右拐上等扭曲状态便于 Game 子类在后续操作中便于画图。
同时为了满足后续 idea 实现过程中的需求本程序在编写时还对 draw 方法根据 a 的规格进行了重载。
④fruit 类fruit 类即在游戏过程中不断出现的食物抽象而成的类这个类的方法与属性都较少这与食物本身性质不多有很大的关系。
其属性与方法的详细介绍如下该类中的属性有1addpos 类型。
add 用于该食物在地图中的横纵坐标便于后续绘制使用。
2stylechar 类型默认为g。
style 用于记录当前食物的性质当为g时说明该食物是正常水果当为b时说明该食物是恶魔果。
该类中的方法仅有draw(char a[][])void 返回类型。
该方法用于在 a即 Game 子类中的 map 属性中在对应的位置根据该 fruit 实体的 style 赋上不同的值以便在后期绘制过程中能够展示出该食物。
2.
2 其他 Game 子类思路简要说明①Game2Game2 实现的是 OJ 提到的“进阶版”内容该内容要求实现蛇死去后身体变成边界再生成一条新的蛇直至地图被撑满。
该过程的实现主要在于修改判定到蛇死去的逻辑加写一个 stone()方法在蛇死去后将其身体所在的所有位置赋值为“硬墙”。
②Game3Game3 实现的是“高级版”内容该内容要求蛇死后其身体变成实物同时再生成一批新食物直到死亡次数大于 5。
该过程的实现与 Game2 的实现相仿加写一个 juice()方法在蛇死后将其身体全部赋值为“食物”。
同时还需要对死亡后的逻辑加以修改使得蛇死后让其 life 值自减life 自减为 0 时游戏结束。
③Game4Game4 实现的是 idea1 的要求即额外加入了“软墙”的概念。
当蛇碰到软墙后长度减 2碰到“硬墙”后长度减半。
该过程的实现首先要对 init()方法进行修改在地图上加入软墙元素其次就是要额外加入软墙的素材包同时要修改碰撞判定之后的逻辑即不再是碰到墙就游戏结束而是要到蛇的长度小于一定值才结束。
④Game5Game5 实现的是 idea2 的要求即额外加入“加速区”和“减速区”。
该过程的实现首先是 init()方法的重写与素材包的更新同时要对 listen()方法进行修改当蛇头在加速区时将每隔 1 秒刷新一次更改为每隔
5 秒刷新一次而当蛇头在减速区时更改为每隔 2 秒刷新一次。
⑤Game6Game6 实现的是 idea3 的要求即额外引入“体能槽”的概念每次蛇的运动都会消耗体能当体能清零时游戏结束。
该过程的实现首先要修改 life 属性的含义将 life 属性视作体能初始时为 100每次 move()都会消耗体能同时还要修改 drawui()方法将原来的字符串更改为能量条的形式最后还要修改碰撞判定的逻辑当 life 小于等于 0 时游戏结束。
⑥Game7Game7 实现的是 idea4 的要求即额外引入“恶魔果”的概念当蛇碰到恶魔果时直接死亡游戏结束。
该过程的实现首先是要修改 refresh()方法在每次生成好果子的同时生成恶魔果其次是要更新恶魔果的素材同时要更改碰撞的判定逻辑当碰到恶魔果时游戏直接结束。
⑦Game8Game8 实现的是 idea6 的内容即扩大地图的边界每次只绘制蛇头在正中心的部分。
前提是不能绘制到边界以外的内容该过程的实现主要是要重新修改 map[][]的属性同时对 snake 类、fruit 类中的相关函数进行重载使之可以接受更大规格的 map 值传入。
同时还要对 draw()方法进行修改确保能够满足题目的要求。
⑧Game9Game9 实现的是 AI 对战的内容即添加了另一条蛇由 AI 操控当有一条蛇死亡时另一方获胜。
该过程的实现是新添加了 snake2 类与 ai 类其中 sanke2 类的定义与 sanke 类的定义相仿只是对蛇段材质的内容进行了更新与修改而 ai 类的实现将在后续进行详细说明。
同时还对 listen()方法进行了修改加写了 aplay()等方法加设了 ames 等属性。
使得该 Game 类能够适应 AI 对战的相应需求。
⑨Game10Game10 实现的是双人对战的内容即可以实现对两条蛇的同时控制当一条蛇死亡后另一条蛇获胜。
该过程的实现主要是基于 Game9将其中的 ai 对象删除后修改 listen()方法使之能够同时监听两种键盘实现对 mes 和 ames 属性的同时监控从而实现同时控制的效果。
2.
3 菜单设计思路说明在本次程序设计中菜单的设计也是基于面向对象的思想进行编写的其类图为通过该类图不难看出其逻辑。
在程序开始运行时首先实例化一个 Menu 对象该对象中包含 12 个 Btn 对象这些 Btn 对象均会在被鼠标点击时生成对应的 Game 子类对象由此可开始游戏。
由于 Menu 与 Btn 类的实现较为简单且没有特别的亮点故不在此赘述其具体属性与方法。
2.
4 AI 设计在我初次拿到本次题目时我首先想到的是从头部开始对所有“草坪”单元格采用广度优先BFS的遍历算法找到距离其最近的果实而该路径的第一步就是 AI 此时会采取的策略。
在程序中 3293~3367 行注释掉的代码即为 AI 采用 BFS 的算法。
但使用该 AI 时我发现其一定不会作出错误的决定导致玩家很难战胜这个 AI。
所以我将 AI 的策略“傻瓜化”使得玩家可以获得一定的游戏体验。
其策略为首先判断当前方向是否有食物如果有则继续前进没有则搜索左右两侧是否有食物如果有则立刻拐弯。
而当三个方向都没有食物时会随机在地图内部生成一个“标记点”然后让贪吃蛇作出尽可能向“标记点”的决策。
基于这样的 AI 设计玩家可以看到 AI 作出很傻的决定也能在一定程度下战胜 AI获得游戏快感。
2 游戏功能描述
2.
1 菜单功能展示该游戏的菜单界面由 12 个按钮与对应的提示语组成鼠标点击对应的按钮即可进入相应的游戏
2.
2 入门游戏展示在入门游戏中玩家通过控制 WASD 四个键即可控制蛇的移动按下 Q 键退出游戏下方的字符串作为 UI 提示当按下 Q 键时结束游戏并弹出姓名输入框碰壁或是失败后弹出“游戏结束”提示语同时弹出姓名输入框。
2.
3 进阶版游戏展示贪吃蛇死后其尸体变为边界
2.
4 高级版游戏展示贪吃蛇死后尸体变成食物
2.
5 得分榜展示
2.
6 idea1 展示
2.
7 idea2 展示其中黄色块为加速区域蓝色快为减速区域。
2.
8 idea3 展示
2.
9 idea4 展示
2.
10 idea6 展示
2.
11 人机模式展示
2.
12 双人模式展示其中绿色蛇由 WASD 控制方向紫色蛇由 IJKL 控制方向
项目亮点与情况说明
1 项目亮点① 本次大作业的所有素材均为本人在 PS 中制作绘制而得自认为很好地通过方块的拼合实现了贪吃蛇的蠕动效果。
② 自认为本次大作业的完成度较高完成了除多人对战、RPG 模式、地图存储之外的所有功能。
③ 本次大作业实现了两种 AI 逻辑的编写。
2 诸多情况说明① 未对多人模式、RPG 模式、地图存储的功能进行实现。
其中 RPG 模式与地图存储未能实现的原因在于我个人认为这两个功能的难度不大但需要耗费的精力会比较多所以放弃了这两个模块的编写实现。
而对于多人模式主要是由于我之前错误估计了本次大作业所要花费的时间浪费了大量时间在绘制.jpg 图片上导致后来留给研究多人模式的时间不足。
② 采用了较为愚蠢的 AI 来实现。
正如前文所述我发现采用 BFS 遍历搜索算法的 AI 所采取的的策略总是正确的导致用户很难战胜电脑而且测试时间也过长。
所以我处于游戏性的考虑最终选择了这个较为愚蠢的 AI采用 BFS 的 AI 逻辑我用代码进行了注释TA 可以取消注释并对程序进行一定的修改来进行验证。
遇到的问题与解决方法
1 遇到的问题在进行初级模式程序编写的过程中我发现蛇总会在自己的身后留下一串尾巴如下图所示
2 解决方法这里问题出错的原因其实很容易查明——就是当我们在调用 snake 类的 move()方法时会删除掉蛇尾但是这里的删除仅仅是在蛇链表中进行了删除而未在 map 中修改该元素的信息导致 map 中依然存储着这个尾巴的信息。
我在一开始我的思路总是局限在要在 snake 类内部解决该问题但是 snake 的 draw()方法只能在 map 中绘制其占据的单元格而不能更改其他位置的信息。
于是我又试图重写了一个新的方法专门用来去除该元素的信息。
但如此做之后整个程序会显得十分冗余甚至要为了这一个元素将整个 map 数组传递给 snake 的新方法。
后来我意识到我其实可以在 Game 类的 play()方法中很轻易地解决这个问题只要在移动钱获得尾巴的位置再在移动后将尾巴的位置变成草坪即可而不用再新建一个方法经过这一改写后可以得到蛇的尾巴可以正常抹去♻️ 资源大小