【C语言】扫雷游戏详解

🥰扫雷游戏的功能说明

  • 使用控制台来模拟扫雷游戏
  • 控制台中棋盘的大小为 9*9(后续也可以扩大)
  • 默认棋盘中有10个雷
  • 如果排雷位置不是雷,就显示周围有几个雷
  • 如果排雷位置是雷,就被炸死结束游戏
  • 把除了10个雷以外的位置排查完,则排雷成功,游戏结束

🥰扫雷游戏的功能分析

  • 游戏的菜单设计
  • 游戏可能不止玩一次,是否进行游戏,这里我们使用switch通过用户的输入来判定是否继续
  • 在扫雷的过程中,布置好的雷的棋盘和打印在控制台上排雷的棋盘都需要存储,所以这里我们使用两个数组分别进行存储
  • 棋盘是 9*9 的大小,我们用二维数组来实现
  • 考虑到排查的雷如果在99的边缘的某一个,在计算周围雷的数量就会发生越界,所以实际上的棋盘应该是 1111,显示在控制台的大小是 9*9
  • 在棋盘中,没有雷的位置存放0,有雷的位置存放1,雷的生成通过randsrand来实现
  • 通过字符的形式表示1和0,为了表示神秘感,打印在控制台排雷时,棋盘所有的位置都是*,如果是用整形,有可能会有冲突,为了避免这些情况,我们统一使用字符来表示

🥰实现扫雷游戏必备的知识

  • 头文件的作用
  • 判断
  • 循环
  • 数组
  • 函数

🥰游戏的框架

为了养成良好的代码习惯,我们将使用三个文件分别来实现扫雷游戏

test.c:所有的在进入游戏逻辑之前的代码

game.c: 所有游戏的功能

game.h:所有函数的声明,包含的头文件

🥰游戏的实现

❤️游戏的启动

  • 我们使用 input 这个整形变量来判断游戏是否进行
  • 使用 do...while 循环来包含整个游戏的启动逻辑,需要保证就算游戏不进行第二次,也要有至少一次启动
  • do...while 条件通过 input 来判断,用户输入 1 就表示继续游戏,输入 0 表示退出游戏,当 input 为 0 时,条件值为假,do...while 就不会执行函数体了
  • 使用 srand 来生成 rand 函数的种子,srand 中使用 time 函数来计算当下的时间戳,保证每次生成的种子都不一样,srand 函数中参数要求 unsigned int 类型
  • 游戏启动打印菜单,菜单等后续继续介绍
  • 打印完菜单后,让用户输入 input 的值
  • 使用 switch 语句来判断游戏是退出还是继续
  • 这里使用 game() 函数,所有函数的功能都将放在这个函数中实现
c 复制代码
test.c
int main() {
	int input = 0;
	srand((unsigned int)time(NULL));
	do{
		menu();
		printf("请输入:> ");
		scanf("%d", &input);
		switch (input) {
            case 0:
                printf("退出游戏\n");
                break;
            case 1:
                game();
                break;
            default:
                printf("选择错误,请重新输入\n");
                break;
        }
	}while(input);
	return 0;
}

❤️菜单的设计

  • 菜单使用 menu() 函数来实现
c 复制代码
test.c
void menu() {
    printf("------------------------\n");
    printf("-------- 1. play -------\n");
    printf("-------- 0. exit--------\n");
    printf("------------------------\n");
}

❤️游戏逻辑的设计

  • 在整个游戏逻辑的设计中,调用各个游戏的功能来实现游戏
  • 在 game.h 头文件中包含对应的常量和库文件
  • 初始化棋盘:将棋盘初始化为 11*11 的大小,所以使用 ROWSCOLS,将数组还有初始化成的字符传入函数
  • 打印棋盘:初始化好后,将棋盘打印,因为只需要打印 9*9 大小的棋盘,所以是 ROWCOL
  • 布置雷:传入的参数与上同理,具体实现看下
  • 排查雷:传入的参数与上同理,具体实现看下
c 复制代码
game.h
#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
c 复制代码
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);

    // 布置雷
    SetMine(mine,ROW,COL);
    //DisplayBoard(mine,ROW,COL);

    // 排查雷
    FindMine(mine,show,ROW,COL);
}

❤️初始化棋盘

  • 使用嵌套循环来控制数组,分别使用 row、col 来控制循环,set 控制存放的字符
c 复制代码
void InitBoard(char board[ROWS][COLS],int row,int col,char set) {
    for (int i = 0; i < row; i++) {
        for (int j = 0; j < col; j++) {
            board[i][j] = set;
        }
    }
}

❤️打印棋盘

  • rowcol 参数传进函数,使用循环来打印棋盘
  • 并且在打印棋盘的基础上,让棋盘有序号来标识
  • 第一次循环,先使用循环,打印 0~9,来表示每列的序号
  • 第二次循环,每行打印棋盘前,先打印行号,因为第一次循环,已经打印了 0,并且,打印棋盘从 1 开始打印,如果从 0 开始打印,打印的是棋盘的边界,所以变量 ij 都从 1 开始打印
c 复制代码
void DisplayBoard(char board[ROWS][COLS],int row,int col) {
    printf("------扫雷游戏------\n");
    for (int i = 0 ; i <= row; i++) {
        printf("%d ",i);
    }
    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");
    }
}

