基于C语言的扫雷游戏系统设计与实现

        扫雷(Minesweeper)自1992年随Windows平台问世以来,便以其独特的魅力风靡全球,成为无数玩家的经典记忆。其精巧的设计理念与核心玩法规则影响深远。在本博客中,我将深入解析并亲手实践如何从零开始构建一个功能完备的扫雷游戏。它要求你精心设计数据结构、有效应用搜索算法,并切实体会软件工程实践。我将分享一路走来的技术思考,以及重现经典的代码魔法。本文旨在提供一个全面的实战指南,系统阐述扫雷游戏的设计与实现全过程。此项目是对核心编程概念(如数据结构、算法)和软件工程实践的一次集中演练。

目录

一、游戏设计思路

1.1 核心游戏机制

1.2 整体架构设计

1.3 游戏棋盘设计

二、详细实现步骤

2.1 主函数和菜单循环

2.2 游戏初始化

2.2.1 棋盘定义

2.2.2 棋盘初始化

2.2.3 铺设地雷

2.2.4 打印棋盘

2.2.5 玩家扫雷

2.2.5.1 扫雷整体流程

2.2.5.2 周围格子的地雷总数统计方式

2.2.5.3  递归展开功能实现

三、头文件定义

3.1 引入库函数

3.2 定义难度设置

 3.3 定义游戏区域大小

3.4 函数声明

四、代码实现

4.1 菜单

4.2 游戏初始化

4.3 设置地雷

4.3.1 难度选择菜单

4.4打印棋盘

4.5 扫雷过程

4.5.1 安全区域展开实现(递归实现)

4.5.2 检查剩余未扫区域

4.6 记录最佳成绩

五、游戏逻辑图

5.1  游戏初始化流程

​5.2 游戏主循环流程

​5.3 递归展开算法流程

5.4 游戏结束处理流程

5.5 菜单系统循环流程

​5.6 核心函数调用关系

​5.7 游戏状态转换图

5.8 完整逻辑流程图

六、完整游戏流程说明

七、完整代码展示:


一、游戏设计思路

1.1 核心游戏机制

        扫雷游戏的核心机制非常简单:玩家需要在不触发地雷的情况下,揭开所有非地雷的格子。游戏包含以下关键元素:

  • 游戏区域​:9×9的网格(可扩展)
  • 地雷分布​:随机分布在网格中
  • 数字提示​:显示每个格子周围的地雷数量
  • 递归展开​:当揭开空白区域时自动展开相邻区域

1.2 整体架构设计

        为提升项目的长期可维护性与开发效率,采用模块化架构设计。该设计以“高内聚、低耦合”为指导原则,确保各功能模块职责清晰、边界明确。这不仅极大增强了代码的可读性,也为未来功能的灵活增删奠定了坚实基础,使得代码库的管理与演进更为高效可控。

├── test.c         // 程序入口和主循环
├── game.c         // 游戏核心逻辑实现
└── game.h         // 全局定义和函数声明

1.3 游戏棋盘设计

        扫雷的过程中,雷的布局信息和排查出的雷的信息都需要存储,所以需要创建对应的数据进来存储所述信息,那么我们首先想到应该就是创建一个二维数组来存放此类信息。

空棋盘

        在这里如果没有布置雷,则存储0,布置了雷,则存储1。

        假设排查(4,3)这个坐标,则访问该坐标周围8个元素,并统计8个元素中雷的个数为2。

        假设排查(3,9)这个坐标,访问该坐标周围周围8个元素,在统计周围雷的个数时,会发现在访问最右边三个元素出现了越界访问,那么为了避免这个问题,可以采用一个常见技巧:将实际游戏区域(例如9x9)放在一个更大的数组(例如11x11)的中间,这样在游戏区域的边界格子检查周围时,就不会越界,因为大数组的边缘实际上并没有被用作游戏区域,但可以作为缓冲区。

