前言
通过对代码的学习,一步步了解经典扫雷游戏是如何编程语言实现的,同时增加对C语言的认识和熟练掌握
这款游戏的玩法是在一个9x9(初级),16x16(中级),16x30(高级),或自定义大小的方块矩阵中随机布置一定量的地雷(初级为10个,中级为40个,高级为99个)。由玩家逐个翻开方块,方块内的数字代表着该方块八个方位的方块(即九宫格)藏有几个雷,通过数字推断藏雷的方块,以找出所有地雷为最终游戏目标。如果玩家翻开的方块有地雷,则游戏结束。
首先,我们想象一下,我们进入游戏会有菜单栏,控制游戏开始和结束。然后我们开始游戏,游戏棋盘在我们面前,我们输入坐标,找出所有的雷。若踩到雷,输掉游戏,找到所有的雷,赢得游戏。接着程序会再次显示菜单栏,提醒我们继续游戏还是,退出游戏。
我们l来创建一个test.c文件,这是我们游戏空的框架,game()函数将实现我们游戏逻辑。
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
#include
#include
//菜单栏
void menu()
{
printf("|-----扫雷游戏------|\n");
printf("|------1.play------|\n");
printf("|------0.exit------|\n");
}
int main()
{
//生成随机数
srand((unsigned int)time(NULL));
int input = 0;
//进入游戏
do
{
menu();
printf("请输入:>");
scanf("%d", &input);
//清空缓存区
while (getchar() != '\n');
switch (input)
{
case 1:
//游戏运行逻辑
game();
break;
case 0:
printf("结束游戏\n");
break;
default:
printf("请重新输入0或1\n");
break;
}
} while (input);
}
在打造棋盘前我们,需要确认它的大小。为了后期方便更改大小,我们将行和列(ROW和COL)预处理,在接下来的代码中只要出现ROW和COL,那它们就是10和10。
在判断周围有多少个雷时,防止数组越界,所以预处理ROWS和COLS,这是我们数组大小,而实际上ROW和COL是我们的棋盘大小
#define ROW 10
#define COL 10
#define ROWS ROW + 2
#define COLS COL + 2
红圈我们需要判断应该放数字几,但当我们在排查时需要排查到棋盘外面一圈了,因此ROWS=ROW+2 COLS=COL+2
我们在game函数内部定义两个数组,试想一下,棋盘有行还有列,所以应该是二维数组。其次,为什么我们要创建两个数组,当我们输入一个坐标时,屏幕上会在该位置显示数字,告诉我们周围有几个雷。雷的个数我们需要在mine数组中确认,在mine中‘1’表示雷,‘0’表示无雷,为了和玩家屏幕上的数字区分开,因此创建两个数组,mine存放雷,由我们控制,而show给玩家使用。
void game()
{
char mine[ROWS][COLS] = { 0 };//雷的存放,‘0’表示无雷,‘1’表示雷
char show[ROWS][COLS] = { 0 };//玩家操作的棋盘
InitBoard(mine, ROWS, COLS, '0');//初始化棋盘
InitBoard(show, ROWS, COLS, '*');//初始化玩家棋盘
SetMine(mine, ROW, COL);//开始布置雷
system("cls");//清除屏幕
DisplayBoard(show, ROW, COL);//打印起始的玩家棋盘
FineMine(mine, show, ROW, COL);//排查雷
}
接着我们要初始化棋盘,创建函数(为了方便查阅代码,我们重新创建一个game.c文件来存放函数)。
这里我们将实参字符‘0’和‘*’传给了形参set,为的是不用分别给mine和show分别创建函数
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i, j;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
我们可以设置雷的个数,对雷的数量预处理
#define COUNT 20
放置雷在mine数组中
在main函数里我们使用了srand((unsigned int)time(NULL))函数,它是随机数发生器的初始化函数,需要提供一个种子,这里我们使用time(NULL),并强制转化为无符号整型。在自定义SetMine函数,随机生成x和y(即横坐标和纵坐标)。
srand和time对应的头文件分别是stdlib.h和time.h。
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int x, y;
int count = COUNT;
while (count)
{
x = rand() % row + 1;//生成横坐标1~10
y = rand() % col + 1;//生成纵坐标,公式“a+rand()%col+b”
if (mine[x][y] == '0')
{
//将雷放入mine数组
mine[x][y] = '1';
count--;
}
}
}
在每次我们选择坐标或标记后需要打印一次棋盘,踩到地雷后,将显示所有雷的位置
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i, j;
//第一行“----...”
for (j = 0; j <= col; j++)
{
printf("----");
}
printf("\n");
//第二行“| 0 | 1 |...”
for (j = 0; j <= col; j++)
{
if (j==0)
{
printf("|");
}
printf(" %-2d|", j);
}
printf("\n");
//第三行“----...”
for (i = 0; i <= col;i++) {
printf("----");
}
printf("\n");
//四行往下
for (i = 1; i <= row; i++)
{
printf("| %-2d|", i);//“| 1 |”数字列
for (j = 1; j <= col; j++)
{
printf(" %c |", board[i][j]);//“*”
}
printf("\n");
for (j = 0; j <= col; j++)
{
printf("----");//行间分隔
}
printf("\n");
}
}
如果只是输入一个坐标,展开一个方格,实现这样的功能很容易。但我们要实现,像这样“炸开”的功能,要使用函数递归,难度就提升了不少。
为了实现这个功能,我们要明白三个点:
1.该坐标不是雷
2.该坐标周围不是雷
3.向周围排查时,排查过的坐标不用再排查
像下面这张图,首先1位置不是雷,且周围没有雷,接着以2为中心,接着排查,这个时候1已经排查过了,所以不要再排查。如果继续排查,那么就会出现死递归,一直在1和2之间来回递归。
int get_mine_count(char board[ROWS][COLS], int x, int y)
{
//计算周围8个格子有多少雷,int类型
return board[x - 1][y - 1] +
board[x][y - 1] +
board[x + 1][y - 1] +
board[x + 1][y] +
board[x + 1][y + 1] +
board[x][y + 1] +
board[x - 1][y + 1] +
board[x - 1][y] - 8 * '0';
}
//炸弹式展开
void explode_spread(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y)
{
//限制非法坐标的展开
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
//计算该位置附近四周地雷的个数
int count = get_mine_count(mine, x, y);
//若四周没有一个地雷,则需要向该位置的四周展开,直到展开到某个位置附近存在地雷为止
if (count == 0)
{
//把附近没有地雷的位置变成字符 “空格”
show[x][y] = ' ';
int i = 0;
//向四周共8个位置递归调用
for (i = x - 1; i <= x + 1; i++)
{
int j = 0;
for (j = y - 1; j <= y + 1; j++)
{
//限制对点位置的重复展开调用,使得每一个位置只能向四周展开一次
if (show[i][j] == '*')
{
explode_spread(mine, show, row, col, i, j);
}
}
}
}
//若四周存在地雷则应该在这个位置上标注上地雷的个数
else
{
show[x][y] = count + '0';
}
}
}
void MarkerMine(char show[ROWS][COLS], int row, int col)
{
int input = 0;
int x, y;
do {
printf("进行标记输入:1 ");
printf("删除标记输入:2 ");
printf("退出标记输入:0\n");
printf("请选择:>");
scanf("%d",&input);
//清理缓存区
while (getchar() != '\n');
switch (input) {
case 1:
printf("请输入坐标进行标记:>");
scanf("%d %d", &x, &y);
//清理缓存区
while (getchar() != '\n');
show[x][y] = '!';
system("cls");
DisplayBoard(show, ROW, COL);
break;
case 0:
printf("完成标记\n");
break;
case 2:
printf("请选择坐标删除标记:>");
scanf("%d %d", &x, &y);
//清理缓存区
while (getchar() != '\n');
show[x][y] = '*';
//清除屏幕
system("cls");
DisplayBoard(show, ROW, COL);
break;
}
} while (input);
}
判断输赢主要根据win的值,(row * col - COUNT)这是无雷的数量,只要win大于它即可判断赢
//踩雷后将mine数组中的雷‘1’换做‘#’,告诉玩家雷的位置
void DisplayAllMines(char mine[ROWS][COLS], int row, int col)
{
int i,j;
for (i = 1; i <= row ; i++)
{
for (j = 1; j <= col ; j++)
{
if (mine[i][j]=='1')
{
mine[i][j] = '#';
}
}
}
}
void FineMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int i, j;
int x, y;
int win =0;
while (win < row * col - COUNT)
{
win = 0;
printf("请输入坐标:>");
scanf("%d %d", &x, &y);
//清理缓存区
while (getchar() != '\n');
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] != '*')
{
printf("该处已被排查,请重新选择\n");
}
else
{
if (mine[x][y] == '1')
{
system("cls");
//将所有存放在mine数组中的‘1’替代为‘#’
DisplayAllMines(mine, ROW, COL);
DisplayBoard(mine, ROW, COL);
printf("对不起,您踩到雷\n\n");
break;
}
//该位置未排查
else if(show[x][y] == '*')
{
NoMineSpace(mine,show, x, y);//展开无雷区域
system("cls");
DisplayBoard(show, ROW, COL);//没踩到雷打印
//计算‘*’剩余个数,用于判断输赢
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)
{
if (show[i][j] != '*'&&show[i][j]!='!')
{
win++;
}
}
}
if (win >= row * col - COUNT)
{
printf("恭喜你赢了!\n\n");
break;
}
MarkerMine(show, x, y);//标记
system("cls");
DisplayBoard(show, ROW, COL);//标记后打印
}
}
}
else
{
printf("坐标非法,请重新输入");
}
}
}
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void menu()
{
printf("|-----扫雷游戏-----|\n");
printf("|------1.play------|\n");
printf("|------0.exit------|\n");
}
void game()
{
char mine[ROWS][COLS] = { 0 };//雷的存放,‘0’表示无雷,‘1’表示雷
char show[ROWS][COLS] = { 0 };//玩家操作的棋盘
InitBoard(mine, ROWS, COLS, '0');//初始化棋盘
InitBoard(show, ROWS, COLS, '*');//初始化玩家棋盘
SetMine(mine, ROW, COL);//开始布置雷
system("cls");
DisplayBoard(show, ROW, COL);//打印起始的玩家棋盘
FineMine(mine, show, ROW, COL);//排查雷
}
int main()
{
//生成随机数
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
printf("请输入:>");
scanf("%d", &input);
//清空缓存区
while (getchar() != '\n');
switch (input)
{
case 1:
game();
break;
case 0:
printf("结束游戏\n");
break;
default:
printf("请重新输入0或1\n");
break;
}
} while (input);
}
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i, j;
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, j;
//第一行“----”
for (j = 0; j <= col; j++)
{
printf("----");
}
printf("\n");
//第二行“| 0 | 1 |”
for (j = 0; j <= col; j++)
{
if (j==0)
{
printf("|");
}
printf(" %-2d|", j);
}
printf("\n");
//第三行“----”
for (i = 0; i <= col;i++) {
printf("----");
}
printf("\n");
//四行往下
for (i = 1; i <= row; i++)
{
printf("| %-2d|", i);//“| 1 |”数字序列
for (j = 1; j <= col; j++)
{
printf(" %c |", board[i][j]);//“*”
}
printf("\n");
for (j = 0; j <= col; j++)
{
printf("----");//行间分隔
}
printf("\n");
}
}
void DisplayAllMines(char mine[ROWS][COLS], int row, int col)
{
int i,j;
for (i = 1; i <= row ; i++)
{
for (j = 1; j <= col ; j++)
{
if (mine[i][j]=='1')
{
mine[i][j] = '#';
}
}
}
}
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int x, y;
int count = COUNT;
while (count)
{
x = rand() % row + 1;//生成横坐标1~10
y = rand() % col + 1;//生成纵坐标,公式“a+rand()%col+b”
if (mine[x][y] == '0')
{
//将雷放入mine数组
mine[x][y] = '1';
count--;
}
}
}
int get_mine_count(char board[ROWS][COLS], int x, int y)
{
//计算周围8个格子有多少雷,int类型
return board[x - 1][y - 1] +
board[x][y - 1] +
board[x + 1][y - 1] +
board[x + 1][y] +
board[x + 1][y + 1] +
board[x][y + 1] +
board[x - 1][y + 1] +
board[x - 1][y] - 8 * '0';
}
void MarkerMine(char show[ROWS][COLS], int row, int col)
{
int input = 0;
int x, y;
do {
printf("进行标记输入:1 ");
printf("删除标记输入:2 ");
printf("退出标记输入:0\n");
printf("请选择:>");
scanf("%d",&input);
//清理缓存区
while (getchar() != '\n');
switch (input) {
case 1:
printf("请输入坐标进行标记:>");
scanf("%d %d", &x, &y);
//清理缓存区
while (getchar() != '\n');
show[x][y] = '!';
system("cls");
DisplayBoard(show, ROW, COL);
break;
case 0:
printf("完成标记\n");
break;
case 2:
printf("请选择坐标删除标记:>");
scanf("%d %d", &x, &y);
//清理缓存区
while (getchar() != '\n');
show[x][y] = '*';
//清除屏幕
system("cls");
DisplayBoard(show, ROW, COL);
break;
}
} while (input);
}
void NoMineSpace(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
//合法区域内展开
if (x >= 1 && x <= ROWS-1 && y >= 1 && y <= COLS-1)
{
//计算该位置周围的雷数
int num = get_mine_count(mine, x, y);
if (num == 0)
{
//若四周无雷,该位置存放空格
show[x][y] = ' ';
int i = 0;
//向周围8个位置递归
for (i = x - 1; i <= x + 1;i++)
{
int j = 0;
for (j = y - 1; j <= y + 1; j++)
{
//对未展开的位置进行递归,防止死递归
if (show[i][j]=='*')
{
//此处新的坐标(i,j)代替坐标(x,y)
NoMineSpace(mine,show,i,j);
}
}
}
}
//若对一个位置四周有雷,则在该位置标注雷的个数
else
{
show[x][y] = num + '0';
}
}
}
void FineMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int i, j;
int x, y;
int win =0;
while (win < row * col - COUNT)
{
win = 0;
printf("请输入坐标:>");
scanf("%d %d", &x, &y);
//清理缓存区
while (getchar() != '\n');
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] != '*')
{
printf("该处已被排查,请重新选择\n");
}
else
{
if (mine[x][y] == '1')
{
system("cls");
//将所有存放在mine数组中的‘1’替代为‘#’
DisplayAllMines(mine, ROW, COL);
DisplayBoard(mine, ROW, COL);
printf("对不起,您踩到雷\n\n");
break;
}
//该位置未排查
else if(show[x][y] == '*')
{
NoMineSpace(mine,show, x, y);//展开无雷区域
system("cls");
DisplayBoard(show, ROW, COL);//没踩到雷打印
//计算‘*’剩余个数,用于判断输赢
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)
{
if (show[i][j] != '*'&&show[i][j]!='!')
{
win++;
}
}
}
if (win >= row * col - COUNT)
{
printf("恭喜你赢了!\n\n");
break;
}
MarkerMine(show, x, y);//标记
system("cls");
DisplayBoard(show, ROW, COL);//标记后打印
}
}
}
else
{
printf("坐标非法,请重新输入");
}
}
}
#pragma once
#include
#include
#include
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define COUNT 10
//初始化棋盘
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 mine[ROWS][COLS], int row, int col);
//扫雷
void FineMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//周围雷的个数
int get_mine_count(char board[ROWS][COLS], int x, int y);
//标记
void MarkerMine(char show[ROWS][COLS], int row, int col);
//展开一片无雷区域
void NoMineSpace(char show[ROWS][COLS], char mine[ROWS][COLS], int x, int y);
//用‘#’代替雷‘1’展示给玩家
void DisplayAllMines(char mine[ROWS][COLS], int row, int col);
这是扫雷的全部内容,这不仅仅是自我的总结,同时也希望能给你们带来微不足道的帮助!