C语言-----扫雷游戏 - 指南

扫雷游戏的功能说明

• 使⽤控制台实现经典的扫雷游戏
• 游戏可以通过菜单实现继续玩或者退出游戏
• 扫雷的棋盘是9*9的格⼦
• 默认随机布置10个雷
• 可以排查雷:
◦ 如果位置不是雷,就显⽰周围有⼏个雷
◦ 如果位置是雷,就炸死游戏结束
◦ 把除10个雷之外的所有⾮雷都找出来,排雷成功,游戏结束

test.c //⽂件中写游戏的测试逻辑
game.c //⽂件中写游戏中函数的实现等
game.h //⽂件中写游戏需要的数据类型和函数声明等

逻辑开始:

一、菜单

  1. 输入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 的游戏界面

  1. 使用二维数组实现
  2. 运用两个棋盘,一个用于展示,一个用于设置雷,写出初始化棋盘的函数
  3. 将展示的棋盘char show全部初始化为 '*',将布置雷的棋盘char mine全部初始化为0
  4. 为方便后边测试,可以先把打印棋盘的函数写出

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);

三、随机布置雷

  1. 使用srand((unsigned int) time(NULL))rand()
  2. 将雷设置为1,雷只能布置在char mine[x][y] == '0'的地方

game.h文件增加了
#include <time.h>
#include "stdlib.h"
#define MINE 10
void 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);

四、排雷

  1. 注意输入的坐标,横纵坐标都只能是0~9,出现其他数字报错,并重新输入
  2. 所排的坐标要显示周围雷的个数,如果为0,展开周围的棋盘(运用到递归)
  3. 如果所排的坐标是雷,显示游戏结束
  4. 如果输入的坐标是已经输入过的坐标,显示该坐标已经排除
  5. 判断游戏胜利,排除的坐标个数与减掉雷后的格子数相等

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);
posted @ 2025-12-08 17:16  clnchanpin  阅读(22)  评论(0)    收藏  举报