周围加上缓冲区后的棋盘
排雷的假设

        接下来继续分析,当我们在棋盘上布置了雷,然后排查了一个坐标,假设这个坐标不是雷,但是周围有1个雷,那么我们就需要把这个坐标周围的雷的个数存放在一个位置中,并且打印出来,那么存放到哪里比较合适呢?如果直接存储到当前棋盘中,则会与雷的信息产生混淆,无法分清是雷还是周围的雷的个数。那么如何解决这个办法呢?

        其实很简单,只需要再创建一个相同的棋盘,一个棋盘(mine)用于存放雷的布局信息,另一个棋盘(show)用于存放排查出雷的信息。这样就能避免信息产生混淆,把雷都布置在mine数组中去,排查出的雷都存放在show数组中,在游戏过程中打印show数组以便让玩家排查。

对应的数组初始化如下:

1.    char mine[11][11] = { 0 };
2.    char show[11][11] = { 0 };

二、详细实现步骤

2.1 主函数和菜单循环

        游戏开始界面首当其冲的便是菜单界面,所以首先进行菜单的打印

void menu()
{
	printf("*****************************\n");
	printf("*****       1.play      *****\n");
	printf("*****       0.exit		*****\n");
	printf("*****************************\n");
}

        然后需要玩家进行游戏是否开始的选择, 为了保证菜单至少打印一次,使用do..while循环保证程序至少执行一次,然后根据玩家输入决定是否继续循环。如果玩家输入0则退出游戏,输入1则开始游戏,输入其他值需要提玩家输入出错,需要重新输入。

代码实现如下:

void test()
{
    int input = 0;
    do
    {
        menu(); // 打印菜单
        printf("请选择:>");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
            printf("开始游戏\n");//后续使用Game()函数运行游戏逻辑
            int now_time = difftime(end, start);
            break;
        case 0:
            printf("退出游戏\n");
            break;
        default:
            printf("选择错误,重新选择\n");
            break;
        }
    } while (input);
}

2.2 游戏初始化

        在Game中列出游戏运行整体逻辑

2.2.1 棋盘定义

        首先定义两个二维数组,mine_Board和show_Board,一个用于存储地雷分布,一个用于存储排查雷的信息和展示给玩家的棋盘。

2.2.2 棋盘初始化

        在游戏开始时,两个棋盘区域会先进行初始化设置:存放真实地雷信息的mine_Board棋盘每个位置都会显示'0'(代表无地雷),随后程序会在该棋盘上随机布置地雷;而展示给玩家操作的show_Board棋盘每个位置则显示'*'(代表未探索状态),当玩家输入需要排查的坐标后,该棋盘会根据周围8个相邻格子中实际埋藏的地雷数量,自动更新为对应的数字提示。

2.2.3 铺设地雷

        在mine_Board数组初始化完成后,需要进行随机排雷,通过使用srand设置随机数种子,确保每一次游戏地雷位置都不相同

2.2.4 打印棋盘

        打印show_Board数组,让玩家进行排雷操作,打印mine_Board棋盘用于调试游戏整体运行逻辑是否正确。

2.2.5 玩家扫雷
2.2.5.1 扫雷整体流程

        玩家在扫雷的时候是一个持续循环的过程,直到玩家踩雷或达成胜利条件才会终止。每次循环开始时,首先向玩家展示当前的游戏盘面状态(显示盘),这个盘面会清晰标记已翻开的格子(显示数字或空白)和玩家标记的雷区位置。接着程序会等待玩家输入要操作的坐标位置。获得坐标后,紧接着验证该坐标是否有效(是否在棋盘范围内且未被翻开),若坐标非法则提示重新输入。若玩家不幸选中埋雷位置,游戏立即结束,程序会完整揭示雷盘布局并显示所有地雷位置。若玩家选择的是安全区域,则翻开该坐标周围不是雷的区域,直到出现有格子显示周围雷的个数时停止翻开,然后等待玩家进行下一次排雷。

