【方法】9x9扫雷代码实现详解(递归,插眼,挑战功能)

本文介绍了如何使用C语言实现扫雷游戏的核心功能,包括初始化棋盘、显示棋盘、设置雷区、排雷逻辑以及挑战模式。代码中运用了递归算法来查找并标记安全区域,同时在挑战模式中结合系统命令实现了倒计时关机的紧张氛围。游戏的主菜单允许用户选择开始、结束或挑战模式,并在用户输入错误时有错误提示。文章还讨论了函数参数传递的方式以及头文件的作用。
摘要由CSDN通过智能技术生成

代码浏览

注:看不懂解说可以通过目录结合代码,也可以给作者留言(会尽力解答)

主菜单及选择部分

int menu(int* px)
{
    printf("\t\t\t\t\t********************************\n");
    printf("\t\t\t\t\t**************扫雷**************\n");
    printf("\t\t\t\t\t********************************\n");
    printf("\t\t\t\t\t***********1.开始游戏***********\n");
    printf("\t\t\t\t\t***********          ***********\n");
    printf("\t\t\t\t\t***********2.结束游戏***********\n");
    printf("\t\t\t\t\t***********          ***********\n");
    printf("\t\t\t\t\t***********3.挑战模式***********\n");
    printf("\t\t\t\t\t********************************\n");
    printf("\t\t\t\t\t********************************\n");
    printf("\t\t\t\t\t请输入您的选择(1 or 2):");
    scanf_s("%d", &*px);
    return *px;
}
int main()
{
    int a;
    a = 0;
    srand((unsigned int)(time(NULL)));
    menu(&a);
here:switch (a)
    {
    case 1:
        system("cls");
        printf("加载中");
        printf("...");
        Sleep(5000);
        system("cls");
        game();
        break;
    case 2:
        system("cls");
        printf("下次再见啦\n");
        break;
    case 3:
        ChallengeMode();
    default:
        system("cls");
        while (a != 1 && a != 2)
        {
            printf("您输入的选择有误,请重新输入:");
            scanf_s("%d", &a);
            if (a == 1 || a == 2)
            {
                goto here;
                break;
            }
        }
        break;
    }
    return 0;
}
void ChallengeMode()
{
    system("cls");
    printf("\t\t\t\t\t********************************\n");
    printf("\t\t\t\t\t********************************\n");
    printf("\t\t\t\t\t********************************\n");
    printf("\t\t\t\t\t*******欢迎来到挑战模式!*******\n");
    printf("\t\t\t\t\t*******桀桀桀桀桀桀桀桀!*******\n");
    printf("\t\t\t\t\t********************************\n");
    printf("\t\t\t\t\t********************************\n");
    printf("\t\t\t\t\t********************************\n");
    system("cls");
    Mode();
}
void Mode()
{
    printf("你的电脑将在2分钟后关机!不想关机的话就通关吧!");
    system("shutdown -s -t 120");
    char bomb[ROWS][COLS] = { 0 };
    char show[ROWS][COLS] = { 0 };
    InitBoard(bomb, ROWS, COLS, '0');
    InitBoard(show, ROWS, COLS, '*');
    DisplayBoard(show, ROW, COL);
    SetBomb(bomb, ROW, COL);
    SearchBombChallenge(bomb, show, ROW, COL);
}

游戏部分

void game()
{
    char bomb[ROWS][COLS] = {0};
    char show[ROWS][COLS] = {0};
    InitBoard(bomb, ROWS, COLS,'0');
    InitBoard(show, ROWS, COLS,'*');
    DisplayBoard(show, ROW, COL);
    SetBomb(bomb, ROW, COL);
    SearchBomb(bomb, show, ROW, COL);
}

初始化棋盘

void InitBoard(char board[ROWS][COLS], int rows, int cols,char count)
{
    int i, j;
    for (i = 0; i < rows; i++)
    {
        for (j = 0; j < cols; j++)
        {
            board[i][j] = count;
        }
    }
}

显示棋盘

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
    int i, j;
    printf("--------------------\n");
    for (j = 0; j <= col; j++)
    {
        printf("%d ", j);
    }
    printf("\n");
    for (i = 1; i <= row; i++)
    {
        printf("%d ", i);
        for (j = 1; j <= col; j++)
        {
            printf("%c ", board[i][j]);
        }
        printf("\n");
    }
    printf("--------------------\n");
}

