C 语言 第七讲:数组和函数实践:扫雷游戏

目录

1.扫雷游戏分析和设计

1.1扫雷游戏的功能说明

1.1.1游戏的界面

[1.2 游戏的分析和设计](#1.2 游戏的分析和设计)

[1.2.1 数据结构的分析](#1.2.1 数据结构的分析)


1.扫雷游戏分析和设计

2.扫雷游戏的代码实现

3.扫雷游戏的拓展

1.扫雷游戏分析和设计

1.1扫雷游戏的功能说明

使用控制台实现经典的扫雷游戏

·游戏可以通过菜单实现继续玩或者退出游戏

·扫雷的棋盘是9*9的格子

·默认随机布置10个雷 ·可以排查雷

·如果位置不是雷,就显示周围有几个雷

·如果位置是雷,就炸死游戏结束

·把除10个雷之外的所有非雷都找出来,排雷成功,游戏结束

1.1.1游戏的界面

1.2 游戏的分析和设计

1.2.1 数据结构的分析

扫雷的过程中,布置的雷和排查出的雷的信息都需要存储,所以我们需要一定的数据结构来存储这些信息。

因为我们需要在9 * 9的棋盘上布置雷的信息和排查雷,我们首先想到的就是创建一个9 * 9的数组来存放信息

1.默认十个雷

2.随机找10个位

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

再继续分析,我们在棋盘上布置了雷,棋盘上雷的信息(1)和非雷的信息(0),假设我们排查了某一个位置后,这个坐标处不是雷,这个坐标的周围有1个雷,那我们需要将排查出的雷的数量信息记录存储,并打印出来,作为排雷的重要参考信息的,那这个雷的个数信息存放在哪里呢?如果存放在布置雷的数组中,这样雷的信息和雷的个数信息就可能或产生混淆和打印上的困难。

这里我们肯定有办法解决,比如:雷和非富的信息不要使用数字,使用某些字符就行,这样就避免冲突了,但是这样做棋盘上有雷和非雷的信息,还有排查出的雷的个数信息,就比较混杂,不够方便。

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

同时为了保持神秘,show数组开始时初始化为字符"*,为了保持两个数组的类型一致,可以使用同一套函数处理,mine数组最开始也初始化为字符0,布置雷改成"1',如下如

cs 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>  // 用于 rand()、srand()
#include <time.h>    // 用于 time() 生成随机种子

// 宏定义:棋盘尺寸(实际游戏区9x9,棋盘设11x11避免边缘越界)
#define ROW 9    // 游戏区行数
#define COL 9    // 游戏区列数
#define ROWS ROW + 2  // 实际存储的棋盘行数(11)
#define COLS COL + 2  // 实际存储的棋盘列数(11)
#define MINE_COUNT 10 // 雷的总数

// 函数声明(必须在调用前声明)
void menu();          // 菜单
void test();          // 游戏入口
void game();          // 游戏核心逻辑
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); // 排查雷
int GetMineCount(char mine[ROWS][COLS], int x, int y); // 计算周围雷数

int main()
{
    // 设置随机数种子(只需要在程序开头执行1次,保证每次雷的位置不同)
    srand((unsigned int)time(NULL));
    test();
    // 删掉多余的 scanf,否则退出游戏后会莫名等待输入
    return 0;
}

// 菜单函数(已完善格式)
void menu()
{
    printf("************************\n");
    printf("****  1. 开始游戏   ****\n");
    printf("****  0. 退出游戏   ****\n");
    printf("************************\n");
}

// 游戏入口函数(修正循环逻辑)
void test()
{
    int input = 0;
    do
    {
        menu();
        printf("请选择:");
        scanf("%d", &input);
        switch (input)
        {
        case 0:
            printf("退出游戏成功!\n");
            break; // 退出循环(do-while条件为input,input=0时循环结束)
        case 1:
            printf("扫雷游戏开始!\n");
            game(); // 调用游戏核心逻辑
            break;
        default:
            printf("输入错误,请重新选择!\n");
            break;
        }
    } while (input); // input=0时退出,非0时继续循环
}

// 游戏核心逻辑
void game()
{
    // 两个棋盘:
    // mine[ROWS][COLS]:存雷信息('1'=雷,'0'=非雷),不展示给用户
    // show[ROWS][COLS]:展示给用户(初始'*',排查后显示雷数或雷)
    char mine[ROWS][COLS] = {0};
    char show[ROWS][COLS] = {0};

    // 1. 初始化棋盘:mine初始化为'0',show初始化为'*'
    InitBoard(mine, ROWS, COLS, '0');
    InitBoard(show, ROWS, COLS, '*');

    // 2. 显示初始棋盘(只显示9x9游戏区)
    DisplayBoard(show, ROW, COL);

    // 3. 埋雷(在mine棋盘的9x9区域随机埋10个雷)
    SetMine(mine, ROW, COL);
    // (调试用:显示雷的位置,发布时注释掉)
    // DisplayBoard(mine, ROW, COL);

    // 4. 排查雷(核心交互逻辑)
    FindMine(mine, show, ROW, COL);
}

// 初始化棋盘:将board的所有元素设为set
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++)
        {
            board[i][j] = set;
        }
    }
}