void Game()
{
    // 定义两个二维数组:雷盘和显示盘
    char mine_Board[ROWS][COLS]; // 存储地雷分布
    char show_Board[ROWS][COLS]; // 显示给玩家的棋盘

    // 初始化雷盘(全部置为'0',表示无雷)
    Init_Board(mine_Board, ROWS, COLS, '0');
    // 初始化显示盘(全部置为'*',表示未翻开)
    Init_Board(show_Board, ROWS, COLS, '*');

    // 设置地雷(返回设置的地雷总数)
    int all_mine = Set_Mine(mine_Board, ROW, COL);
    
    // 打印显示盘给玩家看(刚开始都是*)
    Display_Board(show_Board, ROW, COL);
    // 打印雷盘(调试用,实际游戏中应隐藏)
    Display_Board(mine_Board, ROW, COL);

    // 开始扫雷过程
    Find_Mine(mine_Board, show_Board, ROW, COL, all_mine);
}
2.2.5.2 周围格子的地雷总数统计方式

        在扫雷游戏中,每个翻开的安全格子显示的数字,其核心含义就是它本身所在的格子周围紧邻的八个方向(包括水平、垂直和对角线方向)上,所有地雷的总数。要计算任意一个中心格(例如图中标示的黄色格子位置 (4,5))周围的地雷数量,然后以这个中心格的坐标 (x,y) 为基准点,系统性地检查其所有直接相邻的格子是否包含地雷。这些邻近格子被精确定位为中心格的周围8个坐标,分别对应(x-1,y-1),(x-1,y),(x-1,y+1),......(x+1,y+1),需要逐一确认这些位置上的格子是否存在地雷,并将所有存在地雷的格子数量加总起来,最终得到的这个总和,就是目标中心格子所显示的数字——通过这种计算方式可以直观地告诉玩家,在其周边这一圈紧邻的八个格子范围内,总共隐藏了多少个地雷。

代码实现:

int Get_mine(char mine_Board[ROWS][COLS], int x, int y)
{
	int i = 0;
	int count = 0;
	for (i = -1; i <= 1; i++)
	{
		int j = 0;
		for (j = -1; j <= 1; j++)
		{
			if (mine_Board[x + i][y + j] == '1')
			{
				count++;
			}
		}
	}
	return count;
}
2.2.5.3  递归展开功能实现

至此,扫雷的基础功能(打印棋盘、排查格子、显示结果)已经实现。

但还有一个关键效果缺失:​​ 目前程序每次只能翻开玩家点击的单个格子。而在真正的扫雷游戏中,当玩家点到一个周围没有雷的格子(标记为“0”)时,应该会自动翻开周围一大片相连的安全区域,直到遇到有数字提示的格子或棋盘边界才停止。这种扩散翻开的效果大大减少了玩家的重复操作。

如何实现这种“大范围翻开”呢?​

关键在于识别出“0”格并触发递归翻查​:

  1. 起点:​​ 当玩家排查到一个标记为“0”的格子(即周围八格内无雷)时,将其显示为“空格”(视觉效果上通常留白)。
  2. 扩散:​​ 接着,程序需要自动地去翻查这个“0”格周围的所有8个相邻格子
  3. 处理相邻格子:​
    • 如果相邻格子是雷​:安全起见,此格子只显示周围雷的个数(不会被翻开,此规则由“递归翻查”的定义边界保证,通常我们规定只自动翻开标记为“0”及其触发连锁的空格,安全数字格和雷格不自动翻,防止踩雷)。
    • 如果相邻格子也是“0”​​:将它也翻成“空格”,然后立即对这个新的“0”格重复步骤2和步骤3(这就是递归)​
    • 如果相邻格子有数字​(1-8):只需显示该数字,翻查到此为止(数字格就是递归的边界条件)。
  4. 结束:​​ 这个递归过程会像水波一样扩散开来,直到所有连续的“0”区域都被翻开(显示为空格),并且所有与这些“0”区域相邻的数字格子都被显示出来。

流程如图所示:

 代码实现:

void Fill_Mine(char mine_Board[ROWS][COLS],char show_Board[ROWS][COLS], int x, int y, int row, int col)
{
	int i = 0;
	if (show_Board[x][y]!='*')
	{
		return;
	}
	int count = Get_mine(mine_Board, x, y);
	if (count == 0)
	{
		show_Board[x][y] = ' ';
		for (i = -1; i <= 1; i++)
		{
			int j = 0;
			for (j = -1; j <= 1; j++)
			{
				if (mine_Board[x + i][y + j] == '0')
				{
					
					Fill_Mine(mine_Board, show_Board, x + i, y + j, row, col);
				}
			
			}
		}
	}
	else
	{
		show_Board[x][y] = count + '0';
	}
}

三、头文件定义

3.1 引入库函数

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

3.2 定义难度设置

        定义三种难度的地雷数量,增加游戏挑战性。

#define EASY_GAME 10
#define MIDDLE_GAME 20
#define DIFFCULT_GAME 35
// 难度设置:初级10雷,中级20雷,高级35雷

 3.3 定义游戏区域大小

        实际游戏区域是9x9(ROW x COL),但创建数组时使用11x11(ROWS x COLS),多出来的两行两列作为边界,简化边界检查。

// 游戏区域大小:9x9,但加上边界后为11x11
#define ROW 9
#define COL 9
#define ROWS (ROW+2)
#define COLS (COL+2)

3.4 函数声明

        声明所有游戏相关函数。


void Init_Board(char Board[ROWS][COLS], int row, int col, char flag);
int Set_Mine(char Mine_Board[ROWS][COLS], int row, int col);
void Display_Board(char Board[ROWS][COLS], int row, int col);
void Find_Mine(char mine_Board[ROWS][COLS], char show_Board[ROWS][COLS], int row, int col, int all_mine);
void best_game_time(int* best_time, int now_time);

四、代码实现

4.1 菜单

功能详解:​

  1. menu()函数​:打印主菜单界面,使用多个printf语句输出菜单选项。

  2. do...while循环​:保证程序至少执行一次,然后根据用户输入决定是否继续循环。

  3. switch语句​:处理用户输入,1开始游戏,0退出游戏,其他值提示错误。

  4. ​时间记录​:使用time(NULL)获取当前时间,difftime计算时间差(秒)。

  5. ​最佳时间更新​:调用best_game_time函数,比较并更新最佳成绩。

// test.c
int main()
{
    test(); // 进入测试函数(游戏主循环)
    return 0;
}

// test.c
void menu()
{
    printf("*****************************\n");
    printf("*****       1.play      *****\n");
    printf("*****       0.exit      *****\n");
    printf("*****************************\n");
}

void test()
{
    int input = 0;
    int best_time = 0; // 记录最佳时间
    do
    {
        menu(); // 打印菜单
        printf("请选择:>");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
            // 记录游戏开始时间
            const time_t start = time(NULL);
            // 开始游戏
            Game();
            // 记录游戏结束时间
            const time_t end = time(NULL);
            // 计算本次游戏时间(秒)
            int now_time = difftime(end, start);
            // 更新最佳时间
            best_game_time(&best_time, now_time);
            printf("本次游戏时间:%d s\n", now_time);
            printf("最佳游戏时间:%d s\n", best_time);
            break;
        case 0:
            printf("退出游戏\n");
            break;
        default:
            printf("选择错误,重新选择\n");
            break;
        }
    } while (input);
}

4.2 游戏初始化

功能详解:​

  1. 使用两个二维数组:mine_Board存储实际地雷分布('1'表示雷),show_Board存储玩家看到的界面。

  2. Init_Board函数初始化两个数组,分别用'0'(无雷)和'*'(未翻开)填充。

  3. Set_Mine函数在mine_Board中随机布置地雷,并返回地雷总数。

  4. Display_Board分别打印两个棋盘(实际游戏中只应显示show_Board,另一个用于调试)。

  5. Find_Mine函数处理玩家的扫雷操作。

