先看一波效果图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/69ac7e48f7774136bfb938c56402844b.gif#pic_center)
话不多说,接下来开撸。
1.编写分析:
先看扫雷基本规则:
- 玩家在一定的规格棋盘中扫雷
- 玩家下棋后显示周围雷数或扫到雷则游戏结束
玩家根据棋盘上周围雷数推理藏雷的格子,插祺- 当棋盘满格时,显示输赢。
由规则可以看出,在编写当中需要解决的问题:
- 需要棋盘作为游戏主要承载体,后续操作在此棋盘中进行。
- 玩家下棋后,我们需要访问周围格子是否存在雷,并累加在当前棋子显示。若那棋格存在雷则爆炸,游戏结束
- 当棋盘上只有雷位置未动时游戏提示输赢。
大致分析已经完成。我们使用多文件形式来实现这个扫雷游戏。
多文件编程:
main.c
(游戏开始、难度选择、游戏运行逻辑、测试)game.h
(游戏功能函数定义、头文件)game.c
(游戏功能的函数实现)
2.代码实现及逻辑
2.1 游戏界面及难度选择
先创建一个main.c
作为游戏开始界面,并使用do…while函数实现玩家选择游戏难度前后或在输(赢)后,继续游戏。
int main(int argc, char** argv)
{
int i = 0;
do
{
print();
printf("待输入->");
scanf("%d", &i);
switch (i)
{
case 1:
if (gameDif())
break;
game();
break;
default:
break;
}
} while (i);
}
print函数随意定义即可,用于提示玩家输出 1 或 0,进行进入游戏或退出,
switch
语句用于接受到玩家输入的信息进入下一个阶段。
gameDif函数会根据玩家选择的难度继而定义棋盘的规格,当玩家选择0时返回1,进而break
返回到游戏开始界面。
在编写gameDif函数之前我们需要定义一个全局变量,这样才能玩家选择难度后并赋值,其余文件才能够使用到棋盘规格。
int RowCol;
在定义棋盘规格前,我们需要思考玩家下在边角及边框的棋子该怎么解决问题2,正常的棋子只需要访问八个周围棋格,而边角或边框则访问3个或5个棋格。那么可以把棋盘做大一圈,将雷不予放入外圈,玩家也不能将棋下到外圈,也不把外圈显示给玩家。这样就所有棋子都访问8个棋格
int gameDif(){
printf("***游戏难度***\n");
printf("****3.困难****\n");
printf("****2.中等****\n");
printf("****1.简单****\n");
printf("****0.返回****\n");
int i = 0;
beReborn:
printf("待输入->");
scanf("%d", &i);
switch (i)
{
case 0:
return 1;
case 3:
RowCol = 10;
break;
case 2:
RowCol = 8;
break;
case 1:
RowCol = 6;
break;
default:
printf("选择错误请重新选择:>");
goto beReborn;
break;
}
return 0;
}
这里,定义了棋盘三个规格(10、8、6)对应难、中、简三种难度,玩家所见到的则是88、66、4*4的棋盘。
2.2 问题一
由问题一我们可以知道,棋盘需要存放雷的信息(将雷用’1’来标识)又需要存放玩家下的棋子信息,那么是否有点过于复杂?那么我们在game.h
创建一个结构体,结构体中包含两个char变量(gameMIne
、gameTray
),并给个这个结构体定义名为 gameS
。并使用这个结构体类型创建一个数组。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
typedef struct gameS
{
char gameMine;
char gameTray;
}gameS;
gameMIne
用于放置雷的信息,gameTray
初始空格,用于接受玩家下的棋子和显示雷数。使gameTray
与gameMine
构建映射关系。并使用gameTray
与玩家进行交互。
注: 这里引入stdlib
、time
头文件用于下面初始化雷在棋盘中的位置。
结构体定义了,那么将创建数组,创建数组则需要在main.c
文件中测试函数里创建。
void game(){
gameS tray[RowCol][RowCol];
srand((unsigned int)time(NULL));
}
使用之前定义的规格
RowCol
作为棋盘的行列构成一个变长数组。注意变长数组是由c99标准及以后的标准实现,部分编译器(msvc)不支持变长数组
srand
初始化rand
函数用于后续初始化棋盘。
现在需要给游戏功能进行编写,在编写中我们需要给game.h
文件声明函数,给予main.c
文件测试函数中测试
在game.h
头文件中声明函数
extern int RowCol;
void trayInit(gameS tray[RowCol][RowCol]);//初始化
void trayPrint(gameS tray[RowCol][RowCol]);//打印
int playTray(gameS tray[RowCol][RowCol]);//下棋
int victoryTray(gameS tray[RowCol][RowCol]);//判断
棋盘初始化我们需要放置雷的信息,雷使用 '1’标识,不是雷使用’0’表示,方便后续玩家下棋操作的累加。
void trayInit(gameS tray[RowCol][RowCol]){
int mine = ((RowCol - 2) * (RowCol - 2)) * 0.3;
while (mine)
{
int row = rand() % (RowCol - 1);
int col = rand() % (RowCol - 1);
if (row == 0 || col == 0 || tray[row][col].gameMine == '1')
continue;
else
tray[row][col].gameMine = '1';
mine--;
}
for (int i = 0; i < RowCol; i++)
{
for (int j = 0; j < RowCol; j++)
{
if (tray[i][j].gameMine != '1')
tray[i][j].gameMine = '0';
tray[i][j].gameTray = '*';
}
}
}
雷的数量我们使用玩家所见的规格 30%的数量。并使用循环将雷放置完后,并进行结构体数组
gameMine
和gameTray
的初始化 。
棋盘的打印,我们则只需要打印结构体数组的gameTray
,并赋加边框
void trayPrint(gameS tray[RowCol][RowCol]){
for (int i = 1; i < RowCol - 1; i++)
{
for (int j = 1; j < RowCol - 1; j++)
{
printf(" %c ", tray[i][j].gameTray);
if ( j != RowCol - 2)
printf("|");
}
printf("\n");
if (i == RowCol -2)
break;
for (int j = 1; j < RowCol - 1; j++)
{
printf("---");
if (j != RowCol - 2)
printf("|");
}
printf("\n");
}
}
2.3 问题 2
玩家在输入坐标后,需要判断玩家输入的坐标是否存在于棋盘外圈和该位置是否有雷,若此位置没雷,则需要访问周围雷的数量并累加显示在该坐标上。
int playTray(gameS tray[RowCol][RowCol]){
while (1)
{
int x, y;
printf("请输入:>");
scanf("%d %d", &x, &y);
if (x >= RowCol - 1 || y >= RowCol - 1 || tray[x][y].gameTray != '*' || x == 0 || y == 0)
{
printf("你输入的坐标以被占用或错误!\n");
continue;
}
if (tray[x][y].gameMine == '1')
return 1;
char Count = getMine(tray,x,y);
if (Count != '0')
tray[x][y].gameTray = Count;
else
tray[x][y].gameTray = ' ';
break;
}
return 0;
}
在玩家输入的坐标无误时,若无雷则,进行访问周围雷的数量,这里我们使用的getMine函数来获取周围雷的属性。并用
Count
变量来接收,若周围没有雷时给该位置赋值空格,进行简易的美化,若周围有雷时则给该位置赋值Count
。
getMine函数的实现,之前定义有雷为’1’,无雷为‘0’的优势就来了,因为数字0的ASCII码表的码值为 48 , 而有雷与无雷的ASCII码值差值只为1,只需要将周围的棋格累加再== - (48 * 7) ==就能反应周围雷的属性。
char getMine(gameS tray[RowCol][RowCol],int x,int y){
return tray[x - 1][y - 1].gameMine +
tray[x - 1][y].gameMine +
tray[x - 1][y + 1].gameMine +
tray[x][y - 1].gameMine +
tray[x][y + 1].gameMine +
tray[x + 1][y - 1].gameMine +
tray[x + 1][y].gameMine +
tray[x + 1][y + 1].gameMine - 48 * 7;
}
附上ASCII码表
2.4 问题4
游戏交互,可以判断棋盘中剩余的棋子是否等于 雷的数量,若等于雷的数量则玩家获取胜利,多于雷的数量,则继续游戏
int victoryTray(gameS tray[RowCol][RowCol]){
int mine = ((RowCol - 2) * (RowCol - 2)) * 0.3;
int count = 0;
for (int i = 1; i < RowCol - 1; i++)
{
for (int j = 1; j < RowCol - 1; j++)
{
if (tray[i][j].gameTray == '*')
count++;
if(count > mine)
return 0;
}
}
return 2;
}
使用循环便利棋盘除外圈外的所有位置,等于雷的数量时返回0,多于时返回2。
到此,我们游戏核心功能已经写好。接下来继续完善测试函数
测试
回到main.c
文件中,因为玩家下棋是循环的直到游戏结束,所以需要将打印骑盘放入循环中,与玩家进行交互,创建i变量用于接受玩家下棋函数和游戏状态的返回值。正常游玩未输未赢的情况下,i的值都是0;当接受到大于0 的信息则跳出循环并打印。
void game(){
gameS tray[RowCol][RowCol];
srand((unsigned int)time(NULL));
trayInit(tray);
int i = 0;
while (1)
{
trayPrint(tray);
i = playTray(tray);
if (i)
break;
i = victoryTray(tray);
if (i)
break;
}
switch (i)
{
case 1:
printf("您输了!\n");
break;
case 2:
printf("胜利了!\n");
break;
default:
printf("ERROR\n");
break;
}
}
到此我们简易版扫雷已经完成。新人编写博客不易,请给个三连谢谢
代码在此获取:简易版扫雷