小游戏——扫雷(可以标记)

这款游戏相信大家并不陌生,关于它的玩法,在这里就不赘述了,重点来介绍一下如何用C语言完成这个项目。我们先来思考一下,扫雷的棋盘肯定是通过二维数组来实现的,在这个二维数组中既要埋雷,又要扫雷,还得给玩家展示,实在是容易混淆,这里我们不妨通过两个二维数组来实现,一个用来埋雷,另一个用来扫雷并且这个棋盘可以向玩家展示,降低了难度。


首先,我们来屡一下这个游戏的思路。

  1. 埋雷;这里可以设置难度扫10个雷或者20个雷,相应的雷数多的话棋盘也应该设置大一些,这样才好玩。
    但这里我设置的棋盘大小都是一样的,只是增加雷数来改变难度。

  2. 扫雷 ;通过用户输入坐标来确定要排雷的位置,可以在棋盘周围加上行号和列号,便于玩家确定坐标。这部分可以实现以下三个功能:
    ①.第一次玩家输入有雷的坐标,把雷挪开,保证第一次不死,增强可玩性;
    ②.玩家输入的坐标周围没有雷,就利用递归把周围没有雷的地方都展开并且最外圈显示的雷数;
    ③.玩家觉得哪个位置有雷,可以实现用“ * ”标记雷,便于观察。
    有一个问题:在统计某个坐标周围的雷数时,若坐标位于边角处,肯定会出现越界访问。为了避免这种情况,我们可以在原棋盘的基础上多创建一圈。(如下图)
    这里写图片描述

  3. 检查是否胜利;我的判断方法是玩家无论是否标记雷,只要没展开的格子数和标记的雷数相加等于总的雷数时,玩家获胜。

然后在屡清了思路后,可以开始敲代码了,为了方便起见,我们创建3 个文件来写这个项目:测试文件、游戏文件和头文件。(如下图)


这里写图片描述


先来写 test.c 文件,写出游戏的逻辑以及需要用到的函数,在 game.c 里来实现具体函数的内容。
(ROW和COL为9,分别代表行和列,在 game.h 中有定义)

#include "game.h"
void menu()
{
    printf("*******************************************\n");
    printf("*       欢迎来到扫雷小游戏(*^▽^*)        *\n");
    printf("*               1.play                    *\n");
    printf("*               0.exit                    *\n");
    printf("*******************************************\n");
}

void game()
{
    int count = 0;
    char mine[ROWS][COLS]={0};//创建一个埋雷的数组
    char show[ROWS][COLS]={0};//创建一个可以向玩家展示的数组
    Init(mine,ROWS,COLS,'0');//初始化埋雷的数组,
    Init(show,ROWS,COLS,'\03');//初始化展示的数组,在这里我用的是爱心,也可以自己定义字符
    do
    {
        printf("请选择难度:1.EASY(10个雷)      2.HARD(20个雷)\n");
        scanf("%d",&count);
        switch(count)
        {
        case 1:
            SetMine(mine,ROW,COL,EASY);//埋雷
            //Print(mine,ROW,COL);
            Print(show,ROW,COL);//打印棋盘
            Safe(mine,show,ROW,COL);//第一次不炸
            FindMine(mine,show,ROW,COL,EASY);//排雷
            break;
        case 2 :
            SetMine(mine,ROW,COL,HARD);//埋雷
            //Print(mine,ROW,COL);
            Print(show,ROW,COL);//打印棋盘
            Safe(mine,show,ROW,COL);//第一次不炸
            FindMine(mine,show,ROW,COL,HARD);//排雷
            break;
        default:
            printf("选择错误,请重新选择:>\n");
            break;
        }
    }while((count!=1)&&(count!=2));

}

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

int main()
{
    test();
}

一、初始化棋盘
这里介绍一个C语言的库函数 memset 函数,void * memset(void *s, int value, size_t num);
memset 是内存设置函数,它的功能是将s中当前位置后面的num个字节 (typedef unsigned int size_t )用 value 替换并返回 s 。它的第一个参数为要替换的地址,第二个参数是你想要替换成的字符,第三个参数为替换的字节数。
一个小小的 :可以将数组初始化成全 0,但不能将数组替换成全1,因为 memset 函数是按字节进行替换的。

void Init(char board[ROWS][COLS], int rows, int cols, char str)//初始化棋盘
{
    memset(board, str,sizeof(board[0][0])*rows*cols);
}

二、输出棋盘
打印棋盘,顺便打印上行列号,便于观察。

