本期主要介绍如何用C语言实现扫雷优化版本
文章目录
一、扫雷游戏介绍
《扫雷》是一款大众类的益智小游戏,于1992年发行。游戏目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。扫雷游戏中存在两个元素“雷”和“空”,一个方格不是“雷”即是“空”。游戏初始,所有的方格都是被覆盖的。点开的“空”会标识数字。如图中所示黄色框,中心的“空”标识数字“1”,即表示其周围8个方格内仅有1颗雷,玩家需要通过这样的信息来判断正确的雷区位置并予以标识,并完成找到所有的雷
二、test.c的实现
2.1扫雷游戏的运行
当我们运行扫雷程序的时候,会打印出菜单,让我们进行选择是否进行扫雷
1.开始游戏 ,0.退出游戏
2.2 程序的大体思路
程序运行起来的时候,到我们选择一的时候,进入到游戏当中,首先我们会创建两个棋盘,一个用来存放地雷,另一个用来展示给我们并且将棋盘初始化,接下来便是布置地雷和展示给我们棋盘可以打印出来,最后便是进行排查地雷
1.初始化两个大小一样的棋盘,一个存放地雷,另一个用来展示
2.将两个棋盘进行初始化
3将存放地雷的棋盘进行随机布置
4.打印展示的棋盘,进行游戏
5.排查地雷
#include"game.h"
void menu()
{
printf("***********************\n");
printf("***** 1.开始游戏 ****\n");
printf("***** 0.退出游戏 ****\n");
printf("***********************\n");
}
void game()
{
char mine[ROWS][COLS] = {
0 };
char show[ROWS][COLS] = {
0 };
//初始化棋盘
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//打印棋盘
Display(show, ROW, COL);
//布置雷
Setmine(mine, ROW, COL);
//Display(mine, ROW, COL);
//排查雷
MineClearance( 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();//选择1进入游戏
break;
case 0:
printf("退出游戏\n");//选择0退出游戏
break;
default:
printf("重新选择\n");
break;
}
} while (input);
return 0;
}
仔细观察代码的话,你会发现代码当中有许多不认识的标识符,其实这些已经事先做了预处理 的,包含在game.h当中,引用了头文件相当于引用了这些标识符| |,接下来呈现game.h
#include<stdio.h>
#include<string.h>
#include<time.h>
#include<stdlib.h>
#include<Windows.h>
#define ROW 9 //定义行
#define COL 9 //定义列
#define ROWS ROW+2
#define COLS COL+2
#define easycount 10 //将雷的数量定义为10
//初始化棋盘
void InitBoard(char Board[ROWS][COLS], int rows, int cols, char set);
//打印棋盘
void Display(char showBoard[ROWS][COLS], int row, int col);
//随即布置地雷
void Setmine(char mineBoard[ROWS][COLS], int row, int col);
//进行排查地雷
void MineClearance(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
2.3 很妙的想法
为何将数组定义为11 * 11的呢?其实是因为想要为了免去麻烦,因为我们是需要对9*9的区域进行排查的,而将数组定义为11 * 11的话可以避免角上的几个以特殊的方式进行排查,而不是9 * 9,如果不这样做,很显然代码就变得复杂起来了,所以说这个想法很好这也是
这也是前人的智慧,为我们提供了便利
如图所显示,如果这样子的话,不进行特殊处理,数组便会越界访问
三、game.c文件的实现
3 .1初始化棋盘
进行数组的初始化,话不多说,上代码
想将数组初始化什么,只需要在调用的时候将内容传进来就行
//初始化棋盘
void InitBoard(char Board[ROWS][COLS], int rows, int cols, char set)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
Board[i][j] = set;
}
}
}
3 .2随机布置地雷
随机布置地雷,该函数是通过随机数函数来进行布置地雷的,srand与rand函数共同调用,完成布置地雷的操作
//布置雷
void Setmine(char mineBoard[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int count = easycount;
while (count)
{
//0~36767
x = rand() % row + 1;
y = rand() % col + 1;
if (mineBoard[x][y] == '0')
{
mineBoard[x][y] = '1';
count--;
}
}
}
3 .3将棋盘打印出来
打印这个棋盘是特别地灵活,因为只需要在头文件game.h当中修改ROW和COL的值便可以,想打印几行几列只需稍作修改
//打印棋盘
void Display(char showBoard[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("----------------扫雷-----------------\n");
for (j = 1; j <= row; j++)
{
if (j == 1) //打印列
{
printf(" %d ", j);
}
else
printf(" %d ", j);
}
printf("\n"); //进行分隔
for (i = 1; i <= row; i++)
{
printf("%d ", i); //打印行
for (j = 1; j <= col; j++)
{
printf(" %c ", showBoard[i][j]);
if (j <= col - 1)
printf("|");
}
printf("\n");
if (i <= row-1 )
{
printf(" ");
for ( j = 1; j <= col ; j++)
{
printf("---");
if (j <= col-1 )
printf("|");
}
}
printf("\n"); //进行分隔行
}
}
3 .4排查雷函数
排查地雷这个函数是有很多函数共同完成我们接下来先看一下里面的函数但是总体的代码运行是,创建一个循环,如果满足条件则进行,然后进行相关的排雷标记,取消标记等相关操作
3.4.1 判断周围有几个雷
遍历自己在内的九个格子便可以,或者可以通过减去字符0来实现
//统计周围地雷的个数
int Surroundingmines(char mine[ROWS][COLS], int x, int y)
{
int i = 0;
int j = 0;
int count = 0;
//遍历自己在内的九个格子
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (mine[i][j] == '1')
count++;
}
}
return count;
//通过减去'0'来实现
/*return (mine[x - 1][y - 1] +
mine[x][y - 1] + mine[x + 1][y - 1] +
mine[x - 1][y] + mine[x][y] + mine[x + 1][y + 1]
+ mine[x + 1][y + 1] + mine[x][y + 1] + mine[x - 1][y + 1] - 8 * '0');*/
}
3.4.2展开函数(炸金花)
我们知道点击某一个格子的,是可以将周围不是雷的格子也排查出来,大大节省了我们的时间,不需要一个一个的进行排查
这个展开函数是利用递归来实现的,如果排查周围的位置没有内,则继续向四周进行排查。直到遇到周围有雷的坐标才停下来,是呈现爆炸式的
在递归这个函数的时候,我也遇到了一系列的问题如果条件不对的话,他会进入死循环,直到程序是挂掉但是后来参考了一下别人的思路以及热心的好友帮忙解答,我也解决了这个问题,当展开(x,y)的时候,从他作为八个格子当中进行排查然后这八个格子当中(x,y)也在里面,所以这就会造成死地归,互相调用所以这个会一直死递归下去但是只要加限制条件便可以
//递归实现展开一片
void ExpandAround(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
int i = 0;
int j = 0;
if (x >= 1 && x <= ROW && y >= 1 && y <=COL)
{
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (mine[i][j] == '0')
{
int count = Surroundingmines(mine, i, j);
if(count==0)
{
if (show[i][j] == '*')
{
show[i][j] = ' ';
ExpandAround(mine, show, i, j);
}
}
else
{
show[i][j] = count + '0';
}
}
}
}
}
}
调用展开函数的话将作为自己在内的九个坐标进行判断,调用Surroundingmines来判断周围到底有几个雷,如果周围没有一个地雷的话,继续进行展开,不是的话将周围有几个地雷显示在坐标上,并且需要判断坐标是不是合法的,如果合法才能进去,不合法的话数组越界
3.4.3标记地雷
手动标记地雷的坐标,该程序是每次排查后都有一次选择的机会,是否需要标记地雷只有当方格是 ‘*’ 的时候才能进行标记
标记雷
void MarkingMine(char show[ROWS][COLS], int row, int col)
{
int x = 0, y = 0;
while (1)
{
printf("请输入需要标记的雷->");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] == '*')
{
show[x][y] = 'W';
break;
}
else
{
printf("该坐标无法被标记\n");
}
}
else
printf("该坐标非法,重新输入\n");
}
system("cls");
Display(show, ROW, COL);
}
3.4.4取消标记地雷
手动取消标记已经标记过的坐标,但是只有已经标记过了坐标才能进行取消标记,不然的话则退出该程序,但是需要注意的是,仍然需要保持坐标的合法性,且不能越界
//取消标记雷
void Cancel(char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int flag = 0;
for (int i = 1; i <= row; i++)
{
for (int j = 1; j <= col; j++)
{
if (show[i][j] == 'W')
{
flag = 1;
break;
}
}
}
if (flag)
{
printf("请输入需要取消标记的坐标->");
while (1)
{
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] == 'W')
{
show[x][y] = '*';
break;
}
else
printf("该坐标无法被取消标记,重新输入->");
}
else
printf("该坐标非法,重新输入\n");
}
}
system("cls");
Display(show, ROW, COL);
if (flag == 0)
printf("还未标记坐标!!!!!\n");
}
3.4.5判断是否排雷成功&&提示还有多少地雷
每一次的while循环都会进行判断是否排雷成功。如果排了成功,则跳出循环并启用庆祝函数如果没有成功,则继续进入循环,直到成功为止或者踩到地雷结束循环,并且该函数还可以提示还有多少个地雷,具体情况见代码
int RemainingMines(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
int count1 = 0, count2 = 0;
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)
{
if (show[i][j] == '*' || show[i][j] == 'W' && mine[i][j] == '1')
count1++; //count1是还有多少未知的和已经标记的
if (show[i][j] == 'W' && mine[i][j] == '1')
count2++; //count2是判断还有多少雷,并打印
}
}
printf(" 还剩下%d个雷 \n ", easycount - count2);
if (count1 == easycount)
return 1;
return 0;
}
//判断是否成功
int ret = RemainingMines(show, mine, ROW, COL);
if (ret==1)
{
Congratulation();
Show_all_mines(mine, show, ROW, COL);
break;
}
3.4.5庆祝游戏胜利
当游戏结束并且胜利的时候,我们将会已成功排雷,创建两个数组,实现从两端移动并向中间靠拢的打印庆祝,并用system函数进行清屏,最后再打印一下数组
//庆祝
void Congratulation()
{
char arr[] = "Congratulations on your success in demining!!!!!!!!!!";
char str[100] = "0";
int sz = strlen(arr);
int i = 0;
while (sz)
{
str[i] = '*';
i++;
sz--;
}
sz = strlen(arr);
int left = 0, right = sz - 1;
while (left <= right)
{
str[left] = arr[left];
str[right] = arr[right];
printf("%s\n", str);
Sleep(100);
system("cls");
left++; right--;
}
printf(" %s\n", str);
printf(" 恭喜你排雷成功!!!!!!!!!!!!!!!\n");
}
3.4.6将地雷给显示出来
不管游戏成功与否在本轮游戏结束之后,将地雷的位置告诉玩家,为了使玩家更方便的查找地雷将四地雷的坐标用特殊字符给显示出来,不是地雷的用空格显示
//将雷的位置告知
void Show_all_mines(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
for (int i = 1; i <= row; i++)
{
for (int j = 1; j <= col; j++)
{
if (mine[i][j] == '1')
show[i][j] = '$';
else
show[i][j] = ' ';
}
}
Display(show, ROW, COL);
}
四、完整的程序
4.1头文件game.h
在这里插入代#include<stdio.h>
#include<string.h>
#include<time.h>
#include<stdlib.h>
#include<Windows.h>
#define ROW 9 //定义行
#define COL 9 //定义列
#define ROWS ROW+2
#define COLS COL+2
#define easycount 10 //将雷的数量定义为10
//初始化棋盘
void InitBoard(char Board[ROWS][COLS], int rows, int cols, char set);
//打印棋盘
void Display(char showBoard[ROWS][COLS], int row, int col);
//随即布置地雷
void Setmine(char mineBoard[ROWS][COLS], int row, int col);
//进行排查地雷
void MineClearance(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);码片
4.2 test.c代码
#define _CRT_SECURE_NO_WARNINGS
#include"game.h"
void menu()
{
printf("***********************\n");
printf("***** 1.开始游戏 ****\n");
printf("***** 0.退出游戏 ****\n");
printf("***********************\n");
}
void game()
{
char mine[ROWS][COLS] = {
0 };
char show[ROWS][COLS] = {
0 };
//初始化棋盘
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//打印棋盘
Display(show, ROW, COL);
//布置雷
Setmine(mine, ROW, COL);
//Display(mine, ROW, COL);
//排查雷
MineClearance( 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");
break;
default:
printf("重新选择\n");
break;
}
} while (input);
return 0;
}
4.3 game.c代码
#define _CRT_SECURE_NO_WARNINGS
#include"game.h"
//初始化棋盘
void InitBoard(char Board[ROWS][COLS], int rows, int cols, char set)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
Board[i][j] = set;
}
}
}
//打印棋盘
void Display(char showBoard[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("----------------扫雷-----------------\n");
for (j = 1; j <= row; j++)
{
if (j == 1) //打印列
{
printf(" %d ", j);
}
else
printf(" %d ", j);
}
printf("\n"); //进行分隔
for (i = 1; i <= row; i++)
{
printf("%d ", i); //打印行
for (j = 1; j <= col; j++)
{
printf(" %c ", showBoard[i][j]);
if (j <= col - 1)
printf("|");
}
printf("\n");
if (i <= row-1 )
{
printf(" ");
for ( j = 1; j <= col ; j++)
{
printf("---");
if (j <= col-1 )
printf("|");
}
}
printf("\n"); //进行分隔
}
}
//布置雷
void Setmine(char mineBoard[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int count = easycount;
while (count)
{
//0~36767
x = rand() % row + 1;
y = rand() % col + 1;
if (mineBoard[x][y] == '0')
{
mineBoard[x][y] = '1';
count--;
}
}
}
int RemainingMines(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
int count1 = 0, count2 = 0;
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)
{
if (show[i][j] == '*' || show[i][j] == 'W' && mine[i][j] == '1')
count1++;
if (show[i][j] == 'W' && mine[i][j] == '1')
count2++;
}
}
printf(" 还剩下%d个雷 \n ", easycount - count2);
if (count1 == easycount)
return 1;
return 0;
}
//递归实现展开一片
void ExpandAround(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
int i = 0;
int j = 0;
if (x >= 1 && x <= ROW && y >= 1 && y <=COL)
{
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (mine[i][j] == '0')
{
int count = Surroundingmines(mine, i, j);
if(count==0)
{
if (show[i][j] == '*')
{
show[i][j] = ' ';
ExpandAround(mine, show, i, j);
}
}
else
{
show[i][j] = count + '0';
}
}
}
}
}
}
//统计周围地雷的个数
int Surroundingmines(char mine[ROWS][COLS], int x, int y)
{
int i = 0;
int j = 0;
int count = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (mine[i][j] == '1')
count++;
}
}
return count;
/*return (mine[x - 1][y - 1] +
mine[x][y - 1] + mine[x + 1][y - 1] +
mine[x - 1][y] + mine[x][y] + mine[x + 1][y + 1]
+ mine[x + 1][y + 1] + mine[x][y + 1] + mine[x - 1][y + 1] - 8 * '0');*/
}
标记雷
void MarkingMine(char show[ROWS][COLS], int row, int col)
{
int x = 0, y = 0;
while (1)
{
printf("请输入需要标记的雷->");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] == '*')
{
show[x][y] = 'W';
break;
}
else
{
printf("该坐标无法被标记\n");
}
}
else
printf("该坐标非法,重新输入\n");
}
system("cls");
Display(show, ROW, COL);
}
//取消标记雷
void Cancel(char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int flag = 0;
for (int i = 1; i <= row; i++)
{
for (int j = 1; j <= col; j++)
{
if (show[i][j] == 'W')
{
flag = 1;
break;
}
}
}
if (flag)
{
printf("请输入需要取消标记的坐标->");
while (1)
{
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] == 'W')
{
show[x][y] = '*';
break;
}
else
printf("该坐标无法被取消标记,重新输入->");
}
else
printf("该坐标非法,重新输入\n");
}
}
system("cls");
Display(show, ROW, COL);
if (flag == 0)
printf("还未标记坐标!!!!!\n");
}
//将雷的位置告知
void Show_all_mines(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
for (int i = 1; i <= row; i++)
{
for (int j = 1; j <= col; j++)
{
if (mine[i][j] == '1')
show[i][j] = '$';
else
show[i][j] = ' ';
}
}
Display(show, ROW, COL);
}
//庆祝
void Congratulation()
{
char arr[] = "Congratulations on your success in demining!!!!!!!!!!";
char str[100] = "0";
int sz = strlen(arr);
int i = 0;
while (sz)
{
str[i] = '*';
i++;
sz--;
}
sz = strlen(arr);
int left = 0, right = sz - 1;
while (left <= right)
{
str[left] = arr[left];
str[right] = arr[right];
printf("%s\n", str);
Sleep(100);
system("cls");
left++; right--;
}
printf(" %s\n", str);
printf(" 恭喜你排雷成功!!!!!!!!!!!!!!!\n");
}
//排查雷
void MineClearance(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
char ch = 0;
printf(" 还剩下%d个雷 \n ",easycount);
while (1)
{
判断是否成功 当游戏扫雷个数每个格子都有时,该代码有用,开始即赢
// int ret = RemainingMines(show, mine, ROW, COL);
//if (ret==1)
//{
// Congratulation();
// printf("%恭喜你排雷成功\n");
// Show_all_mines(mine, show, ROW, COL);
// break;
//}
printf("请输入需要排雷的坐标->");
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");
system("cls");
Show_all_mines(mine, show, ROW, COL);
break;
}
else
{
ExpandAround(mine, show, x, y);
system("cls");
Display(show, ROW, COL);
//Display(mine, ROW, COL); //测试时候
//判断是否成功
int ret = RemainingMines(show, mine, ROW, COL);
if (ret==1)
{
Congratulation();
Show_all_mines(mine, show, ROW, COL);
break;
}
printf("是否需要标记地雷(Y/N)\n");
while ((ch = getchar()) !='\n')
{
;
}
scanf("%c", &ch);
switch (ch)
{
case 'Y':
MarkingMine(show, ROW, COL);
break;
default:
break;
}
printf("是否需要取消标记地雷(Y/N)\n");
while ((ch = getchar()) != '\n')
{
;
}
scanf("%c", &ch);
switch (ch)
{
case 'Y':
Cancel(show, ROW, COL);
break;
default:
break;
}
}
}
else
printf("已经排查过此坐标,重新排查\n");
}
else
printf("排查的坐标越界,重新排查\n");
}
}
五、感谢以及交流
如果这一份扫雷优化的博客对你有所帮助的话,请给博主一个免费点赞,以示鼓励!!!欢迎各位点赞,评论,收藏,一键三连哦,谢谢捧场!!!!
如果对博客有什么疑问或者不同的见解以及博客当中有什么错误的话,欢迎评论区留言!!!!!!!!!!