要想实现扫雷的游戏我们要知道扫雷的规则是什么:
在一个99(初级),1616(中级),16*30(高级),或自定义大小的方块矩阵中随机布置一定量的地雷(初级为10个,中级为40个,高级为99个)。由玩家逐个翻开方块,以找出所有地雷为最终游戏目标。如果玩家翻开的方块有地雷,则游戏结束。
游戏主区域由很多个方格组成。使用鼠标左键随机点击一个方格,方格即被打开并显示出方格中的数字;方格中数字则表示其周围的8个方格隐藏了几颗雷。
我们这以9*9的矩阵为例,要想实现这个游戏我选择如果写在一个文件里面会显得十分混乱,所以我们分为三个文件来完成:
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include"game.h"
#include<stdlib.h>
#include<time.h>
void game()
{
char showboard[ROWS][COLS] = { 0 };
char botboard[ROWS][COLS] = { 0 };
initboard(showboard, COLS, ROWS, '*');
initboard(botboard, COLS, ROWS, '0');
setMine(botboard, COL, ROW);
char botboard2[ROWS][COLS];
copy(botboard2, botboard, COL, ROW);
//displayboard(botboard, COL, ROW); //这个显示随机地雷埋在哪个位置,调试的是候可以使用
int x, y;
int n = 0;
while (1)
{
displayboard(showboard, COL, ROW);
printf("请输入坐标值:");
scanf("%d%d", &x, &y);
if (x > 0 && x <=COL && y > 0 && y <= COL)
{
n++;
int ret = findMine(botboard, x, y);
if (ret == 10)
{
showboard[x][y] = '#';
printf("你被炸死了!!\n");
break;
}
if (n == COL * ROW - 10)
{
printf("你获得了胜利\n");
break;
}
if (ret == 0)
{
fun(botboard2, showboard, botboard, x, y);
}
else
{
showboard[x][y] = ret + '0';
continue;
}
}
printf("坐标错误!\n");
}
displayboard(showboard, COL, ROW);
//displayboard(botboard, COL, ROW);
}
void manu() //打印菜单
{
printf("***************************\n");
printf("***************************\n");
printf("*******1 . play************\n");
printf("*******2 . excite**********\n");
printf("***************************\n");
printf("***************************\n");
}
int main()
{
int n;
srand((unsigned)time(NULL));
do
{
manu();
printf("玩家请选择:");
scanf("%d", &n);
switch (n)
{
case 1:
game();
break;
case 2:
n = 0;
break;
default:
n = 0;
break;
}
} while (n);
}
在game()函数里设置了两个二维字符数组showboard()用来打印给玩家看到显示数组 和 botboard()用来储存雷的位置的数组,实际上我们下面函数在进行运算的时候使用的是botboard()来获得棋盘的信息。这里把数组设置成1111的数组但是我们只显示其中99的部分,原因下面分析findmine函数时会解析。
game.c:
#pragma once
#define COL 9
#define ROW 9
#define COLS 11
#define ROWS 11
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
//初始化棋盘
void initboard(char board[ROWS][COLS], int col, int row, char set);
//展示棋盘
void displayboard(char board[ROWS][COLS], int col, int row);
//设置地雷
void setMine(char board[ROWS][COLS], int col, int row);
//排地雷
int findMine(char board[ROWS][COLS], int col, int row);
//快速排除周围没有一个地雷的点
void fun(char board2[ROWS][COLS], char board1[ROWS][COLS], char board[ROWS][COLS], int x, int y);
void copy(char board[ROWS][COLS], char board1[ROWS][COLS], int col, int row);
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include"game.h"
void initboard(char board[ROWS][COLS], int col, int row, char set)
{
int i = 0, j = 0;
for (i = 0; i < col; i++)
{
for (j = 0; j < row; j++)
{
board[j][i] = set;
}
}
}
void displayboard(char board[ROWS][COLS], int col, int row)
{
int i = 0, j = 0;
for (i = 0; i <=col; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <=col; i++)
{
printf("%d ", i);
for (j = 1; j <= row; j++)
{
printf("%c ", board[j][i]);
}
printf("\n");
}
}
void setMine(char board[ROWS][COLS], int col, int row)
{
int x, y;
int n = 1;
while (n<=10)
{
x = rand() % (col - 1) + 1;
y = rand() % (row - 1) + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
n++;
}
}
}
int findMine(char board[ROWS][COLS], int x, int y)
{
while (1)
{
if (board[x][y] == '1')
{
return 10;
}
else
{
return board[x - 1][y - 1] +
board[x][y - 1] +
board[x + 1][y - 1] +
board[x - 1][y] +
board[x + 1][y] +
board[x - 1][y + 1] +
board[x][y + 1] +
board[x + 1][y + 1] - 8 * board[x][y];
}
}
}
void fun(char board2[ROWS][COLS],char board1[ROWS][COLS],char board[ROWS][COLS], int x, int y) //fun(botboard, showboard, botboard, x, y);
{
int ret = findMine(board, x, y);
if (ret == 0 && x > 0 && y > 0 && board2[x][y] != ' ')
{
board2[x][y] = ' ';
fun(board2, board1, board, x - 1, y);
fun(board2, board1, board, x + 1, y);
fun(board2, board1, board, x, y + 1);
fun(board2, board1,board, x, y - 1);
}
board1[x][y] = ret + '0';
}
void copy(char board[ROWS][COLS], char board1[ROWS][COLS],int col,int row)
{
int i = 0, j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
board[i][j] = board1[i][j];
}
}
}
game.c函数的解析:
initboard 和 displayboard
两个函数 一个是用来初始化字符数组 一个是将字符数组打印在屏幕上,玩家输入的是坐标,而我们要输出的是该坐标周围有几个雷。用来实际计算的数组botboard我们将所有元素设为’0’,地雷设为0这样我们在后面计算地雷数时会方便不少,showboard全部设为’*’。
setMine
用来设置地雷数,我们这里是藏十个地雷,调用函数rand()生成随机数来随机取横纵坐标,随机坐标的值赋值为‘1’,但只在9*9的范围里赋值。
findMine
这里是一个有返回值的函数,这个函数的目的是先进行判断如果输入的那点值正好为’1’就返回一个特定的值,如果不是就返回周围’1’的个数也就是炸弹数。由于字符在内存中是以ASCII码的形式存储所以炸弹数等于周围八个坐标对应的值减去8*‘0’。这里还可以解释为什么要将数组设成1111的大小,如果输入的坐标在最后一行/列或第一行/列那么他周围只有3或5个坐标,如果分情况讨论会过于麻烦,不如设成1111多出两行两列,最边缘的点周围也有八个点,而且由于setmine只在9*9的范围里埋地雷顾多出来的行和列不影响结果。
递归函数fun
程序到上面其实就可以正常运行了,但在玩的时候你会发现如果你输入的点得到0即周围八个点没有地雷,那你还要再把周围八个点的坐标一个一个输入游戏才能继续,如果输出是零能自动展开周围的点该多好呀!
void fun(char board2[ROWS][COLS],char board1[ROWS][COLS],char board[ROWS][COLS], int x, int y)
{
int ret = findMine(board, x, y);
if (ret == 0 && x > 0 && y > 0 && board2[x][y] != ' ')
{
board2[x][y] = ' ';
fun(board2, board1, board, x - 1, y);
fun(board2, board1, board, x + 1, y);
fun(board2, board1, board, x, y + 1);
fun(board2, board1,board, x, y - 1);
}
board1[x][y] = ret + '0';
}
void copy(char board[ROWS][COLS], char board1[ROWS][COLS],int col,int row)
{
int i = 0, j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
board[i][j] = board1[i][j];
}
}
}
fun(botboard2, showboard, botboard, x, y);
char botboard2[ROWS][COLS];
copy(botboard2, botboard, COL, ROW);
这里我们采用递归的方法这边我传进去了三个数组botboard2,botboard,showboard,由后两段代码我们可以知道其实botboard2和botboard是两个完全一样的数组但是地址不一样。
每一次递归我们先通过findmine函数的返回值得到ret,然后我们判断ret是否为0(即周围八个点是否都没有地雷) x > 0 && y > 0是判断是否到达边界防止递归越过9*9边界,board2[x][y] != ’ '判断字符串是否为我们已经检查过的点不在进入递归以防死循环(比如由于我们是向一个点的四个方向递归,第一个点右边的点进入下一次递归,第二层递归四个方向 向左边 的递归实际上又回到了第一层递归,但是board2[x][y] != ’ ‘的判断条件可以免除这种情况发生)。进入判断的第一件事就是将我们检查的点设成’ ‘,然后向该店的左右上下四个方向递归。
botboard2 作用就是将递归的点设成’ '防止死循环
botboard 作用是由于botboard2递归过的点值被修改会导致ret=finmine无法判断周围八个点是否为炸弹,故需要一个botboard2一模一样的数组来专门输入findmine函数
这就是递归函数的逻辑,这个函数是我自己想出来的,可能不是非常好但完成了目标,欢迎大家提出改进意见和自己的想法