仍有许多需要改进的地方,但是如果不在乎效率,这个代码已经可以正确运行了。
在递归展开的部分,我使用了一个笨方法。
在理想情况下,递归应该在不能够继续展开(空格的周围已经没有未检查过的空格可以继续递下去了,空格的周围都是检查过的空格和数字字符)的情况下“归”。
但是呢,这里的递归次数是我自己定死的,可以在程序运行前修改,但是在程序运行起来之后就不能修改了。毫无疑问,这种方法的效率是很低的——即使我点到的位置,它周围的八个格子全部都是地雷,递归仍然会执行设定次数。
在game.c的144行,可以调整递归的次数。我所设定的递归次数是比较合适的次数,90%的情况都可以满足了。但是也发生过没能把所有应该展开的地方全部展开的情况(当路径弯弯绕绕的时候就会出现这种情况),当然我可以增大递归执行的次数,但是这也会使程序的效率进一步变低。
另外,在程序中,无论一个坐标之前是否被检查过,都会再次检查,这也是程序效率变低。
有一些思路:
- 如果运气不好,在第一次输入坐标时就会引爆地雷,导致游戏失败。这个是容易避免的(虽然我程序中并没有避免),就是在输入了一个坐标之后再设置地雷,只要避开被检查了的那个坐标即可,当然,获胜的条件(次数)也要修改一下
- 如果想提高程序的效率,避免重复检查同一个坐标,可以创建一个数组Mark,用于记录哪个坐标被检查过了。
- 标记功能也可以用数组Mark来实现。检查模式和标记模式的切换可以利用输入的坐标中的x来实现。例如:先输入x,判断x是否等于100,如果不等于,继续输入另一个坐标y,如果等于100,切换为标记模式。同样可以通过这种方式从标记模式切换回检查模式
- 如果想要实现递归在不能够继续展开(空格的周围已经没有未检查过的空格可以继续递下去了,空格的周围都是检查过的空格和数字字符)的情况下“归”,那首先要处理的,是数组ShowMineNumber的外围(第一行和最后一行,第一列和最后一列),让它的外围在一开始就是型号‘*’和空格‘ ’以外的字符(我在程序中限制了递归的中心坐标不能超出中间的9*9范围)
由于时间关系,就不继续改了。下面就是具体的程序,带有详细的注释。
main.c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include "game.h"
#include <stdlib.h>
#include <time.h>
int main()
{
srand((unsigned int)time(NULL)); //使用时间戳作为随机数生成的起点
int selection = 0;
do
{
Menu(); //打印菜单
printf("请输入你的选择:》");
scanf("%d", &selection);
switch (selection)
{
case 1:
printf("玩游戏——扫雷\n");
PlayGame();
break;
case 0:
printf("退出\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (selection != 0);
return 0;
}
game.h
#pragma once
#define ROW 9
#define COLUMN 9
#define ROW_EXTEND (ROW+2)
#define COLUMN_EXTEND (COLUMN+2)
#define MINE_NUMBER_EASY 10
#define MINE_NUMBER_MIDDLE 20
#define MINE_NUMBER_HARD 30
/*
* 简介:打印菜单
* 参数:none
* 返回值:none
*/
void Menu();
// 简介:二维数组初始化,根据传进来的symbol进行初始化
// 参数:board,11*11二维数组,想要将其初始化的数组
// 参数:row,行
// 参数:column,列
// 参数:symbol,初始化的目标符号,如:‘*’
// 返回值:none
void BoardInit(char board[ROW_EXTEND][COLUMN_EXTEND], int row, int column, char symbol);
// 简介:在屏幕上打印出数组的中间部分,11*11的数组打印出中间的9*9,另外,给行、列加标号
// 参数:board,11*11二维数组,想要打印的数组
// 参数:row,行
// 参数:column,列
// 返回值:none
void DisplayBoard(char board[ROW_EXTEND][COLUMN_EXTEND], int row, int column);
// 简介:设置地雷的位置,传进来11*11的数组,雷可能出现的位置在中间的9*9
// 参数:board,11*11二维数组,想要设置雷的数组
// 参数:row,行
// 参数:column,列
// 返回值:none
void SetMine(char board[ROW_EXTEND][COLUMN_EXTEND], int row, int column);
// 简介:输入一个坐标,统计这个坐标周围有多少个地雷
// 参数:MinePosition,11*11二维数组,储存有雷的位置信息
// 参数:x,行坐标
// 参数:y,列坐标
// 返回值:counter,这个坐标周围地雷的个数
int CountMine(char MinePosition[ROW_EXTEND][COLUMN_EXTEND], int x, int y);
// 简介:输入一个坐标和这个坐标周围的地雷个数counter
// 简介:如果counter == 0,展开,将坐标周围的‘*’变成‘ ’
// 简介:如果counter! = 0,不展开,修改ShowMineNumber中该坐标处的‘数字字符’
// 参数:ShowMineNumber,11*11二维数组,存放排查的信息
// 参数:x,行坐标
// 参数:y,列坐标
// 参数:counter,这个坐标周围地雷的个数
// 返回值:none
void Spread(char ShowMineNumber[ROW_EXTEND][COLUMN_EXTEND], int x, int y, int counter);
// 简介:输入一个坐标,统计坐标附近是否有空格,如果有空格,统计空格周围有多少个雷
// 简介:根据counter,以该空格为中心来展开ShowMineNumber中的内容,并利用递归重复这个过程
// 参数:MinePosition,11*11二维数组,储存有雷的位置信息
// 参数:ShowMineNumber,11*11二维数组,存放排查的信息
// 参数:x,行坐标
// 参数:y,列坐标
// 参数:CallTime,这个函数可以被递归的次数
// 返回值:none
void SpreadSpace(char MinePosition[ROW_EXTEND][COLUMN_EXTEND], char ShowMineNumber[ROW_EXTEND][COLUMN_EXTEND], int x, int y, int CallTime);
// 简介:统计一个坐标周围有多少个雷,并展开一片,展开的位置用空格表示
// 参数:MinePosition,11*11二维数组,储存有雷的位置信息
// 参数:ShowMineNumber,11*11二维数组,存放排查的信息
// 参数:x,行坐标
// 参数:y,列坐标
// 返回值:none
void CountSpreadMine(char MinePosition[ROW_EXTEND][COLUMN_EXTEND], char ShowMineNumber[ROW_EXTEND][COLUMN_EXTEND], int x, int y);
// 简介:找地雷的位置,在9*9的范围内排雷
// 参数:MinePosition,11*11二维数组,储存有雷的位置信息
// 参数:ShowMineNumber,11*11二维数组,存放排查的信息
// 参数:row,行
// 参数:column,列
void FindMine(char MinePosition[ROW_EXTEND][COLUMN_EXTEND], char ShowMineNumber[ROW_EXTEND][COLUMN_EXTEND], int row, int column);
// 简介:玩游戏
// 参数:none
// 返回值:none
void PlayGame();
game.c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include "game.h"
// 简介:打印菜单
// 参数:none
// 返回值:none
void Menu()
{
printf("******************************\n");
printf("********** 1. play ***********\n");
printf("********** 0. exit ***********\n");
printf("******************************\n");
}
// 简介:二维数组初始化,根据传进来的symbol进行初始化
// 参数:board,11*11二维数组,想要将其初始化的数组
// 参数:row,行
// 参数:column,列
// 参数:symbol,初始化的目标符号,如:‘*’
// 返回值:none
void BoardInit(char board[ROW_EXTEND][COLUMN_EXTEND], int row, int column, char symbol)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < column; j++)
{
board[i][j] = symbol; //把二维数组的每一个元素都变成symbol
}
}
}
// 简介:在屏幕上打印出数组的中间部分,11*11的数组打印出中间的9*9,另外,给行、列加标号
// 参数:board,11*11二维数组,想要打印的数组
// 参数:row,行
// 参数:column,列
// 返回值:none
void DisplayBoard(char board[ROW_EXTEND][COLUMN_EXTEND], int row, int column)
{
printf("------Mine Sweeper------\n"); //分割行
for (int j = 0; j <= column; j++)
{
printf("%d ", j); //在第一行打印列标号
}
printf("\n");
for (int i = 1; i <= row; i++)
{
printf("%d ", i); //在每一行的第一个(第一列)打印行标号
for (int j = 1; j <= column; j++)
{
printf("%c ", board[i][j]); //打印数组中间部分的内容
}
printf("\n");
}
printf("------Mine Sweeper------\n"); //分割行
}
// 简介:设置地雷的位置,传进来11*11的数组,雷可能出现的位置在中间的9*9
// 参数:board,11*11二维数组,想要设置雷的数组
// 参数:row,行
// 参数:column,列
// 返回值:none
void SetMine(char board[ROW_EXTEND][COLUMN_EXTEND], int row, int column)
{
int Counter = MINE_NUMBER_EASY;
while (Counter > 0)
{
int x = (rand() % row) + 1; //产生1-9的随机数
int y = (rand() % column) + 1; //产生1-9的随机数
if (board[x][y] == '0') //如果随机坐标处没有雷,就在随机坐标处放一个雷
{
board[x][y] = '1';
Counter--;
}
}
}
// 简介:输入一个坐标,统计这个坐标周围有多少个地雷
// 参数:MinePosition,11*11二维数组,储存有雷的位置信息
// 参数:x,行坐标
// 参数:y,列坐标
// 返回值:counter,这个坐标周围地雷的个数
int CountMine(char MinePosition[ROW_EXTEND][COLUMN_EXTEND], int x, int y)
{
int counter = 0;
for (int i = (x - 1); i <= (x + 1); i++) //row circulation
{
for (int j = (y - 1); j <= (y + 1); j++) //column circulation
{
if (MinePosition[i][j] == '1') //如果这个位置是地雷
{
counter++;
}
}
}
return counter;
}
// 简介:输入一个坐标和这个坐标周围的地雷个数counter
// 简介:如果counter == 0,展开,将坐标周围的‘*’变成‘ ’
// 简介:如果counter! = 0,不展开,修改ShowMineNumber中该坐标处的‘数字字符’
// 参数:ShowMineNumber,11*11二维数组,存放排查的信息
// 参数:x,行坐标
// 参数:y,列坐标
// 参数:counter,这个坐标周围地雷的个数
// 返回值:none
void Spread(char ShowMineNumber[ROW_EXTEND][COLUMN_EXTEND], int x, int y, int counter)
{
if (counter == 0) //如果周围的地雷数目为0
{
for (int i = (x - 1); i <= (x + 1); i++) //row circulation
{
for (int j = (y - 1); j <= (y + 1); j++) //column circulation
{
if (ShowMineNumber[i][j] == '*') //ShowMineNumber[i][j]的位置还没有被排查(*代表还没有被排查)
{
ShowMineNumber[i][j] = ' ';
}
}
}
}
else //如果周围的地雷数目不为0
{
ShowMineNumber[x][y] = counter + '0'; //修改ShowMineNumber中对应坐标存储的‘字符’
}
}
// 简介:输入一个坐标,统计坐标附近是否有空格,如果有空格,统计空格周围有多少个雷
// 简介:根据counter,以该空格为中心来展开ShowMineNumber中的内容,并利用递归重复这个过程
// 参数:MinePosition,11*11二维数组,储存有雷的位置信息
// 参数:ShowMineNumber,11*11二维数组,存放排查的信息
// 参数:x,行坐标
// 参数:y,列坐标
// 参数:CallTime,这个函数可以被递归的次数,调用时,默认给 0
// 返回值:none
void SpreadSpace(char MinePosition[ROW_EXTEND][COLUMN_EXTEND], char ShowMineNumber[ROW_EXTEND][COLUMN_EXTEND], int x, int y, int CallTime)
{
if (CallTime >= ROW) //当递的次数达到最大值,就开始归
{
return;
}
for (int i = (x - 1); i <= (x + 1); i++) //row circulation
{
for (int j = (y - 1); j <= (y + 1); j++) //column circulation
{
if ((1 <= i) && (i <= ROW) && (1 <= j) && (j <= COLUMN)) //判断坐标是否越界了
{
if (ShowMineNumber[i][j] == ' ') //遍历3*3,9个格子,看他们是不是空格,如果这个地方是空格
{
int counter = 0;
counter = CountMine(MinePosition, i, j); //统计空格坐标i,j周围有多少个雷
Spread(ShowMineNumber, i, j, counter); //根据counter,以该空格为中心来展开ShowMineNumber中的内容
SpreadSpace(MinePosition, ShowMineNumber, i, j, CallTime+1);//递归,注意,CallTime+1
}
}
}
}
}
// 简介:统计一个坐标周围有多少个雷,并展开一片,展开的位置用空格表示
// 参数:MinePosition,11*11二维数组,储存有雷的位置信息
// 参数:ShowMineNumber,11*11二维数组,存放排查的信息
// 参数:x,行坐标
// 参数:y,列坐标
// 返回值:none
void CountSpreadMine(char MinePosition[ROW_EXTEND][COLUMN_EXTEND], char ShowMineNumber[ROW_EXTEND][COLUMN_EXTEND], int x, int y)
{
int counter = 0;
counter = CountMine(MinePosition, x, y); //统计xy周围有多少个雷
ShowMineNumber[x][y] = counter + '0'; //修改ShowMineNumber相应坐标的内容(数字字符),只有第一个坐标xy需要这样做
Spread(ShowMineNumber, x, y, counter); //根据counter来展开ShowMineNumber中的内容
SpreadSpace(MinePosition, ShowMineNumber, x, y, 0); //递归
DisplayBoard(ShowMineNumber, ROW, COLUMN); //在屏幕上打印(刷新)二维数组ShowMineNumber,只需要刷新一次即可
}
// 简介:找地雷的位置,在9*9的范围内排雷
// 参数:MinePosition,11*11二维数组,储存有雷的位置信息
// 参数:ShowMineNumber,11*11二维数组,存放排查的信息
// 参数:row,行
// 参数:column,列
void FindMine(char MinePosition[ROW_EXTEND][COLUMN_EXTEND], char ShowMineNumber[ROW_EXTEND][COLUMN_EXTEND], int row, int column)
{
int x = 0; //row coordinate
int y = 0; //column coordinate
int WinFlag = 0; //每次找到一个不是地雷的坐标,WinFlag就++
while (1)
{
printf("Please Input A Coordinate( (1 1) is the top left corner):>");
scanf("%d %d", &x, &y);
if ((1 <= x) && (x <= row) && (1 <= y) && (y <= column))//判断合法性, 输入的坐标已经是在9*9的范围内了,不需进行偏移
{
if (ShowMineNumber[x][y] != '*') //如果重复输入同一个坐标(输入的坐标不是未查看的),跳过一轮循环
{
printf("Repeat the coordinate, please input again\n");
continue;
}
if (MinePosition[x][y] == '1') //如果点到的是地雷
{
printf("I am sorry to say that you failed\n");
DisplayBoard(MinePosition, ROW, COLUMN); //将地雷的位置打印出来给玩家看看
break;
}
else //如果点到的不是地雷
{
WinFlag++;
CountSpreadMine(MinePosition, ShowMineNumber, x, y); //统计周围的地雷的个数,并打印刷新,并自动展开
}
}
else
{
printf("The coordinate that you input is illegal, please input again\n");
}
if (WinFlag == ((row * column) - MINE_NUMBER_EASY)) //赢了,如果WinFlag到达某个数值,此时已经把炸弹全部找出来了
{
printf("Congratulation! Congratulation!\n");
DisplayBoard(MinePosition, ROW, COLUMN); //将地雷的位置打印出来给玩家看看
break;
}
}
}
// 简介:玩游戏
// 参数:none
// 返回值:none
void PlayGame()
{
char MinePosition[ROW_EXTEND][COLUMN_EXTEND] = { 0 }; //存放布置好的地雷的位置,‘0’-没有雷,‘1’-有雷
char ShowMineNumber[ROW_EXTEND][COLUMN_EXTEND] = { 0 }; //存放排查出的地雷的信息,‘*’-还没有排查
BoardInit(MinePosition, ROW_EXTEND, COLUMN_EXTEND, '0'); //初始化数组
BoardInit(ShowMineNumber, ROW_EXTEND, COLUMN_EXTEND, '*'); //初始化数组
SetMine(MinePosition, ROW, COLUMN); //设置地雷的位置
DisplayBoard(MinePosition, ROW, COLUMN); //展示数组,地雷的位置/调试用
DisplayBoard(ShowMineNumber, ROW, COLUMN); //展示数组(棋盘)
FindMine(MinePosition, ShowMineNumber, ROW, COLUMN); //找地雷的位置,在9*9的范围内排雷
}