游戏规则
《扫雷》是一款大众类的益智小游戏,于1992年发行。游戏目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。
算法设计
- 数据结构:
- 扫雷的界面需要用char类型二维数组进行存储输出(显示排雷[以其为中心3*3格子雷的个数]、未排雷 [’ * ‘]、标记雷[’ ! ']三种标志)。
- 布雷的隐藏状态也需要一个char类型二维数组存储(显示有雷[’ 1 ‘]和无雷[’ 0 ']两种状态)。
- 在扫雷时为了避免越界访问的问题,我们选择将数组扩大一圈,则上图9 * 9的界面用11 * 11的数组存储。
- 设计游戏菜单总体逻辑,用循环嵌套选择语句即可。
- 游戏流程设计主要为先对布雷数组与显示数组初始化,然后进行随机布雷,完成后即可开始扫雷,功能包含输出、标记雷和展开雷。
具体介绍
game.h
其中包含了各个函数的声明,以及各个指标的宏定义。
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9 //未扩展行
#define COL 9 //为扩展列
#define ROWS ROW + 2 //扩展行
#define COLS COL + 2 //扩展列
#define EASY_COUNT 10 //游戏难度(雷的个数)
void minesweeper(); //游戏流程函数
void init_board(char real_mine[ROWS][COLS], char set); //初始化数组函数
void print_board(char show_mine[ROWS][COLS]); //打印函数
void set_real(char real_mine[ROWS][COLS]); //布雷函数
int count_mine(char real_mine[ROWS][COLS], int x, int y); //统计周围雷个数函数
void open_own(char show_mine[ROWS][COLS], char real_mine[ROWS][COLS], int x, int y); //展开本身函数
void open_around(char show_mine[ROWS][COLS], char real_mine[ROWS][COLS], int x, int y); //展开周围函数
void sign_mine(char show_mine[ROWS][COLS]); //标记雷函数
void findmine(char real_mine[ROWS][COLS], char show_mine[ROWS][COLS]); //扫雷函数
game.c
其中包含了各个函数的具体实现。
初始化函数
函数逻辑:
- 传入要进行初始化的数组和想要初始化的字符,将整个二维数组遍历赋值即可。
代码实现:
void init_board(char real_arr[ROWS][COLS], char set) {
int i = 0;
int j = 0;
for (i = 0; i < ROWS; i++){
for (j = 0; j < COLS; j++){
real_arr[i][j] = set;
}
}
}
布雷函数
函数逻辑:
- 使用rand()函数生成1~9范围的横纵坐标即可,不可重复,直到生成想要数量。(srand()函数放在游戏流程函数中)
代码实现:
void set_real(char real_mine[ROWS][COLS]) {
int count = EASY_COUNT;
while (count) {
int x = rand() % ROW + 1;
int y = rand() % COL + 1;
if (real_mine[x][y] != '1') {
real_mine[x][y] = '1';
count--;
}
}
}
打印函数
函数逻辑:
- 输出时范围为COL*ROW(9 * 9)即可,还应该呈现相应格式,如:扫雷数组的坐标体现。
代码实现:
void print_board(char show_mine[ROWS][COLS]) {
printf("--------------------\n");
printf(" ");
for (int i = 1; i <= COL; i++){
printf("%d ", i);
}
printf("\n");
for (int i = 1; i <= ROW; i++){
printf("%d ", i);
for (int j = 1; j <= COL; j++){
printf("%c ", show_mine[i][j]);
}
printf("\n");
}
统计周围雷个数函数
函数逻辑:
- 传入布雷数组和排查雷的坐标(x,y),遍历以它为中心的3*3范围内统计 ’ 1 ‘ 的个数,并将其返回。
代码实现:
int count_mine(char real_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 (real_mine[i][j] == '1'){ //判断是否为雷
ret++;
}
}
}
return ret;
}
展开本身函数
函数逻辑:
- 使用本函数的场景为:当以该扫雷位置(本身不为雷)为中心3*3范围内有雷,则只展开自己即可。
- 首先为了避免重复扫雷,应判断是否已扫雷,然后调用统计周围雷个数函数得到周围雷个数,转化为字符赋值给该位置即可。
代码实现:
void open_own(char show_mine[ROWS][COLS], char real_mine[ROWS][COLS], int x, int y) {
if (show_mine[x][y] == '*'){ //判断是否已扫雷
int count = count_mine(real_mine, x, y);
show_mine[x][y] = '0' + count; //记录个数
}
}
展开周围函数
函数逻辑:
- 使用本函数的场景为:当以该扫雷位置(本身不为雷)为中心3*3范围内没有雷,则将周围展开并判断递归。
- 同理也需要先避免重复扫雷,然后现将自己展开后,再遍历3*3周围坐标,判断该位置展开自己还是继续递归展开周围。
代码实现:
void open_around(char show_mine[ROWS][COLS], char real_mine[ROWS][COLS], int x, int y) {
if (show_mine[x][y] == '*'){
int count1 = count_mine(real_mine, x, y);
show_mine[x][y] = '0' + count1; //展开本身
for (int i = x - 1; i <= x + 1; i++){ //遍历
for (int j = y - 1; j <= y + 1; j++){
if (i >= 1 && i <= ROW && j >= 1 && j <= COL){
if (show_mine[i][j] == '*'){
int count2 = count_mine(real_mine, i, j);
if (count2 != 0)
open_own(show_mine, real_mine, i, j); //展开自己(递归出口)
else
open_around(show_mine, real_mine, i, j); //递归展开周围
}
}
}
}
}
}
标记雷函数
函数逻辑:
- 首先我们采用循环+判断结构实现标记雷函数的整体逻辑,玩家选择是否标记雷。
- 其次判断该处是否已扫雷。已扫雷处无法再被标记;未扫雷则进行标记赋值为’ ! '即可。
代码实现:
void sign_mine(char show_mine[ROWS][COLS]) {
char flag = 0;
do{
getchar();
printf("是否选择标记雷(Y/N):\n");
scanf("%c", &flag);
switch (flag){
case 'y':
case 'Y':
printf("输入标记雷的坐标:\n");
int x, y;
scanf("%d%d", &x, &y);
system("cls");
if (show_mine[x][y] != '*') {
printf("该处已经扫雷!\n");
}
else {
show_mine[x][y] = '!';
}
print_board(show_mine);
break;
case 'n':
case 'N':
flag = 0;
system("cls");
break;
default:
printf("输入不合法!\n");
break;
}
} while (flag);
}
扫雷函数
函数逻辑:
-
首先设置循环只要扫雷的个数没到ROW * COL - EASY_COUNT则说明雷没有扫完。
-
判断输入的合法性:
- 不合法(该位置已扫雷或输入坐标超出限制):输出提示信息,并重新输入。
- 合法:则判断该位置是否为雷:
-
为雷:跳出循环,输出布置雷数组,告知游戏结束。
-
不为雷:判断周围雷的个数,选择展开自己还是展开周围,最后选择是否要标记雷。
-
-
若出循环win == ROW * COL - EASY_COUNT,则告知游戏胜利,输出布置雷数组。
代码实现:
void findmine(char real_mine[ROWS][COLS], char show_mine[ROWS][COLS]) {
int win = 0;//判断是否扫完雷
int x, y;
while (win < ROW * COL - EASY_COUNT) {
print_board(show_mine);
printf("请输入你要扫雷的坐标:\n");
scanf("%d%d", &x, &y);
system("cls");
if (x < 1 || x > ROW || y < 1 || y > COL) {
printf("输入不合法!\n");
}
else if (show_mine[x][y] != '*') {
printf("该位置已经扫雷!\n");
}
else {
if (real_mine[x][y] == '1') {
printf("该位置为雷,游戏结束!\n");
print_board(real_mine);
break;
}
else {
//求周围雷的个数
int count = count_mine(real_mine, x, y);
if (count != 0) {
//展开自己
open_own(show_mine, real_mine, x, y);
win++;
}
else {
//展开周围
open_around(show_mine, real_mine, x, y);
int i;
int j;
for (i = 1; i <= ROW; i++){
for (j = 1; j <= COL; j++){
if (show_mine[i][j] != '*'){
win++;
}
}
}
}
print_board(show_mine);
sign_mine(show_mine);
}
}
}
if (win == ROW * COL - EASY_COUNT) {
print_board(real_mine);
printf("恭喜你扫雷成功!\n");
}
}
游戏流程函数
函数逻辑:
- 创建布雷和显示的二维数组,并将其都初始化,然后布雷,最后进行扫雷即可。
代码实现:
void minesweeper(){
char real_mine[ROWS][COLS];
char show_mine[ROWS][COLS];
srand((unsigned)time(NULL));
//初始化布雷数组
init_board(real_mine, '0');
//初始化显示数组
init_board(show_mine, '*');
//布雷
set_real(real_mine);
//扫雷
findmine(real_mine, show_mine);
}
test.c
菜单展示函数
代码实现:
void menu() {
printf("******************\n");
printf("**** 1.扫雷 ***\n");
printf("**** 0.退出 ***\n");
printf("******************\n");
}
主函数
函数逻辑:
- 对程序的菜单进行循环输出,并对玩家输入进行判断。
代码实现:
int main() {
int flag = 0;
do
{
menu();
printf("请输入你的选项:\n");
scanf("%d", &flag);
system("cls");
switch (flag)
{
case 1:
minesweeper();
break;
case 0:
printf("退出游戏\n");
return 0;
break;
default:
printf("输入不合法,请重新输入\n");
break;
}
system("pause");
system("cls");
} while (flag);
}
整体代码
game.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9 //未扩展行
#define COL 9 //为扩展列
#define ROWS ROW + 2 //扩展行
#define COLS COL + 2 //扩展列
#define EASY_COUNT 10 //游戏难度(雷的个数)
void minesweeper(); //游戏流程函数
void init_board(char real_mine[ROWS][COLS], char set); //初始化数组函数
void print_board(char show_mine[ROWS][COLS]); //打印函数
void set_real(char real_mine[ROWS][COLS]); //布雷函数
int count_mine(char real_mine[ROWS][COLS], int x, int y); //统计周围雷个数函数
void open_own(char show_mine[ROWS][COLS], char real_mine[ROWS][COLS], int x, int y); //展开本身函数
void open_around(char show_mine[ROWS][COLS], char real_mine[ROWS][COLS], int x, int y); //展开周围函数
void sign_mine(char show_mine[ROWS][COLS]); //标记雷函数
void findmine(char real_mine[ROWS][COLS], char show_mine[ROWS][COLS]); //扫雷函数
game.c
#define _CRT_SECURE_NO_WARNINGS
#include"game.h"
//初始化
void init_board(char real_arr[ROWS][COLS], char set) {
int i = 0;
int j = 0;
for (i = 0; i < ROWS; i++){
for (j = 0; j < COLS; j++){
real_arr[i][j] = set;
}
}
}
//布雷
void set_real(char real_mine[ROWS][COLS]) {
int count = EASY_COUNT;
while (count) {
int x = rand() % ROW + 1;
int y = rand() % COL + 1;
if (real_mine[x][y] != '1') {
real_mine[x][y] = '1';
count--;
}
}
}
//打印
void print_board(char show_mine[ROWS][COLS]) {
printf("--------------------\n");
printf(" ");
for (int i = 1; i <= COL; i++){
printf("%d ", i);
}
printf("\n");
for (int i = 1; i <= ROW; i++){
printf("%d ", i);
for (int j = 1; j <= COL; j++){
printf("%c ", show_mine[i][j]);
}
printf("\n");
}
}
//周围雷个数
int count_mine(char real_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 (real_mine[i][j] == '1'){
ret++;
}
}
}
return ret;
}
//展开自己
void open_own(char show_mine[ROWS][COLS], char real_mine[ROWS][COLS], int x, int y) {
if (show_mine[x][y] == '*'){
int count = count_mine(real_mine, x, y);
show_mine[x][y] = '0' + count;
}
}
//展开周围
void open_around(char show_mine[ROWS][COLS], char real_mine[ROWS][COLS], int x, int y) {
if (show_mine[x][y] == '*'){
int count1 = count_mine(real_mine, x, y);
show_mine[x][y] = '0' + count1;
for (int i = x - 1; i <= x + 1; i++){
for (int j = y - 1; j <= y + 1; j++){
if (i >= 1 && i <= ROW && j >= 1 && j <= COL){
if (show_mine[i][j] == '*'){
int count2 = count_mine(real_mine, i, j);
if (count2 != 0)
open_own(show_mine, real_mine, i, j);
else
open_around(show_mine, real_mine, i, j);
}
}
}
}
}
}
//标记雷
void sign_mine(char show_mine[ROWS][COLS]) {
char flag = 0;
do{
getchar();
printf("是否选择标记雷(Y/N):\n");
scanf("%c", &flag);
switch (flag){
case 'y':
case 'Y':
printf("输入标记雷的坐标:\n");
int x, y;
scanf("%d%d", &x, &y);
system("cls");
if (show_mine[x][y] != '*') {
printf("该处已经扫雷!\n");
}
else {
show_mine[x][y] = '!';
}
print_board(show_mine);
break;
case 'n':
case 'N':
flag = 0;
system("cls");
break;
default:
printf("输入不合法!\n");
break;
}
} while (flag);
}
//扫雷
void findmine(char real_mine[ROWS][COLS], char show_mine[ROWS][COLS]) {
int win = 0;//判断是否扫完雷
int x, y;
while (win < ROW * COL - EASY_COUNT) {
print_board(show_mine);
printf("请输入你要扫雷的坐标:\n");
scanf("%d%d", &x, &y);
system("cls");
if (x < 1 || x > ROW || y < 1 || y > COL) {
printf("输入不合法!\n");
}
else if (show_mine[x][y] != '*') {
printf("该位置已经扫雷!\n");
}
else {
if (real_mine[x][y] == '1') {
printf("该位置为雷,游戏结束!\n");
print_board(real_mine);
break;
}
else {
//求周围雷的个数
int count = count_mine(real_mine, x, y);
if (count != 0) {
//展开自己
open_own(show_mine, real_mine, x, y);
win++;
}
else {
//展开周围
open_around(show_mine, real_mine, x, y);
int i;
int j;
for (i = 1; i <= ROW; i++){
for (j = 1; j <= COL; j++){
if (show_mine[i][j] != '*'){
win++;
}
}
}
}
print_board(show_mine);
sign_mine(show_mine);
}
}
}
if (win == ROW * COL - EASY_COUNT) {
print_board(real_mine);
printf("恭喜你扫雷成功!\n");
}
}
//游戏流程
void minesweeper(){
char real_mine[ROWS][COLS];
char show_mine[ROWS][COLS];
srand((unsigned)time(NULL));
//初始化布雷数组
init_board(real_mine, '0');
//初始化显示数组
init_board(show_mine, '*');
//布雷
set_real(real_mine);
//扫雷
findmine(real_mine, show_mine);
}
main.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void menu() {
printf("******************\n");
printf("**** 1.扫雷 ***\n");
printf("**** 0.退出 ***\n");
printf("******************\n");
}
int main() {
int flag = 0;
do
{
menu();
printf("请输入你的选项:\n");
scanf("%d", &flag);
system("cls");
switch (flag)
{
case 1:
minesweeper();
break;
case 0:
printf("退出游戏\n");
return 0;
break;
default:
printf("输入不合法,请重新输入\n");
break;
}
system("pause");
system("cls");
} while (flag);
}
IDE
- Visual Studio 2019