前言:
上一期博客与大家分享了C语言三子棋的教程,今天,我又来辣!
这一期博客给大家带来了C语言扫雷的教程,是不是很期待呢,接着往下看吧!
步骤:
一、游戏的布局设计
玩过扫雷的小伙伴都知道,扫雷是一个可以自定义难度的小游戏,也就是说玩家在玩游戏的时候可以自主控制游戏难度,那么这是怎么实现的呢?
让我们来看看页游版本的扫雷是如何布局的:
我们能够很直观地感受到这个游戏的最主要结构:9*9的表格。
(当然这个表格可以由玩家自定义设置)
谈及表格,最容易让人联想到的便是二维数组,由二维数组来实现对游戏表格的打印、游戏元素的填充以及后续游戏逻辑应用。简单的讲,就是拿二维数组自定义大小划了一块土地,然后我们可以在这片土地里面挖坑、埋雷,挖的坑和埋的雷数目都由我们说了算。
So,我们在挖完坑埋完雷之后又应该怎么告诉玩家踩的是坑还是雷呢?又页游扫雷我们可以知道,如果我们踩的是坑,这个坑会告诉我们以这个坑为中心的九宫格里有多少个地雷。
二、相关的逻辑代码实现
游戏菜单
扫雷游戏菜单跟与上次的三子棋可以说是一样,可以照搬三子棋的游戏菜单打印以及功能选择,直接依葫芦画瓢即可。
(此处帮大家直接把上次完成的这部分的相关代码贴出来,方便观看理解)
#include "game.h"
void menu()
{
printf("****************************************\n");
printf("*********** MY CHEST GAME **************\n");
printf("*********** 1. PLAY **************\n");
printf("*********** 0. EXIT **************\n");
printf("****************************************\n");
}
void game()
{
printf("-------------- 扫 雷 -------------------\n");
}
int main()
{
int input;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏!\n");
break;
default:
printf("选择错误,请重新选择!\n");
break;
}
} while (input);
}
1、游戏表格
构造游戏表格,就相当于要划一块土地,而这片土地的用途是要用来挖坑和埋雷——也就是说我们要用二维数组来实现游戏表格的构造,后续还要储存与游戏相关的元素。当然,这个游戏表格还有后续与游戏相关的所有逻辑代码都是放在game函数里,作为一个游戏功能模块总集。
那么,接下来是最值得思考的一点——游戏有哪些是能够让玩家看到的?又有哪些是不能让玩家看到的?
如图,这个表格是我们希望让玩家在玩游戏的时候看见的。
而实际上,游戏内部的内容是不让玩家看见的,也就是埋雷这一步骤是被我们包装起来的,不对玩家展示。
因此,游戏表格又分为里与表两个表格,“里”就是我们的游戏内容,“表”就是对玩家展示的游戏部分。为此,我们要定义两个二维数组,方便后续完善游戏。
表格初始化
我们在定义数组之前,先观察一下表格分布,再确定数组的规格。
考虑到后续排雷操作涉及到遍历数组,而遍历数组自然也就逃不过考虑数组引用是否会发生下标越界,因此,看似这个表格规格是9*9,实际上为了后续的排雷操作统计数目,我们应该在边界之外在多扩一行,也就是数组的实际大小应该是 “预定设置的行数+2”,“预定设置的列数+2”。
在确定好数组大小之后,接着就是初始化数组——填充元素以及打印。我们写一个Initboard函数用于实现初始化。我们在写这个函数的时候,需要先考虑传参部分都有哪些变量。先传入用来布置雷的数组,再传入作为玩家游戏画面的数组。而在传参时我们可以传入一个字符用于作为初始化字符,后续我们想要改游戏的画面更为方便,也便于代码块调试。
#include "game.h"
void Initboard(char board[ROWS][COLS], int row, int col,char set)
{
int i = 0;
int j = 0;
for (i = 0;i <ROWS;i++)
{
for (j = 0;j < COLS;j++)
{
board[i][j] = set;
}
}
for (i = 0;i <ROWS;i++)
{
for (j = 0;j < COLS;j++)
{
printf(" %c ", board[i][j]);
}
printf("\n");
}
}
(game.c文件中)
void game()
{
char mine[ROWS][COLS];
char show[ROWS][COLS];
Initboard(mine, ROWS, COLS, '0');
Initboard(show, ROWS, COLS, '*');
}
(主函数中)
(预期中的初始化效果)
在后续的游戏画面中初始化以及埋雷完毕后的表格是不对玩家展示的,因此可以把遍历数组中打印这一操作的代码模块给注释掉。
2、完成埋雷步骤
埋雷部分的操作相对而言简单,只需要将生成的随机值作为坐标将设定好作为雷的符号作为元素填充进数组即可。
我们要应用到随机数生成时总是用到srand函数,用srand来模上已经设定好的数组行列就可以得到对应的随机值,然后将随机值作为坐标,在对应的位置上埋下地雷。
void setMine(char board[ROWS][COLS], int row, int col,int count,char set)
{
while (count)
{
int x = rand() % ROW + 1;
int y = rand() % COL + 1;
if (board[x][y] != set)
{
board[x][y] = set;
count--;
}
}
for (int i = 0;i <ROWS;i++)
{
for (int j = 0;j < COLS;j++)
{
printf(" %c ", board[i][j]);
}
printf("\n");
}
}
(完成这个函数之后可以将下面遍历数组的部分代码注释掉,不将埋雷后的内容对玩家进行展示)
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
printf("****************************************\n");
printf("*********** MY CHEST GAME **************\n");
printf("*********** 1. PLAY **************\n");
printf("*********** 0. EXIT **************\n");
printf("****************************************\n");
}
void game()
{
char mine[ROWS][COLS];
char show[ROWS][COLS];
Initboard(mine, ROWS, COLS, '0');
//printf("\n");
Initboard(show, ROWS, COLS, '*');
setMine(mine, ROW, COL, 10,'1');
//Display(show, ROWS, COLS, '*');
}
int main()
{
srand((unsigned int)time(NULL));
int input;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏!\n");
break;
default:
printf("选择错误,请重新选择!\n");
break;
}
} while (input);
}
(完成后如图有10颗地雷)
在这之后,不展示已经埋完雷的表格,将经过优化的初始化游戏画面对玩家进行展示。
代码如下
void Display(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("---------- 扫 雷 ------------\n");
for (i = 0;i <= ROW;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");
}
printf("---------- 扫 雷 ------------\n");
}
3、地雷计数
到了最为核心的一步,排雷的时候如果该坐标上对应的元素不是我们预设的雷,则会显示以该坐标为中心,九宫格之内雷的数目。由此我们可以构造一个函数用于统计每个输入的坐标周围雷的数目。
小思考:最简单的计数方法是什么? ——没错!当然是直接数啦(bushi)。
开个小玩笑^_^,但是玩笑归玩笑,这确实是最为容易实现的一种方法!
那么要完成这个计数过程,就只需要统计周围八个坐标有多少个代表地雷的字符即可。
但是!此前我们定义的数组是字符型,元素的数据类型自然而然也是char型,前面我们用字符'0'代表空白的部分,字符'1'代表地雷,那么我若想要计数便只需要将其余八个坐标的和值计算出来,然后把这个值作为计数结果赋值给向玩家展示的表格上面相对应的元素即可。
然后就是计算部分,要把一个字符数字转化为整形数字,用这个字符数字减去字符'0'即可。
int getMine(char board[ROWS][COLS], int x, int y)
{
return board[x - 1][y - 1] + board[x - 1][y] + board[x - 1][y+1] + board[x][y-1] + board[x][y+1] + board[x+1][y-1] + board[x+1][y] + board[x+1][y+1] - 8 * '0';
}
4、游戏结果判断
我们在输入排查坐标后能够推断哪些坐标不是雷,由此游戏的结果就是当未排查的坐标数目等于雷的数目时游戏胜利!而要判断是否踩雷,只需要确定输入坐标对应的元素是否为字符'1',如果是游戏结束,否则继续排查坐标直至未排查坐标数目等于雷的数目或者踩雷。
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 - ROOKIE)
{
printf("请输入要排查的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
{
if (mine[x][y] == '1')
{
printf("游戏结束,你被炸死了!\n");
Display(mine, ROW, COL);
break;
}
else
{
int num = getMine(mine, x, y);
show[x][y] = num + '0';
Display(show, ROW, COL);
win++;
}
}
else
{
printf("坐标非法,请重新输入!\n");
}
}
if (win == row * col - ROOKIE)
{
printf("恭喜你,所有的雷都已经被排除了!\n");
}
}
最后把代码送上!
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define ROOKIE 10
//初始化棋盘
void Initboard(char board[ROWS][COLS], int row, int col, char set);
//打印棋盘
void Display(char board[ROWS][COLS], int row, int col);
//埋雷
void setMine(char board[ROWS][COLS], int row, int col, int count,char set);
//计数
int getMine(char board[ROWS][COLS], int x, int y);
//坐标排雷
void findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
game.h
#include "game.h"
void Initboard(char board[ROWS][COLS], int row, int col,char set)
{
int i = 0;
int j = 0;
for (i = 0;i <ROWS;i++)
{
for (j = 0;j < COLS;j++)
{
board[i][j] = set;
}
}
//for (i = 0;i <ROWS;i++)
//{
// for (j = 0;j < COLS;j++)
// {
// printf(" %c ", board[i][j]);
// }
// printf("\n");
//}
}
void setMine(char board[ROWS][COLS], int row, int col,int count,char set)
{
while (count)
{
int x = rand() % ROW + 1;
int y = rand() % COL + 1;
if (board[x][y] != set)
{
board[x][y] = set;
count--;
}
}
/*for (int i = 0;i <ROWS;i++)
{
for (int j = 0;j < COLS;j++)
{
printf(" %c ", board[i][j]);
}
printf("\n");
}*/
}
void Display(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("---------- 扫 雷 ------------\n");
for (i = 0;i <= ROW;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");
}
printf("---------- 扫 雷 ------------\n");
}
int getMine(char board[ROWS][COLS], int x, int y)
{
return board[x - 1][y - 1] + board[x - 1][y] + board[x - 1][y+1] + board[x][y-1] + board[x][y+1] + board[x+1][y-1] + board[x+1][y] + board[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 - ROOKIE)
{
printf("请输入要排查的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
{
if (mine[x][y] == '1')
{
printf("游戏结束,你被炸死了!\n");
Display(mine, ROW, COL);
break;
}
else
{
int num = getMine(mine, x, y);
show[x][y] = num + '0';
Display(show, ROW, COL);
win++;
}
}
else
{
printf("坐标非法,请重新输入!\n");
}
}
if (win == row * col - ROOKIE)
{
printf("恭喜你,所有的雷都已经被排除了!\n");
}
}
game.c
#include "game.h"
void menu()
{
printf("****************************************\n");
printf("*********** MY CHEST GAME **************\n");
printf("*********** 1. PLAY **************\n");
printf("*********** 0. EXIT **************\n");
printf("****************************************\n");
}
void game()
{
char mine[ROWS][COLS];
char show[ROWS][COLS];
Initboard(mine, ROWS, COLS, '0');
//printf("\n");
Initboard(show, ROWS, COLS, '*');
setMine(mine, ROW, COL, 10,'1');
Display(show, ROWS, COLS);
findmine(mine,show,ROW,COL);
}
int main()
{
srand((unsigned int)time(NULL));
int input ;
do
{
menu();
printf("请选择:>");
scanf("%d",&input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏!\n");
break;
default:
printf("选择错误,请重新选择!\n");
break;
}
} while (input);
}
main.c
看到最后,给个三连支持一下吧!