// test.c
void Game()
{
    // 定义两个二维数组:雷盘和显示盘
    char mine_Board[ROWS][COLS]; // 存储地雷分布
    char show_Board[ROWS][COLS]; // 显示给玩家的棋盘

    // 初始化雷盘(全部置为'0',表示无雷)
    Init_Board(mine_Board, ROWS, COLS, '0');
    // 初始化显示盘(全部置为'*',表示未翻开)
    Init_Board(show_Board, ROWS, COLS, '*');

    // 设置地雷(返回设置的地雷总数)
    int all_mine = Set_Mine(mine_Board, ROW, COL);
    
    // 打印显示盘给玩家看(刚开始都是*)
    Display_Board(show_Board, ROW, COL);
    // 打印雷盘(调试用,实际游戏中应隐藏)
    Display_Board(mine_Board, ROW, COL);

    // 开始扫雷过程
    Find_Mine(mine_Board, show_Board, ROW, COL, all_mine);
}

4.3 设置地雷

功能详解:​

  1. 调用count_menu()显示难度选择菜单。

  2. 调用pick_count()让玩家选择难度,返回对应地雷数(初级10,中级20,高级35)。

  3. 使用srand设置随机数种子,确保每次运行地雷位置不同。

  4. 循环布置地雷直到达到指定数量:

    • 生成随机坐标(在有效游戏区域内:1~row,1~col)

    • 如果该位置是'0'(无雷),则改为'1'(有雷),同时减少剩余雷数

// game.c
int Set_Mine(char Mine_Board[ROWS][COLS], int row, int col)
{
    // 显示难度选择菜单
    count_menu();
    // 让玩家选择难度,返回对应的地雷数
    int count = pick_count();
    int all_mine = count; // 记录总雷数

    // 设置随机数种子
    srand((unsigned int)time(NULL));

    while (count)
    {
        // 生成1~row和1~col范围内的随机坐标
        int x = rand() % row + 1;
        int y = rand() % col + 1;

        // 如果该位置无雷,则布置一颗雷
        if (Mine_Board[x][y] == '0')
        {
            Mine_Board[x][y] = '1';
            count--; // 剩余雷数减一
        }
    }
    return all_mine;
}
4.3.1 难度选择菜单

功能详解:​

  • count_menu()函数打印难度选择菜单。

  • pick_count()使用do...while循环确保玩家输入有效的难度级别(1/2/3),返回对应的地雷数常量。

// game.c
void count_menu()
{
    printf("*****************************\n");
    printf("*******    游戏难度   *******\n");
    printf("*****************************\n");
    printf("*****     1.初级难度    *****\n");
    printf("*****     2.中级难度    *****\n");
    printf("*****     3.高级难度    *****\n");
    printf("*****************************\n");
}

int pick_count()
{
    int input = 0;
    do
    {
        printf("请选择难度:>");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
            return EASY_GAME; // 10
        case 2:
            return MIDDLE_GAME; // 20
        case 3:
            return DIFFCULT_GAME; // 35
        default:
            printf("选择错误,重新选择\n");
            break;
        }
    } while (1); // 直到选择正确才退出
}

4.4打印棋盘

功能详解:​

  1. 首先打印一行列号(1到列数col)。

  2. 然后遍历每一行(1到行数row):

    • 打印当前行号

    • 遍历该行的每个格子(1到col),打印其内容

  3. 注意:只打印游戏区域(1~row,1~col),不打印扩展的边界(0和row+1,col+1)

// game.c
void Display_Board(char Board[ROWS][COLS], int row, int col)
{
    printf("------ 扫雷游戏 ------\n");
    // 打印列号(1~col)
    printf("   ");
    for (int j = 1; j <= col; j++)
    {
        printf("%2d ", j);
    }
    printf("\n");

    // 打印每行
    for (int i = 1; i <= row; i++)
    {
        printf("%2d ", i); // 打印行号
        for (int j = 1; j <= col; j++)
        {
            printf(" %c ", Board[i][j]); // 打印每个格子
        }
        printf("\n");
    }
    printf("----------------------\n");
}

4.5 扫雷过程

流程详解:​

  1. 循环直到游戏结束(踩雷或胜利)

  2. 每次循环:

    • 打印当前显示盘

    • 获取玩家输入的坐标

    • 验证坐标合法性

    • 踩雷:游戏结束,显示雷盘

    • 安全:调用Fill_Mine递归展开周围区域

    • 调用Ret_Mine检查剩余未翻开的格子数是否等于总雷数(即所有非雷区已扫完),是则胜利

