C语言-----扫雷游戏 - 指南
扫雷游戏的功能说明 :
• 使⽤控制台实现经典的扫雷游戏
• 游戏可以通过菜单实现继续玩或者退出游戏
• 扫雷的棋盘是9*9的格⼦
• 默认随机布置10个雷
• 可以排查雷:
◦ 如果位置不是雷,就显⽰周围有⼏个雷
◦ 如果位置是雷,就炸死游戏结束
◦ 把除10个雷之外的所有⾮雷都找出来,排雷成功,游戏结束
test.c //⽂件中写游戏的测试逻辑
game.c //⽂件中写游戏中函数的实现等
game.h //⽂件中写游戏需要的数据类型和函数声明等
逻辑开始:
一、菜单
- 输入1进入游戏,输入0退出游戏,输入其他数字显示输入错误,并且重新输入
test.c
#include "game.h"
int main()
{
menu();
{
regain:
printf("请输入你的选择:");
int input1;
scanf("%d", &input1);
switch (input1)
{
case 1:
{
printf("进入游戏\n");
game();
break;
}
case 0:
{
printf("退出游戏\n");
break;
}
default:
{
printf("输入错误,请重新输入:");
goto regain;
}
}
}
return 0;
}
game.c
#include "game.h"
void menu()
{
printf("****************\n");
printf("**** 1.Play ****\n");
printf("**** 0.Quit ****\n");
printf("****************\n");
}
game.h
#pragma once
#include <stdio.h>
#include "game.h"
//菜单
void menu();
二、生成 9X9 的游戏界面
- 使用二维数组实现
- 运用两个棋盘,一个用于展示,一个用于设置雷,写出初始化棋盘的函数
- 将展示的棋盘
char show全部初始化为'*',将布置雷的棋盘char mine全部初始化为0 - 为方便后边测试,可以先把打印棋盘的函数写出
test.c文件增加了以下代码
test.c
#include "game.h"
void game()
{
//用于布置雷的二维数组
char mine[ROWS][COLS] = { 0 };
//用于游戏界面的的二维数组
char show[ROWS][COLS] = { 0 };
//用于游戏界面的的二维数组全部初始为 '*'
set_keyboard(show, ROWS, COLS, '*');
//用于布置雷的二维数组全部初始化为 '0'
set_keyboard(mine, ROWS, COLS, '0');
//打印函数
printf_keyboard(show, ROW, COL);
printf_keyboard(mine, ROW, COL);
}
game.c文件增加了以下代码
game.c
#include "game.h"
//初始化棋盘
void set_keyboard(char board[ROWS][COLS], int rows, int cols, char set)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
//展示棋盘
void printf_keyboard(char board[ROWS][COLS], int row, int col)
{
printf("-------扫雷--------\n");
for (int r = 0; r <= row; r++)
{
printf("%d ", r);
}
printf("\n");
for (int i = 1; i <= row; i++)
{
printf("%d ", i);
for (int j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
game.c文件增加了以下代码
#pragma once
#include <stdio.h>
#include "game.h"
#define ROW 9
#define COL 9
#define ROWS ROW+3
#define COLS COL+2
//菜单
void menu();
//初始化棋盘
void set_keyboard(char board[ROWS][COLS], int rows, int cols, char set);
//展示棋盘
void printf_keyboard(char board[ROWS][COLS], int row, int col);
三、随机布置雷
- 使用
srand((unsigned int) time(NULL))和rand() - 将雷设置为
1,雷只能布置在char mine[x][y] == '0'的地方
game.h文件增加了#include <time.h>#include "stdlib.h"#define MINE 10void set_mine(char board[ROWS][COLS], int row, int col, int mine);
game.h
#pragma once
#include <stdio.h>
#include "game.h"
#include <time.h>
#include "stdlib.h"
#define ROW 9
#define COL 9
#define ROWS ROW+3
#define COLS COL+2
#define MINE 10
//菜单
void menu();
//初始化棋盘
void set_keyboard(char board[ROWS][COLS], int rows, int cols, char set);
//展示棋盘
void printf_keyboard(char board[ROWS][COLS], int row, int col);
//随机布置雷
void set_mine(char board[ROWS][COLS], int row, int col, int mine);
game.c文件增加以下代码
game.c
//随机布置雷
void set_mine(char board[ROWS][COLS], int row, int col, int mine)
{
srand((unsigned int) time(NULL));
while (mine)
{
int x = (rand() % row) + 1;
int y = (rand() % col) + 1;
if (board[x][y] == '0');
{
board[x][y] = '1';
mine--;
}
}
}
test.c文件增加以下代码
test.c
//随机布置雷
set_mine(mine, ROW, COL, MINE);
四、排雷
- 注意输入的坐标,横纵坐标都只能是
0~9,出现其他数字报错,并重新输入 - 所排的坐标要显示周围雷的个数,如果为
0,展开周围的棋盘(运用到递归) - 如果所排的坐标是雷,显示
游戏结束 - 如果输入的坐标是已经输入过的坐标,显示
该坐标已经排除 - 判断游戏胜利,
排除的坐标个数与减掉雷后的格子数相等
test.c文件布局改为以下情况
test.c
void game()
{
//用于布置雷的二维数组
char mine[ROWS][COLS] = { 0 };
//用于游戏界面的的二维数组
char show[ROWS][COLS] = { 0 };
//用于游戏界面的的二维数组全部初始为 '*'
set_keyboard(show, ROWS, COLS, '*');
//用于布置雷的二维数组全部初始化为 '0'
set_keyboard(mine, ROWS, COLS, '0');
//随机布置雷
set_mine(mine, ROW, COL, MINE);
//打印函数
printf_keyboard(show, ROW, COL);
//printf_keyboard(mine, ROW, COL);
//排雷
move_mine(show, mine, ROW, COL);
}
game.c文件增加了以下代码
game.c
//计算周围雷的个数
int Count_mine(char mine[ROWS][COLS], int x, int y)
{
return mine[x][y] - '0';
}
//展开棋盘----递归
void Open_keyboard(char show[ROWS][COLS], char mine[ROWS][COLS], int x, int y)
{
//写递归首先写结束条件
//越界时,返回
if ((x > (ROWS - 2)) || (x < 1) || (y > (COLS - 2)) || (y < 1))
{
return;
}
//遇到以及排过雷的坐标返回
if (show[x][y] != '*')
{
return;
}
//计算雷的个数
int count = 0;
for (int i = -1; i <= 1; i++)
{
for (int j = -1; j <= 1; j++)
{
count += Count_mine(mine, x + i, y + j);
}
}
show[x][y] = count + '0';
//按照游戏规则,如果坐标显示雷的数目不为零,则返回
if (show[x][y] != '0')
{
return;
}
//展开雷
for (int i = -1; i <= 1; i++)
{
for (int j = -1; j <= 1; j++)
{
Open_keyboard(show, mine, x + i, y + j);
}
}
}
//排雷
void move_mine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col)
{
int x, y;
while (1)
{
printf("请输入要排查的坐标:");
regain2:
scanf("%d %d", &x, &y);
if ((x >= 1 && x <= 9) && (y >= 1 && y <= 9))
{
if (show[x][y] == '*')
{
if (mine[x][y] == '0')
{
Open_keyboard(show,mine,x,y);
printf_keyboard(show, ROW, COL);
}
else
{
printf("很遗憾,踩到雷了,游戏结束\n以下是雷的位置:");
printf_keyboard(mine, ROW, COL);
break;
}
}
else
{
printf("该坐标已经排查过了,请输入别的坐标:");
goto regain2;
}
}
else
{
printf("输入错误,重新输入:");
goto regain2;
}
//判断赢
int Remove_mine_count = 0;
for (int i = 1; i <= row; i++)
{
for (int j = 1; j <= col; j++)
{
if (show[i][j] != '*')
{
Remove_mine_count++;
}
}
}
if (Remove_mine_count == ((ROW * COL) - MINE))
{
printf("恭喜你,排除所有的雷,游戏胜利\n");
printf_keyboard(mine, ROW, COL);
break;
}
}
}
game.h文件的代码不变
game.h
#pragma once
#include <stdio.h>
#include "game.h"
#include <time.h>
#include "stdlib.h"
#define ROW 9
#define COL 9
#define ROWS ROW+3
#define COLS COL+2
#define MINE 10
//菜单
void menu();
//初始化棋盘
void set_keyboard(char board[ROWS][COLS], int rows, int cols, char set);
//展示棋盘
void printf_keyboard(char board[ROWS][COLS], int row, int col);
//随机布置雷
void set_mine(char board[ROWS][COLS], int row, int col, int mine);
//排雷
void move_mine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col);
五、扫雷游戏完整代码
test.c
#include "game.h" // 包含扫雷游戏所需的头文件(声明函数、宏定义等)
// 游戏核心逻辑函数,负责初始化游戏数据、布置雷、处理排雷过程
void game()
{
// 定义二维数组mine,用于存储雷的位置信息('1'表示有雷,'0'表示无雷)
// ROWS和COLS是宏定义,通常比实际游戏区域大2(用于处理边界判断,避免越界)
char mine[ROWS][COLS] = { 0 };
// 定义二维数组show,用于展示给玩家的界面(初始为'*',排雷后显示周围雷数或雷)
char show[ROWS][COLS] = { 0 };
// 初始化show数组,全部元素设为'*'(表示未探索的格子)
// set_keyboard是自定义函数,用于批量初始化二维数组
set_keyboard(show, ROWS, COLS, '*');
// 初始化mine数组,全部元素设为'0'(先默认所有格子无雷,后续再随机布置雷)
set_keyboard(mine, ROWS, COLS, '0');
// 在mine数组中随机布置雷,MINE是宏定义(雷的总数)
// ROW和COL是宏定义,代表实际游戏区域的行数和列数(比ROWS、COLS小2)
set_mine(mine, ROW, COL, MINE);
// 打印玩家界面(show数组),展示当前未探索的格子(全为'*')
printf_keyboard(show, ROW, COL);
// 调试用:打印雷的位置(mine数组),实际游戏中不会显示给玩家
// printf_keyboard(mine, ROW, COL);
// 进入排雷逻辑,玩家输入坐标,处理排雷结果(显示周围雷数、踩雷结束等)
move_mine(show, mine, ROW, COL);
}
// 主函数,程序入口,负责展示菜单和处理用户选择
int main()
{
menu(); // 调用menu函数,打印游戏菜单(如"1. 开始游戏 0. 退出游戏")
{ // 局部作用域,隔离内部变量
regain1: // 跳转标签,用于输入错误时重新回到输入步骤
printf("请输入你的选择:"); // 提示用户输入选项(1或0)
int input1; // 存储用户输入的选项
scanf("%d", &input1); // 读取用户输入
// 根据用户输入的选项执行对应操作
switch (input1)
{
case 1: // 用户选择"开始游戏"
{
printf("进入游戏\n"); // 提示进入游戏
game(); // 调用game函数,启动扫雷游戏逻辑
break; // 退出switch分支
}
case 0: // 用户选择"退出游戏"
{
printf("退出游戏\n"); // 提示退出游戏
break; // 退出switch分支
}
default: // 用户输入了无效选项(非1和0)
{
printf("输入错误,请重新输入:"); // 提示输入错误
goto regain1; // 跳转到regain1标签,重新等待用户输入
}
}
}
return 0; // 程序正常结束
}
game.c
#include "game.h" // 包含扫雷游戏所需的头文件(宏定义、函数声明等)
// 打印游戏菜单
void menu()
{
printf("****************\n");
printf("**** 1.Play ****\n"); // 1表示开始游戏
printf("**** 0.Quit ****\n"); // 0表示退出游戏
printf("****************\n");
}
// 初始化棋盘(二维数组)
// board:要初始化的二维数组
// rows、cols:数组的行数和列数
// set:初始化填充的字符(如'*'或'0')
void set_keyboard(char board[ROWS][COLS], int rows, int cols, char set)
{
// 遍历数组的每个元素,设置为指定字符set
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
// 展示棋盘(打印到控制台)
// board:要展示的二维数组(玩家界面或雷区)
// row、col:实际游戏区域的行数和列数(不包含边界)
void printf_keyboard(char board[ROWS][COLS], int row, int col)
{
printf("-------扫雷--------\n");
// 打印列号(0到col),方便玩家定位坐标
for (int r = 0; r <= row; r++)
{
printf("%d ", r);
}
printf("\n");
// 打印每行内容(包含行号和对应格子的字符)
for (int i = 1; i <= row; i++)
{
printf("%d ", i); // 打印行号(1到row)
// 打印当前行的每个格子(从第1列到第col列)
for (int j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n"); // 每行结束后换行
}
}
// 随机布置雷到雷区数组
// board:存储雷区信息的二维数组('1'表示有雷,'0'表示无雷)
// row、col:实际游戏区域的行数和列数
// mine:要布置的雷的总数
void set_mine(char board[ROWS][COLS], int row, int col, int mine)
{
srand((unsigned int)time(NULL)); // 初始化随机数种子,确保每次雷的位置不同
// 循环布置雷,直到雷的数量为0
while (mine)
{
// 生成1到row范围内的随机行坐标x
int x = (rand() % row) + 1;
// 生成1到col范围内的随机列坐标y
int y = (rand() % col) + 1;
// 如果当前位置没有雷(为'0'),则布置雷(设为'1')
if (board[x][y] == '0') // 注意:原代码此处多了一个分号,可能是笔误,实际应去掉
{
board[x][y] = '1';
mine--; // 雷的数量减1
}
}
}
// 计算指定坐标周围8个方向的雷的总数
// mine:雷区数组
// x、y:要检查的坐标
int Count_mine(char mine[ROWS][COLS], int x, int y)
{
// 计算坐标(x,y)周围8个相邻格子中雷的总数
// 原理:将周围8个格子的字符值('0'表示无雷,'1'表示有雷)转换为数字后求和
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' // 减去8个'0'的ASCII值(将字符转为数字:'0'→0,'1'→1)
);
}
// 递归展开无雷区域(当某个格子周围无雷时,自动展开周围所有无雷格子)
// show:玩家界面数组
// mine:雷区数组
// x、y:当前要展开的坐标
void Open_keyboard(char show[ROWS][COLS], char mine[ROWS][COLS], int x, int y)
{
// 递归结束条件1:坐标越界(超出实际游戏区域)
if ((x > (ROWS - 2)) || (x < 1) || (y > (COLS - 2)) || (y < 1))
{
return;
}
// 递归结束条件2:该坐标已被探索过(非'*')
if (show[x][y] != '*')
{
return;
}
// 计算当前坐标周围8个方向的雷的总数
int count = 0;
for (int i = -1; i <= 1; i++) // 行方向:-1(上)、0(当前)、1(下)
{
for (int j = -1; j <= 1; j++) // 列方向:-1(左)、0(当前)、1(右)
{
count += Count_mine(mine, x + i, y + j); // 累加周围每个格子的雷数
}
}
// 在玩家界面显示当前格子周围的雷数(字符形式,如'0'表示无雷)
show[x][y] = count + '0';
// 递归结束条件3:如果周围有雷(count≠0),则停止展开
if (show[x][y] != '0')
{
return;
}
// 如果周围无雷(count=0),递归展开周围8个方向的格子
for (int i = -1; i <= 1; i++)
{
for (int j = -1; j <= 1; j++)
{
Open_keyboard(show, mine, x + i, y + j);
}
}
}
// 排雷核心逻辑
// show:玩家界面数组
// mine:雷区数组
// row、col:实际游戏区域的行数和列数
void move_mine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col)
{
int x, y; // 存储玩家输入的排查坐标
while (1) // 循环处理排雷操作,直到游戏结束
{
printf("请输入要排查的坐标:");
regain2: // 跳转标签,用于输入错误时重新输入坐标
scanf("%d %d", &x, &y); // 读取玩家输入的坐标(x为行,y为列)
// 检查坐标是否在有效范围内(1到row行,1到col列)
if ((x >= 1 && x <= 9) && (y >= 1 && y <= 9)) // 假设row=col=9,可改为x<=row && y<=col
{
// 检查该坐标是否未被排查过(仍为'*')
if (show[x][y] == '*')
{
// 如果该位置无雷(mine[x][y]为'0')
if (mine[x][y] == '0')
{
Open_keyboard(show, mine, x, y); // 展开周围无雷区域
printf_keyboard(show, ROW, COL); // 刷新并显示玩家界面
}
// 如果该位置有雷(mine[x][y]为'1')
else
{
printf("很遗憾,踩到雷了,游戏结束\n以下是雷的位置:");
printf_keyboard(mine, ROW, COL); // 显示所有雷的位置
break; // 退出循环,游戏结束
}
}
// 该坐标已被排查过
else
{
printf("该坐标已经排查过了,请输入别的坐标:");
goto regain2; // 跳转到regain2,重新输入坐标
}
}
// 坐标输入无效(超出范围)
else
{
printf("输入错误,重新输入:");
goto regain2; // 跳转到regain2,重新输入坐标
}
// 判断玩家是否获胜(已排查所有非雷格子)
int Remove_mine_count = 0; // 记录已排查的非雷格子数量
for (int i = 1; i <= row; i++)
{
for (int j = 1; j <= col; j++)
{
if (show[i][j] != '*') // 非'*'表示已排查
{
Remove_mine_count++;
}
}
}
// 获胜条件:已排查的格子数 = 总格子数 - 雷的总数
if (Remove_mine_count == ((ROW * COL) - MINE))
{
printf("恭喜你,排除所有的雷,游戏胜利\n");
printf_keyboard(mine, ROW, COL); // 显示所有雷的位置
break; // 退出循环,游戏结束
}
}
}
game.h
#pragma once // 防止头文件被重复包含(只编译一次)
// 包含所需的标准库头文件
#include <stdio.h> // 提供输入输出函数(如printf、scanf)
#include "game.h" // 包含游戏相关的其他声明(注意:此处可能存在循环包含,实际应避免)
#include <time.h> // 提供时间相关函数(如time,用于初始化随机数种子)
#include "stdlib.h" // 提供标准库函数(如rand、srand,用于生成随机数)
// 宏定义:游戏核心参数
#define ROW 9 // 实际游戏区域的行数(9行)
#define COL 9 // 实际游戏区域的列数(9列)
#define ROWS ROW+3 // 雷区数组的总行数(比实际行数多3,用于处理边界,避免越界访问)
#define COLS COL+2 // 雷区数组的总列数(比实际列数多2,用于处理边界)
#define MINE 10 // 游戏中雷的总数(10个)
// 函数声明:声明游戏中用到的所有函数(供其他文件调用)
// 打印游戏菜单(如开始/退出选项)
void menu();
// 初始化棋盘数组
// 参数:board-要初始化的二维数组,rows-数组行数,cols-数组列数,set-初始化填充的字符
void set_keyboard(char board[ROWS][COLS], int rows, int cols, char set);
// 打印展示棋盘(玩家界面或雷区)
// 参数:board-要展示的二维数组,row-实际游戏区域行数,col-实际游戏区域列数
void printf_keyboard(char board[ROWS][COLS], int row, int col);
// 在雷区数组中随机布置雷
// 参数:board-雷区数组,row-实际游戏区域行数,col-实际游戏区域列数,mine-要布置的雷数
void set_mine(char board[ROWS][COLS], int row, int col, int mine);
// 处理玩家的排雷操作(核心游戏逻辑)
// 参数:show-玩家界面数组,mine-雷区数组,row-实际游戏区域行数,col-实际游戏区域列数
void move_mine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col);
浙公网安备 33010602011771号