[C语言]简易版扫雷详解


先看一波效果图:
在这里插入图片描述
话不多说,接下来开撸。

1.编写分析:

先看扫雷基本规则:

  • 玩家在一定的规格棋盘中扫雷
  • 玩家下棋后显示周围雷数或扫到雷则游戏结束
  • 玩家根据棋盘上周围雷数推理藏雷的格子,插祺
  • 当棋盘满格时,显示输赢。

由规则可以看出,在编写当中需要解决的问题:

  1. 需要棋盘作为游戏主要承载体,后续操作在此棋盘中进行。
  2. 玩家下棋后,我们需要访问周围格子是否存在雷,并累加在当前棋子显示。若那棋格存在雷则爆炸,游戏结束
  3. 当棋盘上只有雷位置未动时游戏提示输赢。

大致分析已经完成。我们使用多文件形式来实现这个扫雷游戏。

多文件编程:

  • main.c (游戏开始、难度选择、游戏运行逻辑、测试)
  • game.h (游戏功能函数定义、头文件)
  • game.c (游戏功能的函数实现)

2.代码实现及逻辑

2.1 游戏界面及难度选择

先创建一个main.c作为游戏开始界面,并使用do…while函数实现玩家选择游戏难度前后或在输(赢)后,继续游戏。

int main(int argc, char** argv)
{
    int i = 0;
    do
    {
        print();
        printf("待输入->");
        scanf("%d", &i);
        switch (i)
        {
        case 1:
        	if (gameDif())
                break;
            game();
            break;
        default:
            break;
        }
    } while (i);
}

print函数随意定义即可,用于提示玩家输出 1 或 0,进行进入游戏或退出,switch语句用于接受到玩家输入的信息进入下一个阶段。
gameDif函数会根据玩家选择的难度继而定义棋盘的规格,当玩家选择0时返回1,进而break返回到游戏开始界面。

在编写gameDif函数之前我们需要定义一个全局变量,这样才能玩家选择难度后并赋值,其余文件才能够使用到棋盘规格。

int RowCol;

在定义棋盘规格前,我们需要思考玩家下在边角及边框的棋子该怎么解决问题2,正常的棋子只需要访问八个周围棋格,而边角或边框则访问3个或5个棋格。那么可以把棋盘做大一圈,将雷不予放入外圈,玩家也不能将棋下到外圈,也不把外圈显示给玩家。这样就所有棋子都访问8个棋格

int gameDif(){
    printf("***游戏难度***\n");
    printf("****3.困难****\n");
    printf("****2.中等****\n");
    printf("****1.简单****\n");
    printf("****0.返回****\n");
    int i = 0;
    beReborn:
    printf("待输入->");
    scanf("%d", &i);
    switch (i)
    {
    case 0:
        return 1;
    case 3:
        RowCol = 10;
        break;
    case 2:
        RowCol = 8;
        break;
    case 1:
        RowCol = 6;
        break;
    default:
        printf("选择错误请重新选择:>");
        goto beReborn;
        break;
    }
    return 0;
}

这里,定义了棋盘三个规格(10、8、6)对应难、中、简三种难度,玩家所见到的则是88、66、4*4的棋盘。

2.2 问题一

由问题一我们可以知道,棋盘需要存放雷的信息(将雷用’1’来标识)又需要存放玩家下的棋子信息,那么是否有点过于复杂?那么我们在game.h创建一个结构体,结构体中包含两个char变量(gameMInegameTray),并给个这个结构体定义名为 gameS。并使用这个结构体类型创建一个数组。

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

typedef struct gameS
{
    char gameMine;
    char gameTray;
}gameS;

gameMIne用于放置雷的信息,gameTray初始空格,用于接受玩家下的棋子和显示雷数。使gameTraygameMine构建映射关系。并使用gameTray与玩家进行交互。
: 这里引入stdlibtime头文件用于下面初始化雷在棋盘中的位置。

结构体定义了,那么将创建数组,创建数组则需要在main.c文件中测试函数里创建。

void game(){
    gameS tray[RowCol][RowCol];
    srand((unsigned int)time(NULL));
}

使用之前定义的规格RowCol作为棋盘的行列构成一个变长数组。注意变长数组是由c99标准及以后的标准实现,部分编译器(msvc)不支持变长数组
srand 初始化 rand函数用于后续初始化棋盘。

现在需要给游戏功能进行编写,在编写中我们需要给game.h文件声明函数,给予main.c文件测试函数中测试
game.h头文件中声明函数

extern int RowCol;
void trayInit(gameS tray[RowCol][RowCol]);//初始化
void trayPrint(gameS tray[RowCol][RowCol]);//打印
int playTray(gameS tray[RowCol][RowCol]);//下棋
int victoryTray(gameS tray[RowCol][RowCol]);//判断

棋盘初始化我们需要放置雷的信息,雷使用 '1’标识,不是雷使用’0’表示,方便后续玩家下棋操作的累加。