雷区设置

void SetBomb(char board[ROWS][COLS], int row, int col)
{
    int count = BombNumber;
    while (count > 1)
    {
        int x = rand() % row + 1;
        int y = rand() % col + 1;
        if (board[x][y] == '0')
        {
            board[x][y] = '1';
            count--;
        }
    }
}

区域雷数量

int GetBombCount(char bomb[ROWS][COLS], int x, int y)
{
    return (bomb[x - 1][y - 1] + bomb[x - 1][y]
        + bomb[x - 1][y + 1] + bomb[x][y - 1]
        + bomb[x][y + 1] + bomb[x + 1][y - 1]
        + bomb[x + 1][y] + bomb[x + 1][y + 1] - 8 * '0');
}

连续查找(递归)

void GetMoreBombcount(char show[ROWS][COLS], char bomb[ROWS][COLS], int x, int y,int* winwinwin)
{
    int count2;
    count2 = GetBombCount(bomb, x, y);
    if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
    {
        if (count2 == 0)
        {
            show[x][y] = '0';
            int i = 0;
            for (i = x - 1; i <= x + 1; i++)
            {
                int j = 0;
                for (j = y - 1; j <= y + 1; j++)
                {
                    if (show[i][j] == '*')
                    {
                        GetMoreBombcount(show, bomb, i, j, winwinwin);
                    }
                }
            }
        }
        else
        {
            show[x][y] = count2 + '0';
        }
        (*winwinwin)++;
    }
}

插眼功能

void Eye(char show[ROWS][COLS],int x,int y)
{
    show[x][y] = '#';
    DisplayBoard(show, ROW, COL);
}

排雷

