首页 > 其他分享 >C语言实现简易版扫雷

C语言实现简易版扫雷

时间:2023-08-03 22:03:08浏览次数:44  
标签:cont 地雷 int mine C语言 ++ 简易版 扫雷 数组


 扫雷作为一款内置于windows XP系统的游戏,相信大多数人都有游玩过。接下来我将带着各位用C语言来实现这个游戏。

首先,我们来了解扫雷游戏的规则,将这些规则逐步用函数来实现,再经过逻辑的调整即可得到所需的代码。

可以试着先自己玩一把再继续看本文章。

扫雷游戏网页版 - Minesweeper

C语言实现简易版扫雷_C语言

C语言实现简易版扫雷_数组_02编辑

规则如下: 

第一次点击不会是雷。格子里的数字表示它周围有几个雷。游戏目标是找出所有雷。" 触雷" 则输。点击表情重新开始。二选一留到最后, 可任选, 需先清完其他方块。

为了达到在编程中学习的目的,我们降低难度,让初学函数的同学也能写出来、有收获,我们把需求简化如下:

1、打印游戏菜单,让玩家确定是否开始游戏

2、生成特定尺寸的棋盘格子(对应网页上的不同难度)

3、生成地雷

4、玩家输入排雷位置

5、判断此处是否有地雷,如果有雷,游戏失败,如果此处无地雷,就显示这个格子周围的地雷个数。

6、把不是地雷的位置都排查完后游戏胜利。

接下来对需求进行分析:

打印菜单很简单,设计一个menu()函数即可,代码如下:

void menu()
{

    printf("*****************\n");
    printf("*****1_play******\n");
    printf("*****0_exit******\n");
    printf("*****************\n");
}

C语言实现简易版扫雷_C语言_03

玩家输入数字1来进行游戏,在主函数中用scanf()函数和if()语句即可实现即可实现。

一、创建数组——表示棋盘格和地雷位置

为了方便调整棋盘格的尺寸,我们使用宏定义定义全局变量ROW和CLO用来表示行数和列数。

C语言实现简易版扫雷_数组_04

C语言实现简易版扫雷_C语言_05编辑

如何生成棋盘格呢?很容易想到用二维数组,那数组的大小应该是多少呢?

我们来转到一个现成的棋盘——excel:

C语言实现简易版扫雷_C语言_06

C语言实现简易版扫雷_数组_07编辑

 这是一个9*9的棋盘,假设我们用*来表示没有排查的位置,并且为了方便玩家知道几行几列还加入了一个坐标轴,所以变成了一个10*10的棋盘,变为更加通用的情况就是[ROW+1][CLO+1]的情况。

我们先暂定一个字符型数组show[ROW+1][CLO+1]。

接下来,要有一个数组来存放地雷的相关位置,地雷不仅要作为判断是否踩雷的依据,还要知道某一格周围有几个地雷,我们有两种选择,第一种是选择用某个记号来表示有或者没有比如用Y表示有地雷,N表示没有地雷,第二个选择是使用数字来表示,1表示有地雷,0表示没有地雷,要得到周围地雷数的时候只需要加上周围这一圈就可以了。

C语言实现简易版扫雷_C语言_08

C语言实现简易版扫雷_C语言_09编辑(此处用黄色来表示选中的位置,绿色表示计数范围)

那么边上这一圈怎么办呢?

不妨在定义数组时再扩大一点,变成下图

C语言实现简易版扫雷_C语言_10

C语言实现简易版扫雷_C语言_11编辑

 这样就可以在任意实际展示出的9*9格子正确计数了。将地雷数组记为char mine[12][12];

为了统一坐标,我们也将show数组扩大为char show[12][12],为什么要定义成字符型而不定义成整形数组呢?下一小节中我会解释。

为了方便之后修改棋盘格大小,我们使用宏定义 ROWS和CLOS

C语言实现简易版扫雷_数组_12

C语言实现简易版扫雷_数组_13编辑

这样就完成了数组的创建。

二、初始化数组

我们定义一个Initboard()函数用来初始化数组,之前定义的数组均为字符型,这样用一个函数就可以完成初始化。

void Initboard(char arr0[ROWS][CLOS], char set)
{
    int i, j;
    for (i = 0; i < ROWS; i++)
    {
        for (j = 0; j < CLOS; j++)
        {
            arr0[i][j] = set;
        }
    }
    if (set == '*')
    {
        for (i = 0; i < ROWS - 1; i++)
            arr0[i][0] = i + '0';
        for (j = 0; j < CLOS - 1; j++)
            arr0[0][j] = j + '0';
    }
}

C语言实现简易版扫雷_C语言_14

在初始化show数组时在set处传入'*' , mine数组传入'0',在最后一条if语句是为了当初始化show数组时加入坐标轴。这样就用一个函数实现了两个数组的初始化,并且可以自定义初始化符号。

三、打印数组

要将show数组展示给玩家看,就要打印这一数组。定义一个Displayboard()函数用来打印。