void Print(char board[ROWS][COLS], int row, int col)//输出棋盘
{
    int i = 0;
    int j = 0;
    for(i=0; i<=row; i++)//打印列号
    {
        printf("%d ",i);
    }
    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");

}

三、埋雷
利用随机数生成雷的坐标,每埋一个雷,雷数减一。rand()函数虽然是生成随机数的函数,但它并不是实际意义的随机数,每次编译,出现的随机数都是一样的。为了让雷的埋更随机些,需要在 test.c 中的 test 函数中加入srand((unsigned int)time(NULL));来实现真正意义的随机。

void SetMine(char board[ROWS][COLS], int row, int col, int count)//埋雷
{

    int x = 0;
    int y = 0;
    while(count)
    {
        x = rand()%9+1;//1-9
        y = rand()%9+1;//1-9
        if(board[x][y]=='0')
        {
            board[x][y]='1';
            count--;
        }
    }
}

四、计算某个坐标周围的雷数
通过传递来的一个坐标,来计算周围一圈8个格子中的雷数,由于雷和非雷是字符型的 ‘ 1 ’ 、 ‘ 0 ’,我们要的是数字 1、0,因此需要将字符转换为数字。字符 ‘ 0 ’比数字 0 的 ASCII 码值大48,可以把8个格子的雷数加起来减去 8*48 ,或者可以直接减去 8 *‘ 0 ’也是一样的。

static int CountMine(char board[ROWS][COLS], int x, int y)
{
    return (board[x-1][y]+board[x-1][y+1]+
            board[x][y+1]+board[x+1][y+1]+
            board[x+1][y]+board[x+1][y-1]+
            board[x][y-1]+board[x-1][y-1])-8*'0';
            //值为数字
}

五、展开函数
通过递归函数实现展开,并且调用计算某个坐标周围雷数的函数来输出雷数。

static void OpenMine(char mine[ROWS][COLS],char show[ROWS][COLS],int row ,int col, int x,int y)
{
    int ret = 0;
    ret = CountMine(mine, x, y);//计算x,y周围的雷数
    if(ret == 0)
    {
        show[x][y]=' ';//若周围没有雷,输出空格
        if(x-1>0 && y>0 && show[x-1][y]=='\03')
            OpenMine(mine, show, row, col, x-1, y);

        if(x-1>0 && y+1<=col && show[x-1][y+1]=='\03')
            OpenMine(mine, show, row, col,  x-1, y+1);

        if(x>0 && y+1<=col && show[x][y+1]=='\03')
            OpenMine(mine, show, row, col, x, y+1);

        if(x+1<=row && y+1<=col && show[x+1][y+1]=='\03')
            OpenMine(mine, show, row, col, x+1, y+1);

        if(x+1<=row && y>0 && show[x+1][y]=='\03')
            OpenMine(mine, show, row, col, x+1, y);

        if(x+1<=row && y-1>0 && show[x+1][y-1]=='\03')
            OpenMine(mine, show, row, col, x+1, y-1);

        if(x>0 && y-1>0 && show[x][y-1]=='\03')
            OpenMine(mine, show, row, col, x, y-1);

        if(x-1>0 && y-1>0 && show[x-1][y-1]=='\03')
            OpenMine(mine, show, row, col, x-1, y-1);

    }
    else
    {
        show[x][y] = CountMine(mine, x, y)+'0';//若坐标周围有雷,显示雷数
    }

}

六、确保第一次不死
当第一次玩家踩到雷时,悄悄地把雷挪走,保证玩家第一次不被炸死,增强可玩性。

void Safe(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col)
{
    int x = 0;
    int y = 0;
    int count = 0;
    int ret = 1;
    int a = 0;
    int b = 0;
    printf("请输入要排查位置的坐标:>");
    scanf("%d%d",&x,&y);
    a = x;
    b = y;//保存一下玩家输入的坐标值
    if(mine[x][y]=='1')
    {
        mine[x][y]='0';//把雷变成非雷
        count = CountMine(mine, x,y);
        show[x][y] = count+'0';
        while(ret)//重新设一个雷
        {
            x = rand()%9+1;
            y = rand()%9+1;
            if(mine[x][y]=='0')
            {
                mine[x][y] = '1';
            }
            ret--;
        }
    }
    OpenMine(mine, show, row, col, a, b);//展开玩家输入坐标的周围
    //Print(mine,ROW,COL);
    Print(show,ROW,COL);

}

七、检查是否胜利
我的判断方法是玩家无论是否标记雷,只要没展开的格子数和标记的雷数相加等于总的雷数时,玩家就获胜了。

