当前位置:网站首页>鹏哥C语言——扫雷2021-08-16
鹏哥C语言——扫雷2021-08-16
2022-07-26 10:36:00 【竹某】
这个程序实际上还是比较冗长的,体现了我编程上的一些缺点;另外自动展开功能也没有实现,这个以后使用递归可以解决。
#1 源文件
//头文件 supportingGame.h
#include <stdio.h>
#include <malloc.h>
#include <time.h>
#include <stdlib.h>
//一场游戏。需要:1.雷区(int[][]);2.地雷数目(int);3.死亡与否(int)
struct oneGame {//一个问题就是这个二维数组其实是不定的,在创建结构体时又不能进行动态内存分配。
char* mineField = NULL;//退而求其次,不能创建动态分配的二维数组,就创建一个指针。用这个指针指向并且操作二维数组。
int mines = 0;
int deadOrNot = 1;//1表示没死,0表示挂了
char* workDone = NULL;//用于表示已经完成的工作,大小和mineField一致。0代表没有完成,1代表完成。
};
int menu();//欢迎界面和模式选择。返回选择的模式。
//1:初级模式(9*9,10);2.中级模式(16*16,40);3.高级模式(16*30,99)4.自定义模式
//自定义不在menu中进行,而在game()中实现
int game(int);//读入游戏模式,返回是否愿意进行下一个游戏
void generateMineField(int row, int col, int mines, oneGame* thisGame);//读入行列和雷的数目,用于初始化oneGame对象和生成雷区。
//由下列几项功能组成:1.生成雷区的二维数组,提供给oneGame对象;给oneGame.mines赋值(初始化oneGame对象)
//2.随机分配地雷埋藏的位置(x,y)。并且在雷区这个二维数组的相应位置赋值为'#'。generateMine();
//3.根据地雷埋藏的位置,生成数字,用于提示。generateNumber();基本原理是数字检测旁边8个方块的地雷数目。
void generateMine(int row0, int col0, int mines, oneGame* thisGame);//在指定数组生成雷
void generateNumber(int row0, int col0, int mines, oneGame* thisGame);
//int displayMineField();
//展示一个简单的扫雷界面。行列标注(不然数起来麻烦),剩余雷的数目,死亡与否,花费时间,雷区组成。
void displayMineField(oneGame*, int row, int col);//每一次扫雷打印一份界面
//扫一次雷。(对workDone数组中的一个元素加以改变)。并且判断有无死亡或是游戏胜利(游戏结束)。
void sweepFieldOnce(oneGame* thisGame, int row, int col);
//源文件supportingGame.cpp
#include "supportingGame.h"
int menu() {
char mode = 0;
printf("*******************************\n");
printf("****Welcome to MineSweeper!****\n");
printf("*******************************\n");
printf("**Please choose the game mode**\n");
printf("#1.primary######\n");
printf("#2.middle#######\n");
printf("#3.advanced#####\n");
printf("#4.user-defined#\n");
printf("Your choice>: ");
//这个容错机制(%d)只能对数字容错,改为%c之后能对字符容错
while (1) {
scanf("%c", &mode);//这时往往还有字符在缓冲区中,比如\n,会引发一些意想不到的问题。
/*scanf("%*[^\n]%*c");*///这段代码的意思有待探究,但是使用getchar就可以解决缓冲取的问题
while (getchar()!='\n') {
}
switch (mode) {
case '1': printf("primary mode starts!\n"); return 1;
case '2': printf("middle mode starts!\n"); return 2;
case '3': printf("advanced mode starts!\n"); return 3;
case '4': printf("define yourself!\n"); return 4;
default:
printf("Wrong input! Again>: ");
break;
}
}
return -1;
}
int game(int mode) {
oneGame thisGame;
int row = 0;
int col = 0;
int mines = 0;
//游戏生成阶段,生成地雷和数字。
switch (mode) {
case 1:
row = 9;
col = 9;
mines = 10; break;
case 2:
row = 16;
col = 16;
mines = 40;
break;
case 3:
row = 16;
col = 32;
mines = 99;
break;
case 4:
printf("self-defined\n");
break;
default:
printf("Wrong input!\n");
break;
}
generateMineField(row,col, mines, &thisGame);
while (thisGame.deadOrNot) {
displayMineField(&thisGame, row, col);
sweepFieldOnce(&thisGame, row, col);
system("cls");
}
displayMineField(&thisGame, row, col);
//最后要free一下
free(thisGame.mineField);
free(thisGame.workDone);
return 0;
}
void generateMineField(int row0, int col0, int mines, oneGame* thisGame) {
thisGame->mines = mines;
thisGame->mineField = (char*) malloc(row0*col0);//到游戏结束时应该有一个free函数
thisGame->workDone = (char*)malloc(row0*col0);
for (int i = 0; i < row0 * col0; ++i) {//先把这里的内存的内容都赋为0
*(thisGame->mineField+i)= '0';
*(thisGame->workDone + i) = '0';
}
generateMine(row0, col0, mines, thisGame);
generateNumber(row0, col0, mines, thisGame);
return;
}
void generateMine(int row0, int col0, int mines, oneGame* thisGame) {
srand((unsigned long)time(NULL));
for (int i = mines; i > 0; --i) {
int x = rand() % row0;
int y = rand() % col0;
//如果(x,y)还没有放置地雷
if (*(thisGame->mineField + y + x * col0)=='#') {
++i;
continue;
}
else *(thisGame->mineField + y + x * col0) = '#';
}
return;
}
void generateNumber(int row0, int col0, int mines, oneGame* thisGame) {
char* arr = thisGame->mineField;
int count = 0;
//首先遍历四周,这些比较特殊
//四角
{ //左上角
if (*(arr) == '#') {
;//不做事
}
else {
if (*(arr + 1) == '#') {
count++;
}
if (*(arr + col0) == '#') {
count++;
}
if (*(arr + col0 + 1) == '#') {
count++;
}
}
if (*(arr) != '#') {
*(arr) = count + 48;
}
count = 0;
//右上角
if (*(arr + col0 - 1) == '#') {
;//不做事
}
else {
if (*(arr + col0 - 2) == '#') {
count++;
}
if (*(arr + col0 - 1 + col0) == '#') {
count++;
}
if (*(arr + col0 - 2 + col0) == '#') {
count++;
}
}
if (*(arr + col0 - 1) != '#') {
*(arr + col0 - 1) = count + 48;
}
count = 0;
//左下角
if (*(arr + (row0 - 1) * col0) == '#') {
;//不做事
}
else {
if (*(arr + (row0 - 1) * col0 + 1) == '#') {
count++;
}
if (*(arr + (row0 - 1) * col0 - col0) == '#') {
count++;
}
if (*(arr + (row0 - 1) * col0 - col0 + 1) == '#') {
count++;
}
}
if (*(arr + (row0 - 1) * col0) != '#') {
*(arr + (row0 - 1) * col0) = count + 48;
}
count = 0;
//右下角
if (*(arr + col0 - 1 + (row0 - 1) * col0) == '#') {
;//不做事
}
else {
if (*(arr + col0 - 1 + (row0 - 1) * col0 - 1) == '#') {
count++;
}
if (*(arr + col0 - 1 + (row0 - 1) * col0 - col0) == '#') {
count++;
}
if (*(arr + col0 - 1 + (row0 - 1) * col0 - col0 - 1) == '#') {
count++;
}
}
if (*(arr + col0 - 1 + (row0 - 1) * col0) != '#') {
*(arr + col0 - 1 + (row0 - 1) * col0) = count + 48;
}
count = 0;
}
//东南西北
count = 0;
{ //北
for (int i = 1; i <= col0 - 2; ++i) {
if (*(arr + i) == '#')
continue;
if (*(arr + i) != '#') {
if (*(arr + i - 1) == '#') {
++count;
}
if (*(arr + i + 1) == '#') {
++count;
}
if (*(arr + i + col0) == '#') {
++count;
}
if (*(arr + i + col0 - 1) == '#') {
++count;
}
if (*(arr + i + col0 + 1) == '#') {
++count;
}
*(arr + i) = count + 48;
}
count = 0;
}
count = 0;
//南
for (int i = 1; i <= col0 - 2; ++i) {
if (*(arr + i + (row0 - 1) * col0) == '#')
continue;
if (*(arr + i + (row0 - 1) * col0) != '#') {
if (*(arr + i + (row0 - 1) * col0 + 1) == '#') {
++count;
}
if (*(arr + i + (row0 - 1) * col0 - 1) == '#') {
++count;
}
if (*(arr + i + (row0 - 1) * col0 - col0) == '#') {
++count;
}
if (*(arr + i + (row0 - 1) * col0 - col0 - 1) == '#') {
++count;
}
if (*(arr + i + (row0 - 1) * col0 - col0 + 1) == '#') {
++count;
}
*(arr + i + (row0 - 1) * col0) = count + 48;
}
count = 0;
}
count = 0;
//西
for (int j = 1; j <= row0 - 2; ++j) {
if (*(arr + j * col0) == '#')
continue;
if (*(arr + j * col0) != '#') {
if (*(arr + j * col0 + 1) == '#') {
++count;
}
if (*(arr + j * col0 - col0) == '#') {
++count;
}
if (*(arr + j * col0 - col0 + 1) == '#') {
++count;
}
if (*(arr + j * col0 + col0) == '#') {
++count;
}
if (*(arr + j * col0 + col0 + 1) == '#') {
++count;
}
*(arr + j * col0) = count + 48;
}
count = 0;
}
count = 0;
//东
for (int j = 1; j <= row0 - 2; ++j) {
if (*(arr + j * col0 + row0 - 1) == '#')
continue;
if (*(arr + j * col0 + row0 - 1) != '#') {
if (*(arr + j * col0 + row0 - 1 - 1) == '#') {
++count;
}
if (*(arr + j * col0 + row0 - 1 + col0) == '#') {
++count;
}
if (*(arr + j * col0 + row0 - 1 + col0 - 1) == '#') {
++count;
}
if (*(arr + j * col0 + row0 - 1 - col0) == '#') {
++count;
}
if (*(arr + j * col0 + row0 - 1 - col0 - 1) == '#') {
++count;
}
*(arr + j * col0 + row0 - 1) = count + 48;
}
count = 0;
}
count = 0;
}
//遍历中间
count = 0;
{
count = 0;
for (int i = 1; i <= row0 - 2; ++i) {
for (int j = 1; j <= col0 - 2; ++j) {
if (*(arr + j + i * col0) == '#')
continue;
if (*(arr + j + i * col0) != '#') {
if (*(arr + j + i * col0 + 1) == '#')
++count;
if (*(arr + j + i * col0 - 1) == '#')
count++;
if (*(arr + j + i * col0 - col0) == '#')
count++;
if (*(arr + j + i * col0 - col0 - 1) == '#')
count++;
if (*(arr + j + i * col0 - col0 + 1) == '#')
count++;
if (*(arr + j + i * col0 + col0) == '#')
count++;
if (*(arr + j + i * col0 + col0 - 1) == '#')
count++;
if (*(arr + j + i * col0 + col0 + 1) == '#')
count++;
*(arr + j + i * col0) = count + 48;
}
count = 0;
}
}
}
}
void displayMineField(oneGame* thisGame, int row, int col) {
//打印坐标和雷区,
//将二维数组workDone和thisGame.mineField相乘得到可以打印的雷区
for (int i = -1; i < row;++i) {
printf("%2d ",i+1);
}
printf("\n");
for (int i = 0; i < row;++i) {
printf("%2d ", i+1);
for (int j = 0; j < col;++j) {
if (*(thisGame->workDone + j + i * col) == '0') {
printf(" ");
}
else {
printf("%2c ", *(thisGame->mineField + j + i * col));
}
}
printf("\n");
}
//打印死亡与否
if (thisGame->deadOrNot == 1) {
;
}
else {
printf("You are dead!\n");
return;
}
//打印剩余地雷数量
if (thisGame->mines == 0) {
printf("Mission completed!\n");
thisGame->deadOrNot = 0;
}
else {
printf("%d mines left!\n", thisGame->mines);
}
}
void sweepFieldOnce(oneGame* thisGame, int row, int col) {
int x = 0;
int y = 0;
int choice = 0;
printf("\n");
printf("------------------------------------------------------------\n");
scanf("%d %d %d", &x, &y, &choice);//x,y为选定坐标,而choice为:1.将选中处标记为雷;0.点击选中坐标。
//判断游戏是否结束(死亡,胜利。先判断死亡,后修改thisGame,最后判断有无胜利。)
int x0 = x - 1;
int y0 = y - 1;
if (choice == 1) {
if (*(thisGame->mineField + y0 + x0 * col) != '#') {
thisGame->deadOrNot = 0;
}
else {
thisGame->mines--;
*(thisGame->workDone + y0 + x0 * col) = '1';
}
}
if (choice == 0) {
if (*(thisGame->mineField + y0 + x0 * col) == '#') {
thisGame->deadOrNot = 0;
}
else {
*(thisGame->workDone + y0 + x0 * col) = '1';
}
}
displayMineField(thisGame, row, col);
return;
}//主函数
#include "supportingGame.h"
int main() {
int mode = menu();
game(mode);
return 0;
}
#2 经验或教训
这次的扫雷程序吸取了之前制作三子棋程序的教训,加强了编程之前的准备工作(建立程序的结构并且分析出了基本的对象),而且在代码的可扩展性方面下了功夫。总的来讲,这次的扫雷编写得很有逻辑感,算是成功的。但是还是有几点缺憾的地方:
1.编程之前的分析工作还是没有做足,内心还是有抗拒的情绪,这导致没有考虑清楚如何打印已经扫过的范围。具体表现为后续对oneGame结构体的修改,增加了workDone这一个二维数组用来记录已经扫过的范围——而正是这个数组的引入带来了内存泄漏的bug(return -1073740940)。这个bug后来解决了(把int[][]改为了char[][])。所以下一次要以耐心和理智做好编程前的分析工作,以达更高的编程效率。
具体的方法为:TTD思想。首先考虑清楚代码的实际应用场景和可能出现的问题,进而分析出程序的基本结构(控制流),需要进行的动作(函数)和所需基本的数据(数据结构,往往是结构体或是类,目前是这么理解)。可以反复地分析,用以发现更多的问题。分析出函数和结构体(或是数据结构)之后,就可以写头文件了,相应的文档一定要写好,用以解释结构体的作用(存放什么数据),和函数的参数的意义,函数的作用(完成什么事情)。也可以写函数的实现原理。 这之前是要给出程序的基本结构(main.cpp)。 最后才是具体实现(源文件.cpp)。
2.另外是scanf缓冲区的问题,使用了getchar和while循环解决。(0816)
3.鹏哥程序的一些好处:鹏哥的扫雷程序使用了200多行代码,而我的使用了400多行。他的程序在设计思路上选择了简洁的方案,没有像我一样着急地去实现功能。确实是这样,设计思路清楚固然很重要,但是简洁性也同样重要。
他简洁的地方首先在于:a.数据存储上,设计了一个show的二维数组用于展现已经完成扫雷的区域,比我的workDone数组可要好很多,这带来了display函数的简洁;b.统计一个坐标旁的雷数,我采用了分类讨论,很是麻烦,而他采用了扩大数组以求统一化的思路,简化这个步骤。所以在理清了思路之后,最好还是要去优化一下思路。
具体的步骤为:分析测试逻辑(main函数)--->分析基本步骤和基本对象--->抽象出函数和数据存储方式。这是总体的设计方案,可以进一步优化,要求写出函数文档(document)和伪代码(体现各个函数之间的数据传输)。伪代码帮助我在函数的实际应用场景下考虑函数的接口,便于写出函数声明;而文档则在此基础上详细阐述函数的功能,输入(形式参数的意义),输出(返回值的意义)和可能会出现的问题(比如内存泄漏等等问题),同时函数的具体实现也应该加入考虑范围。数据的存储方式应该与函数接口的设计相辅相成。
(0819)
边栏推荐
- .NET操作Redis Set无序集合
- 移动端H5开发常用技巧总结
- 父类对子类的引用(父类引用指向子类对象)
- Using native JS to realize custom scroll bar (click to reach, drag to reach)
- 【机器学习小记】【搭建循环神经网络及其应用】deeplearning.ai course5 1st week programming(keras)
- .NET操作Redis Hash对象
- Some cutting-edge research work sharing of SAP ABAP NetWeaver containerization
- 从蚂蚁的觅食过程看团队研发(转载)
- .net operation redis string string
- 第5期:大学生入职必备技能之二
猜你喜欢

