数组和函数实践:扫雷游戏
扫雷游戏的分析和设计
需要在9*9的棋盘上布置雷的信息和排查雷,我们⾸先想到的就是创建⼀个9*9的数组来存放
信息。

那如果这个位置布置雷,我们就存放1,没有布置雷就存放0.

在布置雷后,寻找周围的雷的数量,为了数组不越界访问:可以设置一个11*11的棋盘。

我们专⻔给⼀个棋盘(对应⼀个数组mine)存放布置好的雷的信息,再给另外⼀个棋盘(对应另外⼀个数组show)存放排查出的雷的信息。这样就互不⼲扰了,把雷布置到mine数组,在mine数组中排查雷,排查出的数据存放在show数组,并且打印show数组的信息给后期排查参考。

同时为了保持神秘,show数组开始时初始化为字符 '*',为了保持两个数组的类型⼀致,可以使⽤同⼀套函数处理,mine数组最开始也初始化为字符'0',布置雷改成'1'。【一个棋盘方便我们测试与操作,另一个棋盘给用户显示。】
1.先设置游戏开始的界面。
源文件test.c中进行扫雷游戏的控制流程操作。【有进入游戏,棋盘的显示,布置雷,排查雷的相关操作,关于他们的具体操作应写在game.c源文件里。】
cs
#include <stdio.h>
void menu()
{
printf("***********************\n");
printf("***** 1. play *****\n");
printf("***** 0. exit *****\n");
printf("***********************\n");
}
//void game()
//{
// //完成扫雷游戏
//
//
//}
void test()
{
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("扫雷\n");
break;
case 0:
printf("游戏结束,退出游戏\n");
default:
printf("选择错误,重新选择\n");
}
} while (input);
}
int main()
{
test();
return 0;
}

或者直接放在主函数中:

cs
#include <stdio.h>
void menu()
{
printf("***********************\n");
printf("***** 1. play *****\n");
printf("***** 0. exit *****\n");
printf("***********************\n");
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
//game();
printf("开始扫雷\n");
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,重新选择\n");
break;
}
} while (input);
return 0;
}
2.扫雷游戏的具体逻辑代码
(1)扫雷游戏的棋盘是9*9,为了方便显示棋盘给玩家看,所以需要两个棋盘进行操作。

实现初始化棋盘与打印出棋盘的操作:

进行函数声明:

cs
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];//存放布置好的雷,// 真实棋盘,存放地雷位置
char show[ROWS][COLS];//存放排查出的雷的信息// 显示棋盘,给玩家看
//初始化棋盘
//1. mine数组最开始是全'0'
//2. show数组最开始是全'*'
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//打印棋盘
DisplayBoard(mine, ROW, COL);
DisplayBoard(show, ROW, COL);
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
//printf("开始扫雷\n");
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,重新选择\n");
break;
}
} while (input);
return 0;
}
cs
game.h文件中
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define EASY_COUNT 10
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);
cs
game.c文件中:
#include "game.h"
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)//定义set,是为了不重复写相同的代码,避免代码冗余。
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = set;//此处的set可以是*,也可以是'0'。
}
}
}
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
//为了看起来更加美观。
printf("--------扫雷游戏-------\n");
//定义列的输出
for (i = 0; i <= col; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
//定义行的输出
printf("%d ", i);
int j = 0;
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
(2)布置雷


cs
再test.c文件中添加:
void game()
{
//完成扫雷游戏
char mine[ROWS][COLS];//存放布置好的雷,// 真实棋盘,存放地雷位置
char show[ROWS][COLS];//存放排查出的雷的信息// 显示棋盘,给玩家看
//初始化棋盘
//1. mine数组最开始是全'0'
//2. show数组最开始是全'*'
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//打印棋盘
DisplayBoard(mine, ROW, COL);//真实棋盘
DisplayBoard(show, ROW, COL);
//1. 布置雷
SetMine(mine, ROW, COL);
DisplayBoard(mine, ROW, COL);//真实棋盘
}
再game.h文件中添加:
//布置雷
void SetMine(char board[ROWS][COLS], int row, int col);
再gamen.c文件中添加:
void SetMine(char board[ROWS][COLS], int row, int col)
{
//布置10个雷
//⽣成随机的坐标,布置雷
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
此外在主函数中要添加随机数种子:
srand((unsigned int)time(NULL));
随机雷布置完成【开始排雷】
(3)排雷
【排雷后查看周围是否有雷,存在;
巧妙之处:
计算周围雷数时,可以直接用字符相减:
// 周围8个格子的字符值相加
// 如果是'0'(无雷),ASCII码是48
// 如果是'1'(有雷),ASCII码是49
// 周围雷数 = (字符总和 - 8 * '0')

如果一开始排的雷是'1',那么你就被炸死了;如果不是,是'0',那么就会显示周围的雷数,继续游戏。把雷排完后,没死,游戏胜利。
】
cs
在test.c文件中:
void game()
{
//完成扫雷游戏
char mine[ROWS][COLS];//存放布置好的雷,// 真实棋盘,存放地雷位置
char show[ROWS][COLS];//存放排查出的雷的信息// 显示棋盘,给玩家看
//初始化棋盘
//1. mine数组最开始是全'0'
//2. show数组最开始是全'*'
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
// mine棋盘:
//'0' - 无雷
// '1' - 有雷(注意:这里是字符'1',不是数字1)
// // show棋盘:
// '*' - 未翻开
// 数字字符'0' - '8' - 周围雷数
//打印棋盘
DisplayBoard(mine, ROW, COL);//真实棋盘
DisplayBoard(show, ROW, COL);
//1. 布置雷
SetMine(mine, ROW, COL);
DisplayBoard(mine, ROW, COL);//真实棋盘
//2. 排查雷
FindMine(mine, show, ROW, COL);
}
在game.h文件中:
//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
在game.c文件中:
//方法一:
//计算周围雷数时,可以直接用字符相减:
//// 周围8个格子的字符值相加
//// 如果是'0'(无雷),ASCII码是48
//// 如果是'1'(有雷),ASCII码是49
//// 周围雷数 = (字符总和 - 8 * '0')
//static int GetMineCount(char mine[ROWS][COLS], int x, int y)
//{
// return mine[x - 1][y] +
// mine[x - 1][y - 1] +
// mine[x][y - 1] +
// mine[x + 1][y - 1] +
// mine[x + 1][y] +
// mine[x + 1][y + 1]+
// mine[x][y + 1] +
// mine[x - 1][y + 1] - 8 * '0';
//}
//方法二:
//为了得到周围雷的个数:
static int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
int i = 0;
int count = 0;
//遍历九个坐标,中间的是'0',不会有任何影响,不需要规避。
for (i = x - 1; i <= x + 1; i++)
{
int j = 0;
for (j = y - 1; j <= y + 1; j++)
{
count += (mine[i][j] - '0');
}
}
return count;
}
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)//程序结束标志,
//想要测试,直接把雷安排成80,那么只需要排一次就知道程序是否可行。
{
printf("请输⼊要排查的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了\n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
//该位置不是雷,就统计这个坐标周围有⼏个雷
int count = GetMineCount(mine, x, y);
show[x][y] = count + '0';
DisplayBoard(show, ROW, COL);
win++;
}
}
else
{
printf("坐标⾮法,重新输⼊\n");
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜你,排雷成功\n");
DisplayBoard(mine, ROW, COL);
}
}
扫雷游戏的代码实现
test.c
cs
#include "game.h"
void menu()
{
printf("***********************\n");
printf("***** 1. play *****\n");
printf("***** 0. exit *****\n");
printf("***********************\n");
}
void game()
{
//完成扫雷游戏
char mine[ROWS][COLS];//存放布置好的雷,// 真实棋盘,存放地雷位置
char show[ROWS][COLS];//存放排查出的雷的信息// 显示棋盘,给玩家看
//初始化棋盘
//1. mine数组最开始是全'0'
//2. show数组最开始是全'*'
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
// mine棋盘:
//'0' - 无雷
// '1' - 有雷(注意:这里是字符'1',不是数字1)
// // show棋盘:
// '*' - 未翻开
// 数字字符'0' - '8' - 周围雷数
//打印棋盘
//DisplayBoard(mine, ROW, COL);//真实棋盘
DisplayBoard(show, ROW, COL);
//1. 布置雷
SetMine(mine, ROW, COL);
//DisplayBoard(mine, ROW, COL);//真实棋盘
//2. 排查雷
FindMine(mine, show, ROW, COL);
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
//printf("开始扫雷\n");
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,重新选择\n");
break;
}
} while (input);
return 0;
}
game.c
cs
#include "game.h"
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)//定义set,是为了不重复写相同的代码,避免代码冗余。
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = set;//此处的set可以是*,也可以是'0'。
}
}
}
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
//为了看起来更加美观。
printf("--------扫雷游戏-------\n");
//定义列的输出
for (i = 0; i <= col; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
//定义行的输出
printf("%d ", i);
int j = 0;
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
void SetMine(char board[ROWS][COLS], int row, int col)
{
//布置10个雷
//⽣成随机的坐标,布置雷
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
//方法一:
//计算周围雷数时,可以直接用字符相减:
//// 周围8个格子的字符值相加
//// 如果是'0'(无雷),ASCII码是48
//// 如果是'1'(有雷),ASCII码是49
//// 周围雷数 = (字符总和 - 8 * '0')
//int GetMineCount(char mine[ROWS][COLS], int x, int y)
//{
// return mine[x - 1][y] +
// mine[x - 1][y - 1] +
// mine[x][y - 1] +
// mine[x + 1][y - 1] +
// mine[x + 1][y] +
// mine[x + 1][y + 1]+
// mine[x][y + 1] +
// mine[x - 1][y + 1] - 8 * '0';
//}
//方法二:
//为了得到周围雷的个数:
static int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
int i = 0;
int count = 0;
//遍历九个坐标,中间的是'0',不会有任何影响,不需要规避。
for (i = x - 1; i <= x + 1; i++)
{
int j = 0;
for (j = y - 1; j <= y + 1; j++)
{
count += (mine[i][j] - '0');
}
}
return count;
}
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)//程序结束标志,
//想要测试,直接把雷安排成80,那么只需要排一次就知道程序是否可行。
{
printf("请输⼊要排查的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了\n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
//该位置不是雷,就统计这个坐标周围有⼏个雷
int count = GetMineCount(mine, x, y);
show[x][y] = count + '0';
DisplayBoard(show, ROW, COL);
win++;
}
}
else
{
printf("坐标⾮法,重新输⼊\n");
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜你,排雷成功\n");
DisplayBoard(mine, ROW, COL);
}
}
game.h
cs
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define EASY_COUNT 10
#define ROW 9
#define COL 9
//好处:
//1. 计算边界格子周围雷数时,不需要特殊判断边界
//2. 避免数组越界访问
//3. 简化代码逻辑
#define ROWS ROW+2
#define COLS COL+2
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
//打印棋盘
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文件里添加:
cs
#include "game.h"
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)//定义set,是为了不重复写相同的代码,避免代码冗余。
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = set;//此处的set可以是*,也可以是'0'。
}
}
}
//展示棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
//为了看起来更加美观。
printf("--------扫雷游戏-------\n");
//定义列的输出
for (i = 0; i <= col; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
//定义行的输出
printf("%d ", i);
int j = 0;
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
void SetMine(char board[ROWS][COLS], int row, int col)
{
//布置10个雷
//⽣成随机的坐标,布置雷
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
//方法一:
//计算周围雷数时,可以直接用字符相减:
//// 周围8个格子的字符值相加
//// 如果是'0'(无雷),ASCII码是48
//// 如果是'1'(有雷),ASCII码是49
//// 周围雷数 = (字符总和 - 8 * '0')
//int GetMineCount(char mine[ROWS][COLS], int x, int y)
//{
// return mine[x - 1][y] +
// mine[x - 1][y - 1] +
// mine[x][y - 1] +
// mine[x + 1][y - 1] +
// mine[x + 1][y] +
// mine[x + 1][y + 1]+
// mine[x][y + 1] +
// mine[x - 1][y + 1] - 8 * '0';
//}
//方法二:
//为了得到周围雷的个数:
static int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
int i = 0;
int count = 0;
//遍历九个坐标,中间的是'0',不会有任何影响,不需要规避。
for (i = x - 1; i <= x + 1; i++)
{
int j = 0;
for (j = y - 1; j <= y + 1; j++)
{
count += (mine[i][j] - '0');
}
}
return count;
}
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)//程序结束标志,
//想要测试,直接把雷安排成80,那么只需要排一次就知道程序是否可行。
{
printf("请输⼊要排查的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
//如果是未排查坐标
if (show[x][y] == '*') {
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了\n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
//该位置不是雷,就统计这个坐标周围有⼏个雷
int count = GetMineCount(mine, x, y);
show[x][y] = count + '0';
DisplayBoard(show, ROW, COL);
win++;
}
}
else//如果是已排查坐标
{
printf("该坐标已经被排查了,重新输入坐标\n");
}
}
else
{
printf("坐标⾮法,重新输⼊\n");
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜你,排雷成功\n");
DisplayBoard(mine, ROW, COL);
}
}


【*是未翻开,未排查的雷。】
解释:为什么要有两个棋盘?
两个棋盘是一样的类型(都是 char 数组)对 mine棋盘(隐藏棋盘)进行操作和计算把结果"映射"到 show棋盘(显示棋盘)上显示给玩家。【两个棋盘是一样的类型,为了方便操作与区分,在看不到的棋盘进行操作后,把它显示到玩家能看得到的棋盘上。就像考试时,老师有答案+试题,而学生只有试题。】