static int CheckWin(char show[ROWS][COLS],int row,int col)
{
    int i = 0;
    int j = 0;
    int c = 0;
    for(i=1; i<=row; i++)
    {
        for(j=1; j<=col; j++)
        {
            if((show[i][j] == '*') || (show[i][j] == '\03'))
                c++;
        }
    }
    return c;
}

八、标记雷
这里我写了标记雷的函数,也可以不写,只是为了方便观察,当玩家已经断定某个位置有雷的时候,可以进行标记。(用 “ * ” 标记雷)

static void SignMine(char show[ROWS][COLS], int row, int col, const int count)
{
    int input = 0;
    int a = 0;
    int b = 0;
    do
    {
        printf("是否需要标记雷: 1.是        0.否\n");
        scanf("%d", &input);
        switch(input)
        {
        case 1:
            printf("请输入要标记的坐标:>");
            scanf("%d%d", &a, &b);
            if(show[a][b]=='\03')
            {
                show[a][b]='*';
                Print(show,row,col);//标记后打印一下棋盘
            }
            else if(show[a][b]=='*')
            {
                printf("该坐标已经被标记\n");
            }
            break;
        case 0:
            break;
        }
    }while(input);
}

九、排雷

void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col, const int count)//排雷
{
    int x = 0;
    int y = 0; 
    while(1)
    {
        printf("请输入要排查位置的坐标:>");
        scanf("%d%d",&x,&y);
        if(x>=1 && x<=row && y>=1 && y<=col)//检查输入的坐标是否合理
        {
            if(mine[x][y]=='1')
            {
                printf("很遗憾,你输了\n");
                Print(mine,ROW,COL);
                printf("\a\a\a");
                break;
            }
            else
            {
                int c = CountMine(mine,x,y);
                show[x][y]=c+'0';
                OpenMine( mine, show,row,col,x,y);//展开函数
                //Print(mine,ROW,COL);
                Print(show,ROW,COL);//打印棋盘
                if(CheckWin(show, row, col) == count)//检查是否胜利
                {
                    break;
                }
            }
        }
            else
            {
                printf("坐标非法\n");
            }
        SignMine(show, row, col, count);//标记雷

    }
    if(CheckWin(show, row, col) == count)
    {
        printf("恭喜你,胜利了!\n");
        Print(mine,ROW,COL);
    }
}

将上述函数写全部在 game.c 中,源文件如下:

#include "game.h"

void Init(char board[ROWS][COLS], int rows, int cols, char str)//初始化棋盘
{
    memset(board, str,sizeof(board[0][0])*rows*cols);
}

