前言
????????利用C语言数组、函数等一些基础知识实现简单的扫雷游戏。此游戏需有一定的数组、函数知识支撑。
1. 游戏简要及获胜规则
? ? ? ? 扫雷是电脑上一个十分经典的小游戏,相信大家都和我一样在小的时候胡乱点过,今天我将带大家用C语言的知识重现一个简易版扫雷游戏,只要避开棋盘上所有地雷,我们就可以获得胜利。
2. 游戏逻辑
? ? ? ? 实现此游戏,我们需要思考出一个可靠的逻辑
- 打印菜单给玩家进行选择,玩家可选择play(玩)或者exit(退出)
- 玩家选择play,进入游戏环节后,给玩家展示加密后的棋盘
- 玩家选择坐标排雷
- 玩家选到雷,游戏结束,玩家失败
- 玩家选择坐标不为地雷,回到第3步骤继续排雷
- 直到棋盘上只剩下雷时,游戏结束,玩家获胜
3.设计思路
? ? ? ? 想让代码达到你想让它运行的结果,我们必须有一个清晰的思路。
3.1 写代码前的思考
????????要想实现游戏的整个过程,我们必须先弄明白游戏最重要的棋盘该如何设计,这时就需要利用数组来创建好我们的棋盘,并利用各个函数进行相互调动,即可完成。所以我们需要创建两个二维数组来实现棋盘的逻辑,一个用来存放雷,一个提供给玩家进行排雷。在存放雷的棋盘中,我们用 '0' 来代表非雷,用 '1' 来代表地雷;在排雷棋盘中,我们存放 '*' 来供玩家排雷,排雷成功后将 '*' 变为显示此坐标周围八个坐标雷数。
? ? ? ? 如果我们需要打印9 * 9的棋盘,我们应该创建多大的二维数组呢?也应该创建一个9 * 9的二维数组吗?其实仔细想想,要想实现,选择坐标后显示周围雷数的功能,我们必然会访问该坐标周围的空间,如果选择的坐标在棋盘的边缘的话,进行数雷功能时就会发生数组越界,要想解决此问题我们就需要在棋盘周围多一圈元素,也就是创建一个11 * 11的二维数组,当然在游戏环节中,我们并不需要将全部元素打印出来,打印一个9 * 9的棋盘即可。
? ? ? ? ?上面就是我们设置棋盘的大概思路,蓝色为最终显示的棋盘大小,接下来我们就编写代码来实现扫雷游戏。
3.2 创建文件
? ? ? ? 首先先创建我们的文件,test.c(游戏的整体框架),game.c(游戏涉及的自定义函数),game.h(头文件,用于声明各种函数等)
?3.3 打印游戏菜单
????????我们先写一个主函数,并创建test函数将游戏的整体框架放入其中
int main()
{
test();//函数主体
return 0;
}
????????接着在test函数中调用menu函数打印菜单,选择1表示进行游戏,选择0表示退出游戏
void menu()
{
printf("*********************\n");
printf("**** 1. play ****\n");
printf("**** 0. exit ****\n");
printf("*********************\n");
}
????????我们利用do-while循环实现扫雷游戏的整体简单逻辑,其中利用了switch语句对玩家的选择(input)进行了判断,选择为0就会退出游戏打破do-while循环,选择为1就会进入game函数
void test()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();//打印菜单
printf("请玩家进行选择>:");
scanf("%d", &input);
switch (input)//判断玩家的选择
{
case 1:
game();//进入游戏函数
break;
case 0:
printf("你已退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
}
?????????以上就是游戏开始前的整体准备,接下来我们实现玩家选择1. play后进行的game函数。玩家在选择1后就会开始调用game函数,所以我们需要自定义一个game函数,我们分开来讲解game函数的实现逻辑。
? ? ? ? 在进入游戏后,我们就需要创建二维数组来布置雷。
3.4 初始化棋盘
????????我们先创建两个二维数组,一个用来布置雷,一个用来排地雷。
char mine[ROWS][COLS] = { 0 };//存放布置好的雷的信息
char show[ROWS][COLS] = { 0 };//存放排查出的雷的信息
????????这里ROWS,COLS分别代表行数和列数,是由我们通过define定义的常量,这里就需要我们的头文件了(一般在代码工程中,函数声明、自定义常量、库函数的调用等一般都写在头文件中,需要使用时直接调用头文件即可,调用方式与调用库函数略有不同,格式为:#include "xxxx.h",在这里调用的话就是,#include "game.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 EASY_COUNT 10
//各种函数声明
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);//初始化数组
void DisplyBoard(char board[ROWS][COLS], int row, int col);//打印数组
void SetMine(char mine[ROWS][COLS], int row, int col);//布置雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);//排雷
? ? ? ? 在创建好数组后,我们就可以对他们进行初始化
InitBoard(mine, ROWS, COLS, '0');//初始化mine数组全为'0'
InitBoard(show, ROWS, COLS, '*');//初始化show数组全为'*'
? ? ? ? 这时我们便需要自定义一个初始化函数InitBoard来实现此功能
????????自定义函数首先在头文件中进行声明(可参考上述头文件代码),然后在game.c文件中编写实现逻辑,每一个自定义函数都是如此,之后的就不再进行说明了
????????我们利用for循环嵌套来对数组进行初始化
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
3.5 布置地雷
? ? ? ? 在棋盘初始化后,我们就可以在棋盘中布置地雷了
SetMine(mine, ROW, COL);
????????我们自定义SetMine函数来实现此功能,在每一次进行游戏时,我们都希望雷的排列位置不同,所以我们利用rand函数来生成随机数,使用它时需要和srand函数来进行搭配使用,在srand函数中,我们又需要time函数来利用时间戳来生成不同的数,三个函数都为C语言中的库函数,我们需要调用他们
#include <time.h>
#include <stdlib.h>
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;//设置雷数
while (count)
{
//rand是一个库函数,用来生成随机数,范围设置为0~9
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';//地雷用'1'代表
count--;
}
}
}
3.6 打印棋盘
? ? ? ? 在随机布置好地雷后,就可以打印出show数组来供玩家进行排雷行动,利用自定义打印数组的函数来实现此功能
? ? ? ? 注意:在打印棋盘时,我们只需要打印其中 9*9 的元素即可
void DisplyBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
//打印列号
for (i = 0; i <= col; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
//打印行号
printf("%d ", i);
//打印数组
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
?????????实现后如图:
?3.7 游戏环节
? ? ? ? 在以上操作都准备就绪后,玩家就可以选择坐标开始排雷了,这里自定义一个找雷函数
FindMine(mine, show, ROW, COL);
? ? ? ? 要想将此环节实现,我们需要思考玩家如何获胜,和玩家怎样算做失败,在找雷的过程中,如果玩家排查坐标为 '1',则代表玩家选择了雷,被炸死,游戏失败;玩家排查坐标为 '0',则计算周围八个坐标雷数并显示,当棋盘中元素只剩下雷('1')时,玩家获胜。
? ? ? ? 计算周围坐标雷数并显示,我们就需要另一个函数来实现了,可以自定义一个函数来辅助FindMine函数的实现,其返回值应该为雷的个数。
int get_mine_count(char mine[ROWS][COLS], int x, int y)
{
//返回周围雷数总和
return mine[x - 1][y - 1] +
mine[x - 1][y] +
mine[x - 1][y + 1] +
mine[x][y - 1] +
mine[x][y + 1] +
mine[x + 1][y - 1] +
mine[x + 1][y] +
mine[x + 1][y + 1] -
8 * '0';
//数字对应的ASCII值和数字字符对应的ASCII值相差48,即'0'的ASCII值
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
while (win < row * col - EASY_COUNT)
{
printf("请输入要排查坐标(行 列)>:");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("游戏结束,你被炸死了!\n");
DisplyBoard(mine, row, col);
printf("\n");
break;
}
else
{
//计算坐标x,y周围雷的个数
int n = get_mine_count(mine, x, y);
show[x][y] = n + '0';
//数字对应的ASCII值和数字字符对应的ASCII值相差48,即'0'的ASCII值
DisplyBoard(show, row, col);
win++;
}
}
else
{
printf("坐标非法,请重新输入>:");
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜你,排完所有雷,获得胜利!\n");
DisplyBoard(mine, row, col);
printf("\n");
}
}
3.8 实现game函数总逻辑
????????将上文提到的所有块合起来,就能实现我们的game函数的整体逻辑
? ? ????即为我们的game.c源文件
#include "game.h"
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void DisplyBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
//打印列号
for (i = 0; i <= col; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
//打印行号
printf("%d ", i);
//打印数组
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;//设置雷数
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
int get_mine_count(char mine[ROWS][COLS], int x, int y)
{
//返回周围雷数总和
return mine[x - 1][y - 1] +
mine[x - 1][y] +
mine[x - 1][y + 1] +
mine[x][y - 1] +
mine[x][y + 1] +
mine[x + 1][y - 1] +
mine[x + 1][y] +
mine[x + 1][y + 1] -
8 * '0';
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
while (win < row * col - EASY_COUNT)
{
printf("请输入要排查坐标(行 列)>:");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("游戏结束,你被炸死了!\n");
DisplyBoard(mine, row, col);
printf("\n");
break;
}
else
{
//计算坐标x,y周围雷的个数
int n = get_mine_count(mine, x, y);
show[x][y] = n + '0';
DisplyBoard(show, row, col);
win++;
}
}
else
{
printf("坐标非法,请重新输入>:");
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜你,排完所有雷,获得胜利!\n");
DisplyBoard(mine, row, col);
printf("\n");
}
}
4 游戏实现的整个代码工程
? ? ? ? game.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 EASY_COUNT 10
//各种函数声明
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);//初始化数组
void DisplyBoard(char board[ROWS][COLS], int row, int col);//打印数组
void SetMine(char mine[ROWS][COLS], int row, int col);//布置雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);//排雷
? ? ? ? game.c 源文件
#include "game.h"
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void DisplyBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
//打印列号
for (i = 0; i <= col; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
//打印行号
printf("%d ", i);
//打印数组
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;//设置雷数
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
int get_mine_count(char mine[ROWS][COLS], int x, int y)
{
//返回周围雷数总和
return mine[x - 1][y - 1] +
mine[x - 1][y] +
mine[x - 1][y + 1] +
mine[x][y - 1] +
mine[x][y + 1] +
mine[x + 1][y - 1] +
mine[x + 1][y] +
mine[x + 1][y + 1] -
8 * '0';
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
while (win < row * col - EASY_COUNT)
{
printf("请输入要排查坐标(行 列)>:");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("游戏结束,你被炸死了!\n");
DisplyBoard(mine, row, col);
printf("\n");
break;
}
else
{
//计算坐标x,y周围雷的个数
int n = get_mine_count(mine, x, y);
show[x][y] = n + '0';
DisplyBoard(show, row, col);
win++;
}
}
else
{
printf("坐标非法,请重新输入>:");
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜你,排完所有雷,获得胜利!\n");
DisplyBoard(mine, row, col);
printf("\n");
}
}
? ? ? ? test.c 源文件
#include "game.h"
void menu()
{
printf("*********************\n");
printf("**** 1. play ****\n");
printf("**** 0. exit ****\n");
printf("*********************\n");
}
void game()
{
//创建数组
char mine[ROWS][COLS] = { 0 };//存放布置好的雷的信息
char show[ROWS][COLS] = { 0 };//存放排查出的雷的信息
//初始化数组
InitBoard(mine, ROWS, COLS, '0');//初始化mine数组全为'0'
InitBoard(show, ROWS, COLS, '*');//初始化show数组全为'*'
//布置雷的位置
SetMine(mine, ROW, COL);
//打印数组
DisplyBoard(show, ROW, COL);
//玩家找雷
FindMine(mine, show, ROW, COL);
}
void test()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();//打印菜单
printf("请玩家进行选择>:");
scanf("%d", &input);
switch (input)//判断玩家的选择
{
case 1:
game();//进入游戏函数
break;
case 0:
printf("你已退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
}
int main()
{
test();//函数主体
return 0;
}
5. 此代码的缺陷
? ? ? ? 该逻辑下的代码只可实现扫雷游戏的简单操作,还是无法还原原版扫雷的功能,比如:
- 无法一下子展开周围空白元素,需要自己一个个去排查坐标
- 没办法保证玩家选择的第一个坐标是否为地雷,如果选到地雷坐标,玩家只能重新开始
- 无法计算玩家扫雷完成后的用时?
? ? ? ? 读者可以自由发挥想象来完善该代码,比如1的问题就可以利用函数递归来解决,2的问题只要玩家在选择该坐标后,将该位置雷移到另一空白位置继续游戏即可
?结言
? ? ? ? 这也是博主C语言学习路上的小小创作,肯定无法避免有一些不足和缺陷,希望各位大佬能指出博主的错误,我一定会积极修改,不断改进!
|