一.初阶版
1.菜单设置
进入游戏时,首先映入眼帘的应该是游戏菜单,提供用户选项以进行游戏。对应用户所选而执行相应的程序,首先想到使用switch语句。考虑到用户玩完一局后还会有再来一次的想法,于是可以将游戏整体放入一个循环do-while,而巧妙地使用用户输入来进行判断,输入0自然不进行循环,直接退出游戏。可以看见以下的效果。
#include"test.h"
void menu()
{
printf("*************************\n");
printf("***** 1.Play *****\n");
printf("***** 0.exit *****\n");
printf("*************************\n");
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
2.棋盘函数设置
扫雷可以看作是在一个大棋盘上进行的一系列操作,自然有关棋盘的函数必须设置好,其中包括:棋盘初始化,棋盘展示,埋雷,排查雷(这是游戏主体部分,在下一小节)等函数,先写好这些基础函数有利于设计游戏中的调试进行。
不过在设置之前,必须对该扫雷游戏的实现原理进行剖析。棋盘,方正周整,如同二维数组,故用二维数组实现棋盘最合适不过。在设置棋盘时可以用0,1分别表示无雷和有雷的状态,这样设置不仅方便埋雷,并且在排查雷的过程中便于统计,排查时若无雷则显示数字,数字代表周围含有雷的数量,最多为8,用周围的0,1直接相加便能得到数字,十分便捷。然而此时就遇到一个问题,0,1固然方便,可显示给用户的不可能是直接的0,1棋盘,在用户眼里必须全是未知状态,故此游戏需要用两个棋盘的实现。接下来就来一一实现。
棋盘的初始化,使用二维数组,在此基础版上,使用9*9的棋盘足矣,然而此时应当初始化9*9的棋盘吗?作者此时选择初始化原先行列分别+2的棋盘(即11*11),如此,不仅方便排查雷时直接使用1-9的坐标,更重要的是在排查边缘坐标时,不会出现数组越界的情况,使最边缘皆为0,也不会使统计雷数量出现错误,一举两得。
下面是初始化棋盘和打印棋盘的函数实现,在此,在初始化棋盘initboard函数时加入了参数char ch,便是由于需要有两个棋盘(两个char类型的二维数组mineboard和showboard)而设置的,mineboard需要用字符0初始化,表示有雷无雷,而showboard则需要用*初始化,是展示给用户的界面。另外,在实现printboard函数时可以考虑美观性,适当添加了一些如坐标等的修饰。
#define EASY_COUNT 10
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
//初始化棋盘
void initboard(char arr[ROWS][COLS], int rows, int cols, char ch)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
arr[i][j] = ch;
}
}
}
//打印棋盘
void printboard(char arr[ROWS][COLS], int row, int col)
{
printf("-------扫雷--------\n");
for (int i = 0; i <= col; i++)
{
printf("%d ", i);
}
printf("\n");
for (int i = 1; i <= row; i++)
{
printf("%d ", i);
for (int j = 1; j <= col; j++)
{
printf("%c ", arr[i][j]);
}
printf("\n");
}
}
3.埋雷,查雷函数设置
埋雷设置必然是随机数,所以在此使用srand,rand,time等库函数来解决,随机生成两个1-9的数字来做雷的坐标,在mineboard中用1替换0作为雷。在排查雷中,应重复循环执行,直到排查到雷(游戏失败)或者排查结束(游戏胜利)为止,用户指定输入坐标进行排查,若对应mineboard为1则是雷,直接结束游戏,若为0则显示周围雷数量到对应showboard上。这里得到雷的数量也可以作为一个函数进行书写。判定游戏胜利则需要一个计数器cnt,每次探查成功cnt都+1,直到cnt==棋盘总格数-雷数为止,游戏胜利。
Tips:mineboard初始化时是由字符'0''1'表示有雷与否,但在getmine得到目标周围雷数量需要进行相加,需要知道 字符数字减去字符‘0’得到整形数字 ,以下相加8个字符自然也是减去8个字符‘0’.
//埋雷
void setmine(char arr[ROWS][COLS],int row,int col)
{
int count = 1;
while (count <= EASY_COUNT)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (arr[x][y] == '0')
{
arr[x][y] = '1';
count++;
}
}
}
//得到目标周围雷的数量
int getmine(char arr[ROWS][COLS], int x, int y)
{
return arr[x - 1][y - 1]
+ arr[x - 1][y]
+ arr[x - 1][y + 1]
+ arr[x][y - 1]
+ arr[x][y + 1]
+ arr[x + 1][y - 1]
+ arr[x + 1][y]
+ arr[x + 1][y + 1]
- 8 * '0';
}
//排查雷
void searchmine(char mineboard[ROWS][COLS],char showboard[ROWS][COLS],int row,int col)
{
int x, y;
int cnt = 0;
while (cnt < ROW * COL - EASY_COUNT)
{
printf("请输入你要排查的对象:");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (showboard[x][y] != '*')
{
printf("该点已排查,请重新选择\n");
}
else
{
if (mineboard[x][y] == '1')
{
printf("很遗憾,这是炸弹,游戏结束\n");
printboard(mineboard, ROW, COL);
break;
}
else
{
showboard[x][y] = getmine(mineboard, x, y) + '0';
printboard(showboard, ROW, COL);
cnt++;
}
}
}
else
printf("输入不合法,请重新输入\n");
}
if (cnt == ROW * COL - EASY_COUNT)
printf("恭喜你,排雷成功!\n");
}
二.进阶版
1.计时器
使用时间函数clock()进行计时功能。该函数需包含头文件<time.h>,上述埋雷的随机值的生成中也用到了该头文件里time函数。可以如下实现,如此便能在游戏结束后打印出本次游戏的用时。
//用start,end分别记录开始与结束的时刻
clock_t start, end;
double duration;
//在开始游戏选择case1的情况中加入时刻记录
case 1:
start = clock();
game();
end = clock();
printf("此次用时%lf秒\n", (double)((end - start) / 1000.0));
break;
2.标记雷与取消标记
在此用#代替*来表示标记雷,用mark和unmark函数,标记的结果需展示在showboard上,当然也要考虑重复标记,无效标记(已经探查),错误输入等情况,实现函数如下。
void mark(char showboard[ROWS][COLS], int row, int col)
{
while (1)
{
printf("请输入你要标记的对象:");
int x, y;
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (showboard[x][y] == '*')
{
showboard[x][y] = '#';
break;
}
else if (showboard[x][y] == '#')
{
printf("该对象已被标记,无需重复标记\n");
}
else
{
printf("此处已探查,无需标记\n");
}
}
else
{
printf("输入错误,请重新输入\n");
}
}
}
void unmark(char showboard[ROWS][COLS], int row, int col)
{
while (1)
{
printf("请输入你要取消标记的对象:");
int x, y;
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (showboard[x][y] == '#')
{
showboard[x][y] = '*';
break;
}
else if (showboard[x][y] == '*')
{
printf("该对象没有被标记,无需取消标记\n");
}
else
{
printf("此处已探查,无需取消标记\n");
}
}
else
{
printf("输入错误,请重新输入\n");
}
}
}
函数写出固然容易,然而此处的改动后便需要调整之前的代码,由于用户可能执行侦察,标记,取消标记等操作,因此在此设置第二个菜单menu2,之前菜单重新设置为menu1,用户每次操作前得先从menu2中选择,因此前文的seachmine函数也需调整,首先要把searchmine里while循环排查取消,设置新循环在最外层以便循环选择menu2,此时排到雷便需要返回-1到外围循环来终结循环了。然后有一个小细节,之前的if(showboard[x][y]!='*'还要再加一个!=‘#’,否则标记过的雷就无法排查了。并且设置胜利条件也不能放在searchmine里了,同时为了方便前文返回-1终止循环,故作者将其设置为返回int类型(返回cnt),在函数外记录并判断,不过searchmine函数里的cnt要一直抱有计数功能,不能因为函数使用完后就又归零,因此用static修饰cnt延长其生命周期。作者特地保留注释掉修改前的代码,方便对比。
void menu2()
{
printf("*************************\n");
printf("***** 1.Check *****\n");
printf("***** 2.Mark *****\n");
printf("***** 3.Unmark *****\n");
printf("*************************\n");
}
int searchmine(char mineboard[ROWS][COLS],char showboard[ROWS][COLS],int row,int col)
{
int x, y;
static int cnt = 0;
//while (cnt < ROW * COL - EASY_COUNT)
//{
printf("请输入你要排查的对象:");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (showboard[x][y] != '*' && showboard[x][y] != '#')
{
printf("该点已排查,请重新选择\n");
}
else
{
if (mineboard[x][y] == '1')
{
printf("很遗憾,这是炸弹,游戏结束\n");
printboard(mineboard, ROW, COL);
return -1;
//break;
}
else
{
showboard[x][y] = getmine(mineboard, x, y) + '0';
printboard(showboard, ROW, COL);
cnt++;
return cnt;
}
}
}
else
printf("输入不合法,请重新输入\n");
//}
//if (cnt == ROW * COL - EASY_COUNT)
//{
// printf("恭喜你,排雷成功!\n");
//}
}
int cnt = 0;
while (cnt !=-1)
{
int input = 0;
menu2();
printf("请选择你要执行的操作:");
scanf("%d", &input);
switch (input)
{
case 1:
cnt = searchmine(mineboard, showboard, ROW, COL);
break;
case 2:
mark(showboard,ROW,COL);
printboard(showboard, ROW, COL);
break;
case 3:
unmark(showboard, ROW, COL);
printboard(showboard, ROW, COL);
break;
default:
printf("选择错误,请重新选择\n");
break;
}
if (cnt == ROW * COL - EASY_COUNT)
{
printf("恭喜你,排雷成功!\n");
break;
}
}
//searchmine(mineboard, showboard, ROW, COL);
3.空白展开,难度选择.......
更多扫雷的进阶拓展,敬请期待,欢迎关注持续更新......
三.完整代码
1.头文件test.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define EASY_COUNT 10
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
//棋盘的初始化
void initboard(char arr[ROWS][COLS], int rows, int cols, char ch);
//打印棋盘
void printboard(char arr[ROWS][COLS], int row, int col);
//埋雷
void setmine(char arr[ROWS][COLS],int row,int col);
//得到周围雷的数量
int getmine(char arr[ROWS][COLS], int x, int y);
//排查雷
int searchmine(char mineboard[ROWS][COLS], char showboard[ROWS][COLS], int row, int col);
void menu2();
//标记雷
void mark(char showboard[ROWS][COLS], int row, int col);
//取消标记雷
void unmark(char showboard[ROWS][COLS], int row, int col);
2.源文件test.c
#include"test.h"
void menu1()
{
printf("*************************\n");
printf("***** 1.Play *****\n");
printf("***** 0.exit *****\n");
printf("*************************\n");
}
void game()
{
char mineboard[ROWS][COLS] = { 0 };
char showboard[ROWS][COLS] = { 0 };
initboard(mineboard, ROWS,COLS,'0');
initboard(showboard, ROWS,COLS,'*');
//printboard(mineboard, ROW, COL);
setmine(mineboard,ROW,COL);
//printboard(mineboard,ROW,COL);
printboard(showboard,ROW,COL);
//int x, y;
//scanf("%d %d", &x, &y);
//printf("%d\n", getmine(mineboard, x,y));
int cnt = 0;
while (cnt !=-1)
{
int input = 0;
menu2();
printf("请选择你要执行的操作:");
scanf("%d", &input);
switch (input)
{
case 1:
cnt = searchmine(mineboard, showboard, ROW, COL);
break;
case 2:
mark(showboard,ROW,COL);
printboard(showboard, ROW, COL);
break;
case 3:
unmark(showboard, ROW, COL);
printboard(showboard, ROW, COL);
break;
default:
printf("选择错误,请重新选择\n");
break;
}
if (cnt == ROW * COL - EASY_COUNT)
{
printf("恭喜你,排雷成功!\n");
break;
}
}
//searchmine(mineboard, showboard, ROW, COL);
}
int main()
{
srand((unsigned int)time(NULL));
clock_t start, end;
double duration;
int input = 0;
do
{
menu1();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
start = clock();
game();
end = clock();
printf("此次用时%lf秒\n", (double)((end - start) / 1000.0));
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
3.源文件game.c
#include"test.h"
void initboard(char arr[ROWS][COLS], int rows, int cols, char ch)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
arr[i][j] = ch;
}
}
}
void printboard(char arr[ROWS][COLS], int row, int col)
{
printf("-------扫雷--------\n");
for (int i = 0; i <= col; i++)
{
printf("%d ", i);
}
printf("\n");
for (int i = 1; i <= row; i++)
{
printf("%d ", i);
for (int j = 1; j <= col; j++)
{
printf("%c ", arr[i][j]);
}
printf("\n");
}
}
void setmine(char arr[ROWS][COLS],int row,int col)
{
int count = 1;
while (count <= EASY_COUNT)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (arr[x][y] == '0')
{
arr[x][y] = '1';
count++;
}
}
}
int getmine(char arr[ROWS][COLS], int x, int y)
{
return arr[x - 1][y - 1]
+ arr[x - 1][y]
+ arr[x - 1][y + 1]
+ arr[x][y - 1]
+ arr[x][y + 1]
+ arr[x + 1][y - 1]
+ arr[x + 1][y]
+ arr[x + 1][y + 1]
- 8 * '0';
}
int searchmine(char mineboard[ROWS][COLS],char showboard[ROWS][COLS],int row,int col)
{
int x, y;
static int cnt = 0;
//while (cnt < ROW * COL - EASY_COUNT)
//{
printf("请输入你要排查的对象:");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (showboard[x][y] != '*' && showboard[x][y] != '#')
{
printf("该点已排查,请重新选择\n");
}
else
{
if (mineboard[x][y] == '1')
{
printf("很遗憾,这是炸弹,游戏结束\n");
printboard(mineboard, ROW, COL);
return -1;
//break;
}
else
{
showboard[x][y] = getmine(mineboard, x, y) + '0';
printboard(showboard, ROW, COL);
cnt++;
return cnt;
}
}
}
else
printf("输入不合法,请重新输入\n");
//}
//if (cnt == ROW * COL - EASY_COUNT)
//{
// printf("恭喜你,排雷成功!\n");
//}
}
void menu2()
{
printf("*************************\n");
printf("***** 1.Check *****\n");
printf("***** 2.Mark *****\n");
printf("***** 3.Unmark *****\n");
printf("*************************\n");
}
void mark(char showboard[ROWS][COLS], int row, int col)
{
while (1)
{
printf("请输入你要标记的对象:");
int x, y;
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (showboard[x][y] == '*')
{
showboard[x][y] = '#';
break;
}
else if (showboard[x][y] == '#')
{
printf("该对象已被标记,无需重复标记\n");
}
else
{
printf("此处已探查,无需标记\n");
}
}
else
{
printf("输入错误,请重新输入\n");
}
}
}
void unmark(char showboard[ROWS][COLS], int row, int col)
{
while (1)
{
printf("请输入你要取消标记的对象:");
int x, y;
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (showboard[x][y] == '#')
{
showboard[x][y] = '*';
break;
}
else if (showboard[x][y] == '*')
{
printf("该对象没有被标记,无需取消标记\n");
}
else
{
printf("此处已探查,无需取消标记\n");
}
}
else
{
printf("输入错误,请重新输入\n");
}
}
}