void Print(char board[ROWS][COLS], int row, int col)//输出棋盘
{
    int i = 0;
    int j = 0;
    for(i=0; i<=row; i++)
    {
        printf("%d ",i);
    }
    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 SetMine(char board[ROWS][COLS], int row, int col, int count)//埋雷
{

    int x = 0;
    int y = 0;
    //1-9
    while(count)
    {
        x = rand()%9+1;//%9-> 0-8
        y = rand()%9+1;
        if(board[x][y]=='0')
        {
            board[x][y]='1';
            count--;
        }
    }
}

static int CountMine(char board[ROWS][COLS], int x, int y)//计算某个坐标周围的雷数
{
    return (board[x-1][y]+board[x-1][y+1]+
            board[x][y+1]+board[x+1][y+1]+
            board[x+1][y]+board[x+1][y-1]+
            board[x][y-1]+board[x-1][y-1]) -8*'0';//数字
}

static void OpenMine(char mine[ROWS][COLS],char show[ROWS][COLS],int row ,int col, int x,int y)
{
    int ret = 0;
    ret = CountMine(mine, x, y);//计算x,y周围的雷数
    if(ret == 0)
    {
        show[x][y]=' ';//若周围没有雷,输出空格
        if(x-1>0 && y>0 && show[x-1][y]=='\03')
            OpenMine(mine, show, row, col, x-1, y);

        if(x-1>0 && y+1<=col && show[x-1][y+1]=='\03')
            OpenMine(mine, show, row, col,  x-1, y+1);

        if(x>0 && y+1<=col && show[x][y+1]=='\03')
            OpenMine(mine, show, row, col, x, y+1);

        if(x+1<=row && y+1<=col && show[x+1][y+1]=='\03')
            OpenMine(mine, show, row, col, x+1, y+1);

        if(x+1<=row && y>0 && show[x+1][y]=='\03')
            OpenMine(mine, show, row, col, x+1, y);

        if(x+1<=row && y-1>0 && show[x+1][y-1]=='\03')
            OpenMine(mine, show, row, col, x+1, y-1);

        if(x>0 && y-1>0 && show[x][y-1]=='\03')
            OpenMine(mine, show, row, col, x, y-1);

        if(x-1>0 && y-1>0 && show[x-1][y-1]=='\03')
            OpenMine(mine, show, row, col, x-1, y-1);

    }
    else
    {
        show[x][y] = CountMine(mine, x, y)+'0';//若坐标周围有雷,显示雷数
    }

}

void Safe(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col)
{
    int x = 0;
    int y = 0;
    int count = 0;
    int ret = 1;
    int a = 0;
    int b = 0;
    printf("请输入要排查位置的坐标:>");
    scanf("%d%d",&x,&y);
    a = x;
    b = y;//保存一下玩家输入的坐标值
    if(mine[x][y]=='1')
    {
        mine[x][y]='0';//把雷变成非雷
        count = CountMine(mine, x,y);
        show[x][y] = count+'0';
        while(ret)//重新设一个雷
        {
            x = rand()%9+1;
            y = rand()%9+1;
            if(mine[x][y]=='0')
            {
                mine[x][y] = '1';
            }
            ret--;
        }
    }
    OpenMine(mine, show, row, col, a, b);//展开玩家输入坐标的周围
    //Print(mine,ROW,COL);
    Print(show,ROW,COL);

}

static int CheckWin(char show[ROWS][COLS],int row,int col)//检查有没有赢,计算所有未展开的数量
{
    int i = 0;
    int j = 0;
    int c = 0;
    for(i=1; i<=row; i++)
    {
        for(j=1; j<=col; j++)
        {
            if((show[i][j] == '*') || (show[i][j] == '\03'))
                c++;
        }
    }
    return c;
}

static void SignMine(char show[ROWS][COLS], int row, int col, const int count)
{
    int input = 0;
    int a = 0;
    int b = 0;
    do
    {
        printf("是否需要标记雷: 1.是        0.否\n");
        scanf("%d", &input);
        switch(input)
        {
        case 1:
            printf("请输入要标记的坐标:>");
            scanf("%d%d", &a, &b);
            if(show[a][b]=='\03')
            {
                show[a][b]='*';
                Print(show,row,col);//标记后打印一下棋盘
            }
            else if(show[a][b]=='*')
            {
                printf("该坐标已经被标记\n");
            }
            break;
        case 0:
            break;
        }
    }while(input);
}

void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col, const int count)//排雷
{
    int x = 0;
    int y = 0; 
    while(1)
    {
        printf("请输入要排查位置的坐标:>");
        scanf("%d%d",&x,&y);
        if(x>=1 && x<=row && y>=1 && y<=col)//检查输入的坐标是否合理
        {
            if(mine[x][y]=='1')
            {
                printf("很遗憾,你输了\n");
                Print(mine,ROW,COL);
                printf("\a\a\a");
                break;
            }
            else
            {
                int c = CountMine(mine,x,y);
                show[x][y]=c+'0';
                OpenMine( mine, show,row,col,x,y);//展开函数
                //Print(mine,ROW,COL);
                Print(show,ROW,COL);//打印棋盘
                if(CheckWin(show, row, col) == count)//检查是否胜利
                {
                    break;
                }
            }
        }
            else
            {
                printf("坐标非法\n");
            }
        SignMine(show, row, col, count);//标记雷

    }
    if(CheckWin(show, row, col) == count)
    {
        printf("恭喜你,胜利了!\n");
        Print(mine,ROW,COL);
    }
}

再然后把用到的头文件、函数以及宏定义全部写到 game.h 中,源文件如下:

#ifndef _GAME_H__
#define _GAME_H__

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

#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2

#define EASY 10
#define HARD 20



void Init(char board[ROWS][COLS], int rows, int cols, char str);//初始化棋盘
void Print(char board[ROWS][COLS], int row, int col);//打印棋盘
void SetMine(char board[ROWS][COLS], int row, int col, int count);//埋雷
void FindMine(char board[ROWS][COLS],char show[ROWS][COLS], int row, int col,const int count);//排雷
void Safe(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col);//第一次不炸

#endif //_GAME_H__

最后测试程序,这一步可以先将埋雷的棋盘打印出来,便于测试,测试完无误后,记得将埋雷的棋盘屏蔽掉,不要让玩家看到我们埋雷的棋盘哦~




这里写图片描述

这里写图片描述

这里写图片描述


END

发布了38 篇原创文章 · 获赞 37 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/Lsxlsxls/article/details/80406145