扫雷游戏设计分享

前言

本文衔接上文函数与数组内容,以制作扫雷小游戏的形式对知识进行具象化的体现与应用。对于初识函数与数组的读者们,可以先阅读<扫雷游戏设计思路>与<主函数--显示游戏界面>,再按顺序阅读以更好地理解思路。


扫雷游戏设计思路

扫雷游戏的核心在于玩家交互、地图生成、地雷布置、游戏逻辑判断。设计时需要将整个游戏分解为多个模块,每个模块通过函数实现。

地图采用二维数组表示,一个数组用于存储地雷位置,另一个数组用于显示玩家看到的界面。地雷布置使用随机数生成,确保每次游戏地雷位置不同。

初始化游戏地图

定义两个二维数组,一个用于存储地雷分布(mineMap),另一个用于显示玩家操作后的界面(showMap)。初始化时将所有位置设为未探索状态。

c 复制代码
#define ROW 9
#define COL 9
#define MINES 10

char mine[ROW][COL];
char show[ROW][COL];

void Initboard() {
    for (int i = 0; i < ROW; i++) {
        for (int j = 0; j < COL; j++) {
            mine[i][j] = '0';
            show[i][j] = '*';
        }
    }
}

但在排雷的过程中,我们需要知道周围一圈雷的个数,但当排查位置为边界时,上述代码会出现越界问题。 优化如下:

c 复制代码
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define COUNT 10

char mine[ROWS][COLS];
char show[ROWS][COLS];

void initMap() {
    for (int i = 0; i < ROWS; i++) {
        for (int j = 0; j < COLS; j++) {
            mine[i][j] = '0';
            show[i][j] = '*';
        }
    }
}

随机布置地雷

使用随机数生成地雷位置,确保生成的位置不重复且数量符合设定。地雷在mineMap中用'1'表示。

c 复制代码
#include <stdlib.h>
#include <time.h>

void SetMine() {
    srand((unsigned int)time(NULL));
    int count = 0;
    while (count < MINE) {
        int x = rand() % ROW+1;
        int y = rand() % COL+1;
        if (mine[x][y] == '0') {
            mine[x][y] = '1';
            count++;
        }
    }
}

计算周围地雷数量

遍历每个非地雷格子,统计周围8个格子中的地雷数量,并将结果存入mineMap中。使用递归实现空白区域的自动展开。

c 复制代码
int Getminecount(int x, int y) {
    int count = 0;
    for (int i = -1; i <= 1; i++) {
        for (int j = -1; j <= 1; j++) {
            int nx = x + i;
            int ny = y + j;
            if (nx >= 0 && nx < ROWS && ny >= 0 && ny < COLS && mine[nx][ny] == '1') {
                count++;
            }
        }
    }
    return count;
}

void Expand(char mine[][100], char show[][100], int x, int y, int r, int c)
{
    int i, j;
    if (x < 1 || x > r || y < 1 || y > c)
        return;
    if (show[x][y] != '*')
        return;
    if (mine[x][y] == '1')
        return;
    
    int count = Getminecount(mine, x, y);
    show[x][y] = count + '0';
    
    if (count == 0) {
        for (i = x - 1; i <= x + 1; i++) {
            for (j = y - 1; j <= y + 1; j++) {
                if (i >= 1 && i <= r && j >= 1 && j <= c && show[i][j] == '*')
                    Expand(mine, show, i, j, r, c);
            }
        }
    }
}

玩家输入与游戏逻辑

玩家通过输入坐标来探索格子,如果踩到地雷游戏结束,否则显示周围地雷数量。排完则游戏结束。

