当前位置:网站首页>鹏哥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)
边栏推荐
猜你喜欢
[Halcon vision] threshold segmentation
第6期:大学生应该选择哪种主流编程语言
Dry goods likeshop takeout order system is open source, 100% open source, no encryption
[leetcode每日一题2021/8/30]528. 按权重随机选择【中等】
uniapp使用简单方法signalR(仅用于web调试,无法打包app)
STM32 Alibaba cloud mqtt esp8266 at command
粽子大战 —— 猜猜谁能赢
centos8(liunx)部署WTM(ASP.NET 5)使用pgsql
Issue 7: how do you choose between curling up and lying flat
Agenda express | list of sub forum agenda on July 27
随机推荐
json_object_put: Assertion `jso->_ref_count > 0‘ failed.Aborted (core dumped)
Redis特殊数据类型使用场景
.NET操作Redis sorted set有序集合
.NET操作Redis Set无序集合
canvas上传图片base64-有裁剪功能-Jcrop.js
Redis implementation of distributed lock solution
hx711 数据波动大的问题
Simple use of json-c Library -- converting JSON files to struct
工厂模式详解
.NET操作Redis List列表
如何实现临时的图形要素现实
上传图片获取宽高
MLX90640 红外热成像仪测温传感器模块开发笔记(六)红外图像伪彩色编码
[leetcode每日一题2021/8/30]528. 按权重随机选择【中等】
Issue 5: the second essential skill for College Students
404页面和路由钩子
el-table实现可编辑表格
剑指Offer(十):矩形覆盖
algorithm
关于硕博士开题报告编写的思考