❤️排查雷

  • 定义变量 xy,让 rand 函数随机生辰 1~9 的随机数
  • 再使用 while 循环以 count 来作为直接条件
  • 判断数组中,如果是 0,也就是没有雷,就变成 1,表示布置雷,每变一次,count 值就少一个,就像雷,放一个就少一个,当雷放置完了,count 等于 0,while 循环体就不执行了
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--;
        }
    }
}

❤️排查雷

  • 依旧定义变量 xy,来表示棋盘坐标
  • 定义变量 win,来表示排雷的次数,循环的条件,当 win 的值等于棋盘上所有格子的个数减去雷的个数,就是没有雷的个数,当 win 大于这个值,就表示所有坐标位置都排查完了,排雷成功
  • 首先先判断用户输入的坐标是不是合法的,如果不合法,直接结束循环,重新开始
  • 如果坐标合法,开始判断输入的坐标位置是不是雷,如果是雷就炸死,游戏结束,重新开始
  • 如果不是雷,就显示当前坐标周围的雷的个数,排雷次数 win+1 ,具体实现看下
c 复制代码
void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS],int row,int col) {
    // 初始化坐标
    int x = 0 ;
    int y = 0 ;
    int win = 0; // 排雷的过程,成功排一次 win+1
    while (win < row * col - EASY_COUNT) {   // 排雷的次数应该小于棋盘的总数-雷的个数
        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);
    }
}

❤️排查坐标周围的雷的个数

  • 计算周围雷的个数我们单独用一个函数来计算
  • 当输入合法坐标,并且不是雷时,判断周围坐标中累的个数
  • x 是横坐标,y 是纵坐标
  • 图中是坐标周围的坐标,基于图片的这些坐标,来判断雷的个数
  • 因为我们存储的都是字符,1 的 ascll 是 490 的 ascll 是 48
  • 假设周围有 2 个雷,周围的情况就是 6个02个16*48+2*49-8*48=386-284=2
c 复制代码
int GetMineCount(char mine[ROWS][COLS],int x,int y) {
    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');
}

到这,游戏的功能全都实现了,扩展功能后续再分享

🥰游戏的代码

test.c

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

    // 布置雷
    SetMine(mine,ROW,COL);
    //DisplayBoard(mine,ROW,COL);

    // 排查雷
    FindMine(mine,show,ROW,COL);
}

int main() {
    int input = 0;
    srand((unsigned int)time(NULL));
    do {
        menu();
        printf("请输入:> ");
        scanf("%d", &input);
        switch (input) {
            case 0:
                printf("退出游戏\n");
                break;
            case 1:
                game();
                break;
            default:
                printf("选择错误,请重新输入\n");
                break;
        }
    }while (input);

    return 0;
}

game.h

c 复制代码
#ifndef GAME_H
#define GAME_H

#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 row,int col,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) ;

#endif //GAME_H

game.c

c 复制代码
#include "game.h"
// 初始化棋盘
void InitBoard(char board[ROWS][COLS],int row,int col,char set) {
    for (int i = 0; i < row; i++) {
        for (int j = 0; j < col; j++) {
            board[i][j] = set;
        }
    }
}

// 打印棋盘
void DisplayBoard(char board[ROWS][COLS],int row,int col) {
    printf("------扫雷游戏------\n");
    for (int i = 0 ; i <= row; i++) {
        printf("%d ",i);
    }
    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");
    }
}

// 布置雷
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--;
        }
    }
}

// 排查坐标周围的雷
int GetMineCount(char mine[ROWS][COLS],int x,int y) {
    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 FindMine(char mine[ROWS][COLS],char show[ROWS][COLS],int row,int col) {
    // 初始化坐标
    int x = 0 ;
    int y = 0 ;
    int win = 0; // 排雷的过程,成功排一次 win+1
    while (win < row * col - EASY_COUNT) {   // 排雷的次数应该小于棋盘的总数-雷的个数
        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);
    }
}
相关推荐
丛雨要玩游戏5 小时前
字符函数和字符串函数
c语言·开发语言·算法
ad钙奶长高高5 小时前
【C语言】初始C语言
c语言·开发语言·算法
侯小啾5 小时前
【17】C语言-gets() 与 fgets() 函数
c语言·开发语言
是苏浙6 小时前
零基础入门C语言之枚举和联合体
c语言·开发语言
ACP广源盛139246256738 小时前
(ACP广源盛)GSV2231---DisplayPort 1.4 MST 到 HDMI 2.0/DP/Type-C 转换器(带嵌入式 MCU)
c语言·开发语言·单片机·嵌入式硬件·音视频·mst
QT 小鲜肉8 小时前
【QT/C++】Qt网络编程进阶:UDP通信和HTTP请求的基本原理和实际应用(超详细)
c语言·网络·c++·笔记·qt·http·udp
Yurko139 小时前
【C语言】选择结构和循环结构的进阶
c语言·开发语言·学习
范纹杉想快点毕业9 小时前
12个月嵌入式进阶计划ZYNQ 系列芯片嵌入式与硬件系统知识学习全计划(基于国内视频资源)
c语言·arm开发·单片机·嵌入式硬件·学习·fpga开发·音视频
木木木丫10 小时前
嵌入式项目:韦东山驱动开发第六篇 项目总结——显示系统(framebuffer编程)
c语言·c++·驱动开发·dsp开发