c 复制代码
void Findmine(char mine[][100], char show[][100], int r, int c, int count)
{
    int x, y;
    int op;
    int win = 0;
    int ROWS = r + 2;
    int COLS = c + 2;
    
    while (win < r * c - count) {
        printf("请选择操作: 1.排查 2.标记/取消标记\n");
        if (scanf_s("%d", &op) != 1 || (op != 1 && op != 2)) {
            printf("输入错误,请输入1或2!\n");
            while (getchar() != '\n');
            continue;
        }
        
        if (op == 2) {
            printf("请输入要标记的坐标(1-%d, 1-%d):", r, c);
            if (scanf_s("%d%d", &x, &y) != 2 || x < 1 || x > r || y < 1 || y > c) {
                printf("坐标非法,请输入1到%d之间的数字!\n", r);
                while (getchar() != '\n');
                continue;
            }
            Flagmine(show, x, y);
        } else {
            printf("请输入排查坐标(1-%d, 1-%d):", r, c);
            if (scanf_s("%d%d", &x, &y) != 2 || x < 1 || x > r || y < 1 || y > c) {
                printf("坐标非法,请输入1到%d之间的数字!\n", r);
                while (getchar() != '\n');
                continue;
            }
            
            if (show[x][y] == '!' || show[x][y] == '*') {
                if (mine[x][y] == '1') {
                    printf("很遗憾,你被炸死了!\n");
                    int i, j;
                    for (i = 1; i <= r; i++) {
                        for (j = 1; j <= c; j++) {
                            if (mine[i][j] == '1')
                                show[i][j] = '@';
                        }
                    }
                    Displayboard(show, ROWS, COLS, 1, 1);
                    return;
                } else {
                    Expand(mine, show, x, y, r, c);
                    win = 0;
                    int i, j;
                    for (i = 1; i <= r; i++) {
                        for (j = 1; j <= c; j++) {
                            if (show[i][j] >= '0' && show[i][j] <= '8')
                                win++;
                        }
                    }
                    Displayboard(show, ROWS, COLS, 1, 1);
                }
            } else {
                printf("该位置已被排查,请重新输入坐标!\n");
            }
        }
    }
    
    if (win >= r * c - count) {
        printf("恭喜你!你不怕危险,敢于排除万难取得胜利!\n");
        int i, j;
        for (i = 1; i <= r; i++) {
            for (j = 1; j <= c; j++) {
                if (mine[i][j] == '1')
                    show[i][j] = '!';
            }
        }
        Displayboard(show, ROWS, COLS, 1, 1);
    }
}

显示游戏界面

打印当前游戏界面,展示玩家已探索的区域和未探索的区域。

c 复制代码
void Displayboard(char board[][100], int r, int c, int startRow, int startCol)
{
    int i, j;
    printf("    ");
    for (j = startCol; j < startCol + c - 2; j++)
        printf("%2d ", j);
    printf("\n    ");
    for (j = startCol; j < startCol + c - 2; j++)
        printf("---");
    printf("\n");
    for (i = startRow; i < startRow + r - 2; i++) {
        printf("%2d |", i);
        for (j = startCol; j < startCol + c - 2; j++) {
            printf(" %c ", board[i][j]);
        }
        printf("|\n");
    }
}

主函数整合

将上述函数整合到主函数中,实现完整的游戏流程。

c 复制代码
int main() {
    initMap();
    placeMines();
    calculateNumbers();
    int gameOver = 0;
    while (!gameOver) {
        printMap();
        gameOver = !playGame();
    }
    return 0;
}

代码优化与扩展

可以增加游戏难度选择、计时功能、排行榜等功能。通过函数封装和模块化设计,使代码更易于维护和扩展。

c 复制代码
typedef struct {
    int row;    // 行数
    int col;    // 列数
    int count;  // 雷数
} Difficulty;
int getDiffChoice() {
    int choice;
    if (scanf_s("%d", &choice) != 1 || choice < 1 || choice > 3) {
    printf("输入错误,请输入1、2或3!\n");
    while (getchar() != '\n');
} else {
    switch (choice) {
       case 1:
           diff.row = 9;
           diff.col = 9;
           diff.count = 10;
           break;
       case 2:
           diff.row = 16;
           diff.col = 16;
           diff.count = 40;
           break;
       case 3:
           diff.row = 30;
           diff.col = 16;
           diff.count = 99;
           break;
       }
    }

    int ROW = diff.row;
    int COL = diff.col;
    int ROWS = ROW + 2;
    int COLS = COL + 2;
    int COUNT = diff.count;

    char (*mine)[100] = (char(*)[100])malloc(sizeof(char) * 100 * ROWS);
    char (*show)[100] = (char(*)[100])malloc(sizeof(char) * 100 * ROWS);
}

通过以上步骤,一个简单的扫雷游戏就实现了。后续可以通过添加图形界面或更多功能来提升游戏体验。 在添加了计时,标记功能和优化后,最终代码如下 game.h

c 复制代码
#pragma once

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

/* 
 * 难度结构体 (新增功能4: 难度选择)
 * 包含三个难度级别的参数
 */
typedef struct {
    int row;    // 行数
    int col;    // 列数
    int count;  // 雷数
} Difficulty;

/* 游戏主函数 */
void game();

/* 
 * 函数声明: 初始化棋盘
 * 参数: board-二维数组, r-行数, c-列数, set-初始化值
 */
