本期内容:
本期主要介绍扫雷游戏,详细的讲述如何各个模块的功能如何实现和思考的过程
目录
一. 扫雷游戏简介
扫雷一是款益智类小游戏,主要内容是通过排查某一方格周围雷的数量来找到一个正方形区域内所有的雷,下图是一款扫雷游戏的游戏界面。
这篇文章主要介绍9*9难度下扫雷游戏的实现。
二. game.c文件的实现
首先我们要思考在计算机上进行扫雷游戏的画面,程序运行,首先看到的应该是一个9*9的棋盘,玩家通过点开相应的坐标来排查雷的数量,那么,我们很容易就可以发现三个判断逻辑:
1. 如果玩家点开的坐标是雷,那么玩家会被炸死。
2. 如果玩家点开的不是雷,那么需要显示周围雷的个数。
3. 如果周围没有雷,那么需要以此方块向外展开到周围有雷的方块。
在考虑上面三个逻辑之前,我们先来考虑棋盘的显示。这里可以用到一个二维数组,接着考虑它的大小,如果是一个9*9大小的二维数组,在玩家点开边界方块的时候,对周围元素的遍历很有可能出现越界的现象,而且还要用和棋盘中间格子不一样的函数判断,很麻烦,所以我们可以创建一个大小为11*11的二维数组,这样边界雷的判断就可以和中间雷的判断共用一个函数。
那么,在玩家输入坐标之后,显示的棋盘应该是包含“*”,”1“和”0“的另一个二维数组,所以我们可以把判断和显示两个数组分开,一个放置雷,另一个打印棋盘。
那么首先,我们创建两个大小为11*11的二维数组,并初始化,代码如下:
//创建棋盘,mine是放置雷的数组,show是打印雷的数组
char mine[ROWS][COLS];
char show[ROWS][COLS];
//初始化棋盘
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0, j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
之后,打印,代码如下:
void DisplayBoard(char board[ROWS][COLS], int rows, int cols)
{
int i = 0, j = 0;
for (j = 0; j < COLS-1; j++)
{
printf("%d ", j);
}
printf("\n");
for (i = 1; i < ROWS - 1; i++)
{
printf("%d ", i);
for (j = 1; j < COLS - 1; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
初始化棋盘之后,我们需要对mine数组进行雷的布置,这里需要生成随机数,可以用到库函数中的rand()。关于rand()函数的使用,笔者会在下一篇博客中介绍。
void PlaceMine(char board[ROWS][COLS], int rows, int cols)
{
int count = EasyDegree;
while (count)
{
int i = rand() % ROW + 1;
int j = rand() % COL + 1;
if (board[i][j] == '0')
{
board[i][j] = '1';
count--;
}
}
}
接下来要进行排查雷功能的实现: ( 一个嵌套调用很多函数的函数 )
这里要考虑到上文提到的三个判断逻辑
如果点开的是雷,那么就可以直接把mine即放置雷的数组展示给玩家,并宣布游戏失败;
如果点开的不是雷,那么又要分成两种情况讨论:
1. 周围如果有雷,就要遍历周围的格子来判断雷的个数,但如果一个一个的判断周围是不是雷,就要写出八个if语句来进行判断,很麻烦。这里我们可以思考要实现的功能,即显示周围雷的个数,而我们用到的数组里的元素又都是字符 ’0‘ 和字符 ’1‘ ,那么我们就可以把周围的八个字符加到一起,再减去8* ‘0’,这样就得到了周围雷的数量,而数组中存放的是字符,所以还要加上一个 ’0‘ 来把数字转换成字符。
2. 周围如果没有雷,就需要继续对周围进行展开,很容易就可以想到使用递归函数来求解,但是其中依旧有很多需要思考的地方。
如果我们按照常规思路,对周围没有雷的格子递归调用周围八个格子,就可能会出现死递归的现象,比如(x,y)坐标如果没有雷,并且递归调用的(x+1,y+1)也没有雷,那么(x+1,y+1)就会重复调用(x,y)两个坐标会无限反复调用陷入死递归。
解决办法:我们一共创建了两个字符数组,一个是mine,另一个是show,如果调用mine,程序会陷入死递归,所以可以用到show数组,如果周围没有雷,就把show中对应位置的元素改成’0‘,之后在进行递归之前判断show对应元素是否是’*‘,就可以避免再次调用这个位置。
代码如下:
int CountMine(char place[ROWS][COLS], int x, int y)
{
return (place[x - 1][y - 1] +
place[x - 1][y] +
place[x - 1][y + 1] +
place[x][y - 1] +
place[x][y + 1] +
place[x + 1][y - 1] +
place[x + 1][y] +
place[x + 1][y + 1]) - 8 * '0';
}
void ExtendMine(char mine[ROWS][COLS],char show[ROWS][COLS], int rows, int cols, int x, int y)
{
//判断数组是否越界
if (x == 0 || x == rows - 1 || y == 0 || y == cols - 1)
{
return;
}
//遍历周围元素
int count = CountMine(mine, x, y);
if (count == 0)
{
show[x][y] = '0';
for (int i = x - 1; i <= x + 1; i++)
{
int j = 0;
for (j = y - 1; j <= y + 1; j++)
{
if (show[i][j] == '*')
{
ExtendMine(mine, show, rows, cols, i, j);
}
}
}
}
else
{
show[x][y] = count + '0';
}
}
void FindMine(char place[ROWS][COLS],char show[ROWS][COLS], int rows, int cols)
{
int win = 0;
int i = 0, j = 0;
while (win < ROW * COL - EasyDegree)
{
printf("请输入要排查的坐标\n");
scanf("%d%d", &i, &j);
if (i >= 1 && i <= ROW && j >= 1 && j <= COL)
{
if (show[i][j] == '*')
{
if (place[i][j] == '0')
{
ExtendMine(place, show, ROWS, COLS, i, j);
DisplayBoard(show, ROWS, COLS);
}
else
{
printf("很遗憾,你被炸死\n");
DisplayBoard(place, ROWS, COLS);
break;
}
}
else
{
printf("该坐标已被排查,请重新选择\n");
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
if (win == ROW * COL - EasyDegree)
{
printf("你赢了\n");
}
}
三.test.c文件的实现
在这个文件中主要是体现扫雷游戏的基本逻辑
即首先玩家进入程序后要进行选择,可以用到while循环和switch语句来实现。
开始运行-->出现菜单-->选择扫雷游戏或者退出
game函数的实现顺序是
创建棋盘-->初始化棋盘-->打印棋盘-->放置雷-->排查雷
代码放在文末。
四.完整的程序代码
game.代码:
#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 EasyDegree 10
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int rows, int cols);
//放置雷
void PlaceMine(char board[ROWS][COLS], int rows, int cols);
//排查雷
void FindMine(char place[ROWS][COLS], char show[ROWS][COLS], int rows, int cols);
//计算周围雷的个数
int CountMine(char place[ROWS][COLS], int x, int y);
//延展探索雷的个数
void ExtendMine(char mine[ROWS][COLS], char show[ROWS][COLS], int rows, int cols, int x, int y);
test.c代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
printf("******************************\n");
printf("*********** 1. play **********\n");
printf("*********** 0. exit **********\n");
printf("******************************\n");
}
void game()
{
//创建棋盘,mine是放置雷的数组,show是打印雷的数组
char mine[ROWS][COLS];
char show[ROWS][COLS];
//初始化棋盘
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//打印棋盘
DisplayBoard(show, ROWS, COLS);
//放置雷
PlaceMine(mine, ROWS, COLS);
//排查雷
FindMine(mine, show, ROWS, COLS);
}
int main()
{
int choose;
srand((unsigned int)time(NULL));
do
{
menu();
printf("扫雷游戏,请选择-> \n");
scanf("%d", &choose);
switch (choose)
{
case 1:
game();
break;
case 0:
printf("已退出\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (choose);
return 0;
}
game.c代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0, j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void DisplayBoard(char board[ROWS][COLS], int rows, int cols)
{
int i = 0, j = 0;
for (j = 0; j < COLS-1; j++)
{
printf("%d ", j);
}
printf("\n");
for (i = 1; i < ROWS - 1; i++)
{
printf("%d ", i);
for (j = 1; j < COLS - 1; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
void PlaceMine(char board[ROWS][COLS], int rows, int cols)
{
int count = EasyDegree;
while (count)
{
int i = rand() % ROW + 1;
int j = rand() % COL + 1;
if (board[i][j] == '0')
{
board[i][j] = '1';
count--;
}
}
}
int CountMine(char place[ROWS][COLS], int x, int y)
{
return (place[x - 1][y - 1] +
place[x - 1][y] +
place[x - 1][y + 1] +
place[x][y - 1] +
place[x][y + 1] +
place[x + 1][y - 1] +
place[x + 1][y] +
place[x + 1][y + 1]) - 8 * '0';
}
void ExtendMine(char mine[ROWS][COLS],char show[ROWS][COLS], int rows, int cols, int x, int y)
{
//判断数组是否越界
if (x == 0 || x == rows - 1 || y == 0 || y == cols - 1)
{
return;
}
//遍历周围元素
int count = CountMine(mine, x, y);
if (count == 0)
{
show[x][y] = '0';
for (int i = x - 1; i <= x + 1; i++)
{
int j = 0;
for (j = y - 1; j <= y + 1; j++)
{
if (show[i][j] == '*')
{
ExtendMine(mine, show, rows, cols, i, j);
}
}
}
}
else
{
show[x][y] = count + '0';
}
}
void FindMine(char place[ROWS][COLS],char show[ROWS][COLS], int rows, int cols)
{
int win = 0;
int i = 0, j = 0;
while (win < ROW * COL - EasyDegree)
{
printf("请输入要排查的坐标\n");
scanf("%d%d", &i, &j);
if (i >= 1 && i <= ROW && j >= 1 && j <= COL)
{
if (show[i][j] == '*')
{
if (place[i][j] == '0')
{
ExtendMine(place, show, ROWS, COLS, i, j);
DisplayBoard(show, ROWS, COLS);
}
else
{
printf("很遗憾,你被炸死\n");
DisplayBoard(place, ROWS, COLS);
break;
}
}
else
{
printf("该坐标已被排查,请重新选择\n");
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
if (win == ROW * COL - EasyDegree)
{
printf("你赢了\n");
}
}
好啦,本篇博客到这里就结束啦,感谢您的观看,我们下一篇再见。Bye~Bye~