赞
踩
目录
想必大家在上学时代都或多或少玩过扫雷游戏,通过棋盘上已有的数据分析出雷所在的位置获取胜利,每一次游戏都带给我们强大的满足感与能力的提升。那么今天我就带领大家用C语言实现扫雷的编写。
let's go!
首先,扫雷游戏结局包含两种情况
1.标记所有雷或掀开所有非雷地块
2.踩到雷,游戏结束
基本玩法:
掀开一个格子,如果是雷,游戏结束。
反之,显示周围有多少个雷。
如何达到上图的效果呢?
首先我们需要定义一个二维数组用于表示棋盘,我们在其上布置“地雷”。
假设我们用'0'表示安全,'1'表示雷的话,那么统计周围雷数就变得简单了:雷数(n)=周围八个格子的数值的和。
但这时候我们又会遇见一个问题:
如果一个地方被点开了,显示了其周围的总雷数(例如:4),在下次点开它周围格子时,就不免错误地将这个并不表示雷的'4'加上。就会造成bug
那么,设置表里两层棋盘或许是一个更好的选择:内层设置'雷',外层读取内层数据展示给用户显示周围雷数。
ps:下文中show/board2是外棋盘,mine/board1是雷棋盘。
之后,当用户选择外棋盘的坐标时,只需要统计内棋盘周围雷数就行了。
但这时候我们又会遇见另一个问题:在数组边界的格子并没有八个周围的格子
如何解决这个问题呢?我们可以把两个数组都开大一圈,这样访问时,排查边界的数据就不会越界了。eg:9*9的期望棋盘,我们开辟成11*11的棋盘,刚好大一圈。
首先基于与用户的交互功能,需要以下功能
菜单界面:
用户在此选择开始游戏,结束游戏,输入非法提示重输。
显示棋盘:
未点开的地块统一用'?'表示,点开的地块用空格表示,如果周围有雷,显示'雷'数。
设置雷:
在游戏开始时用时间戳随机生成一定数目的雷。
排查雷
用户输入想排查的地块坐标,判定游戏状况:踩雷,安全
计数雷
用户输入想排查的地块坐标,游戏状况安全,计算周围八个格子的数
胜利,失败判定:
当踩雷时结束游戏,并显示菜单界面
好,现在进入正文——函数功能设置!
代码的功能会在代码段进行解释
达到提示输入的效果就行
eg:
void menu() { printf("*******************************\n"); printf("***** 1. play 0. exit *****\n"); printf("*******************************\n"); }
因为我们玩游戏的时候希望的可能并非只是玩一局,这个时候就需要在游戏函数(game)上添加一个do while()循环!
- void menu()
- {
- printf("*******************************\n");
- printf("***** 1. play 0. exit *****\n");
- printf("*******************************\n");
- }
- void text()
- {
- int input = 0;
- do
- {
- menu();
- printf("请选择:>");
- scanf_s("%d", &input);
- system("cls");
- switch (input)
- {
- case 1:
- printf("游戏开始\n");
- game();
- Sleep(1000);
- system("cls");
- break;
- case 0:
- printf("退出游戏\n");
- break;
- default :
- printf("输入非法,请重输!\n");
- break;
- }
-
- } while (input);
- }