void Initboard(char board[][100], int r, int c, char set);

/* 
 * 函数声明: 显示棋盘
 * 参数: board-二维数组, r-行数, c-列数, startRow-起始行, startCol-起始列
 */
void Displayboard(char board[][100], int r, int c, int startRow, int startCol);

/* 
 * 函数声明: 布置地雷
 * 参数: board-雷阵数组, r-行数, c-列数, count-雷数
 */
void Setmine(char board[][100], int r, int c, int count);

/* 
 * 函数声明: 计算周围雷数
 * 参数: mine-雷阵数组, x-行坐标, y-列坐标
 * 返回值: 周围8个格子的雷数
 */
int Getminecount(char mine[][100], int x, int y);

/* 
 * 函数声明: 查找地雷(核心玩法)
 * 参数: mine-雷阵, show-显示阵, r-行数, c-列数, count-雷数
 */
void Findmine(char mine[][100], char show[][100], int r, int c, int count);

/* 
 * 函数声明: 展开周围(新增功能1: 递归展开无雷区域)
 * 参数: mine-雷阵, show-显示阵, x-行坐标, y-列坐标, r-行数, c-列数
 */
void Expand(char mine[][100], char show[][100], int x, int y, int r, int c);

/* 
 * 函数声明: 标记雷(新增功能2: 标记/取消标记)
 * 参数: show-显示阵, x-行坐标, y-列坐标
 */
void Flagmine(char show[][100], int x, int y);

/* 
 * 函数声明: 获取难度选择(新增功能4: 难度选择)
 * 返回值: 用户选择的难度编号(1-3)
 */
int getDiffChoice();

test.c

c 复制代码
#include"game.h"

/* 
 * 函数名: main
 * 功能: 程序入口,显示游戏菜单并处理用户选择
 * 实现: 
 *   1. 显示开始菜单(1-开始游戏, 0-退出)
 *   2. 用户选择1则调用game()开始游戏
 *   3. 用户选择0则退出程序
 *   4. 选择其他选项提示错误并重新显示菜单
 */
int main()
{
    int input;
    while (1) {
        printf("--------------------------------\n");
        printf("-----------  1.play  -----------\n");
        printf("-----------  0.exit  -----------\n");
        printf("--------------------------------\n");
        printf("请选择(0或1):");
        if (scanf_s("%d", &input) != 1 || (input != 0 && input != 1)) {
            printf("输入错误,请输入0或1!\n");
            while (getchar() != '\n');
            continue;
        }
        
        if (input == 1) {
            printf("开始游戏吧!祝你好运!\n");
            game();
        } else if (input == 0) {
            printf("游戏已退出,感谢游玩!\n");
            break;
        }
    }
    
    return 0;
}

game.c

c 复制代码
#include"game.h"

/* 
 * 函数名: getDiffChoice
 * 功能: 获取用户选择的游戏难度
 * 返回值: 用户选择的难度编号 (1-简单, 2-中等, 3-困难)
 */
int getDiffChoice() {
    int choice;
    while (1) {
        printf("--------------------------------\n");
        printf("----------- 难度选择  -----------\n");
        printf("----------- 1.简单 9*9 -----------\n");
        printf("----------- 2.中等 16*16 --------\n");
        printf("----------- 3.困难 30*16 --------\n");
        printf("--------------------------------\n");
        printf("请选择(1-3):");
        if (scanf_s("%d", &choice) != 1 || choice < 1 || choice > 3) {
            printf("输入错误,请输入1、2或3!\n");
            while (getchar() != '\n');
        } else {
            return choice;
        }
    }
}

/* 
 * 函数名: game
 * 功能: 游戏主函数,实现扫雷游戏的核心逻辑
 *       1. 根据选择的难度初始化游戏参数
 *       2. 分配内存创建雷阵和显示阵
 *       3. 布置地雷并开始游戏
 *       4. 计时并统计游戏用时
 */