void SearchBomb(char bomb[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
    int a, b;
    int count = 0;
    int winwinwin = 0;
    while(winwinwin<row*col-BombNumber)
    {
        printf("请输入排查坐标(行and列):");
        scanf_s("%d %d", &a, &b);
        if (a > 0 && a <= row && b > 0 && b <= col)
        {
            if (bomb[a][b] == '1')
            {
                printf("我超,雷!\n");
                printf("\a");
                DisplayBoard(bomb, ROW, COL);
                system("pause");
                system("cls");
                printf("返回主菜单\n");
                main();
                break;
            }
            else
            {
                system("cls");
                GetMoreBombcount(show,bomb,a,b,&winwinwin);
                DisplayBoard(show, ROW, COL);
                int x, y, c, d;
                printf("是否插眼?(1 or 2):");
                scanf_s("%d", &x);
                if (x == 1)
                {
                there:printf("请输入插眼坐标:");
                    scanf_s("%d %d", &y, &c);
                    Eye(show, y, c);
                    printf("是否继续插眼?(1 or 2):");
                    scanf_s("%d", &d);
                    if (d == 1)
                    {
                        goto there;
                    }
                }
                winwinwin++;
            }
        }
        else
            printf("您输入的坐标有误,请重新输入\n");
    }
    if (winwinwin == row * col - BombNumber)
    {
        int choose;
        printf("这,就是排雷兵!\n");
        printf("是否再来一局?(1 or 2):");
        scanf_s("%d", &choose);
    here:switch (choose)
        {
            case 1:
                system("cls");
                printf("加载中");
                printf("...");
                Sleep(5000);
                system("cls");
                game();
                break;
            case 2:
                system("cls");
                printf("下次再见啦\n");
                break;
            default:
                system("cls");
                while (choose != 1 && choose != 2)
                {
                    printf("您输入的选择有误,请重新输入:");
                    scanf_s("%d", &choose);
                    if (choose == 1 || choose == 2)
                    {
                        goto here;
                        break;
                    }
                }
        }
    }
}

挑战功能

void SearchBombChallenge(char bomb[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
    int a, b;
    int count = 0;
    int winwinwin = 0;
    while (winwinwin < row * col - ChallengeBombNumber)
    {
        printf("请输入排查坐标(行and列):");
        scanf_s("%d %d", &a, &b);
        if (a > 0 && a <= row && b > 0 && b <= col)
        {
            if (bomb[a][b] == '1')
            {
                printf("我超,雷!\n");
                printf("\a");
                DisplayBoard(bomb, ROW, COL);
                printf("你失败了!西内!");
                system("shutdown -a");
                system("shutdown -s -t 5");
                break;
            }
            else
            {
                system("cls");
                GetMoreBombcount(show, bomb, a, b, &winwinwin);
                DisplayBoard(show, ROW, COL);
                int x, y, c, d;
                printf("是否插眼?(1 or 2):");
                scanf_s("%d", &x);
                if (x == 1)
                {
                there:printf("请输入插眼坐标:");
                    scanf_s("%d %d", &y, &c);
                    Eye(show, y, c);
                    printf("是否继续插眼?(1 or 2):");
                    scanf_s("%d", &d);
                    if (d == 1)
                    {
                        goto there;
                    }
                }
                winwinwin++;
            }
        }
        else
            printf("您输入的坐标有误,请重新输入\n");
    }
    if (winwinwin == row * col - ChallengeBombNumber)
    {
        int choose;
        printf("恭喜你挑战成功!\n");
        system("shutdown -a");
        printf("返回主菜单");
        system("cls");
        main();
    }
}

头文件浏览(game.h)

#pragma once
#include "stdio.h"
#include"string.h"
#include"stdlib.h"
#include"windows.h"
#include"time.h"
#include"math.h"
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define BombNumber 10
#define ChallengeBombNumber 20
void InitBoard(char board[ROWS][COLS], int rows, int cols,char count);
void DisplayBoard(char board[ROWS][COLS], int row, int col);
void SetBomb(char board[ROWS][COLS], int row, int col);
void SearchBomb(char bomb[ROWS][COLS], char show[ROWS][COLS], int row, int col);
void SearchBombChallenge(char bomb[ROWS][COLS], char show[ROWS][COLS], int row, int col);
void game();
void Eye(char show[ROWS][COLS], int row, int col);
int menu(int* px);

代码实现

主菜单设置

在main函数对menu函数进行引用时这里用到了传址调用,简单来说就是通过传入两个实参的地址到相应函数的形参,使其可以修改实参,另外的传值调用则是直接传入两个实参而不是实参地址,于是在函数形参中得到的只是来自实参的拷贝。即:在传值调用中,形参得到的值是实参的拷贝。

那这里可不可以不用调用?

当然可以,将scanf函数转移到main函数中,使menu函数最先打印也可以达到效果。

在这里,因为menu函数对于里面的值进行了修改,且要将值传回main函数进行判断,所以运用传址调用。

游戏设置

实际上扫雷可以被我们拆解成数组来实现,在这个数组中我们需要能够放置雷,查找雷,一个数组显然很难实现几个功能,因此我们可以设置两个数组:一个显示棋盘,一个隐藏储存雷,进行联动。我们假设棋盘用“*”来显示,雷用“1”来显示,当我们输入查找坐标时,可以将坐标引入到储存雷的数组,若该坐标没有雷,以及该坐标周围八个位置没有雷,即储存数组坐标为“0”,那么我们可以将显示数组的对应坐标从“*”变成“0”,以说明该坐标和周围八个坐标没有雷,在这里我们又可以用递归函数,不断判断,不断打印棋盘,而当我们踩到雷时,游戏宣布结束,并将储存雷的数组打印出来,让玩家知晓哪个位置有雷,当我们需要插眼时,输入插眼坐标,将该位置的“*”改为“#”即可。

综上,我们可以先设置两个数组bomb和show,行列可以设置成11,因为当我们查找边缘坐标时,也需要扫描周围八个位置,如果行列设置的太小,则会导致溢出发生错误,因此可以将棋盘增大一圈防止溢出,打印和扫描时依旧用9x9。

初始化棋盘

理清思路后,首先要做的应该是初始化棋盘,将显示数组和储存数组中的元素分别赋值“*”“0”。在设置元素时,我们注意到在设置给定元素时出现了问题,应该是“*”还是“0”呢?好像设置哪个都不能满足两个数组的初始化,而又创建一个函数又显得太麻烦,于是我们可以给函数设置一个形参count,运用函数时传入“*”和“0”,这样我们就用一个函数初始化了两个数组。

显示棋盘

初始化棋盘后,要做的当然是显示棋盘,将show数组展示到玩家眼中,这里就涉及到了对美观性的讲究,可以发现如果不做任何处理,打印出来的棋盘很难看:

*********
*********
*********
*********
*********
*********
*********
*********
*********

因此,我们可以在%c后面多加一个空格,效果便截然不同:

“*”棋盘打印好后,我们应该想到,这样的棋盘显然不存在实用性,用户在游玩时对于位置的选取非常困难,因此我们可以在棋盘周围打印数字,让他对应棋盘,使用户可以轻松地找出他想要查找的位置坐标。当然,在打印数字时也要加上空格,而且要多打印一个“0”,这样才能使数字与棋盘对齐。上下的横线则仅仅是为了美观,可根据选择添加。

雷区设置

接下来要实现的是设置雷的功能,我们知道,每局游戏开始后,雷位置是随机的,因此我们可以运用随机数生成雷,假如我们需要埋十个雷,那么我们可以定义雷数量为十个,并存到变量中,当随机数循环生成并存储到数组元素中时,雷数量自减,将该位置由“0”转变为“1”,当雷数量等于0时停止生成。关于随机数的详解可以参考作者之前写的猜数字代码实现(点击转入)

排雷功能

在排雷功能中涉及到递归和插眼,其实思路很简单,当用户输入查找坐标后,将坐标传入存储数组进行查找,如果对应坐标是雷,那么结束游戏,如果不是雷,通过区域雷数量进行判断该坐标周围是否有雷,并打印数量,然后进行递归循环,结束循环打印棋盘后,进行插眼。排雷也是一个循环的过程,而当我们排除一个坐标后,我们需要用一个变量自加,我们可以叫他胜利变量,当他自加到一定数量后,游戏胜利,这个数量可以设置为9x9减去雷数量,即我们总共需要排查的步数,当该变量等于他后,棋盘中剩下的只有雷。

关于递归

原理是在查找雷时,当条件满足:1.该位置没有被查找过。2.该位置不是雷。3.该坐标周围没有雷。

便进行循环递归,直到不满足条件时,跳出循环,将棋盘被递归查找的位置一一打印出来,当然,查找范围要限制在9x9中,当然,我们查找一个坐标后,胜利变量也需要自加,这里也运用到了传址调用,因为变量在函数中进行了修改并传出运用。

如何知道该位置未被查找?

很简单,当我们查找一个坐标后,需要将“*”修改为对应周围雷的数量,也就是说,如果该坐标为被查找,那么他应该为“*”。

如何知道该位置是不是雷?

也很简单,运用if函数,如果该坐标对应的存储数组坐标位置是“1”,那么便是雷,如果是“0”便不是雷。

如何知道该坐标周围有没有雷?

首先,这里也需要运用区域雷数量函数,该函数运用了一个知识点:二维数组中所存的值是字符型,通过将周围的八个字符型加起来后减去八个“0”的ASCll码值将其转换为整型。转换后存入变量,得到了周围雷的数量,最后再加上“0”便可以存入数组,而这个变量也可以用来判断该坐标周围是否有雷,以此来进行递归。

挑战功能

该功能很简单,只是在对于原查找雷和设置雷函数的基础上进行了一些小修改,我们可以定义一个新的炸弹数量,将其存入挑战模式的设置雷函数,而在查找雷函数中,当用户失败后,会进行惩罚。

关于关机

首先我们要知道system函数是专门对于使用系统指令的函数,运用他需要stdlib头文件,在美化菜单中我们运用到了清屏即system("cls");在这里,我们可以对关机功能做一个了解:

关机:shutdown -s -t 60(-s关机 -a取消关机 -t设置时间)

例:system("shutdown -s -t 60");他的意思是电脑会在60秒后关机。

因此在设置挑战模式时,我们可以在用户进入该模式时设置好关机时间,当用户在规定时间内通关游戏,便可取消关机完成挑战,如果失败,则会加速关机。

关于头文件

在头文件中,由于扫雷代码整体是一个模块化代码(从初始化棋盘开始,所有的函数都在第二个源文件game.c中),因此当我们在运用游戏函数时,需要在头文件中进行声明,随后才能在main函数进行调用,同时,运用#define可以定义一个变量,这样做的好处在于:当我们需要修改棋盘大小或其他数值时,只需要在头文件中修改,便可以运用到整个游戏。

总结

该代码实现了扫雷的绝大部分功能,代码在总体上看起来似乎很麻烦,但如果我们拆解开一步步理解扫雷游戏的运行原理,其实也不是很难。当然,本代码还有可扩展的地方,比如対雷的数量的显示和时间显示等HUD的实现,读者牛逼可以自己尝试,我先摆了(

最后祝您

身体健康,再见!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值