// 显示棋盘:只展示row x col的游戏区(9x9),添加坐标方便用户选择
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
    int i = 0;
    int j = 0;
    // 打印列号(1-9)
    printf("  ");
    for (j = 1; j <= col; j++)
    {
        printf("%d ", j);
    }
    printf("\n");

    // 打印行号(1-9)和棋盘内容
    for (i = 1; i <= row; i++)
    {
        printf("%d ", i);
        for (j = 1; j <= col; j++)
        {
            printf("%c ", board[i][j]);
        }
        printf("\n");
    }
}

// 埋雷:在row x col区域(9x9)随机埋MINE_COUNT个雷('1'表示雷)
void SetMine(char board[ROWS][COLS], int row, int col)
{
    int count = MINE_COUNT; // 剩余要埋的雷数
    while (count > 0)
    {
        // 生成1-9的随机坐标(用户输入的坐标是1-based,和棋盘对应)
        int x = rand() % row + 1;
        int y = rand() % col + 1;

        // 确保该位置没有雷,再埋雷
        if (board[x][y] == '0')
        {
            board[x][y] = '1';
            count--; // 雷数减1
        }
    }
}

// 排查雷:用户输入坐标,判断是否踩雷、计算周围雷数
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
    int x = 0;
    int y = 0;
    int win = 0; // 已排查的非雷数量(目标:9*9 - 10 = 71)

    while (win < row * col - MINE_COUNT)
    {
        printf("请输入要排查的坐标(行 列):");
        scanf("%d %d", &x, &y);

        // 1. 判断坐标是否合法(1<=x<=9,1<=y<=9)
        if (x >= 1 && x <= row && y >= 1 && y <= col)
        {
            // 2. 判断该位置是否已排查过
            if (show[x][y] == '*')
            {
                // 3. 判断是否踩雷(mine[x][y] == '1')
                if (mine[x][y] == '1')
                {
                    printf("很遗憾,你踩雷了!游戏结束!\n");
                    DisplayBoard(mine, row, col); // 展示所有雷的位置
                    break;
                }
                else
                {
                    // 4. 未踩雷:计算周围8个格子的雷数
                    int mine_count = GetMineCount(mine, x, y);
                    // 将雷数转换为字符('0' + 数字 = 对应字符)
                    show[x][y] = '0' + mine_count;
                    // 5. 显示更新后的棋盘
                    DisplayBoard(show, row, col);
                    // 6. 排查成功,非雷计数+1
                    win++;
                }
            }
            else
            {
                printf("该坐标已排查过,请重新输入!\n");
            }
        }
        else
        {
            printf("坐标非法(1-9),请重新输入!\n");
        }
    }

    // 7. 判断是否胜利(win达到目标:71)
    if (win == row * col - MINE_COUNT)
    {
        printf("恭喜你,排雷成功!游戏胜利!\n");
        DisplayBoard(mine, row, col); // 展示所有雷的位置
    }
}