void game()
{
    Difficulty diff;
    int choice = getDiffChoice();
    
    switch (choice) {
    case 1:
        diff.row = 9;
        diff.col = 9;
        diff.count = 10;
        break;
    case 2:
        diff.row = 16;
        diff.col = 16;
        diff.count = 40;
        break;
    case 3:
        diff.row = 30;
        diff.col = 16;
        diff.count = 99;
        break;
    }

    int ROW = diff.row;
    int COL = diff.col;
    int ROWS = ROW + 2;
    int COLS = COL + 2;
    int COUNT = diff.count;

    char (*mine)[100] = (char(*)[100])malloc(sizeof(char) * 100 * ROWS);
    char (*show)[100] = (char(*)[100])malloc(sizeof(char) * 100 * ROWS);
    
    srand((unsigned int)time(NULL));
    
    Initboard(mine, ROWS, COLS, '0');
    Initboard(show, ROWS, COLS, '*');
    Setmine(mine, ROWS, COLS, COUNT);
    
    printf("开始游戏吧!\n");
    Displayboard(show, ROWS, COLS, 1, 1);
    
    clock_t start = clock();
    Findmine(mine, show, ROW, COL, COUNT);
    clock_t end = clock();
    double time_used = ((double)(end - start)) / CLOCKS_PER_SEC;
    
    printf("用时: %.2f 秒\n", time_used);
    
    free(mine);
    free(show);
}

/* 
 * 函数名: Initboard
 * 功能: 初始化二维数组,将所有元素设置为指定值
 * 参数: 
 *   board - 要初始化的二维数组
 *   r     - 数组行数
 *   c     - 数组列数
 *   set   - 要设置的值('0'初始化雷阵, '*'初始化显示阵)
 */
void Initboard(char board[][100], int r, int c, char set)
{
    int i, j;
    for (i = 0; i < r; i++)
        for (j = 0; j < c; j++)
            board[i][j] = set;
}

/* 
 * 函数名: Displayboard
 * 功能: 打印游戏棋盘界面,带行列坐标显示
 * 参数:
 *   board     - 要显示的二维数组
 *   r         - 数组行数
 *   c         - 数组列数
 *   startRow  - 显示起始行(用于排除边框)
 *   startCol  - 显示起始列(用于排除边框)
 */
void Displayboard(char board[][100], int r, int c, int startRow, int startCol)
{
    int i, j;
    printf("    ");
    for (j = startCol; j < startCol + c - 2; j++)
        printf("%2d ", j);
    printf("\n    ");
    for (j = startCol; j < startCol + c - 2; j++)
        printf("---");
    printf("\n");
    for (i = startRow; i < startRow + r - 2; i++) {
        printf("%2d |", i);
        for (j = startCol; j < startCol + c - 2; j++) {
            printf(" %c ", board[i][j]);
        }
        printf("|\n");
    }
}

/* 
 * 函数名: Setmine
 * 功能: 随机布置地雷
 * 参数:
 *   board - 雷阵数组
 *   r     - 数组行数
 *   c     - 数组列数
 *   count - 要布置的雷数
 */
void Setmine(char board[][100], int r, int c, int count)
{
    int x, y;
    int ROW = r - 2;
    int COL = c - 2;
    while (count) {
        x = rand() % ROW + 1;
        y = rand() % COL + 1;
        if (board[x][y] == '0') {
            board[x][y] = '1';
            count--;
        }
    }
}

/* 
 * 函数名: Getminecount
 * 功能: 计算指定坐标周围8个格子中的雷数
 * 参数:
 *   mine - 雷阵数组
 *   x    - 要查询的行坐标
 *   y    - 要查询的列坐标
 * 返回值: 周围8个格子中雷的数量(0-8)
 */
int Getminecount(char mine[][100], int x, int y)
{
    int count = 0;
    int i, j;
    for (i = x - 1; i <= x + 1; i++) {
        for (j = y - 1; j <= y + 1; j++) {
            if (mine[i][j] == '1')
                count++;
        }
    }
    return count;
}

/* 
 * 函数名: Expand (新增功能1: 展开周围一片)
 * 功能: 递归展开无雷区域,当点击位置周围没有雷时自动展开周围所有格子
 * 参数:
 *   mine - 雷阵数组
 *   show - 显示数组
 *   x    - 当前排查的行坐标
 *   y    - 当前排查的列坐标
 *   r    - 棋盘行数
 *   c    - 棋盘列数
 */
void Expand(char mine[][100], char show[][100], int x, int y, int r, int c)
{
    int i, j;
    if (x < 1 || x > r || y < 1 || y > c)
        return;
    if (show[x][y] != '*')
        return;
    if (mine[x][y] == '1')
        return;
    
    int count = Getminecount(mine, x, y);
    show[x][y] = count + '0';
    
    if (count == 0) {
        for (i = x - 1; i <= x + 1; i++) {
            for (j = y - 1; j <= y + 1; j++) {
                if (i >= 1 && i <= r && j >= 1 && j <= c && show[i][j] == '*')
                    Expand(mine, show, i, j, r, c);
            }
        }
    }
}

