一、题目概述
完成简单的扫雷游戏(不包括展开一大片、以及插旗方法)
二、基本思路
(1)扫雷的整体框架
扫雷游戏将会用多文件实现
test.c:测试游戏的逻辑
game.c:游戏的实现
game.h:游戏的函数的声明、头文件包含、全局变量的创建等。
在开始玩游戏之前,我们与玩家进行交互,玩家选择玩与不玩。
代码如下:
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void menu()
{
printf("*******************************\n");
printf("********** 1. play **********\n");
printf("********** 0. exit **********\n");
printf("*******************************\n");
}
int main()
{
int n = 0;
do
{
menu();
scanf("%d", &n);
switch (n)
{
case 1:
printf("扫雷游戏\n"); //(1)用扫雷游戏代替一下真正的游戏,证明扫雷游戏的交互是可以运行的
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (n);
}
(2)主要思路
首先,先看一个基础的扫雷游戏
从图中可以看出,这个扫雷是一个9*9
的棋盘,里面布置了10个雷。
在扫雷的之前,我们要生成10个雷,在扫雷的过程中,玩家需要排查雷。所以我们就要做出一个9*9的数组,用来布置雷和排查雷。
如何排查雷?如下图
比如说玩家输入坐标(3,3),就要排查(3,3)周围的8个坐标。如果有雷的话,统计一下周围的个数加起来存放在(3,3)的坐标位置
但是,如果玩家要排查左上角的雷,如下图
如果玩家要排查左上角的雷,那么在排查周围一圈的雷的时候,容易发生数组越界,那么应该如何解决?
如果发生数组越界的话,我们可以把数组扩大一圈,也就是11*11的数组,这样就不容易发生数组越界,但数组的下标会有所改变,后面会说明。如下图
解决了数组越界的问题,但是还有一个问题,也就是布置雷我们用1和0表示,排查雷,排查完一圈如果周围雷有1个,那么还是放入这个二维数组,那么到底这个1,是雷还是我排查出的雷呢?就会产生歧义
所以我们可以用两个同样的数组来解决这个问题,我们用mine数组来存放雷,用show数组来表示排查雷的个数。
这个时候我们就有了两个数组,而show数组是给玩家看的以及游玩的,所以我们需要给他营造点神秘感,把show数组全部初始化为'*'
,而'*'
就可以表示没有排查雷的时候,排查完雷之后统计个数放入show数组中显示给玩家看,但是有一个问题:'*'
是字符,而不是整数,如果我们排查出了个数存放到数组里面,那么会出现又数字又字符,处理起来非常麻烦,为了更方便我们处理,我们把个数变成字符个数,同理,mine数组也用字符存放,字符’1’存放雷,字符‘0’存放非雷。这样子,我们就能开始打印棋盘了。
①初始化棋盘&打印棋盘
game.h
#pragma once
#include <stdio.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
void InitBoard(char arr[ROWS][COLS], int rows, int cols, char set);
void DisplayBoard(char arr[ROW][COL], int row, int col);
设置全局变量,方便后续改动。想玩x*x都行,但是如果设置局部变量,那么就要改来改去,非常麻烦。
game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void InitBoard(char arr[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
for (i = 0; i < ROWS; i++)
{
int j = 0;
for (j = 0; j < COLS; j++)
{
arr[i][j] = set;
}
}
}
void DisplayBoard(char arr[ROW][COL], int row, int col)
{
int i = 0;
printf("------扫雷游戏-----\n");
for (i = 0; i <= row; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
int j = 0;
for (j = 1; j <= col; j++)
{
printf("%c ", arr[i][j]);
}
printf("\n");
}
printf("------扫雷游戏-----\n");
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
printf("*******************************\n");
printf("********** 1. play **********\n");
printf("********** 0. exit **********\n");
printf("*******************************\n");
}
void game()
{
char mine[ROWS][COLS];
char show[ROWS][COLS];
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
DisplayBoard(mine, ROW, COL);
DisplayBoard(show, ROW, COL);
}
int main()
{
int n = 0;
do
{
menu();
scanf("%d", &n);
switch (n)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (n);
}
运行结果如下:
此代码能正确打印两个棋盘,证明没有问题,但是前面布置雷的棋盘我们是不给玩家看的,所以后续会屏蔽掉打印mine数组的代码。
②布置雷
既然要布置雷,就要先知道我们要布置多少个雷,我们能看到上面的图左上角表示的是10个雷,那么我们将布置10个雷,
然后就要保证我们的雷是随机的,是我们不知道的,如果知道雷在哪那就不叫扫雷叫扫盲了。
雷要随机,那么我们就要用rand()函数,要用rand()函数就要用srand()函数
我们把随机出来的数模上一个row,那么就会取模后就能得到08的数字,再+1,就能得到09的随机数了
代码如下:
game.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define EASY_COUNT 10
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
void InitBoard(char arr[ROWS][COLS], int rows, int cols, char set);
void DisplayBoard(char arr[ROW][COL], int row, int col);
void SetMine(char arr[ROW][COL], int row, int col);//我在这里
调用了rand()函数和srand()函数,就要相应的头文件,然后雷的个数使用全局变量,方便以后更改
game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void InitBoard(char arr[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
for (i = 0; i < ROWS; i++)
{
int j = 0;
for (j = 0; j < COLS; j++)
{
arr[i][j] = set;
}
}
}
void DisplayBoard(char arr[ROW][COL], int row, int col)
{
int i = 0;
printf("------扫雷游戏-----\n");
for (i = 0; i <= row; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
int j = 0;
for (j = 1; j <= col; j++)
{
printf("%c ", arr[i][j]);
}
printf("\n");
}
printf("------扫雷游戏-----\n");
}
void SetMine(char arr[ROW][COL], int row, int col)
{
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (arr[x][y] == '0')
{
arr[x][y] = '1';
count--;
}
}
}
因为雷是随机生成的,所以要用一个if来判断雷是否是重复的,如果是重复的就要重新生成随机数。只有不重复才能设置雷。所以即使只设置了10个雷,但是可能因为雷是重复的而整个while循环不止执行了10次。
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
printf("*******************************\n");
printf("********** 1. play **********\n");
printf("********** 0. exit **********\n");
printf("*******************************\n");
}
void game()
{
char mine[ROWS][COLS];
char show[ROWS][COLS];
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//DisplayBoard(mine, ROW, COL);
DisplayBoard(show, ROW, COL);
SetMine(mine, ROW, COL);
DisplayBoard(mine, ROW, COL);
}
int main()
{
int n = 0;
srand((unsigned int)time(NULL));
do
{
menu();
scanf("%d", &n);
switch (n)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (n);
}
我们在这里测试的时候,设置了雷之后,可以打印一下看看是否布置了10个雷,运行结果如下:
很明显,布置了10个雷,那么我们布置雷就成功了。
当然,这10个雷的布置位置是不能被玩家知道的,所以后面也会注释掉。
③排查雷
这个时候就是玩家排查雷的时候了,玩家通过输入坐标来去排查相应坐标的有雷。但是以防玩家手抖输入了点奇怪的数字,所以我们在玩家输入数字后要判断坐标是否合法。如果是合法化的才进行排查。
因为雷设置在mine数组,所以玩家要在mine数组里面排查,而mine数组里面存放着’1’和’0’,如果玩家排查到’1’,也就是雷,就被炸死了,游戏结束。如果玩家排查到’0’,那么我们要返回周围一圈有多少个雷,显示在棋盘上,也就是show数组。那么玩家要排查多少次,才是游戏成功呢?答案很显然,因为我们定义的是9*9的数组,一共有10个雷,那么我们只要用9 * 9 = 81再减去10也就是71次,玩家需要排查71次,游戏就胜利了.
我们来看看排查雷的函数:
int ReturnMine(char mine[ROWS][COLS], int x, int y) //(1)
{
return mine[x - 1][y - 1]
+ mine[x - 1][y - 1]
+ mine[x - 1][y + 1]
+ mine[x][y - 1]
+ mine[x][y + 1]
+ mine[x + 1][y - 1]
+ mine[x + 1][y]
+ mine[x + 1][y + 1]
- 8 * '0';
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int win = 0;
int x = 0;
int y = 0;
int count = 0;
while (win < row * col - EASY_COUNT)
{
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了哈哈哈哈哈哈哈哈哈哈哈哈哈\n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
count = ReturnMine(mine, x, y);
show[x][y] = count + '0';
DisplayBoard(show, ROW, COL);
win++;
}
}
else
{
printf("坐标非法,请重新输入\n");
}
if (win == row * col - EASY_COUNT) //(2)
{
printf("恭喜你,排雷成功\n");
DisplayBoard(mine, ROW, COL);
}
}
}
-
(1)这里需要注意的是排查雷时要返回的雷的个数,是(x, y)坐标周围的8个坐标,如下图
而为什么要这样计算呢?是因为字符本质上是ASCII码值,而’0’ = 48,‘0 + x’ = 48 + x,所以我们只要把所有坐标的字符加起来,得到 一个数,再减去8 * ‘0’(因为x周围的8个坐标)就能得到周围有多少个雷
- (2)这里的判断是因为如果你一开始被炸死了,程序跳出循环,如果你没被炸死,结束了循环,跳出和结束了循环,都不知道你是否是胜利了,所以我们只要用win去判断是否排查了71次,如果排查了71次那么win肯定是等于71的,如果没排查成功被炸死,win肯定是小于71的,那么就不符合if的条件。
三、所有的代码实现
game.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define EASY_COUNT 10
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
void InitBoard(char arr[ROWS][COLS], int rows, int cols, char set);
void DisplayBoard(char arr[ROWS][COLS], int rows, int cols);
void SetMine(char mine[ROWS][COLS], int row, int col);
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS],int row, int col);
game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void InitBoard(char arr[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
for (i = 0; i < ROWS; i++)
{
int j = 0;
for (j = 0; j < COLS; j++)
{
arr[i][j] = set;
}
}
}
void DisplayBoard(char arr[ROWS][COLS], int row, int col)
{
int i = 0;
printf("------扫雷游戏-----\n");
for (i = 0; i <= row; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
int j = 0;
for (j = 1; j <= col; j++)
{
printf("%c ", arr[i][j]);
}
printf("\n");
}
printf("------扫雷游戏-----\n");
}
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
int ReturnMine(char mine[ROWS][COLS], int x, int y)
{
return mine[x - 1][y - 1]
+ mine[x - 1][y - 1]
+ mine[x - 1][y + 1]
+ mine[x][y - 1]
+ mine[x][y + 1]
+ mine[x + 1][y - 1]
+ mine[x + 1][y]
+ mine[x + 1][y + 1]
- 8 * '0';
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int win = 0;
int x = 0;
int y = 0;
int count = 0;
while (win < row * col - EASY_COUNT)
{
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了哈哈哈哈哈哈哈哈哈哈哈哈哈\n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
count = ReturnMine(mine, x, y);
show[x][y] = count + '0';
DisplayBoard(show, ROW, COL);
win++;
}
}
else
{
printf("坐标非法,请重新输入\n");
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜你,排雷成功\n");
DisplayBoard(mine, ROW, COL);
}
}
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
printf("*******************************\n");
printf("********** 1. play **********\n");
printf("********** 0. exit **********\n");
printf("*******************************\n");
}
void game()
{
char mine[ROWS][COLS];
char show[ROWS][COLS];
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//DisplayBoard(mine, ROW, COL);
DisplayBoard(show, ROW, COL);
SetMine(mine, ROW, COL);
//DisplayBoard(mine, ROW, COL);
FindMine(mine, show, ROW, COL);
}
int main()
{
int n = 0;
srand((unsigned int)time(NULL));
do
{
menu();
scanf("%d", &n);
switch (n)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (n);
return 0;
}