void Displayboard(char arr[ROWS][CLOS])
{
    int i, j;
    for (i = 0; i < ROWS - 1; i++)
    {
        for (j = 0; j < CLOS - 1; j++)
        {
            printf("%c ", arr[i][j]);
        }
        printf("\n");
    }
    printf("___________________________\n");
}

C语言实现简易版扫雷_数组_15

最后的一行打印一个横线是为了帮助玩家区分各次操作。

四、埋雷

随机位置埋雷,容易想到rand()用来指定范围生成随机数作为地雷的坐标,但是,思考一下这其中有没有可能两次埋雷放到了同一位置?如何规避这一现象?我们可以对最终的mine数组每个元素求和,看看是否总共有10个'1',如果不是,就再循环,直到满足条件。需要注意,此前你已经对mine数组进行了修改,要想重新正确生成,需要再次对其初始化。有了大致思路,接下来开始撸代码:

先使用宏定义 EASY_cont 来说明我们要生成多少个地雷,之后可以方便地修改,可以先自己尝试写一写,看看我们的实现思路是否相同,有更好的思路欢迎大佬在评论区交流。

int Set_mine(char mine[ROWS][CLOS])
{
    int row[EASY_cont] = { 0 }, clo[EASY_cont] = { 0 }, i = 0, error = 0, cont = 0;
    for (error = 0; error < EASY_cont; error++, i++)
    {
        srand((unsigned int)(time(NULL) + error));
        //for (i = 0; i < EASY_cont; i++)
        row[i] = 1 + rand() % ROW;
        //for (i = 0; i < EASY_cont; i++)
        clo[i] = 1 + rand() % CLO;
    }

    for (i = 0; i < EASY_cont;)
    {
        mine[row[i]][clo[i]] = '1';
        i++;
        continue;

    }
    if(loading)
        Displayboard(mine);//调试用的,实际游戏中注释掉即可
    cont = 0;
    for (i = 0; i < ROWS; i++)
    {
        for (error = 0; error < CLOS; error++)
        {
            cont += (int)(mine[i][error] - '0');//这里太坑了,之前排查很久都没排查到
        }
    }

    if (cont == EASY_cont)
    {
        printf("生成完成,按任意键开始游戏\n");
        system("pause");
        return 0;
    }

    else
    {
        if(loading)
            printf("只生成了%d个地雷,发生错误,正在重新生成地雷(doge)\n", cont);
        return 1;
    }

}

C语言实现简易版扫雷_数组_16

(这里的error可以看成j,程序能跑就不想改了[doge])

为啥有个if(loading)呢?其实这里的loading是一个宏定义的量,作用是为了在自己做测试的时候观察生成时错误的情况。

为啥这里定义成int型呢?在我的主函数里,调用这一函数是这样写的:

menu();
    scanf("%d", &test);
