文章目录
- 前言
- 一、游戏玩法
- 二、创建文件
- test.c文件
- menu()——打印菜单
- game()——调用功能函数,游戏的实现
- main()主函数
- game.c文件
- 初始化棋盘
- 打印棋盘
- 随机布置雷的位置
- 统计周围雷的个数
- 展开周围一片没有雷的区域
- 计算已排查位置的个数
- 排查雷(包括检测输赢):
- game.h文件
- 头文件
- 棋盘的大小以及雷的个数
- 主要功能函数的声明
- 三、完整代码
前言
扫雷游戏与前文的三子棋小游戏有些类似,都是在二维数组的基础上进行的,但扫雷需要考虑的东西更多,也更难一点。今天我们就一起来学习如何实现扫雷小游戏,找回童年的回忆。(最后附有完整代码)
一、游戏玩法
没有玩过扫雷的小伙伴可以打开电脑自带的扫雷小游戏玩两把,很上头有没有,哈哈哈。
游戏规则:
1.首先,扫雷是在一个N*N的棋盘上进行的游戏,这些方格中随机暗藏着一定数量的雷。
2.揭开一个方格,如果没有地雷,则会显示周围8个方格的地雷的数量,如果周围8个方格都没有地雷,则会翻开一片区域。
3.如果揭开的是地雷,那么游戏失败。
4.可以选择标记未揭开的方格为雷,也可以取消标记,方便玩家记忆雷的位置
游戏胜利条件: 不触发地雷,找出所有不是雷的位置。
程序试玩:
初始菜单
上方是玩家棋盘,下方是布置好的雷盘
选择1排查雷,点开坐标(4,3)的方格,显示3,说明该坐标周围有3颗地雷
选择2,标记坐标(4,1)为雷,显示 " ! "
游戏失败!
二、创建文件
像三子棋一样,我们同样采用分模块的编程思想,创建三个文件来分别存放对应功能的代码。
test.c文件:主程序,功能的调用
game.c文件:扫雷游戏的具体功能实现
game.h文件:工程需要的头文件和函数声明以及宏定义
这样做的好处有:提高代码的可读性,方便后续调试,条理清晰,使主程序看起来简洁
test.c文件
menu()——打印菜单
void menu()
{
printf("*******************\n");
printf("***** 0.exit ******\n");
printf("***** 1.play ******\n");
printf("*******************\n");
}
game()——调用功能函数,游戏的实现
1.创建并初始化棋盘
2.随机布置雷的位置
3.打印棋盘信息
4.排查雷(包括判断游戏输赢)
这些是game.h中的宏定义信息,后面都会用到的,这里先声明一下
#define ROW 9
#define COL 9
#define ROWS ROW + 2
#define COLS COL + 2
#define count 10
void game()
{
char mine[ROWS][COLS] = { 0 };//布置好的雷盘
char show[ROWS][COLS] = { 0 };//排查出的雷盘
init_board(mine, ROWS, COLS, '0');//mine棋盘全部初始化为0
init_board(show, ROWS, COLS, '*');//show棋盘全部初始化为*
set_mine(mine, ROW, COL); //随机布置雷的位置
print_board(show, ROW, COL);//打印玩家棋盘
find_mine(mine, show, ROW, COL);//排查雷
}
之所以创建两个棋盘数组,是因为一个棋盘用来存放系统布置的雷的位置,一个棋盘用来存放玩家手中的棋盘信息。相当于一份是有答案的卷子,一份是玩家需要做的白卷。
创建的棋盘多两行两列是为了 方便后面排查周围8个格子时不会产生越界行为,不然要分情况讨论,很麻烦。显然,直接在创建数组时多开辟一圈更省事。
main()主函数
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
printf("请选择->:");
scanf("%d", &input);
switch (input)
{
case 0:
printf("退出游戏\n");
break;
case 1:
system("cls");
game();
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
game.c文件
初始化棋盘
初始化棋盘为ch字符,可以自己定义初始化字符
我们将存放答案的那张棋盘全部初始化为字符0,后面布置有雷则置1
将玩家手中的游戏棋盘全部初始化为 * ,表示未揭开状态
void init_board(char board[ROWS][COLS], int rows, int cols, char ch)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
board[i][j] = ch;
}
}
}
打印棋盘
我们每写完一个功能模块,最好调用打印函数来验证是否正确
void print_board(char board[ROWS][COLS], int row, int col)
{
printf("————扫雷————\n");
printf(" ");
for (int j = 1; j <= col; j++)
printf("%d ", j);
printf("\n");
for (int i = 1; i <= row; i++)
{
printf("%d ", i);
for (int j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
随机布置雷的位置
使用rand()产生随机数,但rand()每次只会产生固定的随机数,所以要在主函数中加入srand((unsigned int)time(NULL));
将时间作为seed产生不断变化的随机数
rand()和srand()的使用需要包括头文件#include<stdlib.h>
time()函数的使用需要包括头文件#include<time.h>
void set_mine(char board[ROWS][COLS], int row, int col)
{
int cnt = count;
while (cnt)
{
int x = rand() % row + 1; //得到1~row的随机数
int y = rand() % col + 1; //得到1~col的随机数
if (board[x][y] == '0')
{
board[x][y] = '1';//表示该位置布置了雷
cnt--;
}
}
}
统计周围雷的个数
int get_mine_count(char mine[ROWS][COLS], int x, int y)
{
int ret = 0;
for (int i = x - 1; i <= x + 1; i++)
{
for (int j = y - 1; j <= y + 1; j++)
{
if(i != x || j != y)//周围8个位置,不包括自己
ret = ret + mine[i][j] - '0';
}
}
return ret;
}
展开周围一片没有雷的区域
如果该位置周围一个雷也没有,则继续打开周围的空白格子直到遇到周围有雷的格子。
void open_area(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y)
{
int cnt = get_mine_count(mine, x, y);
if (cnt == 0)
{
show[x][y] = '0';
for (int i = x - 1; i <= x + 1; i++)
{
for (int j = y - 1; j <= y + 1; j++)
{
if (show[i][j] == '*' && x >= 1 && x <= row && y >= 1 && y <= col)
{
open_area(mine, show, ROW, COL, i, j);//递归
}
}
}
}
else//直到递归到周围8个位置存在雷的区域
{
show[x][y] = cnt + '0';
}
}
计算已排查位置的个数
int get_win(char board[ROWS][COLS], int row, int col)
{
int win = 0;
for (int i = 1; i <= row; i++)
{
for (int j = 1; j <= col; j++)
{
if (board[i][j] != '*' && board[i][j] != '!')
win++;
}
}
return win;
}
排查雷(包括检测输赢):
首先根据选择,走向不同分支
选择1: 排查雷,首先判断坐标,若不在棋盘范围则重新输入,否则进入下一步判断;该位置若已被排查则重新输入坐标,没有排查过则进行排查,并显示周围雷的个数,同时计算已排查位置的个数
选择2: 标记雷,若该坐标已排查或已标记则重新输入,否则将该坐标置为" ! ",表示已被标记,并打印棋盘信息。
选择3: 取消标记,若该坐标已排查或未被标记则重新输入,否则将“ !”重新置为“ * ”。
其他选择则重新输入。
每选择一次,对win进行判断,若win等于棋盘上非雷个数的总和,则排雷成功,否则继续游戏直到游戏成功或失败。
void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
int input = 0;
while (win < (row * col - count))
{
printf("请选择->:1.排查雷 2.标记雷 3.取消标记\n");
scanf("%d", &input);
if (input == 1)
{
printf("请输入要排查的坐标->:");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] == '*' || show[x][y] == '!')
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了!!!\n");
print_board(mine, ROW, COL);
break;
}
else
{
open_area(mine, show, ROW, COL, x, y);
print_board(show, ROW, COL);
win = get_win(show, ROW, COL);
}
}
else
{
printf("该坐标已被排查,请重新输入\n");
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
else if (input == 2)
{
printf("请输入要标记的坐标->:");
scanf("%d %d", &x, &y);
//判断坐标合法性
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] == '*')
{
show[x][y] = '!';
print_board(show, ROW, COL);
}
else if (show[x][y] == '!')
{
printf("该位置已被标记,请重新选择!\n");
}
else
{
printf("该位置已被排查,不能被标记,请重新选择!\n");
}
}
else
{
printf("坐标不合法,请重新输入!\n");
}
}
else if (input == 3)
{
printf("请输入要取消标记的坐标->:");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] == '!')
{
show[x][y] = '*';
print_board(show, ROW, COL);
}
else
{
printf("该位置不能取消标记,请重新选择!\n");
}
}
else
{
printf("坐标不合法,请重新输入!\n");
}
}
else
{
printf("输入有误,请重新输入!\n");
}
}
if (win == row * col - count)
{
printf("\n恭喜你,排雷成功!\n");
printf("雷的分布情况:\n");
print_board(mine, ROW, COL);
}
}
game.h文件
头文件
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
棋盘的大小以及雷的个数
使用宏定义,方便随时修改棋盘规格以及雷的个数
#pragma once//防止头文件重复调用
#define ROW 9
#define COL 9
#define ROWS ROW + 2
#define COLS COL + 2
#define count 10
主要功能函数的声明
//初始化雷盘
void init_board(char board[ROWS][COLS], int row, int col);
//打印雷盘
void print_board(char board[ROWS][COLS], int row, int col);
//布置雷
void set_mine(char board[ROWS][COLS], int row, int col);
//排查雷
void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
三、完整代码
完整代码上传在gitee
点此跳转扫雷gitee源码
以上就是扫雷游戏的简单实现,细心的小伙伴发现错误欢迎指正。最后感谢您的观看和支持!