ps:在这里,我们选择使用switch case语句以达到多分枝的目的
在这里我们设置棋盘的初始状态,使其元素统一地表现。
- initboard(board1, ROW, COL, '0');//mine
- initboard(board2, ROW, COL, '?');//show
由于棋盘初始化本质时将数组元素换成'0'或'?',是一个重复单调的过程,索性就把它封装成一个函数。以char set来确定替换元素。
通过双层循环遍历替换数组元素(set):
- void initboard(char board[ROWS][COLS], int row, int col,char set)//初始化show棋盘和mine棋盘,set,传什么打印什么
- {
- int i = 0;
- int j = 0;
- for (i = 0; i < row+2; i++)
- {
- for (j = 0; j < col+2; j++)
- {
- board[i][j] = set;
- }
- }
- }
函数调用过程:
- char board1[ROWS][COLS] = { 0 };
- char board2[ROWS][COLS] = { 0 };
我们玩游戏显然不可能只是我们凭意识玩,所以就需要对每一步进行及时反馈(显示外棋盘到电脑)——对棋盘(不论内外棋盘)进行遍历打印。 display(board2, ROW, COL);
-
- void display(char board[ROWS][COLS], int row, int col)//打印棋盘,传什么打印什么
- {
- int i = 0;
- int j = 0;
- for (j = 0; j < col + 1; j++)//打印数轴,便于用户输入坐标
- {
- printf(" %d", j);
- }
- printf("\n");
- printf("\n");
- for (i = 1; i < row + 1; i++)
- {
- printf(" %d", i);//美观
- for (j = 1; j < col + 1; j++)//遍历每一行内的元素,遍历列次
- {
- printf(" %c", board[i][j]);
- }
- printf("\n");
- printf("\n\n");//留出空格
- }
- }

ps:11 20行主要是为了保持排版美观,打印效果如下 相当美观
我们需要在mine数组里布置雷。 setmine(board1, ROW, COL);
利用一个循环,每次随机生成一个坐标,如果这个位置不是雷,就在这个位置放雷,直到把所有的雷放完为止。
这一步就比较简单了,但随机数涉及到一个时间戳的概念,我在此不再赘述。
主函数部分要设置一个随机数种子(不需要理解)
-
-
- void setmine(char board[ROWS][COLS], int row, int col)//埋雷
- {
- int count = easy_mine;
- while(count>0)
- {
- /*rand()函数的意思是生成任意大小的一个数,
- 我们使用%row对row取余后,就限制到了0到row-1之间了,
- 为了用户方便输入又进行了+1*/
- int i = rand() % row + 1;//雷的横坐标在[0,row]之间
- int j = rand() % col + 1;//同理
- if (board[i][j] == '0')//只有未被埋雷的地方才能埋雷;
- {
- count--;
- board[i][j] = '1';
- }
- }
- }
-
-
-
-

- #include <time.h>//部分编译器需要加
- int main()
- {
- srand((unsigned int)time(NULL));//设置由时间设置的随机数种子
- text();
- }
-
-
调用count_mine函数计算周围雷数(可以包涵自己格子)设当前格子坐标[i,j]
只需要双层循环遍历行数在[i-1,i+1],列数在[j-1,j+1]的数,计算就行
- int count_mine(char mine[ROWS][COLS], int i, int j)//数包括自己在内的九个格子的雷数
- {
- int a = 0;
- int b = 0;
- int count = 0;
- for (a = i - 1; a <= i + 1; a++)
- {
- for (b = j - 1; b <= j + 1; b++)
- {
- if(a> 0 && b > 0 && a < ROW + 1 && b < COL + 1)
- count =mine[a][b] - '0'+count;//'1'-'0'=1;
- }
- }
- return count;
- }
我看很多博主选择用字符1减去字符0来统计,不经循环直接遍历上八个格子,我只能说:
确实可以,但没必要!
直接++也未免不可!
- int count_mine(char mine[ROWS][COLS], int i, int j)//数包括自己在内的九个格子的雷数
- {
- int a = 0;
- int b = 0;
- int count = 0;
- for (a = i - 1; a <= i + 1; a++)
- {
- for (b = j - 1; b <= j + 1; b++)
- {
- if(mine[a][b]='1')
- count ++;
- }
- }
- return count;
- }
ok!今天的重头戏来了,
布置好雷后,就开始排雷。排查雷需要同时操作两个数组。
- int search_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)//排雷
用户输入坐标,同时操作两个数组的判断条件
-
- if (i > 0 && i < row + 1 && j>0 && j < col + 1 && show[i][j] == '?')//检验坐标合法性
- //排查过的都不能排查
-
-
只有满足条件才进入排雷的环境中
1.无雷,调用count函数计算周围八个格子雷数
2.有雷,提示炸死,结束game返回主菜单
-
- if (show[i][j] == '?' && mine[i][j] == '0')
- {
- show[i][j]=count_mine(mine,i,j);
- system("cls");//清除上一步
- display(show, ROW, COL);
- break;
- }
- else if (show[i][j] == '?' && mine[i][j] == '1')
- {
- step++;
- system("cls");
- printf("很遗憾,你被炸死了\n");
- printf("雷阵如下\n");
- display(mine, ROW, COL);
- break;
- }
- else if (show[i][j] != '?')
- printf("坐标非法,请重输\n");
-
-

