目录
今天我来带大家来写扫雷的小游戏
我们还是用工程化的方法来写,首先创建一个.h文件和两个.文件(一个实现函数接口,一个用来测试)。
同样的,为了避免main函数内过于冗杂,我们代码将写在test函数里,我们首先来实现游戏菜单,只需使用自己喜欢的符号用printf输出即可:
void menu() {
printf("*********************\n");
printf("******* 1. play *****\n");
printf("******* 0. exit *****\n");
printf("*********************\n");
}
大家按照自己的喜好来写即可,接下来,我们实现用户的选择逻辑,使用do-while和switch语句即可完成
int input = 0;
do {
menu();
printf("请选择:\n");
scanf("%d", &input);
switch (input) {
case 1:
game();
break;
case 0:
printf("游戏退出!\n");
break;
default:
printf("选择错误,请重新选择!\n");
break;
}
} while (input);
此时,我们先不要着急,先看看我们的代码能不能正常运行,要记住写一步测一步,否则后期出现几十个错误,心态的一下子就崩了。
很好,我们接着来实现game函数。
这里首先,我们要确定扫雷棋盘的大小,我们选取9*9的棋盘,为了以后修改棋盘大小方便,我们一劳永逸的使用#define来定义。
这里先给不知道扫雷规则的同学科普一下,以上边的棋盘为例:数字1,代表这个格子以自身为中心,周围3*3(除掉自己)的八个格子里有一颗雷,同样的,数字3就代表周围八个格子里有三颗雷,我们需要根据数字来判断哪里有雷,哪里没有雷,我们要把所有没有雷的格子点开,这样就算游戏成功,如果在排雷的过程中点到了雷,那么游戏失败。
我们选用char数组来存放棋盘中的元素,我们先规定,棋盘里边的1为雷,0为非雷,也就是说,这个棋盘里边的元素只有0和1,同时,我们再定义一个char数组,用来存放我们游戏里的周围雷数,比如上面的1,2,3等等,定义两个数组可以使我们写代码的难度降低许多。
我们定义mine数组进行存放雷和非雷的数据,定义show数组存放我们排雷信息。用户在选择要排查的格子后,会像游戏里一样显示出数字,这个数字的计算思路就是把它周围的8个格子里的数据加起来,我们把这个数字存放到show数组里。我们会发现,在我们计算边缘格子的时候,比如第一行第一列这个格子,它的周围不够8个格子,如果我们要加的话会出现数组越界,同时,我们再写一个函数又非常麻烦,这里我们就可以把棋盘扩大一圈,原本是9*9的棋盘,现在就变成了11*11,同时为了存储数据方便,我们把show数组也变成11*11,这样两边的坐标就可以通用了。
所以我们使用#define定义如下:
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
这样我们以后要修改棋盘大小使也非常方便。
//初始化
接着,我们来实现棋盘的初始化,我们要把mine数组里的元素全部初始化为0,当然,我们在给用户展示时,只会打印show数组,为了保持神秘感,我们把show数组里的元素全部初始化为 ” * “ 号,就如同游戏开始全是空白一样。
void InitBoard(char board[ROWS][COLS], int rows, int cols, char x) {//初始化
int i = 0;
for (i = 0; i < rows; i++) {
int j = 0;
for (j = 0; j < cols; j++) {
board[i][j] = x;
}
}
}
因为要初始化的是整个数组(11*11)而不只是我们要打印的部分(9*9),所以传的参数是rows和cols,同时,为了提高代码的复用性,我们要初始化两个数组,两个数组初始化的元素不同,但我们不可能写两个一样的函数,所以我们给定最后一个参数为char类型,传入我们想要初始化的结果。
//打印棋盘
我们再来写打印棋盘的函数,这样就可以打印出来我们的棋盘,看看我们写的对不对
void DisplayBoard(char board[ROWS][COLS], int row, int col)//打印棋盘
{
int i = 0;
for (i = 1; i <= row; i++) {
int j = 0;
for (j = 1; j <= col; j++) {
printf("%c ", board[i][j]);
}
printf("\n");
}
}
因为打印棋盘只需要打印我们需要打印的内容(9*9),所以参数是row和col,但是我们的数组创建时候的类型是board[ROWS][COLS],所以数组传参的括号里的数字不要写错哦。
我们来看看我们的棋盘是什么样子的
很好,上面的0是我们的mine数组,下边的*是我们的show数组
//布置雷
接下来,我们就该在mine数组里边布置雷了,我们还是选取#define的方法来控制雷的个数,这样以后在优化时也更方便,我们还可以设置难度选择,根据用户选择难度的不同,对棋盘的大小和雷数量的多少进行变化,我选取的雷的数量为EASY_COUNT=10,大家根据自己的难度自行定义即可
void SetMine(char board[ROWS][COLS], int row, int col)//布置雷
{
int count = EASY_COUNT;
while (count) {
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0') {
board[x][y] = '1';
count--;
}
}
}
我们在布置雷的时候,要使用rand函数生成随机坐标,rand()%row的结果是0~8(因为row是9),加一的话就是1~9,刚好是我们的棋盘,同时,我们要对生成的坐标进行判断,如果该坐标已经有雷了,就不能再放了,所以我们要把count--写到if语句里边,不然坐标重复的话雷的数量会少于我们规定的数量。使用rand函数前还需要使用srand函数,这个函数我在猜数游戏里进行过详细的介绍,这里就不再叙述
我们再来测试一下代码吧,看看是不是符合我们的需求
可以看到,没有问题,写到这里,大家会发现我们在观察棋盘时非常难受,也不利于用户进行选择格子,我们对打印棋盘的函数进行一下优化,棋盘上方和左边打印出行和列数。
void DisplayBoard(char board[ROWS][COLS], int row, int col)//打印棋盘
{
int i = 0;
for (i = 0; i <= row; i++) {
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++) {
int j = 0;
printf("%d ", i);
for (j = 1; j <= col; j++) {
printf("%c ", board[i][j]);
}
printf("\n");
}
}
我们来看一下优化后打印出的棋盘是什么样子的
这下是不是就舒服多了呢?用户在进行排查雷时也方便了许多
//排查雷
接下来,我们就该写排查雷的函数了
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)//排查雷
{
int x = 0, y = 0;
int win = 0;
while (win < (row * col - EASY_COUNT)) {
printf("请选择排查坐标:\n");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col) {
if (show[x][y] != '*') {
printf("这个坐标被排查过了,请重新选择!\n");
continue;
}
if (mine[x][y] == '1') {
printf("很遗憾,你被炸死了!\n");
DisplayBoard(mine, ROW, COL);
break;
}
else {
int n = get_mine_count(mine, x, y);
show[x][y] = n + '0';
DisplayBoard(show, ROW, COL);
win++;
}
}
else {
printf("排查坐标错误,请重新选择!\n");
}
}
if (win == (row * col - EASY_COUNT)) {
printf("恭喜你!排雷成功!!!\n");
DisplayBoard(mine, ROW, COL);
}
}
我们让用户输入要排查的坐标,我们先对坐标进行检验,看输入坐标是否合法,如果输入坐标合法,那我们进一步对坐标进行检验,如果该坐标已经被排查过了,我们就告诉用户并且直接continue,如果没被排查过,我们先判断坐标在mine数组里是否为1,如果为1,说明这个坐标是雷,我们告诉用户游戏失败,并且打印mine数组,让他死得瞑目,如果不是1,那么我们对该坐标周围8格进行相加并储存在show数组里,因为show和mine数组都是char类型的,字符数字和数字是相差48的,所以我们需要在结果上加上字符0,同时,我们把这个相加8个格子的功能封装为一个函数,这个函数不需要在.h文件中声明,因为这只是一个辅助函数
int get_mine_count(char mine[ROWS][COLS],int x,int y) {
return (mine[x - 1][y] + mine[x - 1][y - 1] + mine[x][y - 1] +
mine[x + 1][y - 1] + mine[x + 1][y]
+ mine[x + 1][y + 1] + mine[x][y + 1] + mine[x - 1][y + 1] - 8 * '0');
}
同样,因为mine是char类型数组,所以我们在相加8个格子后,与我们需要的整数相差了8*48,所以我们最后需要减去8*字符0。
为了让用户不断的排查,我们使用while语句,我们创建一个变量win,初始化为0,代表用户已经排查了多少个格子,用户每排查一个格子,我们让win++,所以我们的循环条件就是win < (row * col - EASY_COUNT),棋盘的总格子数减去雷数,就是用户需要排查格子的数量,win小于这个数字的话,说明用户还没有排查完,就让用户继续排查,相等,说明排查完毕,同时,用户在排查雷的过程中,可以会失败,这时候也会break,所以我们要在最后加上判断win是否等于(row * col - EASY_COUNT),如果相等说明用户是通过排查完所有格子break的,这时候告诉用户游戏胜利,如果不相等,说明用户是通过游戏失败break的,这时候直接结束即可。
现在我们再来测试一下代码,当然,我们可不要傻乎乎的去排查所有格子,我们直接把雷的数量调到79,这样我们只需要排查两个格子,如果程序没问题,我们再改回来就行,同时,我们在布置完雷后就直接打印mine数组,照着排查。
可以看到,我们的程序没有问题。
//展开周围区域
接着我们来完成最后一步,我们游玩扫雷时,点一个格子会展开一片,而我们现在的程序只能显示一个格子,我们要让我们的扫雷也可以展开一片。
void OpenShow(char mine[ROWS][COLS], char show[ROWS][COLS],int x,int y)//展开周围的区域
{
if (x == 0 || y == 0 || x == ROWS - 1 || y == COLS - 1) {
return;
}
if (show[x][y] != '*'){
return;
}
int n = get_mine_count(mine, x, y);
show[x][y] = n + '0';
if (n>0) {
show[x][y] = n+'0';
return;
}
if (n==0) {
OpenShow(mine, show, x - 1, y);
OpenShow(mine, show, x - 1, y - 1);
OpenShow(mine, show, x, y - 1);
OpenShow(mine, show, x + 1, y - 1);
OpenShow(mine, show, x + 1, y);
OpenShow(mine, show, x + 1, y + 1);
OpenShow(mine, show, x, y + 1);
OpenShow(mine, show,x - 1, y + 1);
}
}
展开函数我们要使用递归,我们传入mine和show数组、x,y坐标,我们先对x和y坐标进行判断,因为我们棋盘虽然打印出来是9*9的,但实际上却是11*11的,多余的两行的两行里的元素并不我们正常的元素,所以我们要判断x和y,不能让他们等于外边的那一圈,然后我们要再次进行判断,我们不能对show数组里已经打印出来的再进行打印,所以这里不等于*的也直接return,紧着着,我们要用到排查雷里的东西,我们判断该坐标周围一圈是否有雷,如果有,那么就将雷数赋值给该坐标然后直接返回,否则我们进行递归,对周围八个格子进行判断,这样写的话,我们要对排查雷里边的一部分逻辑进行改变,否则进到展开周围的函数会直接结束(第二个if),因为我们传入的坐标是玩家已经选择的坐标。
我们只需修改一部分即可
else {
OpenShow(mine, show, x, y);
int n = get_mine_count(mine, x, y);
show[x][y] = n + '0';
DisplayBoard(show, ROW, COL);
win++;
}
这样,我们的扫雷就完成了,我们来看看实际效果吧
写完了扫雷游戏,大家也自己玩一把放松一下吧,希望大家可以有所收获
最后附上全部代码
//完整代码
#pragma once
//game.h
#include<stdio.h>
#include<stdlib.h>
#include<time.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 x);//初始化
void DisplayBoard(char board[ROWS][COLS],int row,int col);//打印棋盘
void SetMine(char board[ROWS][COLS],int row,int col);//布置雷
void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS],int row,int col);//排查雷
void OpenShow(char mine[ROWS][COLS], char show[ROWS][COLS],int x,int y);//展开周围的区域
#include "game.h"
//test.c
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');
InitBoard(show, ROWS, COLS, '*');
//打印棋盘
//DisplayBoard(mine, ROW, COL);
DisplayBoard(show, ROW, COL);
//布置雷
SetMine(mine, ROW, COL);
DisplayBoard(mine, ROW, COL);
//排查雷
FindMine(mine, show, ROW, COL);
}
void test() {
srand((unsigned int)time(NULL));
int input = 0;
do {
menu();
printf("请选择:\n");
scanf("%d", &input);
switch (input) {
case 1:
game();
break;
case 0:
printf("游戏退出!\n");
break;
default:
printf("选择错误,请重新选择!\n");
break;
}
} while (input);
}
int main() {
test();
return;
}
#include"game.h"
//game.c
void InitBoard(char board[ROWS][COLS], int rows, int cols, char x) {//初始化
int i = 0;
for (i = 0; i < rows; i++) {
int j = 0;
for (j = 0; j < cols; j++) {
board[i][j] = x;
}
}
}
void DisplayBoard(char board[ROWS][COLS], int row, int col)//打印棋盘
{
int i = 0;
for (i = 0; i <= row; i++) {
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++) {
int j = 0;
printf("%d ", i);
for (j = 1; j <= col; j++) {
printf("%c ", board[i][j]);
}
printf("\n");
}
}
void SetMine(char board[ROWS][COLS], int row, int col)//布置雷
{
int count = EASY_COUNT;
while (count) {
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0') {
board[x][y] = '1';
count--;
}
}
}
int get_mine_count(char mine[ROWS][COLS],int x,int y) {
return (mine[x - 1][y] + mine[x - 1][y - 1] + mine[x][y - 1] +
mine[x + 1][y - 1] + mine[x + 1][y]
+ mine[x + 1][y + 1] + mine[x][y + 1] + mine[x - 1][y + 1] - 8 * '0');
}
void OpenShow(char mine[ROWS][COLS], char show[ROWS][COLS],int x,int y)//展开周围的区域
{
if (x == 0 || y == 0 || x == ROWS - 1 || y == COLS - 1) {
return;
}
if (show[x][y] != '*'){
return;
}
int n = get_mine_count(mine, x, y);
show[x][y] = n + '0';
if (n>0) {
show[x][y] = n+'0';
return;
}
if (n==0) {
OpenShow(mine, show, x - 1, y);
OpenShow(mine, show, x - 1, y - 1);
OpenShow(mine, show, x, y - 1);
OpenShow(mine, show, x + 1, y - 1);
OpenShow(mine, show, x + 1, y);
OpenShow(mine, show, x + 1, y + 1);
OpenShow(mine, show, x, y + 1);
OpenShow(mine, show,x - 1, y + 1);
}
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)//排查雷
{
int x = 0, y = 0;
int win = 0;
while (win < (row * col - EASY_COUNT)) {
printf("请选择排查坐标:\n");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col) {
if (show[x][y] != '*') {
printf("这个坐标被排查过了,请重新选择!\n");
continue;
}
if (mine[x][y] == '1') {
printf("很遗憾,你被炸死了!\n");
DisplayBoard(mine, ROW, COL);
break;
}
else {
OpenShow(mine, show, x, y);
int n = get_mine_count(mine, x, y);
show[x][y] = n + '0';
DisplayBoard(show, ROW, COL);
win++;
}
}
else {
printf("排查坐标错误,请重新选择!\n");
}
}
if (win == (row * col - EASY_COUNT)) {
printf("恭喜你!排雷成功!!!\n");
DisplayBoard(mine, ROW, COL);
}
}