目录
前言
一、扫雷如何基本实现的?
二、代码实现
1.创建事先打包分类:
2.game.h
3.test.c
4.game.c
一、初始化棋盘(init_board)
二、 打印棋盘(print)
三、玩家的行动 (play_action)
四、埋雷(set_mine)
五、排雷(demine)
六、排雷时不是雷,连续翻格子(over_mine)
七、标记雷(mark_mine)
八、判断棋盘是否全部翻开(judge_ogame)
附上源代码链接:扫雷源代码
扫雷包括雷区、地雷计数器(位于左上角,记录剩余地雷数)和计时器(位于右上角,记录游戏时间),确定大小的矩形雷区中随机布置一定数量的地雷(初级为9*9个方块10个雷,中级为16*16个方块40个雷,高级为16*30个方块99个雷,自定义级别可以自己设定雷区大小和雷数,但是雷区大小不能超过24*30),玩家需要尽快找出雷区中的所有不是地雷的方块,而不许踩到地雷。
今天的内容我们就来写一个初级9*9的扫雷
首先它需要来打印一个9*9的‘游戏棋盘’就像左下角那样,由于我们是c语言板就不搞这莫花哨了,就搞个c语言纯手工版吧
原画版游戏棋盘 手工版棋盘然后就是扫雷的关键玩家行动的排雷和标记雷了,当然还有比必不可少的判断函数
我们将游戏实现的底层代码放在test.c里面,将需要使用的且函数的实现放在game.c中,头文件声明一下。 方便我们管理
我们先来看看我们会用到那些函数与它的作用
#pragma once
#include
#include
#define MINES 10//雷的数量
#define ROW 9
#define COL 9
//比棋盘大一圈防止数组越界访问
#define ROWS ROW + 2
#define COLS COL + 2
//初始化棋盘
void init_board(char board[ROWS][COLS], int rows,int cols,char set);
//打印棋盘
void print(char board[ROWS][COLS], int row, int col);
//埋雷
void set_mine(char mine_board[ROWS][COLS], int row, int col);
//玩家行动
int player_action(char show_board[ROWS][COLS], char mine_board[ROWS][COLS]);
//排雷
int demine(char show_board[ROWS][COLS], char mine_board[ROWS][COLS], int row, int col);
//标记雷
void mark_mine(char show_board[ROWS][COLS], char mine_board[ROWS][COLS], int row, int col);
//判断棋盘是否全部翻开
int judge_ogame(char show_board[ROWS][COLS], int row, int col);
//排雷翻格子
void over_mine(char show_board[ROWS][COLS], char mine_board[ROWS][COLS], int x, int y);
先看预期效果如图 :
由于我们需要展示棋盘,又要记录雷的位置,这里我们创建show来展示和mine来记录雷两个 棋盘来分工一下。
int main()
{
//用于随机埋雷
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
printf("用户请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
//开始游戏
start_game();
break;
case 0:
printf("退出游戏!!\n");
break;
default:
printf("输入错误,请重新输入:");
break;
}
} while (input);
}
当然在玩家看不到的地方:两个棋盘的初始化、埋雷等准备工作也是必不可少的 ╰( ̄ω ̄o)
当求边界格子周围9宫格内雷的数量由于在后续求边界雷的数量(如右图的绿框)
为了避免越界访问,我们就将棋盘的定义大一圈 ,则为11*11
#include"game.h"
void menu()
{
printf("**********************\n");
printf("*******1.paly*********\n");
printf("*******0.exct*********\n");
printf("**********************\n");
}
//开始游戏
void start_game()
{
int flag = 1;
char show[ROWS][COLS] = { 0 };//用于展示扫雷棋盘
char mine[ROWS][COLS] = { 0 };//用于记录扫雷信息
//初始化棋盘
init_board(show,ROWS,COLS,'*');
init_board(mine,ROWS,COLS,'0');
//打印棋盘
print(show, ROW, COL);
//埋雷
set_mine(mine, ROW, COL);
//print(mine, ROW, COL);(调试用)
//玩家行动
while (flag)
{
// flag=1 游戏继续
// flag=0 游戏结束
if (player_action(show, mine, ROW, COL) == 0)
break;
//打印棋盘
print(show, ROW, COL);
//判断棋盘是否翻完
flag = judge_ogame(show, ROW, COL);
}
}
4.
game.c
由于我们这里需要初始化两个棋盘,所以为了通用性,我们需要将把棋盘内需要初始化的类型传进去就有了 char set
void init_board(char board[ROWS][COLS], int rows, int cols, char set)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
访问方式类似与 ‘初始化棋盘’,再加一点点修饰
红框为修饰部分void print(char board[ROWS][COLS], int row, int col)
{
int i, j;
printf(" _________扫雷游戏_________\n");
printf(" 1 2 3 4 5 6 7 8 9\n");
printf(" —————————————\n");
for (i = 1; i < row + 1; i++)
{
printf("%d|", i);
for (j = 1; j < col + 1; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
1.这里我们分一下流,分为排雷和标记雷
2.将 flag = 0 时,认定为游戏结束,flag = 1时,认定为游戏继续
(对应了test.c中start_game函数)
int player_action(char show_board[ROWS][COLS], char mine_board[ROWS][COLS])
{
int op = 0;
int flag = 1;
printf("请输入你想\n1.排雷\n2.标记雷\n");
scanf("%d", &op);
switch (op)
{
//排雷
case 1:
flag = demine(show_board, mine_board, ROW, COL);
break;
//标记雷
case 2:
mark_mine(show_board, mine_board, ROW, COL);
break;
default:
printf("输入错误,请重新输入!");
}
return flag;
}
利用rand、srand与time函数生成的时间戳,生成随机数(srand在int main()主函数里一开始就设置好了)
void set_mine(char mine_board[ROWS][COLS], int row, int col)
{
int x, y;
for (int i = 0;i < MINES; )
{
x = rand() % row + 1;
y = rand() % col + 1;
if (mine_board[x][y] == '0')
{
//printf("%d %d\n", x, y);生成雷的坐标(调试检查用)
mine_board[x][y] = '1';
i++;
}
}
}
排雷时如果排的是‘雷’话,那就game over
int demine(char show_board[ROWS][COLS], char mine_board[ROWS][COLS], int row, int col)
{
int x, y;
int flag = 1;
while (1) {
printf("请输入你想排雷的坐标:");
scanf("%d %d", &x, &y);
if (show_board[x][y] == '*')
{
if (mine_board[x][y] == '1')
{
printf("你被炸死了!!游戏结束\n");
return 0;
}
else if (mine_board[x][y] == '0')
{
over_mine(show_board, mine_board, x, y);
return 1;
}
}
else
printf("坐标非法或已被占用,");
}
}
1.我们要筛选一下不合法和已经翻过的坐标(因为“连续”翻会用到递归,可能会越界访问和重复去翻翻过的格子导致死循环)
2.求该坐标(x,y)周围有几个雷
如图我们求(x,y)的坐标,该坐标就为周围8个格子雷数之和。 但是这里有个问题:我们之前定义棋盘时用的是char而不是int及,字符 '0' ≠ 整形 0,如果直接把8个坐标对应的数加起来它(字符对应的ascii码加起来)肯定不对。
举2个列子:char 0 - char 0 = int 0 char 1 - char 0 = int 1
根据上面的例子我们可以得到:char x - char y =int(x,y的ASCII码之差)
解决方法:相加时,将每个格子与char 0('0')相减,最后将结果再加上char 0('0')
特别的:为啥不先加起来在直接减去多余的'0'呢?因为ACSII码,有上限为“127”,如果直接加会溢出的!!!☠☠☠
2.连续的实现:
黄色周围没有雷当(x,y)坐标周围一个雷都没有时,及该点为0,在展示面板(show)为空格 ,记录雷的面板(mine)上就为'0',
此时就该连续翻格子了
炸金花般的连锁反应如左图:我们从(x,y)向周围蔓延开,并求周围格子它们周围的雷数量。在此我们使用一个循环加递归的方式去实现该功能(在 六大点的1小点中我们设好了第一个终止条件:翻过的就不翻),第二个终止条件当然就是周围雷的数量不为' 0 '辣
代码如下:
void over_mine(char show_board[ROWS][COLS], char mine_board[ROWS][COLS], int x, int y)
{
//判断坐标是否翻过与是否合法
if (show_board[x][y] != ' ' && (x >= 1 && y >= 1 && x <= ROW && y <= COL)) {
char num = '0';
int i, j;
for (i = -1;i <= 1;i++) {
for (j = -1;j <= 1; j++) {
num = mine_board[x + i][y + j] + num - '0';
}
}
if (num == '0' )
{
show_board[x][y] = ' ';
for (i = -1;i <= 1;i++) {
for (j = -1;j <= 1; j++) {
over_mine(show_board, mine_board, x + i, y + j);
}
}
}
else if (num != '0') {
show_board[x][y] = num;
}
}
}
标记的话,改一个字符就行
void mark_mine(char show_board[ROWS][COLS], char mine_board[ROWS][COLS], int row, int col)
{
int x, y;
while (1) {
printf("请输入你想标记雷的坐标:");
scanf("%d %d", &x, &y);
if (show_board[x][y] == '*') {
show_board[x][y] = 'X';
break;
}
else
printf("坐标非法或已被占用,");
}
}
实现原理:所以不是雷的格子全翻开,且是雷的格子要标记
int judge_ogame(char show_board[ROWS][COLS],char mine_board[ROWS][COLS], int row, int col) {
int i, j;
int mine = 0;//记录被标记的雷
for (i = 1; i < row; i++)
{
for (j = 1; j < col; j++)
{
if (show_board[i][j] == '*') {
return 1;
}
if (mine_board[i][j] == '1') {
if (show_board[i][j] == 'X') {
mine++;
}
}
}
}
//将雷全部真确标记
if (mine == MINES) {
printf(" 你赢了!!! \n");
return 0;
}
//未将雷全部真确标记
else
return 1;
}