// game.c
void Find_Mine(char mine_Board[ROWS][COLS], char show_Board[ROWS][COLS], 
               int row, int col, int all_mine)
{
    int x = 0, y = 0;
    while (1)
    {
        // 1. 打印当前显示盘
        Display_Board(show_Board, row, col);
        // 2. 获取玩家输入
        printf("请输入要排查的坐标:>>");
        scanf("%d %d", &x, &y);

        // 3. 检查坐标是否在有效范围内
        if (x >= 1 && x <= row && y >= 1 && y <= col)
        {
            // 4. 如果踩到雷
            if (mine_Board[x][y] == '1')
            {
                printf("很遗憾,你被炸死了!\n");
                Display_Board(mine_Board, row, col); // 显示雷的位置
                break;
            }
            // 5. 如果没踩到雷
            else
            {
                // 使用递归展开周围的非雷区域
                Fill_Mine(mine_Board, show_Board, x, y, row, col);
                // 检查是否已扫除所有非雷区域
                if (Ret_Mine(show_Board, row, col) == all_mine)
                {
                    printf("恭喜你,排雷成功!\n");
                    Display_Board(mine_Board, row, col); // 显示雷的位置
                    break;
                }
            }
        }
        else
        {
            printf("坐标非法,请重新输入!\n");
        }
    }
}
4.5.1 安全区域展开实现(递归实现)

算法说明:​

  1. 如果当前位置已经显示(不是'*'),则返回。

  2. 计算当前位置周围8个格子的地雷总数(调用Get_mine)。

  3. 如果有雷(count>0),则显示雷数(将数字转为字符,如1转为'1')。

  4. 如果无雷(count==0):

    • 显示为空格

    • 递归调用自身处理周围的8个格子(使用两层循环遍历偏移量)

// game.c
void Fill_Mine(char mine_Board[ROWS][COLS], char show_Board[ROWS][COLS], 
               int x, int y, int row, int col)
{
    // 如果该位置已经显示过,则返回
    if (show_Board[x][y] != '*')
        return;

    // 计算该位置周围8个格子的雷数
    int count = Get_mine(mine_Board, x, y);

    // 如果周围有雷,显示雷数
    if (count > 0)
    {
        show_Board[x][y] = count + '0'; // 将数字转为字符
    }
    // 如果周围没有雷,显示为空格,并递归展开周围格子
    else
    {
        show_Board[x][y] = ' ';
        // 遍历周围的8个格子
        for (int i = -1; i <= 1; i++)
        {
            for (int j = -1; j <= 1; j++)
            {
                int nx = x + i;
                int ny = y + j;
                // 确保新坐标在有效范围内
                if (nx >= 1 && nx <= row && ny >= 1 && ny <= col)
                {
                    // 递归调用
                    Fill_Mine(mine_Board, show_Board, nx, ny, row, col);
                }
            }
        }
    }
}

        注: 这个函数会计算包括周围8个格子的雷数,不包括自身。因为调用时,当前格子(x,y)肯定是无雷的(否则已经结束游戏)。

4.5.2 检查剩余未扫区域

        该函数统计显示盘中还未翻开的格子数量。在扫雷过程中,当未翻开的格子数等于总雷数时,说明所有非雷区域已被扫开,玩家获胜。

// game.c
int Ret_Mine(char show_Board[ROWS][COLS], int row, int col)
{
    int count = 0;
    // 遍历整个游戏区域
    for (int i = 1; i <= row; i++)
    {
        for (int j = 1; j <= col; j++)
        {
            // 如果该位置还是未翻开状态('*'),则计数
            if (show_Board[i][j] == '*')
                count++;
        }
    }
    return count;
}

4.6 记录最佳成绩

        为增加游戏有趣性和自我挑战性,引入最佳挑战成绩功能

        比较本次游戏时间和之前的最佳时间,如果本次更优则更新。

        注意:第一次游戏时*best_time为0,直接更新为当前时间。

