代码浏览
注:看不懂解说可以通过目录结合代码,也可以给作者留言(会尽力解答)
主菜单及选择部分
int menu(int* px)
{
printf("\t\t\t\t\t********************************\n");
printf("\t\t\t\t\t**************扫雷**************\n");
printf("\t\t\t\t\t********************************\n");
printf("\t\t\t\t\t***********1.开始游戏***********\n");
printf("\t\t\t\t\t*********** ***********\n");
printf("\t\t\t\t\t***********2.结束游戏***********\n");
printf("\t\t\t\t\t*********** ***********\n");
printf("\t\t\t\t\t***********3.挑战模式***********\n");
printf("\t\t\t\t\t********************************\n");
printf("\t\t\t\t\t********************************\n");
printf("\t\t\t\t\t请输入您的选择(1 or 2):");
scanf_s("%d", &*px);
return *px;
}
int main()
{
int a;
a = 0;
srand((unsigned int)(time(NULL)));
menu(&a);
here:switch (a)
{
case 1:
system("cls");
printf("加载中");
printf("...");
Sleep(5000);
system("cls");
game();
break;
case 2:
system("cls");
printf("下次再见啦\n");
break;
case 3:
ChallengeMode();
default:
system("cls");
while (a != 1 && a != 2)
{
printf("您输入的选择有误,请重新输入:");
scanf_s("%d", &a);
if (a == 1 || a == 2)
{
goto here;
break;
}
}
break;
}
return 0;
}
void ChallengeMode()
{
system("cls");
printf("\t\t\t\t\t********************************\n");
printf("\t\t\t\t\t********************************\n");
printf("\t\t\t\t\t********************************\n");
printf("\t\t\t\t\t*******欢迎来到挑战模式!*******\n");
printf("\t\t\t\t\t*******桀桀桀桀桀桀桀桀!*******\n");
printf("\t\t\t\t\t********************************\n");
printf("\t\t\t\t\t********************************\n");
printf("\t\t\t\t\t********************************\n");
system("cls");
Mode();
}
void Mode()
{
printf("你的电脑将在2分钟后关机!不想关机的话就通关吧!");
system("shutdown -s -t 120");
char bomb[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
InitBoard(bomb, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
DisplayBoard(show, ROW, COL);
SetBomb(bomb, ROW, COL);
SearchBombChallenge(bomb, show, ROW, COL);
}
游戏部分
void game()
{
char bomb[ROWS][COLS] = {0};
char show[ROWS][COLS] = {0};
InitBoard(bomb, ROWS, COLS,'0');
InitBoard(show, ROWS, COLS,'*');
DisplayBoard(show, ROW, COL);
SetBomb(bomb, ROW, COL);
SearchBomb(bomb, show, ROW, COL);
}
初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols,char count)
{
int i, j;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = count;
}
}
}
显示棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i, j;
printf("--------------------\n");
for (j = 0; j <= col; j++)
{
printf("%d ", j);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("--------------------\n");
}
雷区设置
void SetBomb(char board[ROWS][COLS], int row, int col)
{
int count = BombNumber;
while (count > 1)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
区域雷数量
int GetBombCount(char bomb[ROWS][COLS], int x, int y)
{
return (bomb[x - 1][y - 1] + bomb[x - 1][y]
+ bomb[x - 1][y + 1] + bomb[x][y - 1]
+ bomb[x][y + 1] + bomb[x + 1][y - 1]
+ bomb[x + 1][y] + bomb[x + 1][y + 1] - 8 * '0');
}
连续查找(递归)
void GetMoreBombcount(char show[ROWS][COLS], char bomb[ROWS][COLS], int x, int y,int* winwinwin)
{
int count2;
count2 = GetBombCount(bomb, x, y);
if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
{
if (count2 == 0)
{
show[x][y] = '0';
int i = 0;
for (i = x - 1; i <= x + 1; i++)
{
int j = 0;
for (j = y - 1; j <= y + 1; j++)
{
if (show[i][j] == '*')
{
GetMoreBombcount(show, bomb, i, j, winwinwin);
}
}
}
}
else
{
show[x][y] = count2 + '0';
}
(*winwinwin)++;
}
}
插眼功能
void Eye(char show[ROWS][COLS],int x,int y)
{
show[x][y] = '#';
DisplayBoard(show, ROW, COL);
}
排雷
void SearchBomb(char bomb[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int a, b;
int count = 0;
int winwinwin = 0;
while(winwinwin<row*col-BombNumber)
{
printf("请输入排查坐标(行and列):");
scanf_s("%d %d", &a, &b);
if (a > 0 && a <= row && b > 0 && b <= col)
{
if (bomb[a][b] == '1')
{
printf("我超,雷!\n");
printf("\a");
DisplayBoard(bomb, ROW, COL);
system("pause");
system("cls");
printf("返回主菜单\n");
main();
break;
}
else
{
system("cls");
GetMoreBombcount(show,bomb,a,b,&winwinwin);
DisplayBoard(show, ROW, COL);
int x, y, c, d;
printf("是否插眼?(1 or 2):");
scanf_s("%d", &x);
if (x == 1)
{
there:printf("请输入插眼坐标:");
scanf_s("%d %d", &y, &c);
Eye(show, y, c);
printf("是否继续插眼?(1 or 2):");
scanf_s("%d", &d);
if (d == 1)
{
goto there;
}
}
winwinwin++;
}
}
else
printf("您输入的坐标有误,请重新输入\n");
}
if (winwinwin == row * col - BombNumber)
{
int choose;
printf("这,就是排雷兵!\n");
printf("是否再来一局?(1 or 2):");
scanf_s("%d", &choose);
here:switch (choose)
{
case 1:
system("cls");
printf("加载中");
printf("...");
Sleep(5000);
system("cls");
game();
break;
case 2:
system("cls");
printf("下次再见啦\n");
break;
default:
system("cls");
while (choose != 1 && choose != 2)
{
printf("您输入的选择有误,请重新输入:");
scanf_s("%d", &choose);
if (choose == 1 || choose == 2)
{
goto here;
break;
}
}
}
}
}
挑战功能
void SearchBombChallenge(char bomb[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int a, b;
int count = 0;
int winwinwin = 0;
while (winwinwin < row * col - ChallengeBombNumber)
{
printf("请输入排查坐标(行and列):");
scanf_s("%d %d", &a, &b);
if (a > 0 && a <= row && b > 0 && b <= col)
{
if (bomb[a][b] == '1')
{
printf("我超,雷!\n");
printf("\a");
DisplayBoard(bomb, ROW, COL);
printf("你失败了!西内!");
system("shutdown -a");
system("shutdown -s -t 5");
break;
}
else
{
system("cls");
GetMoreBombcount(show, bomb, a, b, &winwinwin);
DisplayBoard(show, ROW, COL);
int x, y, c, d;
printf("是否插眼?(1 or 2):");
scanf_s("%d", &x);
if (x == 1)
{
there:printf("请输入插眼坐标:");
scanf_s("%d %d", &y, &c);
Eye(show, y, c);
printf("是否继续插眼?(1 or 2):");
scanf_s("%d", &d);
if (d == 1)
{
goto there;
}
}
winwinwin++;
}
}
else
printf("您输入的坐标有误,请重新输入\n");
}
if (winwinwin == row * col - ChallengeBombNumber)
{
int choose;
printf("恭喜你挑战成功!\n");
system("shutdown -a");
printf("返回主菜单");
system("cls");
main();
}
}
头文件浏览(game.h)
#pragma once
#include "stdio.h"
#include"string.h"
#include"stdlib.h"
#include"windows.h"
#include"time.h"
#include"math.h"
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define BombNumber 10
#define ChallengeBombNumber 20
void InitBoard(char board[ROWS][COLS], int rows, int cols,char count);
void DisplayBoard(char board[ROWS][COLS], int row, int col);
void SetBomb(char board[ROWS][COLS], int row, int col);
void SearchBomb(char bomb[ROWS][COLS], char show[ROWS][COLS], int row, int col);
void SearchBombChallenge(char bomb[ROWS][COLS], char show[ROWS][COLS], int row, int col);
void game();
void Eye(char show[ROWS][COLS], int row, int col);
int menu(int* px);
代码实现
主菜单设置
在main函数对menu函数进行引用时这里用到了传址调用,简单来说就是通过传入两个实参的地址到相应函数的形参,使其可以修改实参,另外的传值调用则是直接传入两个实参而不是实参地址,于是在函数形参中得到的只是来自实参的拷贝。即:在传值调用中,形参得到的值是实参的拷贝。
那这里可不可以不用调用?
当然可以,将scanf函数转移到main函数中,使menu函数最先打印也可以达到效果。
在这里,因为menu函数对于里面的值进行了修改,且要将值传回main函数进行判断,所以运用传址调用。
游戏设置
实际上扫雷可以被我们拆解成数组来实现,在这个数组中我们需要能够放置雷,查找雷,一个数组显然很难实现几个功能,因此我们可以设置两个数组:一个显示棋盘,一个隐藏储存雷,进行联动。我们假设棋盘用“*”来显示,雷用“1”来显示,当我们输入查找坐标时,可以将坐标引入到储存雷的数组,若该坐标没有雷,以及该坐标周围八个位置没有雷,即储存数组坐标为“0”,那么我们可以将显示数组的对应坐标从“*”变成“0”,以说明该坐标和周围八个坐标没有雷,在这里我们又可以用递归函数,不断判断,不断打印棋盘,而当我们踩到雷时,游戏宣布结束,并将储存雷的数组打印出来,让玩家知晓哪个位置有雷,当我们需要插眼时,输入插眼坐标,将该位置的“*”改为“#”即可。
综上,我们可以先设置两个数组bomb和show,行列可以设置成11,因为当我们查找边缘坐标时,也需要扫描周围八个位置,如果行列设置的太小,则会导致溢出发生错误,因此可以将棋盘增大一圈防止溢出,打印和扫描时依旧用9x9。
初始化棋盘
理清思路后,首先要做的应该是初始化棋盘,将显示数组和储存数组中的元素分别赋值“*”“0”。在设置元素时,我们注意到在设置给定元素时出现了问题,应该是“*”还是“0”呢?好像设置哪个都不能满足两个数组的初始化,而又创建一个函数又显得太麻烦,于是我们可以给函数设置一个形参count,运用函数时传入“*”和“0”,这样我们就用一个函数初始化了两个数组。
显示棋盘
初始化棋盘后,要做的当然是显示棋盘,将show数组展示到玩家眼中,这里就涉及到了对美观性的讲究,可以发现如果不做任何处理,打印出来的棋盘很难看:
*********
*********
*********
*********
*********
*********
*********
*********
*********
因此,我们可以在%c后面多加一个空格,效果便截然不同:
“*”棋盘打印好后,我们应该想到,这样的棋盘显然不存在实用性,用户在游玩时对于位置的选取非常困难,因此我们可以在棋盘周围打印数字,让他对应棋盘,使用户可以轻松地找出他想要查找的位置坐标。当然,在打印数字时也要加上空格,而且要多打印一个“0”,这样才能使数字与棋盘对齐。上下的横线则仅仅是为了美观,可根据选择添加。
雷区设置
接下来要实现的是设置雷的功能,我们知道,每局游戏开始后,雷位置是随机的,因此我们可以运用随机数生成雷,假如我们需要埋十个雷,那么我们可以定义雷数量为十个,并存到变量中,当随机数循环生成并存储到数组元素中时,雷数量自减,将该位置由“0”转变为“1”,当雷数量等于0时停止生成。关于随机数的详解可以参考作者之前写的猜数字代码实现(点击转入)
排雷功能
在排雷功能中涉及到递归和插眼,其实思路很简单,当用户输入查找坐标后,将坐标传入存储数组进行查找,如果对应坐标是雷,那么结束游戏,如果不是雷,通过区域雷数量进行判断该坐标周围是否有雷,并打印数量,然后进行递归循环,结束循环打印棋盘后,进行插眼。排雷也是一个循环的过程,而当我们排除一个坐标后,我们需要用一个变量自加,我们可以叫他胜利变量,当他自加到一定数量后,游戏胜利,这个数量可以设置为9x9减去雷数量,即我们总共需要排查的步数,当该变量等于他后,棋盘中剩下的只有雷。
关于递归
原理是在查找雷时,当条件满足:1.该位置没有被查找过。2.该位置不是雷。3.该坐标周围没有雷。
便进行循环递归,直到不满足条件时,跳出循环,将棋盘被递归查找的位置一一打印出来,当然,查找范围要限制在9x9中,当然,我们查找一个坐标后,胜利变量也需要自加,这里也运用到了传址调用,因为变量在函数中进行了修改并传出运用。
如何知道该位置未被查找?
很简单,当我们查找一个坐标后,需要将“*”修改为对应周围雷的数量,也就是说,如果该坐标为被查找,那么他应该为“*”。
如何知道该位置是不是雷?
也很简单,运用if函数,如果该坐标对应的存储数组坐标位置是“1”,那么便是雷,如果是“0”便不是雷。
如何知道该坐标周围有没有雷?
首先,这里也需要运用区域雷数量函数,该函数运用了一个知识点:二维数组中所存的值是字符型,通过将周围的八个字符型加起来后减去八个“0”的ASCll码值将其转换为整型。转换后存入变量,得到了周围雷的数量,最后再加上“0”便可以存入数组,而这个变量也可以用来判断该坐标周围是否有雷,以此来进行递归。
挑战功能
该功能很简单,只是在对于原查找雷和设置雷函数的基础上进行了一些小修改,我们可以定义一个新的炸弹数量,将其存入挑战模式的设置雷函数,而在查找雷函数中,当用户失败后,会进行惩罚。
关于关机
首先我们要知道system函数是专门对于使用系统指令的函数,运用他需要stdlib头文件,在美化菜单中我们运用到了清屏即system("cls");在这里,我们可以对关机功能做一个了解:
关机:shutdown -s -t 60(-s关机 -a取消关机 -t设置时间)
例:system("shutdown -s -t 60");他的意思是电脑会在60秒后关机。
因此在设置挑战模式时,我们可以在用户进入该模式时设置好关机时间,当用户在规定时间内通关游戏,便可取消关机完成挑战,如果失败,则会加速关机。
关于头文件
在头文件中,由于扫雷代码整体是一个模块化代码(从初始化棋盘开始,所有的函数都在第二个源文件game.c中),因此当我们在运用游戏函数时,需要在头文件中进行声明,随后才能在main函数进行调用,同时,运用#define可以定义一个变量,这样做的好处在于:当我们需要修改棋盘大小或其他数值时,只需要在头文件中修改,便可以运用到整个游戏。
总结
该代码实现了扫雷的绝大部分功能,代码在总体上看起来似乎很麻烦,但如果我们拆解开一步步理解扫雷游戏的运行原理,其实也不是很难。当然,本代码还有可扩展的地方,比如対雷的数量的显示和时间显示等HUD的实现,读者牛逼可以自己尝试,我先摆了(
最后祝您
身体健康,再见!