// 计算(x,y)周围8个格子的雷数
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
    // 周围8个格子的和('1'的ASCII码是49,'0'是48,和减8*48就是雷的数量)
    return (mine[x-1][y-1] + mine[x-1][y] + mine[x-1][y+1]
          + mine[x][y-1]       + mine[x][y+1]
          + mine[x+1][y-1] + mine[x+1][y] + mine[x+1][y+1])
          - 8 * '0';
}

或者调用一个源文件和一个头文件来实行

cs 复制代码
1. 头文件:game.h(单独创建,存放宏定义 + 函数声明)
cs 复制代码
#ifndef __GAME_H__
#define __GAME_H__

// 1. 宏定义(棋盘尺寸、雷数)
#define ROW 9     // 游戏区行数(用户可见)
#define COL 9     // 游戏区列数(用户可见)
#define ROWS ROW + 2  // 实际存储行数(11,避免边缘越界)
#define COLS COL + 2  // 实际存储列数(11)
#define EASY_COUNT 10 // 简单难度雷数

// 2. 函数声明(必须和 .c 文件中函数定义完全一致)
void Initboard(char board[ROWS][COLS], int r, int c, char set);   // 初始化棋盘
void Displayboard(char board[ROWS][COLS], int r, int c);          // 显示棋盘
void SetMind(char mine[ROWS][COLS], int r, int c);                // 布置雷
void FindMind(char mine[ROWS][COLS], char show[ROWS][COLS], int r, int c); // 排查雷
void game();                                                       // 游戏入口
void menu();                                                       // 菜单显示
void test();                                                       // 流程控制

#endif // __GAME_H__
cs 复制代码
2. 源文件:mine.c(主代码,存放函数实现 + main)
cs 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>  // 必须包含:srand() 函数在这
#include <time.h>    // 必须包含:time(NULL) 函数在这
#include "game.h"    // 包含头文件,关联宏定义和函数声明

// 游戏核心逻辑实现
void game()
{
    char mine[ROWS][COLS];  // 存放雷的数组(11x11)
    char show[ROWS][COLS];  // 展示给用户的数组(11x11)

    // 1. 初始化棋盘:mine=0(无雷),show=*(未排查)
    Initboard(mine, ROWS, COLS, '0');
    Initboard(show, ROWS, COLS, '*');

    // 2. 显示初始棋盘(取消注释!让用户看到9x9棋盘,知道坐标范围)
    Displayboard(show, ROW, COL);

    // 3. 随机布置10个雷
    SetMind(mine, ROW, COL);

    // (调试用:取消注释可查看雷的位置,发布时注释)
    // Displayboard(mine, ROW, COL);

    // 4. 开始排查雷
    FindMind(mine, show, ROW, COL);
}

// 菜单显示实现
void menu()
{
    printf("************************\n");
    printf("****  1. 开始游戏   ****\n");
    printf("****  0. 退出游戏   ****\n");
    printf("************************\n");
}

// 游戏流程控制实现
void test()
{
    // 初始化随机种子(只执行1次,保证每次雷的位置不同)
    srand((unsigned int)time(NULL));
    int input = 0;

    do
    {
        menu();          // 显示菜单
        printf("请选择:"); // 优化:输入提示更规范
        scanf("%d", &input);

        switch (input)
        {
        case 0:
            printf("退出游戏成功!\n"); // 补充:退出提示
            break; // input=0,循环结束
        case 1:
            printf("扫雷游戏开始了!\n");
            game(); // 进入游戏核心
            break;
        default:
            printf("输入错误(仅支持1/0),请重新选择!\n"); // 优化:错误提示更清晰
            break;
        }
    } while (input); // input=0时退出,非0时重新显示菜单
}

// 主函数(程序入口)
int main()
{
    test();
    return 0;
}

// ---------------------- 以下是 game.h 声明的其他函数实现 ----------------------
// 初始化棋盘:将board所有元素设为set
void Initboard(char board[ROWS][COLS], int r, int c, char set)
{
    int i = 0;
    for (i = 0; i < r; i++)
    {
        int j = 0;
        for (j = 0; j < c; j++)
        {
            board[i][j] = set;
        }
    }
}

