目录
1.扫雷游戏的介绍
2.代码的实现
2.1菜单与游戏入口
?2.2棋盘的初始化
2.3放置地雷
2.4打印地图
2.5正式扫雷
?3.代码分享
3.1game.h
3.2game.c
3.3test.c
1.扫雷游戏的介绍
? 小时候谁不会在电脑课上偷偷点开Windows菜单里的游戏列表呢。蜘蛛纸牌,三维弹球,扫雷,空当接龙...一个个经典小游戏想必是印象深刻了。今天我就带大家还原一下Windows自带小游戏中的经典:扫雷。
? 虽然说扫雷这游戏很多人都玩过,但可能有些人还不知道游戏规则吧。小时候经常点开扫雷一顿左键操作,最后趟雷而死。其实,扫雷的游戏规则很简单。
(游戏界面)
?
? 在一片雷区上,左键点击一块区域,在该区域排雷,若没有雷则会显示附近8个区域地雷的数量,即“扫”雷。
?
若是不小心踩中地雷,就会直接被“炸死”。
而我们的目标,就是在不踩雷的前提下,一步步探索区域,直至将所有区域探明,就可以取得胜利。?
?
? ?说完了游戏规则,我们使用VS进行扫雷的实现。
2.代码的实现
2.1菜单与游戏入口
要复刻一个扫雷游戏,我们得一步步完成。
? 首先,我们需要一个菜单与进入游戏的入口。这里我们在主函数内实现,并创造一个menu函数打印菜单。
? 在主函数内部,我们使用do...while语句与switch语句,达到循环游玩以及选择。? ? ? ?
void menu()
{
printf("****************\n");
printf("*****1.play*****\n");
printf("*****2.exit*****\n");
printf("****************\n");
}
int main()
{
int input = 0;
do
{
menu();//打印菜单
printf("请输入->:");
scanf("%d", input);
switch (input)
{
case 1:
game();//进入游戏主体函数
break;
case 0:
printf("退出游戏\n");
break;
default :
printf("输入错误,请重新选择\n");
break;
}
} while (input);//当input==0时结束循环,退出游戏
}
?2.2棋盘的初始化
? 完成了棋盘的打印,接下来我们就实现棋盘的初始化。这里我们尝试构造一个二维数组作游戏雷区的储存与展现。这里我们初始化游戏的初级难度版本,区域大小为9*9,地雷数量为10,所以我们的二维数组初始化如下。
void game()
{
char mine[9][9] = { 0 };
}
我们设想在没有地雷的位置放上0,有地雷的位置放上1,以作区别。
? 我们知道,扫雷游戏在每走一步时就会给出周围地雷的数量,那么,我们可以通过遍历区域周围的八个区域来计算该区域应该显示的数字。
? ?例如图中蓝色区域,周围有两个雷,我们便在点击蓝色区域后在该区域显示数字2。但是这样,周围一圈的区域在遍历周围区域时,就会出现数组越界现象:
? ?例如图中的蓝色区域,其右侧的三个元素均不在数组范围内,这样会导致程序无法进行下去。
? ?所以,我们在这里将数组大小扩大一圈,形成一个11*11大小的数组,并且在最外圈赋予初值0,这样就不会干扰到正常的地雷数量判断,也不会造成数组越界了。
所以我们将原来的数组大小进行修改:?
void game()
{
char mine[11][11] = { 0 };
}
? ?但是,还有一个问题需要解决。当我们点击一块区域前,若该地区没有地雷,那么该地区保存的是字符0。点击后,若周围有一颗雷,那么就会改变为字符1,这样子,雷会不会越扫越多呢?(笑)
? 这里我们构造一个show数组,作为玩家所能看到的数组,向内填充*字符,在玩家点击区域后显示数字表示附近地雷的数量。
? ? ?此外,为了日后更改地图大小方便,这里我们采用宏定义的方式定义行与列的大小:
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
然后,用字符0和字符*填充两个数组
void game()
{
char mine[ROWS][COLS] = { 0 };//存放地雷信息的界面
char show[ROWS][COLS] = { 0 };//玩家所能看到的界面
InitBoard(mine,ROWS ,COLS,'0');
InitBoard(show,ROWS,COLS, '*');
}
void InitBoard(char arr[ROWS][COLS],int row,int col,char ch)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
arr[i][j] = ch;
}
}
}
2.3放置地雷
初始化好我们的棋盘,我们便可以将地雷埋下。这里我们使用srand函数生成随机数。(注意这里要引用头文件time.h与stdlib.h)
srand函数:
srand((unsigned int)time(NULL));
? 再构造SetMine函数,放置地雷
? 这里地雷坐标范围控制应该控制在mine[1][1]到mine[9][9]内。因为在初始化时,我们将地图大小设置为11*11,而最外面一圈我们不能埋地雷,所以使用rand函数得到随机数后对9取模得到0~8的随机数,再加1,得到1~9的随机数。
void SetMine(char arr[ROWS][COLS],int row,int col)
{
int count = COUNT;//利用宏定义COUNT大小,方便日后修改
while (count > 0)
{
int x = rand() % 9 + 1;//控制埋地雷的范围
int y = rand() % 9 + 1;
if (arr[x][y] != '1')//该位置有地雷就不再放置
{
arr[x][y] = '1';//放置地雷
count--;//控制循环结束
}
}
}
2.4打印地图
? 我们构造一个DisplayBoard函数进行地图的打印,注意控制打印的范围
void DisplayBoard(char arr[ROWS][COLS],int row,int col)
{
int i = 0;
int j = 0;
for (i = 1; i < row-1; i++)
{
printf("%d",i);
for (j = 1; j < col-1; j++)
{
printf("%c ", arr[i][j]);
}
printf("\n");
}
}
? 这样,在game函数内加上DisplayBoard,就可以打印我们的地图了。
void game()
{
?? ?char mine[ROWS][COLS] = { 0 };//构造储存地雷数据的界面
?? ?char show[ROWS][COLS] = { 0 };//构造玩家可见的界面
?? ?InitBoard(mine,ROWS ,COLS,'0');//初始化数组
?? ?InitBoard(show,ROWS,COLS, '*');//初始化数组
?? ?SetMine(mine, ROWS, COLS);//埋地雷
?? ?DisplayBoard(mine, ROWS, COLS);//显示地图
?? ?DisplayBoard(show, ROWS, COLS);//显示地图
}
? 但是,地图好像还不方便我们寻找坐标。所以我们在这里稍作修改?
void DisplayBoard(char arr[ROWS][COLS],int row,int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row - 1; i++)//在第一行前插入一排数字,表示第几列
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i < row-1; i++)
{
printf("%d ",i);//在每一行最前面加上数字,表示第几行
for (j = 1; j < col-1; j++)
{
printf("%c ", arr[i][j]);
}
printf("\n");
}
}
? 这样我们的地图就更加直观了:?
2.5正式扫雷
? 放好了我们的地雷,接下来进入到玩家行动环节。
? 这个环节,玩家先进行坐标的选取,然后程序判断该坐标是否为地雷,若是地雷则玩家失败,否则将会在地图上将该坐标附近的地雷个数显示出来。循环以上步骤,直至玩家取得胜利。
? 我们构造一个Move函数,实现以上步骤。
? 首先,让玩家进行坐标的选择,因为站在玩家的视角,TA所选择的坐标范围在[1][1]到[9][9]之间,所以这里我们形参的行与列选择传入ROW与COL,即9与9。并传入mine与show两个数组。
void Move(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
? ?然后就是判断玩家所选坐标的情况,玩家可能选择超出范围的坐标,如果没有超出坐标,若是踩中地雷则游戏结束。最后一种情况便是玩家没有踩中地雷,游戏继续。
int x=0;
int y=0;
printf("请选择坐标->;");
scanf("%d%d", &x, &y);//读取玩家选择的坐标
if ((x >= 1 && x <= row) && (y >= 1 && y <= col))//判断坐标是否超出范围
{
if (mine[x][y] == '1')//判断该坐标是否为地雷
{
printf("很遗憾,你被炸死了\n");
DisplayBoard(mine, ROWS, COLS);//显示地雷位置,让玩家输的明白
break;
}
else
{
show[x][y]= MinesNumber(mine, x, y)+'0';//用MinesNumber函数判断
//周围地雷数量并且对该位
//置信息进行更改
DisplayBoard(show, ROWS, COLS);//打印地图供玩家下一步行动
}
}
else
printf("坐标超出范围\n");
? ?如果玩家没有被炸,这里我们需要将show数组中玩家选中的坐标修改为周围地雷的数量,这里我们构造一个MinesNumber函数,该函数可以计算传入坐标附近的地雷数量并且返回一个整形。
? 注意我们mine,show数组内存放的都是字符,而MinesNumber函数返回的是整形。因为在ASCII表里,‘1’和‘0’相差1,所以我们可以通过减去元素个数个字符0从而得到需要的整形,所以我们在返回时减去9个字符0(这里mine[i][j]一定是'0',故直接减去9个)。
int MinesNumber(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++)
{
count += mine[i][j];
}
}
return count - 9 * '0';//减去总元素个数个(9个)字符0
}
? ?接下来,就要编写玩家胜利的情况,胜利条件为玩家移动步数=总格子数量-地雷数。
? 所以这里我们使用while循环,当玩家移动步数<总格子数量-地雷数时继续循环,当循环结束,判断玩家是否胜利。
void Move(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int move = 0;
while (move<row*col-COUNT)//当玩家没有胜利时,循环内容
{
printf("请选择坐标->;");
scanf("%d%d", &x, &y);
if ((x >= 1 && x <= row) && (y >= 1 && y <= col))//判断坐标是否超出范围
{
if (mine[x][y] == '1')//判断玩家是否踩中地雷
{
printf("很遗憾,你被炸死了\n");
DisplayBoard(mine, ROWS, COLS);//显示地雷位置
break;
}
else
{
show[x][y]= MinesNumber(mine, x, y)+'0';//用MinesNumber函数判断
//周围地雷数量并且对该位
//置信息进行更改
DisplayBoard(show, ROWS, COLS);//打印地图供玩家下一步行动
move++;
}
}
else
printf("坐标超出范围\n");
}
if (move == row * col - COUNT)//当移动步数等于格子数减去地雷数时,玩家获胜
{
printf("恭喜你,你没有被炸死!\n");
}
}
? 但是,这里还有一个严重的bug,就是当玩家重复选择同一坐标时,count也会进行计数。所以我们对代码进行一下修改。
? ?我们在while语句内加入一条判断,若show数组内该坐标的元素不是*则说明已经踩过,就不进行计数。
else if (show[x][y] != '*')//判断该坐标是否已经输入过,若是
//输入过则进入该判断move不会增加
{
DisplayBoard(show, ROWS, COLS);
}
? 最后,我们的Move函数就构造好了
void Move(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int move = 0;
while (move<row*col-COUNT)
{
printf("请选择坐标->:");
scanf("%d%d", &x, &y);
if ((x >= 1 && x <= row) && (y >= 1 && y <= col))//判断坐标是否超出范围
{
if (mine[x][y] == '1')//判断玩家是否踩中地雷
{
printf("很遗憾,你被炸死了\n");
DisplayBoard(mine, ROWS, COLS);//显示地雷位置
break;
}
else if (show[x][y] != '*')//判断该坐标是否已经输入过,若是
//输入过则进入该判断move不会增加
{
DisplayBoard(show, ROWS, COLS);
}
else
{
show[x][y]= MinesNumber(mine, x, y)+'0';//用MinesNumber函数判断
//周围地雷数量并且对该位
//置信息进行更改
DisplayBoard(show, ROWS, COLS);//打印地图供玩家下一步行动
move++;
}
}
else
printf("坐标超出范围\n");
}
if (move == row * col - COUNT)//当移动步数等于格子数减去地雷数时,玩家获胜
{
printf("恭喜你,你没有被炸死!\n");
}
}
? ?这样,我们的扫雷游戏就算是做好了。
?3.代码分享
? 最后是代码的分享。
? 我创建了3个文件,分别是 头文件game.h,存放函数定义的game.c,存放主函数的test.c,对应的代码放在下面。
3.1game.h
#pragma once
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define COUNT 10
void menu();
void InitBoard(char arr[ROWS][COLS], int row, int col, char ch);
void SetMine(char mine[ROWS][COLS], int row, int col);
void DisplayBoard(char arr[ROWS][COLS], int row, int col);
void Move(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
int MinesNumber(char mine[ROWS][COLS], int x, int y);
3.2game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
printf("****************\n");
printf("*****1.play*****\n");
printf("*****0.exit*****\n");
printf("****************\n");
}
void InitBoard(char arr[ROWS][COLS],int row,int col,char ch)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
arr[i][j] = ch;
}
}
}
void SetMine(char arr[ROWS][COLS],int row,int col)
{
int count = COUNT;
while (count > 0)
{
int x = rand() % 9 + 1;//控制埋地雷的范围
int y = rand() % 9 + 1;
if (arr[x][y] != '1')//该位置有地雷就不再放置
{
arr[x][y] = '1';
count--;
}
}
}
void DisplayBoard(char arr[ROWS][COLS],int row,int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row - 1; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i < row-1; i++)
{
printf("%d ",i);
for (j = 1; j < col-1; j++)
{
printf("%c ", arr[i][j]);
}
printf("\n");
}
}
int MinesNumber(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++)
{
count += mine[i][j];
}
}
return count - 9 * '0';
}
void Move(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int move = 0;
while (move<row*col-COUNT)
{
printf("请选择坐标->:");
scanf("%d%d", &x, &y);
if ((x >= 1 && x <= row) && (y >= 1 && y <= col))//判断坐标是否超出范围
{
if (mine[x][y] == '1')//判断玩家是否踩中地雷
{
printf("很遗憾,你被炸死了\n");
DisplayBoard(mine, ROWS, COLS);//显示地雷位置
break;
}
else if (show[x][y] != '*')//判断该坐标是否已经输入过,若是
//输入过则进入该判断move不会增加
{
DisplayBoard(show, ROWS, COLS);
}
else
{
show[x][y]= MinesNumber(mine, x, y)+'0';//用MinesNumber函数判断周围地雷数量
并且对该位置信息进行更改
DisplayBoard(show, ROWS, COLS);//打印地图供玩家下一步行动
move++;
}
}
else
printf("坐标超出范围\n");
}
if (move == row * col - COUNT)//当移动步数等于格子数减去地雷数时,玩家获胜
{
printf("恭喜你,你没有被炸死!\n");
}
}
void game()
{
char mine[ROWS][COLS] = { 0 };//构造储存地雷数据的界面
char show[ROWS][COLS] = { 0 };//构造玩家可见的界面
InitBoard(mine,ROWS ,COLS,'0');//初始化数组
InitBoard(show,ROWS,COLS, '*');//初始化数组
SetMine(mine, ROWS, COLS);//埋地雷
DisplayBoard(show, ROWS, COLS);//显示地图
Move(mine, show, ROW, COL);//玩家行动
}
3.3test.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();//打印菜单
printf("请输入->:");
scanf("%d", &input);
switch (input)
{
case 1:
game();//进入游戏主体函数
break;
case 0:
printf("退出游戏\n");
break;
default :
printf("输入错误,请重新选择\n");
break;
}
} while (input);//当input==0时结束循环,退出游戏
}
|