void trayInit(gameS tray[RowCol][RowCol]){
    int mine = ((RowCol - 2) * (RowCol - 2)) * 0.3;
    while (mine)
    {
        int row = rand() % (RowCol - 1);
        int col = rand() % (RowCol - 1);
        if (row == 0 || col == 0 || tray[row][col].gameMine == '1')
            continue;
        else
            tray[row][col].gameMine = '1';
        mine--;
    }
    for (int i = 0; i < RowCol; i++)
    {
        for (int j = 0; j < RowCol; j++)
        {
            if (tray[i][j].gameMine != '1')
                tray[i][j].gameMine = '0';
            tray[i][j].gameTray = '*';
        }
    }
}

雷的数量我们使用玩家所见的规格 30%的数量。并使用循环将雷放置完后,并进行结构体数组gameMinegameTray的初始化 。

棋盘的打印,我们则只需要打印结构体数组的gameTray,并赋加边框

void trayPrint(gameS tray[RowCol][RowCol]){
    for (int i = 1; i < RowCol - 1; i++)
    {
        for (int j = 1; j < RowCol - 1; j++)
        {
            printf(" %c ", tray[i][j].gameTray);
            if ( j != RowCol - 2)
                printf("|");
        }
        printf("\n");
        if (i == RowCol -2)
            break;
        for (int j = 1; j < RowCol - 1; j++)
        {
            printf("---");
            if (j != RowCol - 2)
                printf("|");
        }
        printf("\n");
    }
}

2.3 问题 2

玩家在输入坐标后,需要判断玩家输入的坐标是否存在于棋盘外圈和该位置是否有雷,若此位置没雷,则需要访问周围雷的数量并累加显示在该坐标上。

int playTray(gameS tray[RowCol][RowCol]){
    while (1)
    {
        int x, y;
        printf("请输入:>");
        scanf("%d %d", &x, &y);
        if (x >= RowCol - 1 || y >= RowCol - 1 || tray[x][y].gameTray != '*' || x == 0 || y == 0)
        {
            printf("你输入的坐标以被占用或错误!\n");
            continue;
        }
        if (tray[x][y].gameMine == '1')
            return 1;
        char Count = getMine(tray,x,y);
        if (Count != '0')
            tray[x][y].gameTray = Count;
        else
            tray[x][y].gameTray = ' ';
        break;
    }
    return 0;
}

在玩家输入的坐标无误时,若无雷则,进行访问周围雷的数量,这里我们使用的getMine函数来获取周围雷的属性。并用 Count 变量来接收,若周围没有雷时给该位置赋值空格,进行简易的美化,若周围有雷时则给该位置赋值Count

getMine函数的实现,之前定义有雷为’1’,无雷为‘0’的优势就来了,因为数字0的ASCII码表的码值为 48 , 而有雷与无雷的ASCII码值差值只为1,只需要将周围的棋格累加再== - (48 * 7) ==就能反应周围雷的属性。

char getMine(gameS tray[RowCol][RowCol],int x,int y){
    return tray[x - 1][y - 1].gameMine +
           tray[x - 1][y].gameMine +
           tray[x - 1][y + 1].gameMine +
           tray[x][y - 1].gameMine +
           tray[x][y + 1].gameMine +
           tray[x + 1][y - 1].gameMine +
           tray[x + 1][y].gameMine +
           tray[x + 1][y + 1].gameMine - 48 * 7;
}

附上ASCII码表
在这里插入图片描述

2.4 问题4

游戏交互,可以判断棋盘中剩余的棋子是否等于 雷的数量,若等于雷的数量则玩家获取胜利,多于雷的数量,则继续游戏

int victoryTray(gameS tray[RowCol][RowCol]){
    int mine = ((RowCol - 2) * (RowCol - 2)) * 0.3;
    int count = 0;
    for (int  i = 1; i < RowCol - 1; i++)
    {
        for (int j = 1; j < RowCol - 1; j++)
        {
            if (tray[i][j].gameTray == '*')
                count++;
            if(count > mine)
                return 0;
        }
    }
    return 2;
}

使用循环便利棋盘除外圈外的所有位置,等于雷的数量时返回0,多于时返回2。

到此,我们游戏核心功能已经写好。接下来继续完善测试函数

测试

回到main.c文件中,因为玩家下棋是循环的直到游戏结束,所以需要将打印骑盘放入循环中,与玩家进行交互,创建i变量用于接受玩家下棋函数和游戏状态的返回值。正常游玩未输未赢的情况下,i的值都是0;当接受到大于0 的信息则跳出循环并打印。

void game(){
    gameS tray[RowCol][RowCol];
    srand((unsigned int)time(NULL));
    trayInit(tray);
    int i = 0;
    while (1)
    {
        trayPrint(tray);
        i = playTray(tray);
        if (i)
            break;
        i = victoryTray(tray);
        if (i)
            break;
    }
    switch (i)
    {
    case 1:
        printf("您输了!\n");
        break;
    case 2:
        printf("胜利了!\n");
        break;
    default:
        printf("ERROR\n");
        break;
    }
}

到此我们简易版扫雷已经完成。新人编写博客不易,请给个三连谢谢


代码在此获取:简易版扫雷

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值