- 本次想与大家分享的是扫雷用C语言实现,提到扫雷这个游戏想必大家再熟悉不过了,就是一个棋盘上面有许多小方块,当你点击一个若这个方块是雷,你就被炸死了;若这个方块不是雷,这个方块会显示周围雷的个数,然后你要通过一定的分析进行下一步排雷,当然,扫雷成功是有一定的运气成分的,通过分析还是不能完全分析出雷的位置,还得看你是不是那位幸运儿。
- 再加上上一篇和大家分享的三子棋,通过对上一篇内容的学习后再来学习这一篇一定非常容易上手。
- 看到下面这个界面是否有一种熟悉的感觉呢?当然我们今天要分享的并不是带有封装界面的扫雷游戏,只是一个简单的游戏菜单。想做出下图这类界面得学习前端的知识喔。
首先,我们需要建立三个文件,分别是test.c、game.c和game.h
- 当遇到有项目是需要多个过程来完成的,我们都不妨用这种多文件的形式来实现代码的分装。
- 这样做的好处在上一篇文章有提到喔。
- 并且这样做能让我们在后期修改常量数据时,能够快速找到便于我们修改。只需要在另外两个后缀为.c的文件引入(#include"game.h"),这时候这三个文件就能看做是一个整体来使用了。
游戏的界面我们可以做一些简单的封装,就是在进入游戏的界面有一个提示,来提示玩家进行下一步操作。
这里首先需要我们打印一个游戏的建议菜单栏目,以便提示玩家选择,若玩家选择<1>则进入游戏;若玩家选择<0>则退出游戏;若玩家选择除<0><1>以外的数字我们将认定为选择错误。这其中我们要用到do–while循环来实现让玩家进行选择,再使用switch–case来判断玩家输入的值是否正确。并且我们需要调用一个菜单menu()来打印游戏界面。这一部分的代码示例如下:
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
default:
printf("选择错误\n");
break;
}
} while (1);
return 0;
}
棋盘初始化的整体思路:
- 从上方的图片可以知道:游戏的版型是一个9×9的棋盘,那我们在实现代码的时候该如何去设计呢?其实逻辑是很清晰的,只要我们打印出一个二维数组就能够实现。
- 但是在我们真正去玩游戏的时候会发现,如果没有排到雷要打印出周围雷的个数,但是这时候就存在一个问题,是什么呢?
- 就是当我们排到最外层时,此时要统计周围雷的个数可能就会越界访问了,因为棋盘只有9×9,而周围一圈是没有被开放的空间。所以这时候我么要采取一种方式让我们技能统计到周围雷的个数,又不会越界访问。怎么设计呢?
- 这时我们可以采取的方法是将棋盘再向上下左右扩大一圈,这时候就需要打印一个11×11的数组,那这时候又会存在一个问题,我们打印一个棋盘又不能同时做到这些。所以我们可以打印两个数组,一个数组用来打印游戏版型中所需要的9×9的棋盘,另一个用来打印11×11的布雷和排雷的棋盘。这时候我们怎么能让我们所需要的两个棋盘灵活的使用呢?
- 我们可以访问相同的坐标位置,来保证访问到的是两个棋盘中相同的位置。
- 为了我们后期的排雷方便,不妨就将布雷德棋盘设置成‘ 0 ’,将需要打印的棋盘设置成‘ * ’(带有神秘感)
- 我们还可以多考虑一点就是为了统计雷的个数是的方便,我们将雷设置成‘ 1 ’。
这一部分的代码示例:
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
//初始化棋盘
InitBoard(mine, ROWS, COLS,'0');
InitBoard(show, ROWS, COLS,'*');
//函数声明部分
//初始化棋盘
void InitBoard(char Board[ROWS][COLS], int rows, int cols,char set);
//函数主体部分
void InitBoard(char board[ROWS][COLS], int rows, int cols,char set)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;//打印两个棋盘需要怎么打印?分别打印,把要打印的内容当做参数传过来就能正常打印了
}
}
}
- 我们知道在游戏进行时打印出来的界面是一个9×9的棋盘,所以我们只需要打印一个二维数组,每个位置用‘ * ’表示。
- 并且为了能够让玩家快速地找到每个坐标位置,我们可以在每行每列的开头打印相应的行号列号,这样就能很快读出行列的坐标。
这一部分的代码示例:
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
//打印棋盘
DisplayBoard(show, ROW, COL);
//函数声明部分
//打印棋盘
void DisplayBoard(char Board[ROWS][COLS], int rows, int cols);
//函数主体部分
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("<<<<<<<<<<<扫雷>>>>>>>>>>>>>\n");
for (j = 0; j <=col; j++)
{
printf("%d|", j);
}
printf("\n");
for (i = 1; i <=row; i++)
{
printf("%d|", i);
for (j = 1; j <=row; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
思路:
- 本章设计的是简易的扫雷,只要布置10个雷,但是每个雷的位置不一样,所以这里我们需要电脑随机生成10个不同的坐标,此时就需要借助 rand()函数来生成10个不同的坐标,需要srand()调用rand函数,利用时间戳:time(NULL)来生成随机坐标。
- 因为我们是让电脑随机生成的坐标,所以我们需要给它确定一个范围,防止超出我们所定义的棋盘的范围。
- 因为我们是利用坐标来准确传递生成雷的位置,所以我们需要定义两个变量来接受随机生成的雷的坐标。
- 因为是随机生成的坐标,而且最终的坐标都不能相同,所以很有可能生成坐标不止十个。
- 每布置一个雷就是把原来是‘0’用‘1’来替换,以便最后来统计周围雷的个数和区分雷与非雷。
//布置雷
void SetMine(char mine[ROWS][COLS], int row, int col);
//布置雷
SetMine(mine, ROW, COL);
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--;
}
}
}
整体思路:
- 首先要判断排雷的次数是否等于棋盘的总个数减去布置的雷的个数:
若等于则排雷成功。
若不等于,且输入的坐标的行列满足大于1且小于棋盘的行数和列数。
若输入的坐标大于行或列,或小于1,则输出该坐标非法,请重新输入。
若该坐标没有被排查过,并且该坐标存放的是‘1’,则玩家就被炸死了,游戏结束。
若该坐标被排查过,则打印该坐标已被排查。
若该坐标存放的是‘0’,则要统计出周围雷的个数。
统计周围雷的个数时,我们可以重新定义一个函数来计算,将计算结果当成参数传过来。在计算周围雷的个数时,利用了一个巧妙的算法,就是一个字符-字符可以通过他们的ASCLL值来计算大小。
//排查雷
void FineMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//排查雷
FineMine(mine,show, ROW, COL);
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
int count = (mine[x - 1][y - 1] +
mine[x - 1][y] +
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 FineMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int time = 0;
while (time < row * col - EASY_COUNT)
{
printf("请输入要排查的坐标:>\n");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] == '*')
{
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);
}
}
else
{
printf("该坐标已被排查\n");
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
if (time == row * col - EASY_COUNT)
{
printf("恭喜你,排雷成功\n");
}
}
game.h(函数的声明和宏的定义以及头文件的包含)
#pragma once
#include
#include
#include
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EASY_COUNT 10
//初始化棋盘
void InitBoard(char Board[ROWS][COLS], int rows, int cols,char set);
//打印棋盘
void DisplayBoard(char Board[ROWS][COLS], int rows, int cols);
//布置雷
void SetMine(char mine[ROWS][COLS], int row, int col);
//排查雷
void FineMine(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 board[ROWS][COLS], int rows, int cols,char set)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;//打印两个棋盘需要怎么打印?分别打印,把要打印的内容当做参数传过来就能正常打印了
}
}
}
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("<<<<<<<<<<<扫雷>>>>>>>>>>>>>\n");
for (j = 0; j <=col; j++)
{
printf("%d|", j);
}
printf("\n");
for (i = 1; i <=row; i++)
{
printf("%d|", i);
for (j = 1; j <=row; j++)
{
printf("%c ", board[i][j]);
}
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 GetMineCount(char mine[ROWS][COLS], int x, int y)
{
int count = (mine[x - 1][y - 1] +
mine[x - 1][y] +
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 FineMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int time = 0;
while (time < row * col - EASY_COUNT)
{
printf("请输入要排查的坐标:>\n");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] == '*')
{
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);
}
}
else
{
printf("该坐标已被排查\n");
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
if (time == row * col - EASY_COUNT)
{
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] = { 0 };
char show[ROWS][COLS] = { 0 };
//初始化棋盘
InitBoard(mine, ROWS, COLS,'0');
InitBoard(show, ROWS, COLS,'*');
//打印棋盘
DisplayBoard(show, ROW, COL);
//布置雷
SetMine(mine, ROW, COL);
//DisplayBoard(mine, ROW, COL);//观察雷的棋盘
//排查雷
FineMine(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();
break;
case 0:
printf("退出游戏\n");
default:
printf("选择错误\n");
break;
}
} while (1);
return 0;
}
- 好了,到这里整个简易版的扫雷游戏就实现完成了,当然这只是简易版本的,实际的扫雷游戏排一个雷是会像炸金花似的把周围不是雷的部分全部展开,可以去尝试做(需要一定的算法基础)
- 扫雷的实现还是能够很好的检验我们对C语言往前所学知识,所以你们实现了吗?
- 建议大家能够自己独立的去完成喔,最好以博客的形式分享出来哦。
- 最后也希望大家能够在评论区指出我在这篇文章所犯下的错误哦。