文章目录
一.扫雷游戏简介
《扫雷》是一款大众类的益智小游戏,于1992年发行。游戏目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输, 由玩家选择点开的方格,以找出所有地雷为最终游戏目标。如果玩家点开的方格有地雷,则游戏结束。若点开的方格下没有地雷,则会显示其周围一圈格子中包含雷的个数,在游戏过程中我们还可以对雷进行标记,利用这些便可以帮助我们获得游戏的胜利。
二.项目实现分析
- 打开扫雷游戏,映入眼帘的就是棋盘了,我们需要基于棋盘来进行扫雷游戏,首先我们需要打印棋盘,棋盘该怎么打印呢,这里我们需要用到二维数组。
- 然后就是埋雷了,我们这里使用随机数让电脑随机埋雷,但是埋雷后就会显示出雷的位置,这该怎么办呢?我们这里构建两个二维数组,一个是为了埋雷,另一个是为了展示给玩家。
- 接下来就是最重要的排雷,玩家点开一个方格,若该方格下是雷,则游戏结束,若不是雷,则显示周围八格的雷的数量,若为0,则进行展开。
这里只是简单的分析,接下来让我们动手实操,一步步实现扫雷游戏
三.项目实现
1.代码说明
game.h ———— 声明实现游戏的函数
game.c ———— 游戏的实现
test.c ———— 游戏逻辑
2.基本数据的定义
我们为了更加方便的使用和修改数据,我们可以使用宏定义对初始化棋盘的二位数组的行和列进行宏定义
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define MINE 10 //雷的个数
这里需要说明一下为什么会出现ROWS和COLS,这是因为我们要初始化一个9✖9的一个棋盘时,实际要定义一个11✖11的二维数组,这是为了防止越界访问的出现,如下图,我们如果点击绿色的方格,如果是9✖9的话,右面三格就无法进行计数了。
3.棋盘的初始化
我们先要对棋盘进行定义,一个是用来埋雷(mine),另一个用来展示给玩家(show),然后定义一个函数init_board用来对棋盘进行初始化,这样可以大大减少代码量,增加代码的可维护性。
void init_board(char board[ROWS][COLS] , int rows, int cols, char ch)
{
int i = 0 ,j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = ch;
}
}
}
4.棋盘的打印
游戏过程中,我们需要随时将棋盘打印,此时我们就可以定义一个函数print_board,可以随时调用该函数,免得修改时修改一大片,bug还很难找出。
void print_board(char board[ROWS][COLS], int row, int col)
{
printf("--------扫雷游戏-------\n");
int i = 0, 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");
}
printf("----------------------\n");
}
5.埋雷
接着就是进行埋雷,为了使每次雷的位置随机,我们需要使用到 srand() 和 rand() 函数获得随机数,然后对这个随机数进行取模加一,这样就可以得到1到9的随机数,然后将数值放到数组中,实现随机埋雷。
void set_mine(char board[ROWS][COLS], int row, int col)
{
int mine = MINE;
while (mine)
{
int x = (rand()) % row + 1, y = (rand()) % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
mine--;
}
}
}
6.对雷进行计数
我们在翻开方格的时候,若周围有雷,则需要获取周围八格的雷的数量,所以我们定义一个新函数COUNT用来统计雷的数量,假设(x,y)是我们要翻开的方格,我们只需要将其周围的8个数据相加,即可得到雷的数量。
int COUNT(char board[ROWS][COLS], int x, int y)
{
int count = 0;
return count =
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';
}
难点
我们为什么要减去8✖‘0’呢,我们使用的是char类型的二维数组,里面储存的是字符,我们可以通过ASCAll码表得知字符0为48,其它字符数字依次加1,那我们就可以得到以下的结果,1+‘0’=字符1,2+‘0’=字符2,所以上述的表达式中我们减去8✖‘0’可以得到整型数据,然后再将这个值赋值给count。
7.递归展开
在我们翻开一个格子的时候,如果周围有雷,我们需要显示雷的个数,如果没有,将该位置标记为空白,然后对周围的方格进行递归判定。
void expand_blank(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
{
int count = 0, i = 0, j = 0;
count = COUNT(mine, x, y);
if (count)
{
show[x][y] = count + '0';
}
else if (show[x][y] == '*')//*代表为排查,防止死循环
{
show[x][y] = ' ';
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
expand_blank(mine, show, i, j);
}
}
}
}
}
8.排雷
这是扫雷中最重要的一个部分,在游戏的过程中,我们要输入方格对应的坐标,然后与我们生成的棋盘比较,得到的结果有三种,分别为有雷游戏结束,无雷游戏继续和错误的输入
int check_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0, y = 0;
while (1)
{
printf("请输入要排查的坐标\n");
scanf("%d %d", &x, &y);
if (x > 0 && x <= row && y > 0 && y <= col)
{
if (mine[x][y] == '0')
{
expand_blank(mine, show, x, y);
print_board(show, ROW, COL);
return 0;
}
else
{
printf("很遗憾,你被炸死了\n");
return 1;
}
}
else
printf("坐标错误,请重新输入\n");
}
}
9.标记
如果我们中途推测出某个方格的位置是雷,那我们就可以进行标记来实现。
int mark_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int* p)
{
while (1)
{
int i = 0, j = 0;
printf("请输入要标记的坐标\n");
scanf("%d %d", &i, &j);
if (i > 0 && i <= row && j > 0 && j <= col)
{
if (show[i][j] == '*')
{
show[i][j] = '!';
print_board(show, ROW, COL);
if (mine[i][j] == '1')
{
*p = *p - 1;
}
return *p;
}
else
{
printf("坐标已排查,请重新标记\n");
}
}
else
printf("坐标错误\n");
}
}
四.完整代码演示
1.game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void init_board(char board[ROWS][COLS], int rows, int cols, char ch)
{
int i = 0 ,j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = ch;
}
}
}
void print_board(char board[ROWS][COLS], int row, int col)
{
printf("--------扫雷游戏-------\n");
int i = 0, 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");
}
printf("----------------------\n");
}
void set_mine(char board[ROWS][COLS], int row, int col)
{
int mine = MINE;
while (mine)
{
int x = (rand()) % row + 1, y = (rand()) % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
mine--;
}
}
}
int COUNT(char board[ROWS][COLS], int x, int y)
{
int count = 0;
return count =
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 expand_blank(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
{
int count = 0, i = 0, j = 0;
count = COUNT(mine, x, y);
if (count)
{
show[x][y] = count + '0';
}
else if (show[x][y] == '*')
{
show[x][y] = ' ';
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
expand_blank(mine, show, i, j);
}
}
}
}
}
int check_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0, y = 0;
while (1)
{
printf("请输入要排查的坐标\n");
scanf("%d %d", &x, &y);
if (x > 0 && x <= row && y > 0 && y <= col)
{
if (mine[x][y] == '0')
{
expand_blank(mine, show, x, y);
print_board(show, ROW, COL);
return 0;
}
else
{
printf("很遗憾,你被炸死了\n");
return 1;
}
}
else
printf("坐标错误,请重新输入\n");
}
}
int mark_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int* p)
{
while (1)
{
int i = 0, j = 0;
printf("请输入要标记的坐标\n");
scanf("%d %d", &i, &j);
if (i > 0 && i <= row && j > 0 && j <= col)
{
if (show[i][j] == '*')
{
show[i][j] = '!';
print_board(show, ROW, COL);
if (mine[i][j] == '1')
{
*p = *p - 1;
}
return *p;
}
else
{
printf("坐标已排查\n");
}
}
else
printf("坐标错误\n");
}
}
2.test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void game()
{
int m = 0, win = MINE;
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
init_board(mine, ROWS, COLS, '0');
init_board(show, ROWS, COLS, '*');
set_mine(mine, ROW, COL);
print_board(show, ROW, COL);
while (win)
{
printf("输入1排查,输入2标记\n");
scanf("%d", &m);
if (m == 1)
{
if (check_mine(mine, show, ROW, COL))
{
break;
}
}
else if (m == 2)
{
if (!(mark_mine(mine, show, ROW, COL, &win)))
{
printf("恭喜,你赢了!\n");
break;
}
}
}
print_board(show, ROW, COL);
print_board(mine, ROW, COL);
}
void menu()
{
printf("*****************************\n");
printf("********* 1.play **********\n");
printf("********* 0.exit **********\n");
printf("*****************************\n");
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择>:");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
break;
default:
break;
}
} while (input);
return 0;
}
3.game.h
#pragma once
#ifndef _GAME_H
#define _GAME_H
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define MINE 10
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void init_board(char board[ROWS][COLS], int rows, int cols, char);
void set_mine(char board[ROWS][COLS], int row, int col);
void print_board(char board[ROWS][COLS], int row, int col);
int check_mine(char board[ROWS][COLS], char show[ROWS][COLS], int row, int col);
int mark_mine(char board[ROWS][COLS], char show[ROWS][COLS], int row, int col, int* p);
#endif
五.尾声
如果有错误地方,欢迎大家及时指出,也欢迎大家的讨论