设计思路是用两个二维数组,一个二维数组存储雷的位置,另一个二维数组存储显示到UI的信息。
main
函数调用MineSweeperGameStart
函数:
int main(int argc, char* argv[])
{
MineSweeperGameStart();
return 0;
}
MineSweeperGameStart
函数打印菜单,调用StartGame
函数进入游戏:
void MineSweeperGameStart()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
Menu();
printf("请输入你的选择:>");
scanf("%d", &input);
switch (input)
{
case 0:
printf("游戏退出!\n");
break;
case 1:
StartGame();
break;
default:
printf("输入非法,请重新输入!\n");
break;
}
} while (input);
}
void Menu()
{
printf("*****************************\n");
printf("******* 1. Play *******\n");
printf("******* 0. Exit *******\n");
printf("*****************************\n");
}
StartGame
函数进行进行两个二维数组的初始化,以及调用FindMine
函数进入扫雷游戏的循环。
void StartGame()
{
char mine_array[ROWS][COLS] = { 0 }; // 存放雷的数组
char show_array[ROWS][COLS] = { 0 }; // 显示界面的数组
// 初始化数组
InitArray(mine_array, ROWS, COLS, INIT_MINE_SYMBOL);
InitArray(show_array, ROWS, COLS, INIT_SHOW_SYMBOL);
DisplayArray(show_array, ROW, COL);
// 设置雷
SetMine(mine_array, ROW, COL);
// 测试打开
//DisplayArray(mine_array, ROW, COL);
FineMine(mine_array, show_array, ROW, COL);
}
游戏循环中判断用户输入的合法性,如果坐标合法,根据雷数组判断坐标中存放的是否是雷,如果是雷则游戏结束,如果不是雷,则会将该坐标周围有几个雷,显示在UI中。如果所有的雷都排尽则游戏结束。
// 游戏结束:
// 1.遇到雷
// 2.所有非雷都已找出
void FineMine(char mine_array[ROWS][COLS], char show_array[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
while (1)
{
printf("请输入坐标:>");
scanf("%d%d", &x, &y);
// 判断坐标的合法性
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine_array[x][y] == MINE_SYMBOL)
{
printf("游戏结束,被雷炸死!\n");
DisplayArray(mine_array, ROW, COL);
break;
}
else
{
// 没踩到雷 递归展开
RecursionExpansion(mine_array, show_array, row, col, x, y);
DisplayArray(show_array, row, col);
if (IsAllMinesCleared(show_array, row, col))
{
printf("恭喜你已排所有的雷!\n");
DisplayArray(mine_array, row, col);
}
}
}
else
{
printf("输入坐标无效,重新输入!\n");
}
}
}
在StartGame
函数中调用InitArray
函数进行数组的初始化,将两个二维数组分别初始化为'0'
和'*'
。
#define INIT_MINE_SYMBOL ('0')
#define INIT_SHOW_SYMBOL ('*')
void InitArray(char ary[ROWS][COLS], int rows, int cols, char ch)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
ary[i][j] = ch;
}
}
}
UI显示到界面中,打印坐标的索引和显示的内容。
void DisplayArray(char ary[ROWS][COLS], int row, int col)
{
// 打印坐标索引
int j = 0;
int i = 0;
printf("#####################\n");
for (i = 0; i <= col; i++)
{
if (i == 0)
{
printf("%d|", i);
}
else
{
printf("%d ", i);
}
}
printf("\n");
for (i = 0; i <= col; i++)
{
if (i == 0)
{
printf("-|");
}
else
{
printf("--");
}
}
printf("\n");
// 打印棋盘数据
for (i = 1; i <= row; i++)
{
printf("%d|", i);
for (j = 1; j <= col; j++)
{
printf("%c ", ary[i][j]);
}
printf("\n");
}
printf("#####################\n");
}
有个雷的二维数组,设置MINE_COUNT
个雷,雷的坐标随机。
void SetMine(char mine_array[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int count = MINE_COUNT;
while (count > 0)
{
x = rand() % row + 1;
y = rand() % col + 1;
if (mine_array[x][y] == INIT_MINE_SYMBOL)
{
mine_array[x][y] = MINE_SYMBOL;
count--;
}
}
}
统计坐标8邻域中含有雷'1'
的个数,将8邻域中的数加起来,然后减去8 * INIT_MINE_SYMBOL
,也就是减去8 * '0'
作为返回值。
int GetMineCount(char mine_array[ROWS][COLS], int x, int y)
{
return mine_array[x - 1][y + 1] + mine_array[x - 1][y] + mine_array[x - 1][y - 1] +
mine_array[x][y + 1] + mine_array[x][y - 1] +
mine_array[x + 1][y + 1] + mine_array[x + 1][y] + mine_array[x + 1][y - 1] - 8 * INIT_MINE_SYMBOL;
}
当一个坐标周围的8邻域都没有雷时进行递归展开。
// 当show_array周围8个位置都没有雷时进行递归展开
void RecursionExpansion(char mine_array[ROWS][COLS], char show_array[ROWS][COLS], int row, int col, int x, int y)
{
int local_mine_count = GetMineCount(mine_array, x, y);
show_array[x][y] = local_mine_count + '0';
if (local_mine_count == 0)
{
int i = 0;
int j = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (i >= 1 && i <= row && j >= 1 && j <= col) // 坐标不越界
{
if (show_array[i][j] == INIT_SHOW_SYMBOL) // 跳过已经递归展开过的 避免循环递归
{
RecursionExpansion(mine_array, show_array, row, col, i, j);
}
}
}
}
}
}
特别要注意要过滤掉已经展开过的坐标,以免发生死递归。
扫雷游戏github地址