// 显示棋盘(对齐坐标,清晰美观)
void Displayboard(char board[ROWS][COLS], int r, int c)
{
    printf("-----扫雷游戏------\n");
    int i = 0;
    // 打印列号(1~9,和用户坐标对应)
    printf("  "); // 空两格,对齐行号
    for (i = 1; i <= c; i++)
    {
        printf("%d ", i);
    }
    printf("\n");
    // 打印行号+棋盘内容
    for (i = 1; i <= r; i++)
    {
        printf("%d ", i);
        int j = 0;
        for (j = 1; j <= c; j++)
        {
            printf("%c ", board[i][j]);
        }
        printf("\n");
    }
}

// 布置雷(随机10个合法坐标)
void SetMind(char mine[ROWS][COLS], int r, int c)
{
    int count = EASY_COUNT;
    while (count > 0)
    {
        // 生成1~r、1~c的随机坐标
        int x = rand() % r + 1;
        int y = rand() % c + 1;
        // 该位置无雷时才布置
        if (mine[x][y] == '0')
        {
            mine[x][y] = '1';
            count--;
        }
    }
}

// 计算(x,y)周围雷数(static仅当前文件可见)
static int GetMindCount(char mine[ROWS][COLS], int x, int y)
{
    // 8个相邻格子ASCII码和 - 8*'0' = 雷数
    return (mine[x-1][y-1] + mine[x-1][y] + mine[x-1][y+1]
          + mine[x][y-1]       + mine[x][y+1]
          + mine[x+1][y-1] + mine[x+1][y] + mine[x+1][y+1])
          - 8 * '0';
}

// 排查雷(核心交互+胜利/失败判断)
void FindMind(char mine[ROWS][COLS], char show[ROWS][COLS], int r, int c)
{
    int x = 0;
    int y = 0;
    int win = 0; // 已排查非雷数量(目标:9*9-10=71)

    while (win < r*c - EASY_COUNT)
    {
        printf("请输入要排查的坐标(行 列):");
        scanf("%d %d", &x, &y);

        // 1. 判断坐标合法性
        if (x >= 1 && x <= r && y >= 1 && y <= c)
        {
            // 2. 判断是否踩雷
            if (mine[x][y] == '1')
            {
                printf("很遗憾,你被炸死了!\n");
                Displayboard(mine, r, c); // 显示所有雷的位置
                break; // 踩雷退出
            }
            // 3. 判断是否已排查
            else if (show[x][y] == '*')
            {
                // 4. 计算周围雷数并更新显示
                int count = GetMindCount(mine, x, y);
                show[x][y] = count + '0';
                Displayboard(show, r, c);
                win++; // 非雷计数+1
            }
            else
            {
                printf("该坐标已排查,请重新输入!\n");
            }
        }
        else
        {
            printf("坐标非法(需在1~%d行、1~%d列),请重新输入!\n", r, c);
        }
    }

    // 5. 判断胜利
    if (win == r*c - EASY_COUNT)
    {
        printf("恭喜你!排雷成功,游戏胜利!\n");
        Displayboard(mine, r, c);
    }
}
相关推荐
oioihoii2 小时前
C++高并发编程核心技能解析
开发语言·c++
玖剹2 小时前
字符串相关题目
c语言·c++·算法·leetcode
llz_1122 小时前
图(邻接表)-(DFS/BFS)-Dijkstra
算法·深度优先·dijkstra·宽度优先
jimy12 小时前
程序崩溃free(): double free detected in tcache 2
linux·开发语言·数据结构·链表
秋邱2 小时前
Java面向对象进阶:封装、继承、多态的实现逻辑与实战案例
java·开发语言·后端·spring cloud·ar·restful
派葛穆2 小时前
机器人-六轴机械臂的逆运动学
算法·机器学习·机器人
colman wang2 小时前
Java期末
java·开发语言
千里马-horse2 小时前
AsyncContext
开发语言·前端·javascript·c++·napi·asynccontext