在这里,我们还可以添加一个获胜的判断条件
设置一个win=行和列的乘积,每排一个地块,win--,
当win=row*col-easy_mine,即等于所有空地块时,获胜
即,代码如下
-
- int search_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
- {
- int i = 0;
- int j = 0;
- int win = row*col;
- while (win> row * col - easy_mine)
- {
- again:
- printf("请输入坐标:>");
- scanf_s("%d %d", &i, &j);
- if (i > 0 && i < row + 1 && j>0 && j < col + 1 && show[i][j] == '?')
- {//坐标合法
- if (mine[i][j] == '1')
- {//排雷失败
- printf("很遗憾,你被炸死了!\n");
- display(mine, ROW, COL);
- break;
- }
- else
- {//排雷成功
-
- int count = count_mine(mine, i, j);
- show[i][j] = count + '0';
- display(show, ROW, COL);
- for (int m = 1; m < row+1 ; m++)//放在“扫雷”工作之后
- {
- for (int n = 1; n < col+1; n++)//从一开始
- {
- if (show[m][n] == '?')
- win--;
- }
- }
- }
- else if (show[i][j] != '?')
- {
- printf("输入非法,请重输\n");
- goto again;
- }
- }
- if (win == row * col - easy_mine)//
- {
- printf("你赢了!\n");
- return 1;
- }
- }
-
-
-

为了是屏幕干净引入了清屏函数 system("cls");注意头文件。
system("cls");//其头文件是#include<windows.h>,用于清空屏幕,简化画面
排雷是一个很多步骤的一个操作 ,为search_mine函数加上死循环保证其可以一直进行排雷操作,并且以p接受该函数的返回值:
一旦返回值为1(排雷失败),跳出循环,game函数结束,显示主界面。
-
- void game()
- {
- char board1[ROWS][COLS] = { 0 };
- char board2[ROWS][COLS] = { 0 };
- initboard(board1, ROW, COL, '0');//mine
- initboard(board2, ROW, COL, '?');//show
- display(board2, ROW, COL);
- setmine(board1, ROW, COL);
- //display(board1, ROW, COL);//用于debug
- while (1)
- {
- int p = search_mine(board1, board2, ROW, COL);
- if (p == 1)
- break;
- }
-
- }
-
-

综上,最终代码如下
- #include <iostream>
- #define ROWS 11
- #define COLS 11
- #define ROW 9
- #define COL 9
- #define easy_mine 10
- #define mid_mine 30
- #define dif_mine 50
- #define debug_mine 80
- #define debug+_mine 0
- void display(char board[ROWS][COLS], int row, int col)
- {
- int i = 0;
- int j = 0;
- for (j = 0; j < col + 1; j++)
- {
- printf(" %d", j);
- }
- printf("\n");
- printf("\n");
- for (i = 1; i < row + 1; i++)
- {
- printf(" %d", i);
- for (j = 1; j < col + 1; j++)
- {
- printf(" %c", board[i][j]);
- }
- printf("\n");
- printf("\n\n");
- }
- }
- int count_mine(char mine[ROWS][COLS], int i, int j)
- {
- int a = 0;
- int b = 0;
- int count = 0;
- for (a = i - 1; a <= i + 1; a++)
- {
- for (b = j - 1; b <= j + 1; b++)
- {
- if(mine[i][j]=='1')
- count++;
- }
- }
- return count;
- }
- int search_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
- {
- int i = 0;
- int j = 0;
- int win = row*col;
- while (win> row * col - easy_mine)
- {
- again:
- printf("请输入坐标:>");
- scanf_s("%d %d", &i, &j);
- if (i > 0 && i < row + 1 && j>0 && j < col + 1 && show[i][j] == '?')
- {//坐标合法
- if (mine[i][j] == '1')
- {//排雷失败
- printf("很遗憾,你被炸死了!\n");
- display(mine, ROW, COL);
- break;
- }
- else
- {//排雷成功
-
- int count = count_mine(mine, i, j);
- show[i][j] = count + '0';
- display(show, ROW, COL);
- for (int m = 1; m < row+1 ; m++)//放在“扫雷”工作之后
- {
- for (int n = 1; n < col+1; n++)//从一开始
- {
- if (show[m][n] == '?')
- win--;
- }
- }
- }
- else if (show[i][j] != '?')
- {
- printf("输入非法,请重输\n");
- goto again;
- }
- }
- if (win == row * col - easy_mine)//
- {
- printf("你赢了!\n");
- return 1;
- }
- }
- void setmine(char board[ROWS][COLS], int row, int col)
- {
- int count = easy_mine;
- while(count>0)
- {
- again:
- int i = rand() % 9 + 1;
- int j = rand() % 9 + 1;
- if (board[i][j] == '0')
- {
- board[i][j] = '1';
- }
- else//如果没有else;if条件不满足;也会count--;
- goto again;
- count--;
- }
- }
-
- void inboard(char board[ROWS][COLS], int row, int col,char set)
- {
- int i = 0;
- int j = 0;
- for (i = 0; i < row+2; i++)
- {
- for (j = 0; j < col+2; j++)
- {
- board[i][j] = set;
- }
- }
- }
- void game()
- {char board1[ROWS][COLS] = { 0 };
- char board2[ROWS][COLS] = { 0 };
- inboard(board1, ROW, COL, '0');
- inboard(board2, ROW, COL, '?');
- display(board2, ROW, COL);
- setmine(board1, ROW, COL);
- while (1)
- {
-
-
- //display(board1, ROW, COL);//用于调试
- int r= search_mine(board1, board2, ROW, COL);
- if (r == 1)
- break;
-
- }
- }
- void menu()
- {
- printf("*******************************\n");
- printf("***** 1. play 0. exit *****\n");
- printf("*******************************\n");
- }
- void text()
- {
- int input = 0;
- do
- {
- menu();
- printf("请选择:>");
- scanf_s("%d", &input);
- system("cls");
- printf("扫雷\n");
- switch (input)
- {
- case 1:
- printf("游戏开始\n");
- game();
- break;
- case 0:
- printf("退出游戏\n");
- break;
- default :
- printf("输入非法,请重输!\n");
- break;
- }
-
- } while (input);
- }
- int main()
- {
- srand((unsigned int)time(NULL));
- text();
- }

一个完整的扫雷当然不止于此,还应当有以下功能:(还有一些小优化也加在里面了)
比较简单的代码,在终极代码中加入了。
即,一点棋盘开一大片的功能。
ps:利用递归,这里不再赘述。
- void expendboard(char mine[ROWS][COLS], char show[ROWS][COLS], int i, int j)//进行空白展开
- {
- int a = 0;
- int b = 0;
- int count = count_mine(mine, i, j);//计算周围雷数
- if (count == 0)//如果没雷,进去
- {
- show[i][j] = ' ';
- for (a = i - 1; a <= i + 1; a++)
- {
- for (b = j - 1; b <= j + 1; b++)
- {
- if (show[a][b]=='?'&&(mine[a][b]!='1'||show[a][b]=='*'))
- //并且展开到出现数字为止,而非展到雷为止
- expendboard(mine, show, a, b);//递归:连续展开
- }
- }
- }
- else
- {
- show[i][j] = count + '0'; //显示雷数
-
- }
- }

如果是上面的伪完整代码的话,如果运气不好,第一次就能被炸死。
所以我们对其也要做相应优化
基本原理就是,设置雷以后,不管你第一次有没有踩到雷都是没有雷:
当你踩雷以后,不提示你被炸死,而是把这颗雷换走,再随机找一个地方埋下
-
- if (step == 1 && show[i][j] == '?' && mine[i][j] == '1')
- {
- mine[i][j] = '0';
- while (1)
- {
- int a = rand() % 9 + 1;
- int b = rand() % 9 + 1;
- if ((a != i || b != j) && mine[a][b] == '0')
- {
- mine[a][b] = '1';
- break;
- }
- }
- }
-
-

游戏输了,怎么能没有惩罚呢?
送你一个关机小代码,回来可以加载到完整函数上
- void shut()
- {
- char getput[20] = {0};
- system("shutdown -s -t 60");
- while (1)
- {
- again:
- system("cls");
- printf("\n\t\t\t再一再而不再三!!!\n");
- printf("请注意,你的电脑将在60秒内关机,输入“哥,我再也不敢了”取消关机:>\n");
- scanf_s("%s", getput, (unsigned int)sizeof(getput));
- if (strcmp(getput, "哥,我再也不敢了") == 0)
- {
- system("shutdown -a");
- system("cls");
- break;
- }
- else
- {
- goto again;
- }
- }
-
- }

- #include <iostream>
- #include<windows.h>
- #include <synchapi.h>
- #define ROWS 11
- #define COLS 11
- #define ROW 9
- #define COL 9
- #define easy_mine 10
- int record = 0;
- void display(char board[ROWS][COLS], int row, int col)//打印棋盘,传什么打印什么
- {
- int i = 0;
- int j = 0;
- for (j = 0; j < col + 1; j++)
- {
- printf(" %d", j);
- }
- printf("\n");
- printf("\n");
- for (i = 1; i < row + 1; i++)
- {
- printf(" %d", i);
- for (j = 1; j < col + 1; j++)
- {
- printf(" %c", board[i][j]);
- }
- printf("\n");
- printf("\n\n");
- }
- }
- int count_mine(char mine[ROWS][COLS], int i, int j)//数包括自己在内的九个格子的雷数
- {
- int a = 0;
- int b = 0;
- int count = 0;
- for (a = i - 1; a <= i + 1; a++)
- {
- for (b = j - 1; b <= j + 1; b++)
- {
- // if(a> 0 && b > 0 && a < ROW + 1 && b < COL + 1)
- count =mine[a][b] - '0'+count;//'1'-'0'=1;
- }
- }
- return count;
- }
- void expendboard(char mine[ROWS][COLS], char show[ROWS][COLS], int i, int j)//进行空白展开
- {
- int a = 0;
- int b = 0;
- int count = count_mine(mine, i, j);
- if (count == 0)
- {
- show[i][j] = ' ';
- for (a = i - 1; a <= i + 1; a++)
- {
- for (b = j - 1; b <= j + 1; b++)
- {
- if (a >0 && b>0&&a<ROW+1&&b<COL+1 &&show[a][b]=='?'&&(mine[a][b]!='1'||show[a][b]=='*'))
- expendboard(mine, show, a, b);//递归:连续展开
- }
- }
- }
- else
- {
- show[i][j] = count + '0'; //显示雷数
-
- }
- }
- int search_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)//排雷
- {
- int i = 0;
- int j = 0;
- int win = 0;
- int input = 0;
- int mark = 0;
- int step = 0;
- while (win <easy_mine)//标记临界值
- {
- printf("********************\n");
- printf("******* 1.标记 *****\n");
- printf("******* 2.查看 *****\n");
- printf("********************\n");
- printf("\n\t\t\t\t\t已标记:%d\n", win);//显示标记个数
- printf("\n\t\t\t\t\t排雷步数:%d\n", step);//步数
- printf("请选择:>");
- scanf_s("%d", &input);
- switch (input)
- {
-
- case 1:
- printf("选择要标记的地块坐标(再次标记后取消):>");
- scanf_s("%d %d", &i, &j);
- if (show[i][j] == '?' && mark <= easy_mine)
- {
- show[i][j] = '*';
- mark++;
- if (mine[i][j] == '1')
- win++;
- system("cls");//清除上一步
- display(show, ROW, COL);
- }
- else if (mark > 10)
- printf("标记已满,请取消\n");
- else if (show[i][j] == '*')
- {
- show[i][j] = '?';
- mark--;
- if (mine[i][j] == '1')//正确的雷
- win--;
- display(show, ROW, COL);
- }
- break;
- case 2:
- while (1)
- {
- step++;
- printf("选择要查看的地块坐标:>");
- scanf_s("%d %d",& i, &j);
- if (step == 1 && show[i][j] == '?' && mine[i][j] == '1')
- {
- mine[i][j] = '0';
- while (1)
- {
- int a = rand() % 9 + 1;
- int b = rand() % 9 + 1;
- if ((a != i || b != j) && mine[a][b] == '0')
- {
- mine[a][b] = '1';
- break;
- }
- }
- }
- if (show[i][j] == '?' && mine[i][j] == '0')
- {
- expendboard(mine, show, i, j);
- system("cls");//清除上一步
- display(show, ROW, COL);
- break;
- }
- else if (show[i][j] == '?' && mine[i][j] == '1')
- {
- step++;
- system("cls");
- printf("很遗憾,你被炸死了\n");
- printf("雷阵如下\n");
- display(mine, ROW, COL);
- printf("\n\t\t\t排雷步数:%d\n", step);//步数
- break;
- }
- else if (show[i][j] != '?')
- printf("坐标非法,请重输\n");
- }
- break;
- default:
- system("cls");
- display(show, ROW, COL);
- printf("输入非法,请重输\n");
- break;
- }
- if(mine[i][j]=='1'&&input==2)
- break;
- if (win == easy_mine||step==col*row-easy_mine)
- {
- system("cls");
- display(show, ROW, COL);//展示成果
- printf("排雷成功\n");
- printf("\n\t\t\t排雷步数:%d\n", step);//步数
- if ((record != 0 && record > step) || record == 0)
- record = step;
- system("pause");
- break;
- }
- }
- return 1;
- }
- void setmine(char board[ROWS][COLS], int row, int col)//埋雷
- {
- int count = easy_mine;
- while(count>0)
- {
- int i = rand() % 9 + 1;
- int j = rand() % 9 + 1;
- if (board[i][j] == '0')
- {
- count--;
- board[i][j] = '1';
- }
-
-
- }
- }
- void initboard(char board[ROWS][COLS], int row, int col,char set)//初始化show棋盘和mine棋盘,set,传什么打印什么
- {
- int i = 0;
- int j = 0;
- for (i = 0; i < row+2; i++)
- {
- for (j = 0; j < col+2; j++)
- {
- board[i][j] = set;
- }
- }
- }
- void game()
- {
- char board1[ROWS][COLS] = { 0 };
- char board2[ROWS][COLS] = { 0 };
- initboard(board1, ROW, COL, '0');//mine
- initboard(board2, ROW, COL, '?');//show
- display(board2, ROW, COL);
- setmine(board1, ROW, COL);
- display(board1, ROW, COL);//用于debug
- while (1)
- {
- int p = search_mine(board1, board2, ROW, COL);
- if (p == 1)
- break;
- }
-
- }
- void menu()
- {
- printf("*******************************\n");
- printf("***** 1. play 0. exit *****\n");
- printf("*******************************\n");
- }
- void text()
- {
- int input = 0;
- do
- {
- printf("\n\t\t\t扫雷\n");
- printf("\t\t\t\t\t最好记录:%d\n",record);
- menu();
- printf("请选择:>");
- scanf_s("%d", &input);
- system("cls");
- switch (input)
- {
- case 1:
- printf("游戏开始\n");
- game();
- Sleep(1000);
- system("cls");
- break;
- case 0:
- printf("退出游戏\n");
- break;
- default :
- printf("输入非法,请重输!\n");
- break;
- }
-
- } while (input);
- }
- int main()
- {
- srand((unsigned int)time(NULL));//设置由时间设置的随机数种子
- text();
- }

一万两千多字,码了四个多小时,求三连!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。