kali 查看ip地址

.NET5WTM(ASP.NET Core) PGSql开箱操作

uniapp使用简单方法signalR(仅用于web调试,无法打包app)

第4期:大学生提前职业技能准备之一

一文详解Nodejs中fs文件模块与path路径模块

Okaleido ecological core equity Oka, all in fusion mining mode

MLX90640 红外热成像仪测温传感器模块开发笔记(六)红外图像伪彩色编码

Navicat15连接本地虚拟机的Mysql(Centos7)

Application of.Net open source framework in industrial production

【机器学习小记】【风格迁移】deeplearning.ai course4 4th week programming(tensorflow2)
随机推荐
centos8(liunx)部署WTM(ASP.NET 5)使用pgsql
Analyze the hybrid construction objects in JS in detail (construction plus attributes, prototype plus methods)
分布式锁解决方案之Redis实现
datav漂亮数据屏制作体验
剑指Offer(五十二):正则化表达式
Mlx90640 infrared thermal imager temperature sensor module development notes (VI) pseudo color coding of infrared images
一些你不知道的 web API
MD5 encryption
使用Geoprocessor 工具
L2-005 set similarity (intersection of vector and set)
[Halcon vision] threshold segmentation
Redis Docker实例与数据结构
【机器学习小记】【风格迁移】deeplearning.ai course4 4th week programming(tensorflow2)
Summary of common skills in H5 development of mobile terminal
mysql 进不去了怎么办
Unit test, what is unit test and why is it so difficult to write a single test
【机器学习小记】【搭建循环神经网络及其应用】deeplearning.ai course5 1st week programming(keras)
记给esp8266烧录刷固件
.NET操作Redis String字符串
Zongzi battle - guess who can win