目录
1.初始化两个棋盘,一个布雷(mine[][]),一个展示给玩家(show[][])
前言
例如:在继《三子棋》游戏成功实现后,《扫雷》的实现是对自己的又一次提升。
提示:以下是本篇文章正文内容
一、扫雷的游戏规则
这款游戏的玩法是在一个9*9(初级),16*16(中级),16*30(高级),或自定义大小的方块矩阵中随机布置一定量的地雷(初级为10个,中级为40个,高级为99个)。由玩家逐个翻开方块,以找出所有地雷为最终游戏目标。如果玩家翻开的方块有地雷,则游戏结束。(来自百度百科)
二、实现步骤
1.初始化两个棋盘,一个布雷(mine[][]),一个展示给玩家(show[][])
在主函数中调用两次即可。(需要注意的是,如果仅仅是初始化所需要的9x9,在访问(9,0)的时候会出现周围的雷无法遍历的情况,周围的八个格子有一部分会越界,所以我们在初始化时,上下左右需要各多初始化一行,即总共多初始化4行)
int rows = row + 4;
int cols = col + 4;
// 展示给玩家的棋盘
char** show = BoardInit(rows, cols);
// 雷区
char** mine = BoardInit(rows, cols);
代码如下(示例):
char** BoardInit(int row, int col)
{
char** board = (char**)malloc(sizeof(char*) * row);
for (int i = 0; i < row; i++)
{
board[i] = (char*)malloc(sizeof(char) * col);
}
for (int i = 0; i < row; i++)
{
memset(board[i], '*', sizeof(char) * col);
}
return board;
}
2.打印棋盘
采用最基本的两层循环遍历二维数组进行打印,但是需要打印出来行号与列号,方便查找。
代码如下(示例):
void BoardPrint(char** mine, int row, int col)
{
// 打印列号
printf(" # ");
for (int j = 1; j <= col; j++)
{
printf("%2d ", j);
}
printf("\n");
for (int i = 0; i < row; i++)
{
// 打印行号
printf("%2d ", i + 1);
for (int j = 0; j < col; j++)
{
// 打印雷
printf("%2c ", mine[i][j]);
}
printf("\n");
}
}
效果:
3.布置雷场
采用时间戳设置随机数种子的方法获得随机数,后采用取模获得想要的随机数范围。要注意的是在主函数调用一次srand()即可,否则浪费资源(此次需要随机数的时候都获取随机数种子会浪费资源)。
代码如下(示例):
void MineSet(char** mine, int row, int col, int level)
{
int i = 0;
while (i < 1)
{
int mineSetX = rand() % level + 1;
int mineSetY = rand() % level + 1;
if (mine[mineSetX][mineSetY] != '0')
{
// '0'是雷
mine[mineSetX][mineSetY] = '0';
i++;
}
}
}
注意:在布置雷场的时候徐娅布置在小棋盘中(9x9),原本生成的棋盘是10x10(大棋盘)的。
4.排雷过程(用递归实现棋盘扩张)
我们从玩经典的《扫雷》游戏可知,这个游戏在点击安全地点后,会将周围所有的安全地点都筛选出来,直至边缘存在雷的格子暂停扩张。
在此我选择了使用递归来进行解决,一开始想要用循环,但是觉得不怎么好写:周围的八个格子成为新的格子继续扩张。
于是想到了递归:每次只需要将想要探索的格子位置成为新的函数参数即可进行新的探索,递归出口是周围有雷的时候。(可以设置计数器,只要周围8个格子有雷就加一,如果计数器为0,那就递归)扩张后的安全棋盘可以设置为空格,方便观察。
代码如下(示例):
int CountMine(char** mine, int i, int j)
{
int count = ((mine[i - 1][j - 1])
+ (mine[i - 1][j])
+ (mine[i - 1][j + 1])
+ (mine[i][j - 1])
+ (mine[i][j + 1])
+ (mine[i + 1][j - 1])
+ (mine[i + 1][j])
+ (mine[i + 1][j + 1])
- 8 * '*') / 6;
// 地雷是'*',减去'*'剩下的就是'0'与'*'的差值6,除以6就知道有几个'0'
return count;
}
void Click(char** mine, char** show, int x, int y, int row, int col)
{
// 判断排雷区间是否合法
if (!((x >= 0) && (x <= row)
&& (y >= 0) && (y <= col)))
{
return;
}
int i = x + 1;
int j = y + 1;
int count = CountMine(mine, i, j);// 排查(x,y)一圈的雷计数器
show[x][y] = count + '0';
if (count == 0)
{
// 该位置安全
show[x][y] = ' ';
// 继续拓展棋盘的安全地带
// 左上
if (x - 1 >= 0 && x - 1 <= row && y - 1 >= 0 && y - 1 <= col && show[x - 1][y - 1] == '*')
Click(mine, show, x - 1, y - 1, row, col);
// 上
if (x - 1 >= 0 && x - 1 <= row && y >= 0 && y <= col && show[x - 1][y] == '*')
Click(mine, show, x - 1, y, row, col);
// 右上
if (x - 1 >= 0 && x - 1 <= row && y + 1 >= 0 && y + 1 <= col && show[x - 1][y + 1] == '*')
Click(mine, show, x - 1, y + 1, row, col);
// 左
if (x >= 0 && x <= row && y - 1 >= 0 && y - 1 <= col && show[x][y - 1] == '*')
Click(mine, show, x, y - 1, row, col);
// 右
if (x >= 0 && x <= row && y + 1 >= 0 && y + 1 <= col && show[x][y + 1] == '*')
Click(mine, show, x , y + 1, row, col);
// 左下
if (x + 1 >= 0 && x + 1 <= row && y - 1 >= 0 && y - 1 <= col && show[x + 1][y - 1] == '*')
Click(mine, show, x + 1, y - 1, row, col);
// 下
if (x + 1 >= 0 && x + 1 <= row && y >= 0 && y <= col && show[x + 1][y] == '*')
Click(mine, show, x + 1, y, row, col);
// 右下
if (x + 1 >= 0 && x + 1 <= row && y + 1 >= 0 && y + 1 <= col && show[x + 1][y + 1] == '*')
Click(mine, show, x + 1, y + 1, row, col);
}
}
5.判断输赢
输很好判断:只要输入的坐标在雷场棋盘上是雷('0'),那就exit(-1),游戏失败。
// 判输
if (mine[x-1][y-1] == '0')
{
// 炸死结束游戏
printf("Lose.\nMineboard here:\n");
BoardPrint(mine, row, col);
exit(-1);
}
赢则用展示给玩家的棋盘(show[][])上存不存在未知位置('*')即可判断。
代码如下(示例):
bool IsWin(char** show, int row, int col, int level)
{
int count = 0;
// 判断的是小棋盘(小棋盘才被排过雷)
for (int i = 1; i < row-1; i++)
{
for (int j = 1; j < col-1; j++)
{
if (show[i][j] == '?')
{
count++;
}
}
}
if (count == level)
{
return true;
}
else
{
return false;
}
}
6.判断是否已经排过雷
如果玩家(在show[][]棋盘上)输入的位置上不是未知位置,那就说明该位置已经被探索过。
代码如下(示例):
// 判断是否已排过雷
if (show[x][y] != '*')
{
printf("******* The pos has been probed. *******\n");
printf("******* Try again. *******\n");
}
7.判断排雷位置是否合法
注意:此函数在不同的调用位置可能判断数据不同,但是大体上都是不为负,也不出小棋盘的最大限度。
代码如下(示例):
// 判断排雷区间是否合法
if ((x > 0) && (x <= row)
&& (y > 0) && (y <= col))
二、游戏主体
1.Game.c:
#define _CRT_SECURE_NO_WARNINGS
#include "Mine.h"
// 游戏主体
void Game(int selectMenu, int selectLevel)
{
// 布雷
srand((unsigned int)time(NULL));
int row = 10;
int col = 10;
// 难度(布雷个数、棋盘大小)
int level = 10;
if (selectLevel == 1)
{
level = 10;
row = 9;
col = 9;
}
else if (selectLevel == 2)
{
level = 40;
row = 16;
col = 16;
}
else if (selectLevel == 3)
{
level = 99;
row = 16;
col = 30;
}
int rows = row + 4;
int cols = col + 4;
// 排雷坐标
int x = 0, y = 0;
// 展示给玩家的棋盘
char** show = BoardInit(rows, cols);
BoardPrint(show, row, col);
// 雷区
char** mine = BoardInit(rows, cols);
// 布雷只在小棋盘上布
MineSet(mine, row + 1, col + 1, level);
do
{
// 询问是否标记
MarkIntegration(show,row,col);
// 判赢
if (IsWin(show, row, col, level))
{
// 获得胜利
printf("Win.\n");
exit(-1);
}
BoardPrint(show, row, col);
// 排雷
printf("Enter the position mine would be.\n");
scanf("%d %d", &x, &y);
while (getchar() != '\n');// 吸收
// 判断排雷区间是否合法
if ((x > 0) && (x <= row)
&& (y > 0) && (y <= col))
{
// 判赢
if (IsWin(show, row, col, level))
{
// 获得胜利
printf("Win.\n");
exit(-1);
}
// 判断是否已排过雷
if (show[x][y] != '*')
{
printf("******* The pos has been probed. *******\n");
printf("******* Try again. *******\n");
}
// 判输
if (mine[x-1][y-1] == '0')
{
// 炸死结束游戏
printf("Lose.\nMineboard here:\n");
BoardPrint(mine, row, col);
exit(-1);
}
// 排雷
Click(mine, show, x - 1, y - 1, row, col);
// 继续排雷
BoardPrint(show, row, col);
if (IsWin(show, row + 1, col + 1, level))
{
break;
}
}
else
{
printf("Illegal input.\n");
}
} while (selectMenu != 0);
}
int main()
{
printf("******************************\n");
printf("******** 1.Play *********\n");
printf("******** 0.Exit *********\n");
printf("******************************\n");
int selectMenu = 0;
int selectLevel = 0;
scanf("%d", &selectMenu);
while (getchar() != '\n');// 吸收
printf("********************************\n");
printf("******** 1. Easy *********\n");
printf("******** 2.Medium *********\n");
printf("******** 3. Hard *********\n");
printf("********************************\n");
scanf("%d", &selectLevel);
while (getchar() != '\n');// 吸收
if (selectMenu == 1)
{
Game(selectMenu, selectLevel);
}
return 0;
}
2.Mine.c
#define _CRT_SECURE_NO_WARNINGS
#include "Mine.h"
char** BoardInit(int row, int col)
{
char** board = (char**)malloc(sizeof(char*) * row);
for (int i = 0; i < row; i++)
{
board[i] = (char*)malloc(sizeof(char) * col);
}
for (int i = 0; i < row; i++)
{
memset(board[i], '*', sizeof(char) * col);
}
return board;
}
void BoardPrint(char** mine, int row, int col)
{
// 打印列号
printf(" # ");
for (int j = 1; j <= col; j++)
{
printf("%2d ", j);
}
printf("\n");
for (int i = 0; i < row; i++)
{
// 打印行号
printf("%2d ", i + 1);
for (int j = 0; j < col; j++)
{
// 打印雷
printf("%2c ", mine[i][j]);
}
printf("\n");
}
}
void MineSet(char** mine, int row, int col, int level)
{
int i = 0;
while (i < 1)
{
int mineSetX = rand() % level + 1;
int mineSetY = rand() % level + 1;
if (mine[mineSetX][mineSetY] != '0')
{
// '0'是雷
mine[mineSetX][mineSetY] = '0';
i++;
}
}
}
void MarkIntegration(char** show, int row, int col)
{
printf("Mark something?...Or unmark?...(Y/N/U)\n");
char markSel = 'N';
scanf("%c", &markSel);
while (getchar() != '\n');
if (markSel != 'Y' && markSel != 'N' && markSel != 'U')
{
printf("Illegal input.\n");
scanf("%c", &markSel);
while (getchar() != '\n');
}
if (markSel == 'Y')
{
Mark(show, row, col);
}
if (markSel == 'U')
{
Unmark(show, row, col);
}
}
// 建立标记
void Mark(char** show, int row, int col)
{
int markX = 0;
int markY = 0;
printf("Enter the mark position.\n");
scanf("%d %d", &markX, &markY);
while (getchar() != '\n');// 吸收
while (!((markX >= 0) && (markX <= row)
&& (markY >= 0) && (markY <= col)
&& (show[markX-1][markY-1] == '*')))
{
printf("Illegal input.\n");
scanf("%d %d", &markX, &markY);
while (getchar() != '\n');// 吸收
}
show[markX-1][markY-1] = '?';
}
// 取消标记
void Unmark(char** show, int row, int col)
{
int unmarkX = 0;
int unmarkY = 0;
printf("Enter the unmark position.\n");
scanf("%d %d", &unmarkX, &unmarkY);
while (getchar() != '\n');// 吸收
while (!((unmarkX >= 0) && (unmarkX <= row)
&& (unmarkY >= 0) && (unmarkY <= col)
&& (show[unmarkX-1][unmarkY-1] == '*')))
{
printf("Illegal input.\n");
scanf("%d %d", &unmarkX, &unmarkY);
while (getchar() != '\n');// 吸收
}
show[unmarkX-1][unmarkY-1] = '*';
}
void Click(char** mine, char** show, int x, int y, int row, int col)
{
// 判断排雷区间是否合法
if (!((x >= 0) && (x <= row)
&& (y >= 0) && (y <= col)))
{
return;
}
int i = x + 1;
int j = y + 1;
int count = CountMine(mine, i, j);// 排查(x,y)一圈的雷计数器
show[x][y] = count + '0';
if (count == 0)
{
// 该位置安全
show[x][y] = ' ';
// 继续拓展棋盘的安全地带
// 左上
if (x - 1 >= 0 && x - 1 <= row && y - 1 >= 0 && y - 1 <= col && show[x - 1][y - 1] == '*')
Click(mine, show, x - 1, y - 1, row, col);
// 上
if (x - 1 >= 0 && x - 1 <= row && y >= 0 && y <= col && show[x - 1][y] == '*')
Click(mine, show, x - 1, y, row, col);
// 右上
if (x - 1 >= 0 && x - 1 <= row && y + 1 >= 0 && y + 1 <= col && show[x - 1][y + 1] == '*')
Click(mine, show, x - 1, y + 1, row, col);
// 左
if (x >= 0 && x <= row && y - 1 >= 0 && y - 1 <= col && show[x][y - 1] == '*')
Click(mine, show, x, y - 1, row, col);
// 右
if (x >= 0 && x <= row && y + 1 >= 0 && y + 1 <= col && show[x][y + 1] == '*')
Click(mine, show, x , y + 1, row, col);
// 左下
if (x + 1 >= 0 && x + 1 <= row && y - 1 >= 0 && y - 1 <= col && show[x + 1][y - 1] == '*')
Click(mine, show, x + 1, y - 1, row, col);
// 下
if (x + 1 >= 0 && x + 1 <= row && y >= 0 && y <= col && show[x + 1][y] == '*')
Click(mine, show, x + 1, y, row, col);
// 右下
if (x + 1 >= 0 && x + 1 <= row && y + 1 >= 0 && y + 1 <= col && show[x + 1][y + 1] == '*')
Click(mine, show, x + 1, y + 1, row, col);
}
}
int CountMine(char** mine, int i, int j)
{
int count = ((mine[i - 1][j - 1])
+ (mine[i - 1][j])
+ (mine[i - 1][j + 1])
+ (mine[i][j - 1])
+ (mine[i][j + 1])
+ (mine[i + 1][j - 1])
+ (mine[i + 1][j])
+ (mine[i + 1][j + 1])
- 8 * '*') / 6;
// 地雷是'*',减去'*'剩下的就是'0'与'*'的差值6,除以6就知道有几个'0'
return count;
}
bool IsWin(char** show, int row, int col, int level)
{
int count = 0;
// 判断的是小棋盘(小棋盘才被排过雷)
for (int i = 1; i < row-1; i++)
{
for (int j = 1; j < col-1; j++)
{
if (show[i][j] == '?')
{
count++;
}
}
}
if (count == level)
{
return true;
}
else
{
return false;
}
}
3. Mine.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <time.h>
char** BoardInit(int row, int col);
void BoardPrint(char** mine, int row, int col);
void MineSet(char** mine, int row, int col, int level);
// 用户点击后排查后的雷区
void Click(char** mine, char** show, int x, int y, int row, int col);
bool IsWin(char** show, int row, int col, int level);
int CountMine(char** mine, int row, int col);
// 标记
void MarkIntegration(char** show, int row, int col);
// 建立标记
void Mark(char** show, int row, int col);
// 取消标记
void Unmark(char** show, int row, int col);
总结
对这次自己写的程序属于基本满意,最难实现的就是对于玩家点击棋盘进行排雷的过程,卡了好久,一半参考一半自己完成总算是实现出来了,整个程序逻辑稍显复杂,在输入后要判断赢没赢,同时还要给玩家选择需不需要标记等等,菜单的实现其实也是一个大工程。
立个flag:以后技术好了,要实现出来一个游戏的ui界面,变成可玩的游戏。