整体思路:
1. 程序开始时的思路和三子棋一样,展示菜单,1是开始游戏,0是退出程序,其他选项提示错误输入。使用switch语句实现这个逻辑,判断条件就是玩家的输入。这个程序想让玩家可以重复玩,所以在将这个switch语句放入do while循环中,do while的判断条件也是玩家输入。
为此需要菜单函数和游戏功能实现函数。
2. 假设棋盘是9*9大小,并且放置10个雷。没有雷的地方用'0',有雷的地方用'1'。这部分需要一个9*9的数组来存放,即布置好的雷的信息数组( char mine[ROWS][COLS] = { 0 }; )。下面会提到为什么使用字符数组。(后续代码部分会详细解释使用字符数组的原因)
3. 如果选择坐标有雷,则游戏结束;如果没有雷那么要在给定坐标处显示给定坐标周围8个位置有几个雷。对于没有排查的位置用'*'来表示,因为这里使用了*号字符,就要把显示雷个数的数字也转化为字符。同时,为了方便与布置雷的数组进行比较,布置雷的数组也设置为字符数组。
举例说明为什么需要两个数组:
假如周围8个位置有1个雷,那么就需要显示1。但是这里的1跟表示有雷的1可能会产生歧义,所以还需要一个9*9的数组用于显示棋盘即提示周围有多少雷,排查出雷信息的数组( char show[ROWS][COLS] = { 0 }; )。并且在游戏没有结束时,不能显示布置雷信息的数组,只能显示排查雷信息的数组。
4. 在检测周围有多少雷时,有一种特殊情况:如果要检查的给定坐标在棋盘最边缘,此时访问周围坐标就会造成越界访问。如果每次都对周围坐标都进行合法性判断,这种方法太麻烦。换一个思路:在创建数组的时候上下各增加一行,左右各增加1列,但不论是布置雷,还是显示棋盘都在9*9的范围内,这样就可以直接统计给定坐标周围有几个雷。
下方为test.c文件,用于测试游戏逻辑
#include "game.h"
void menu()
{
printf("*********************************\n");
printf("********* 1. play *********\n");
printf("********* 0. exit *********\n");
printf("*********************************\n");
}
void game()
{
//不能使用这种方式初始化char mine[ROWS][COLS] = { '0' };因为只初始化了一个元素
char mine[ROWS][COLS] = { 0 };//存放布置好的雷的信息
char show[ROWS][COLS] = { 0 };//存放排查出雷的信息
//初始化数组的内容为指定的内容
//mine 数组在没有布置累的时候,都是'0'
InitBoard(mine, ROWS, COLS, '0');
//show 数组在没有排查雷的时候,都是'*'
InitBoard(show, ROWS, COLS, '*');
//设置雷,可以放在显示前,也可以放在显示后
SetMine(mine, ROW, COL);
DisplayBoard(show, ROW, COL);
//一般情况不打印mine,因为雷布置好后不能给玩家看
//DisplayBoard(mine, ROW, COL);//打印只需要9*9实际的棋盘
//排查雷
//输入一个坐标:如果有雷,游戏结束;如果没雷,显示周围有几个雷
//排查雷要在布置雷信息的数组(mine)查看,是雷,游戏结束,
//如果不是,就在存放排查出雷信息的数组(show)显示
FindMine(mine, show, ROW, COL);//在mine数组找雷,在show数组显示雷信息
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));//设置随机数的生成起点
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
下方为game.h头文件,用于游戏代码的声明(函数声明,符号定义)
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//11*11和9*9都需要,因为雷只放到9*9里,打印信息也只打印9*9的场地
//但是判断坐标周围是否有雷时,担心越界访问,所以使用11*11
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EASY_COUNT 10
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
//虽然传过来是9*9,但是棋盘还是11*11,但是只接收9*9(只用9*9)。所以前面是ROWS和COLS,后面是row和col
//传参传了数组名,就是把11*11的数组传过来
void DisplayBoard(char board[ROWS][COLS], int row, int col);
void SetMine(char board[ROWS][COLS], int row, int col);
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
game.c中各函数实现
1. 初始化棋盘函数
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
//这里没有办法同时初始化'0'和'*',所以需要再增加一个参数
//参数传过来什么,初始化什么
board[i][j] = set;
}
}
}
2. 显示棋盘函数
棋盘左边和上方需要行号和列好,方便玩家选择坐标。为了上方的列号和棋盘对齐,要从0开始。
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
//打印棋盘时,下标从1开始,因为从1开始的下标才是实际棋盘
int i = 0;
int j = 0;
printf("-----扫雷 游戏-----\n");
//打印列号在最开始,因为在最上方
//这里j要从0开始才能把1~9的数字对齐棋盘
for (j = 0; j <= col; j++)//这里是打印列号
{
printf("%d ", j);
}
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");
}
3. 布置雷函数
生成随机坐标,也就是数组下标。但是要注意,是在11*11数组的中间9*9的范围内。所以下标是1~9。假设'1'表示有雷, '0'表示没雷,总共放置10个雷。找到'0'才放雷,直到10个雷都布置完,即循环上述步骤,直到count = 0
void SetMine(char board[ROWS][COLS], int row, int col)
{
//布置一个少一个
int count = EASY_COUNT;
//行和列都是1~9
while (count)//count为0时,循环停止。此循环最少10次,如果进入循环判断此坐标有雷,count不--
{
int x = rand() % row + 1;//row是9,模9余数是0~8,所以+1
int y = rand() % col + 1;//同上
//如果没有布置雷,可以放置;如果布置了雷,就不能放置
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
4. 统计坐标周围雷函数
int get_mine_count(char board[ROWS][COLS], int x, int y)
{
//如果遍历周围8个坐标,就需要8个if语句,太复杂,不适用
//拿出给定坐标周围的8个坐标储存的字符
//这些字符都是'1'或'0',字符1的ASCII码值是49,字符0的ASCII码值是48
//所以用这些字符分别去减去字符0,如果值是1,说明是雷,0则说明不是雷
//最后把减去字符0的所有值相加,是几说明有几个雷
//或者8个字符全部加起来,在减去字符0乘以8(即48*8)
return (board[x - 1][y] +
board[x - 1][y - 1] +
board[x][y - 1] +
board[x + 1][y - 1] +
board[x + 1][y] +
board[x + 1][y + 1] +
board[x][y + 1] +
board[x - 1][y + 1] - 8 * '0');
}
5. 找雷函数
首先判断输入坐标是否合法,合法:进入下一步判断;不合法:重新输入。
如果坐标合法,还要判断之前是否排查过。
如果没有被排查过判断是否有雷,有雷游戏结束;没有雷调用统计雷的函数,并调用显示棋盘函数。
重复上述步骤,直到找出全部雷。
判断胜负条件是排查次数,如果排查次数=9*9(棋盘大小)-10(雷的个数),说明全部排查完毕。这时游戏胜利。
这里一定要有判断是否排查过的if语句,否则,玩家反复输入用一个没有雷的坐标,会导致排查次数错误的增加,最终即使没有排查完全部棋盘也会因为排查次数达到而判胜。
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;//找到非雷的次数
while (win < row * col - EASY_COUNT)
{
printf("请输入坐标:>");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= 9 && y >= 1 && y <= 9)//判断玩家输入坐标是否合法
{
if (show[x][y] != '*')//这里需要先判断坐标是否排查过,否则重复排查可能倒是win计数不对
{
printf("该坐标被排查过,不能重复排查\n");
}
else
{
//如果是雷
if (mine[x][y] == '1')//判断输入的坐标是否有雷
{
printf("很遗憾,你被炸死了\n");
DisplayBoard(mine, ROW, COL);
break;
}
else//如果不是雷,就要统计周围有几个雷
{
win++;//不是雷,找到非雷的次数就要+1
//统计mine数组中x,y坐标周围有几个雷
int count = get_mine_count(mine, x, y);
//得到的值(count)是周围有几个雷,需要发到show数组展示给玩家
show[x][y] = count + '0';//转换成数字字符
DisplayBoard(show, ROW, COL);//排查完后展示棋盘给玩家
}
}
}
else
{
printf("坐标非法,重新输入\n");
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜,排雷成功\n");
DisplayBoard(mine, ROW, COL);//排雷成功后,显示所有雷的位置
}
}