deal_with_Easter_egg:
    if (test)
    {
        Initboard(show, '*');
        Initboard(mine, '0');
        while (Set_mine(mine))
        {
            goto deal_with_Easter_egg;//异常处理
        }

C语言实现简易版扫雷_C语言_17

如果地雷有重叠就回到标签的位置初始化数组并且重新生成。

五、根据坐标计算周围的地雷数

只需要参考之前的excel棋盘格就,对玩家输入的坐标一圈的mine()数组求和即可。

int Cont_mine(char mine[ROWS][CLOS], int i, int j)
{
    int cont;
    cont = (int)(mine[i - 1][j] + mine[i + 1][j] + mine[i][j - 1] + mine[i][j + 1] + mine[i - 1][j - 1] + mine[i - 1][j + 1] + mine[i + 1][j + 1] + mine[i + 1][j - 1] - 8 * '0')-1;
    return cont;
}

C语言实现简易版扫雷_C语言_18

注意求和的计算方法。

六、关于游戏成功的判定

只要棋盘上已经排除的格子数加上地雷数之和等于展示给玩家的格子总数即可

while (EASY_cont + cont_mine != ROW * CLO)//此处的cont_mine是用来计数已经排查的位置的。

C语言实现简易版扫雷_数组_19

 最后,用合适的逻辑串联起来即可。

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>
#define ROW 3//这个数字和下面这个数字都不能小于3,不然会报错
#define CLO 3
#define ROWS ROW + 2
#define CLOS CLO + 2
#define EASY_cont 1 // 表示炸弹数
#define toushi 1  //这个选项用来开关炸弹位置透视
#define loading 0 //这个参数用来开关生成过程输出


int main()
{
    char show[ROWS][CLOS] = { 0 }, mine[ROWS][CLOS] = { 0 };
    int test, i, j, cont, cont_mine;
    // show数组是打印出来展示的,mine数组用来储存地雷的位置
    // 展示的部分用*表示没有探索的区域,用数字表示已经开发的区域,如果一个格子周围有雷,就用数字来表示周围一圈的地雷数量
    // Initboard用来初始化两个字符型数组
    // Displayboard用来打印棋盘
    // Set_mine用来随机布置地雷
    // Cont_mine用来得出周围的地雷数,在得出地雷数之前还要判断所在位置是否有地雷,有地雷就结束游戏失败,没有地雷就得出周围的地雷数
    menu();
    scanf("%d", &test);
deal_with_Easter_egg:
    if (test)
    {
        Initboard(show, '*');
        Initboard(mine, '0');
        while (Set_mine(mine))
        {
            goto deal_with_Easter_egg;//异常处理
        }
        if (loading)
            Displayboard(show);
        cont_mine = 0;//用于统计已经排除的位置
        while (EASY_cont + cont_mine != ROW * CLO)
        {
            Displayboard(show);
            if (toushi)
                Displayboard(mine);
        error_location:
            printf("请输入坐标\n");
            scanf("%d %d", &i, &j);
            if ((i > 0 && i <= ROW) && (j > 0 && j <= CLO))
            {
                if (mine[i][j] == '0')
                {
                    cont = Cont_mine(mine, i, j);
                    cont++;
                    cont_mine++;
                    show[i][j] = (char)(cont + '0');//相关知识:https://zhidao.baidu.com/question/90654244.html
                    Displayboard(show);
                }
                else
                {
                    printf("游戏失败\n");
                    menu();
                    scanf("%d", &test);
                    goto deal_with_Easter_egg;
                }
            }
            else
            {
                printf("坐标输入错误,请重新输入\n");
                goto error_location;
            }
        }
        printf("游戏成功\n");
        menu();
        scanf("%d", &test);
        goto deal_with_Easter_egg;
    }
    else
        printf("感谢游玩,期待下一次相遇\n");
}

C语言实现简易版扫雷_数组_20



标签:cont,地雷,int,mine,C语言,++,简易版,扫雷,数组
From: https://blog.51cto.com/u_16194134/6953626

相关文章

  • C语言
    C语言基础语法1.程序语言的基本构成要素:自然语言程序设计语言字数字,字母,运算符,分隔符词/词组关键字,标识符,常量句子/段落语句篇章程序1.关键字:也称保留字(ReservedWord),是C语言预先定义的、具有特殊意义的单词2.标识符:是大小写字母,数字和下划......
  • C语言 | extern关键字
    extern是C语言中的关键字,它会声明一个全局变量或者函数,表明变量或者函数是定义在其他其他文件中的。​ 定义:表示创建变量或分配存储单元。​ 声明:说明变量的性质,但并不分配存储单元。externinti; //只是声明,但没有分配内存空间给变量iinti; //是定义,给变量i分配了4......
  • 我的第九次C语言练习
    今天终于学完了弟三章,实际上昨天没剩下多少了,今天主要是在写练习。//intmain(void)//{// inta;// unsignedintb;// a=12;// b='\012';// printf("a=%d,b=%u",a,b);// return0;//}首先试了下unsignedint和正常int的不同,因为书上在打印\012时答案上只显示了unsig......
  • C语言关键字extern。
    extern:声明变量是在其他文件正声明(也可以看做是引用变量):extern用在变量或函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用”。//文件1代码#include<stdio.h>externvoidlbw();//声明外部lbw()intmain(){ lbw();}//文件2代码#include<stdio.h>voidlbw......
  • C语言嵌入式面试
    指针1.数组指针与指针数组,函数指针与指针函数区别?答:函数指针指向函数的指针变量,即本质是一个变量。指针函数是指返回值是指针的函数,即本质是一个函数。数组指针是指向数组首元素的地址的指针,其本质为指针。(这个指针存放的是数组首地址的地址,相当于2级指针,这个指针不可移动)指......
  • C语言嵌入式开发
    第一类问题:专业考察题在下面问题中,我附上自己的理解,可能不全面,用到的话再自行补充一些。问题1:问你写在简历上的项目经历,一般问的很细很细,在此基础上考察你项目里用到的技术知识。问题2:IIC协议(1)I2C使用两条线在主控制器和从机之间进行数据通信。一条是SCL(串行时钟线),另外一条......
  • C语言嵌入式面试3
    第一章、进程与线程1、什么是进程、线程,有什么区别?进程是资源(CPU、内存等)分配的基本单位,线程是CPU调度和分配的基本单位(程序执行的最小单位)。同一时间,如果CPU是单核,只有一个进程在执行,所谓的并发执行,也是顺序执行,只不过由于切换速度太快,你以为这些进程在同步执行而已。多核CPU可以......
  • 初学C语言day09--宏定义
    预处理指令程序员所编写的代码并不是能被编译器直接编译的标准C代码,需要一段程序翻译一下翻译的程序称为预处理器,翻译的过程叫做预处理,需要被翻译的代码叫做预处理指令,以#开头的都是预处理指令查看预处理结果:gcc-Ecode.c把预处理的结果显示到终端gcc-Ecode.c-ocode......
  • 128.用C语言实现C++的继承
    128.用C语言实现C++的继承#include<iostream>usingnamespacestd;//C++中的继承与多态structA{virtualvoidfun()//C++中的多态:通过虚函数实现{cout<<"A:fun()"<<endl;}inta;};structB:publicA//C++中的继承:B类公有继......
  • c语言链表demo
    #include<stdio.h>#include<stdlib.h>//定义节点结构体structnode{intdata;structnode*next;/*注意:这行使用了node,node必须在这行之前定义*/};intmain(intargc,constchar*argv[]){//1.定义链表的头节点,并初始化structno......