C语言之扫雷游戏附完整代码
C语言之扫雷游戏(附完整代码)
扫雷游戏,肯定是很多老铁们在中小学电脑课上必不可少的休闲娱乐的小游戏,那么学过C语言的你们知道怎么实现它么,今天我们就一起来用C语言来实现扫雷游戏。
1 扫雷游戏简介
扫雷游戏,就是在一定大小的方格中埋藏着一定数量的雷(如下图埋藏着10颗雷),而玩家需要做的就是排除这10颗雷,即知道这10颗雷所在的位置。
那么玩家的游戏规则是什么呢?且看下图,玩家随机选择一个位置,如果选取位置不是雷,则显示一个数字,而这个数字的作用是什么呢,以下图中的3为例,这个就表示以自身3为中心,在其周围的8个位置中(即红色方框),一共有3颗雷,这样,玩家就可以根据数字的提示,从而筛选出10颗雷的位置,最终获得游戏胜利。反之,如果不幸选取到雷(即踩雷),则游戏失败,如下图图二。
2 扫雷游戏的实现方案
- 游戏中的方格其实就是一个二维数组,因此我们创建两个两个二维数组,一个存放排查出的雷的信息,另外一个存放布置好雷的信息。
2. 为了防止在统计坐标周围雷的个数的时候越界,我们设计数组的大小为11*11。
数组均为字符数组.
此次游戏实现同样采用 多文件的形式 设计。
test.c —— 测试游戏的代码
game.c —— 游戏的实现
game.h —— 游戏函数的声明
3 test.c文件的实现
3.1 main主函数的实现
我们在实现游戏功能的时候,不是从上往下一直顺序写,一般都是在main函数中写出大体的框架,当遇到需要写函数去实现一些功能时,我们再去将这个函数实现。
我们在这里设定游戏可以玩多局,即当游戏结束是不用退出可以继续选择是否进行下一局游戏,所以这里使用循环。同时,在第一局开始前应该是先弹出界面询问玩家是否玩游戏,而不是先判断是否满足循环条件,因此这里采用do….while()循环最为合适。
这里我们设置一个菜单来提醒玩家是否进行游戏。同时,玩家选择1时表示开始游戏,选择0时表示游戏结束,退出程序,选择其他时要提示玩家输入非法,并重新进行选择,所以这里需要一个switch选择语句。
int main() {
int input = 0;
srand((unsigned int)time(NULL));
do {
menu();
printf("请选择:> ");
scanf("%d", &input);
switch (input) {
case 1:
game();
Sleep(3000);
system("cls");
break;
case 0:
printf("游戏结束,退出游戏\n");
break;
default:
printf("输入非法,请重新选择\n");
break;
}
} while (input);
return 0;
}
3.2 简易菜单menu
void menu() {
printf("*******************************\n");
printf("********* 1.play *********\n");
printf("********* 0.exit *********\n");
printf("*******************************\n");
}
3.3 game游戏函数的实现
当玩家选择1时,游戏开始。而game函数就是用来实现游戏整体框架,并调用其他实现具体功能的函数,具体代码如下。
void game()
{
char mine[ROWS][COLS]; // 存放好布置的雷 -- 埋雷地图
char show[ROWS][COLS]; // 存放排查出的雷的信息 -- 玩家窗口
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
DisplayBoard(show, ROW, COL);
SetMine(mine, ROW, COL);
//DisplayBoard(mine,ROW,COL);
FindMine(mine, show, ROW, COL);
}
如上代码,首先,我们在game函数中创建上文所述的两个二维数组,其中,mine数组是用来存放布置的雷的信息,show数组是用来存放排查出雷的信息。其次,我们将mine数组的内容全部初始化为字符0(后面会讲原因),将show数组的内容初始化为为字符‘*’并打印出来,效果如下:
然后,我们就是在mine数组中布置雷,但不将mine数组的内容打印出来,最后进行排查雷工作。
4 game.c文件的实现
4.1 初始化函数(InitBoard)的实现
我们在InitBoard函数的参数中添加一个字符set,这样在调用InitBoard函数对数组初始化时,就可以自行设定初始化的内容,而不是固定写死,具体代码如下。
void InitBoard(char board[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++) {
board[i][j] = set;
}
}
}
4.2 打印函数(DisplayBoard)的实现
在实现打印函数时,我们不仅仅是只把数组内容给打印出来。为了玩家在玩游戏时输入坐标方便,我们还要将整个方格的横纵坐标给显示出来便于玩家观察。同时,还要提示玩家此局游戏中一共埋藏了多少颗雷。而雷的个数我们用一个全局变量EASY_COUNT来代替,这样也方便后续修改雷的个数。打印代码具体如下:
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
printf("-------扫雷游戏-------\n");
printf("简单版本,一共有 %d 个雷\n", EASY_COUNT);
// 打印列号
printf(" ");
for (i = 1; i <= row; i++) {
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++) {
int j = 0;
// 打印行号
printf("%d ", i);
for (j = 1; j <= col; j++) {
printf("%c ", board[i][j]);
}
printf("\n");
}
}
4.3 布置雷(SetMine)函数的实现
此次游戏设计我们选择布置10个雷,即EASY_COUNT的值设置为10,同时,为了让雷的布置是随机的,我们使用rand函数来随机生成雷的位置的坐标。当然为了保证10颗雷的布置位置不会重复,我们采用while循环,如果成功布置雷就将count-1,直至count的值为0时结束循环。
最后,我们将放置雷的位置上的信息改为字符'1’,而非雷的位置信息依旧为字符'0’(即修改mine数组中对应位置的内容), 修改为1是为了方便后续统计一个坐标周围的8个位置中有多少颗雷。
需要注意的是:由于数组的下标是从0开始,而玩家所看到的方格的坐标是从1开始,因此我们在随机生成的雷的坐标中需要对x,y进行+1操作。代码如下:
void SetMine(char board[ROWS][COLS], int row, int col)
{
// 布置10个雷
// 生成随机的坐标,布置雷
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
// 0 表示非雷,1 表示雷
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
4.4 统计周围雷的个数(GetMineCount函数)
统计一个坐标为[x][y]位置周围雷的个数,就是统计其周围8个位置的内容有多少个字符'1’。其余8个位置的坐标如下图:
因为是字符1,如果想要转化为数字,那我们只需要减去8个字符0的ASCII码值即可。具体代码如下:
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
return (mine[x - 1][y] + mine[x - 1][y - 1] + mine[x][y - 1] + mine[x + 1][y - 1] +
mine[x + 1][y] + mine[x + 1][y + 1] + mine[x][y + 1] + mine[x - 1][y + 1] - 8 * '0');
}
4.5 排查雷函数(FindMine)的实现
如果玩家未将所有雷排查完,则需要继续排查,因此必然需要一个循环,且当排查的个数=总数 — 雷的个数时,循环结束。
其次,如果玩家输入的坐标位置对应的信息为字符'1’时,则说明玩家踩到雷,退出循环,游戏结束。反之,则统计该坐标周围的雷的个数,并告知玩家,即打印show数组。如果输入坐标非法,也要提醒玩家,并让玩家重新输入坐标。最后,当玩家排雷成功后,则告诉玩家游戏获得胜利,并显示出所有雷的位置信息,即打印mine数组。具体代码如下:。
需要注意的是:输入的非法坐标不计入排查的次数中。
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
while (win < row * col - EASY_COUNT)
{
printf("请输入要排查要排查的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '0' && show[x][y] != '*')
{
printf("该位置已经被排查过了\n");
continue;
}
else if (mine[x][y] == '1')
{
printf("你踩到雷了,很遗憾,游戏失败\n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
该位置不是雷,就统计这个坐标周围有几个雷
//int count = GetMineCount(mine, x, y);
//show[x][y] = count + '0';
//DisplayBoard(show, ROW, COL);
//win++;
SearchMine(mine, show, ROW, COL, x, y, &win);
DisplayBoard(show, ROW, COL);
}
}
else
{
printf("坐标非法,请重新输入\n");
continue;
}
}
if (win == row * col - EASY_COUNT) {
printf("恭喜你,排雷成功\n");
DisplayBoard(mine, ROW, COL);
}
}
4.6 SearchMine函数(递归实现大面积扫雷)
使用GetMine函数获取周围8个坐标中雷的数量:
(1)如果是0,即周围8个坐标没有雷,那么就将该坐标设置为空白(即设置为空字符),在棋盘范围内,对该坐标周围的8个坐标再次进行SearchMine(递归);递归时排除已经是空格的坐标,防止陷入死循环。直至遇到一个坐标,其范围为内有雷。
(2)如果不是0,将该坐标设置为雷的数量(注意是字符类型)。
void SearchMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int* win)
{
// 坐标非法直接结束
if (x < 1 || x > row || y < 1 || y > col)
return;
(*win)++; // 统计排除位置的个数
char ret = GetMineCount(mine, x, y) + '0'; // 统计当前位置周围雷的个数
if (ret == '0')
{
show[x][y] = ' ';
int i = 0;
for (i = -1; i <= 1; i++)
{
int j = 0;
for (j = -1; j <= 1; j++)
{
int nx = x + i;
int ny = y + j;
if (show[nx][ny] == '*')
SearchMine(mine, show, ROW, COL, nx, ny, win);
}
}
}
else
{
show[x][y] = ret;
}
}
5 完整源代码
5.1 test.c文件
#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);
// 1. 布置雷
SetMine(mine, ROW, COL);
//DisplayBoard(mine,ROW,COL);
// 2. 排查雷 -- 统计每个坐标周围的雷数
FindMine(mine, show, ROW, COL);
}
int main() {
int input = 0;
srand((unsigned int)time(NULL));
do {
menu();
printf("请选择:> ");
scanf("%d", &input);
switch (input) {
case 1:
game();
Sleep(3000);
system("cls");
break;
case 0:
printf("游戏结束,退出游戏\n");
break;
default:
printf("输入非法,请重新选择\n");
break;
}
} while (input);
return 0;
}
5.2 game.c文件
#include "game.h"
// 初始化棋盘
void InitBoard(char board[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++) {
board[i][j] = set;
}
}
}
// 打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
printf("-------扫雷游戏-------\n");
printf("简单版本,一共有 %d 个雷\n", EASY_COUNT);
// 打印列号
printf(" ");
for (i = 1; i <= row; i++) {
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++) {
int j = 0;
// 打印行号
printf("%d ", i);
for (j = 1; j <= col; j++) {
printf("%c ", board[i][j]);
}
printf("\n");
}
}
// 布置雷
void SetMine(char board[ROWS][COLS], int row, int col)
{
// 布置10个雷
// 生成随机的坐标,布置雷
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
// 0 表示非雷,1 表示雷
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
// 统计周围雷的个数
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
return (mine[x - 1][y] + mine[x - 1][y - 1] + mine[x][y - 1] + mine[x + 1][y - 1] +
mine[x + 1][y] + mine[x + 1][y + 1] + mine[x][y + 1] + mine[x - 1][y + 1] - 8 * '0');
}
// 排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
while (win < row * col - EASY_COUNT)
{
printf("请输入要排查要排查的坐标:>");
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
{
// 该位置不是雷,就统计这个坐标周围有几个雷
int count = GetMineCount(mine, x, y);
show[x][y] = count + '0';
DisplayBoard(show, ROW, COL);
win++;
}
}
else
{
printf("坐标非法,请重新输入\n");
continue;
}
}
if (win == row * col - EASY_COUNT) {
printf("恭喜你,排雷成功\n");
DisplayBoard(mine, ROW, COL);
}
}
5.3 game.h文件
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#include<Windows.h>
#define EASY_COUNT 10
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
// 初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
// 打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);
// 布置雷
void SetMine(char board[ROWS][COLS], int row, int col);
// 排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
6. 游戏结果展示
大家感兴趣的话可以尝试自己动手实现一下呀!!