#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循环解决。
|