/* 
 * 函数名: Flagmine (新增功能2: 标记雷)
 * 功能: 对指定位置进行标记/取消标记,表示怀疑有雷
 * 参数:
 *   show - 显示数组
 *   x    - 要标记的行坐标
 *   y    - 要标记的列坐标
 */
void Flagmine(char show[][100], int x, int y)
{
    if (show[x][y] == '*')
        show[x][y] = '!';
    else if (show[x][y] == '!')
        show[x][y] = '*';
}

/* 
 * 函数名: Findmine
 * 功能: 扫雷游戏的核心玩法,处理用户的排查和标记操作
 * 参数:
 *   mine  - 雷阵数组(包含雷的位置信息)
 *   show  - 显示数组(用户看到的界面)
 *   r     - 棋盘行数
 *   c     - 棋盘列数
 *   count - 雷的总数
 */
void Findmine(char mine[][100], char show[][100], int r, int c, int count)
{
    int x, y;
    int op;
    int win = 0;
    int ROWS = r + 2;
    int COLS = c + 2;
    
    while (win < r * c - count) {
        printf("请选择操作: 1.排查 2.标记/取消标记\n");
        if (scanf_s("%d", &op) != 1 || (op != 1 && op != 2)) {
            printf("输入错误,请输入1或2!\n");
            while (getchar() != '\n');
            continue;
        }
        
        if (op == 2) {
            printf("请输入要标记的坐标(1-%d, 1-%d):", r, c);
            if (scanf_s("%d%d", &x, &y) != 2 || x < 1 || x > r || y < 1 || y > c) {
                printf("坐标非法,请输入1到%d之间的数字!\n", r);
                while (getchar() != '\n');
                continue;
            }
            Flagmine(show, x, y);
        } else {
            printf("请输入排查坐标(1-%d, 1-%d):", r, c);
            if (scanf_s("%d%d", &x, &y) != 2 || x < 1 || x > r || y < 1 || y > c) {
                printf("坐标非法,请输入1到%d之间的数字!\n", r);
                while (getchar() != '\n');
                continue;
            }
            
            if (show[x][y] == '!' || show[x][y] == '*') {
                if (mine[x][y] == '1') {
                    printf("很遗憾,你被炸死了!\n");
                    int i, j;
                    for (i = 1; i <= r; i++) {
                        for (j = 1; j <= c; j++) {
                            if (mine[i][j] == '1')
                                show[i][j] = '@';
                        }
                    }
                    Displayboard(show, ROWS, COLS, 1, 1);
                    return;
                } else {
                    Expand(mine, show, x, y, r, c);
                    win = 0;
                    int i, j;
                    for (i = 1; i <= r; i++) {
                        for (j = 1; j <= c; j++) {
                            if (show[i][j] >= '0' && show[i][j] <= '8')
                                win++;
                        }
                    }
                    Displayboard(show, ROWS, COLS, 1, 1);
                }
            } else {
                printf("该位置已被排查,请重新输入坐标!\n");
            }
        }
    }
    
    if (win >= r * c - count) {
        printf("恭喜你!你不怕危险,敢于排除万难取得胜利!\n");
        int i, j;
        for (i = 1; i <= r; i++) {
            for (j = 1; j <= c; j++) {
                if (mine[i][j] == '1')
                    show[i][j] = '!';
            }
        }
        Displayboard(show, ROWS, COLS, 1, 1);
    }
}

此代码只能提供一个思路,并非最优解。

相关推荐
没文化的阿浩4 小时前
【Linux系统】进程状态详解
android·linux·c++·c
2601_9623004714 小时前
halconpython联合开发网_C#与Halcon联合开发实例,并做了可移动的ROI库
图像处理·c·halcon·软件开发·roi
2601_9620973616 小时前
1. 使用 C 或 C++ 扩展 Python
python·api·c·引用计数·扩展模块
handler012 天前
【Linux】ELF、链接与动态库,程序装载全解析
linux·运维·服务器·chrome·c·elf·文件
C++ 老炮儿的技术栈3 天前
MFC CPtrArray的用法
开发语言·数据结构·c++·算法·mfc·c
handler013 天前
【Linux】信号:内核的“敲门声”
linux·运维·服务器·c++·c·信号·signal
handler013 天前
【Linux】动态库、静态库使用与原理
linux·运维·服务器·c++·c·动态库·静态库
请输入蚊子3 天前
AFL源码分析(一)
c·pwn·afl·fuzz
Lynko4 天前
C语言字符函数和字符串函数的使用与实现
c