// game.c
void best_game_time(int* best_time, int now_time)
{
    // 如果当前成绩比最佳成绩好(时间更短),更新最佳成绩
    if (*best_time == 0 || now_time < *best_time)
    {
        *best_time = now_time;
    }
}

五、游戏逻辑图

5.1  游戏初始化流程

5.2 游戏主循环流程

5.3 递归展开算法流程

5.4 游戏结束处理流程

5.5 菜单系统循环流程

5.6 核心函数调用关系

5.7 游戏状态转换图

5.8 完整逻辑流程图

六、完整游戏流程说明

  1. 程序启动​:从main函数开始执行

  2. ​主菜单显示​:调用menu()函数显示选项

  3. ​用户选择​:

    • 选择1:开始游戏

      • 记录开始时间

      • 显示难度菜单

      • 获取难度选择

      • 初始化游戏面板

      • 随机布置地雷

      • 显示初始界面

      • 进入游戏主循环

    • 选择0:退出程序

  4. ​游戏主循环​:

    • 显示当前游戏状态

    • 获取玩家输入坐标

    • 验证坐标合法性

    • 检查是否踩雷

      • 踩雷:显示所有地雷,游戏失败

      • 安全:递归展开安全区域

    • 检查胜利条件

      • 满足:显示所有地雷,游戏胜利

  5. ​游戏结束处理​:

    • 记录结束时间

    • 计算本次游戏时间

    • 更新最佳时间记录

    • 返回主菜单

  6. ​循环执行​:直到用户选择退出

七、完整代码展示:

       1. test.c文件:

#include "game.h"
void menu()
{
	printf("*****************************\n");
	printf("*****       1.play      *****\n");
	printf("*****       0.exit		*****\n");
	printf("*****************************\n");
}
void Game()
{
	//定义
	char mine_Board[ROWS][COLS];
	char show_Board[ROWS][COLS];

	//初始化
	Init_Board(mine_Board,ROW,COL,'0');
	Init_Board(show_Board, ROW, COL, '*');

	//布置
	srand((unsigned)time(NULL));
	int all_mine= Set_Mine(mine_Board,ROW,COL);
		
	//打印
	Display_Board(show_Board,ROW,COL);
	Display_Board(mine_Board, ROW, COL);


	//排查
	Find_Mine(mine_Board, show_Board, ROW, COL,all_mine);
}
void test()
{
	int input = 0;
	int best_time = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("Play Game\n");
			const time_t start = time(NULL);
			Game();
			const time_t end = time(NULL);
			int now_time = difftime(end, start);
			best_game_time(&best_time,now_time);
			printf("本局游戏时间:%d s\n", now_time);
			printf("最佳游戏时间:%d s\n", best_time);
			break;
		case 0:
			printf("Exit Game\n");
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}
	} while (input);
}
int main()
{
	const time_t start = time(NULL);
	test();
	const time_t end = time(NULL);
	int game_time = difftime(end, start);
	printf("本次游戏游玩总时长:%d s\n", game_time);

	return 0;
}

2.game.c 文件

#include "game.h"
void Init_Board(char Board[ROWS][COLS], int row, int col, char flag)
{
	int i = 0;
	for ( i = 0; i < ROWS; i++)
	{
		int j = 0;
		for ( j = 0; j < COLS; j++)
		{
			Board[i][j] = flag;
		}
	}
}
void count_menu()
{
	printf("*****************************\n");
	printf("*******    游戏难度   *******\n");
	printf("*****************************\n");
	printf("*****     1.简单难度    *****\n");
	printf("*****	  2.中等难度	*****\n");
	printf("*****	  3.困难难度	*****\n");
	printf("*****************************\n");

}
int pick_count()
{
	int input = 0;
	do
	{
		printf("请选择难度:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			return EASY_GAME;
			break;
		case 2:
			return MIDDLE_GAME;
			break;
		case 3:
			return DIFFCULT_GAME;
			break;
		default:
			printf("输入有误,请重新输入\n");
			break;
		}
	} while (input);
	return -1;


}
int Set_Mine(char Mine_Board[ROWS][COLS], int row, int col)
{
	count_menu();
	int count = pick_count();
	int all_mine = count;
	while (count)
	{
		int x = rand() % 9 + 1;
		int y = rand() % 9 + 1;
		if (Mine_Board[x][y] == '0')
		{
			Mine_Board[x][y] = '1';
			count--;
		}
	}
	return all_mine;
}
void Display_Board(char Board[ROWS][COLS], int row, int col)
{
	printf("————————————扫雷游戏————————————\n");
	int i = 0;
	for (i = 0; i <= row; i++)
	{
		printf("%d ", i);
	}
	printf("\n");
	for ( i = 1; i <= row; i++)
	{
		int j = 0;
		printf("%d ", i);
		for ( j = 1; j <= col; j++)
		{
			printf("%c ", Board[i][j]);
		}
		printf("\n");
	}
}
int Ret_Mine(char show_Board[ROWS][COLS],int row,int col)
{
	int i = 0;
	int count = 0;
	for (i = 1; i <= row; i++)
	{
		int j = 0;
		for (j = 1; j <= col; j++)
		{
			if (show_Board[i][j] == '*')
			{
				count++;
			}
		}
	}
	return count;
}
int Get_mine(char mine_Board[ROWS][COLS], int x, int y)
{
	int i = 0;
	int count = 0;
	for (i = -1; i <= 1; i++)
	{
		int j = 0;
		for (j = -1; j <= 1; j++)
		{
			if (mine_Board[x + i][y + j] == '1')
			{
				count++;
			}
		}
	}
	return count;
}
void Fill_Mine(char mine_Board[ROWS][COLS],char show_Board[ROWS][COLS], int x, int y, int row, int col)
{
	int i = 0;
	if (show_Board[x][y]!='*')
	{
		return;
	}
	int count = Get_mine(mine_Board, x, y);
	if (count == 0)
	{
		show_Board[x][y] = ' ';
		for (i = -1; i <= 1; i++)
		{
			int j = 0;
			for (j = -1; j <= 1; j++)
			{
				if (mine_Board[x + i][y + j] == '0')
				{
					
					Fill_Mine(mine_Board, show_Board, x + i, y + j, row, col);
				}
			
			}
		}
	}
	else
	{
		show_Board[x][y] = count + '0';
	}
}
void Find_Mine(char mine_Board[ROWS][COLS], char show_Board[ROWS][COLS], int row, int col,int all_mine)
{
	int x = 0;
	int y = 0;
	while (1)
	{
		printf("请输入要排查的坐标:>>");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (mine_Board[x][y] == '1')
			{
				printf("很遗憾,你被炸死了\n");
				break;
			}
			else
			{
				if (mine_Board[x][y] == '0')
				{
					Fill_Mine(mine_Board, show_Board, x, y, row, col);
					Display_Board(show_Board, ROW, COL);

				}
				else
				{
					printf("坐标不合法,请重新输入\n");
				}
			}
			if (Ret_Mine(show_Board, row, col) == all_mine)
			{
				printf("恭喜你!排雷成功!\n");
				break;
			}
		}
		else
		{
			printf("输入的坐标非法,重新输入\n");
		}
		
	}
}
void best_game_time(int* best_time,int now_time)
{
	if (*best_time > now_time || *best_time == 0)
	{
		*best_time = now_time;
	}
}

3.game.h 文件

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <time.h>
#include <stdlib.h>


#define EASY_GAME 10
#define MIDDLE_GAME 20
#define DIFFCULT_GAME 35

#define	ROW 9
#define COL 9

#define ROWS ROW+2
#define COLS COL+2


void Init_Board(char Board[ROWS][COLS], int row, int col, char flag);
int Set_Mine(char Mine_Board[ROWS][COLS], int row, int col);
void Display_Board(char Board[ROWS][COLS], int row, int col);
void Find_Mine(char mine_Board[ROWS][COLS], char show_Board[ROWS][COLS], int row, int col, int all_mine);
void best_game_time(int* best_time, int now_time);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值