小游戏:扫雷 (C语言实现扫雷的基本功能)

该程序分为三个文件:
1.game.h :包含头文件的引用、函数的声明和宏定义
2.game.c :包含各功能函数的具体实现
3.test.c :各功能函数的调用(程序的流程)

功能介绍:
1.初始化雷盘
2.打印雷盘
3.随机设置雷的分布
4.统计坐标位置周围的雷数
5.扩展式排雷
6.给所选坐标位置做标记
7.取消标记
8.第一次排雷不会被炸死

读者可以自己额外增加新功能,比如计时功能等。

作者提醒:
具体的内容都在代码注释里详细解释(我觉得是非常详细了)
当然,本人并不推荐这个程序中的注释风格,以后编代码千万别这么写注释
这样注释纯粹是为了初学者能轻松理解所有代码的作用

game.h

#ifndef __GAME_H__
#define __GAME_H__

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



#define ROW 10//雷盘行数
#define COL 10//雷盘列数
#define ROWS ROW+2//数组(实际)行数
#define COLS COL+2//数组(实际)列数
#define EASY_COUNT 10//初级难度的雷数

void InitBoard(char board[ROWS][COLS], int row, int col, char set);//初始化雷盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);//打印雷盘
void SetBoard(char board[ROWS][COLS], int row, int col, int count);//布雷
int GetCount(char board[ROWS][COLS], int x, int y);//统计雷数
void expend(char board1[ROWS][COLS], char board[ROWS][COLS], int x, int y,int *num);//扩展式排雷
void sign(char board[ROWS][COLS], int x, int y);//标记猴头(雷)
void unsign(char board[ROWS][COLS], int x, int y);//取消标记

#endif//__GAME_H__

game.c

#include "game.h"

void InitBoard(char board[ROWS][COLS], int row, int col, char set)//初始化雷盘
{
    memset(board,set,row*col*sizeof(board[0][0]));//利用memset初始化,memset用法自行看书学习
}

void DisplayBoard(char board[ROWS][COLS], int row, int col)//打印雷盘
{
    system("CLS");//每次打印雷盘之前清屏一次
    int i = 0;
    int j = 0;
    printf("   ");//为了打印列坐标时对齐
    for (i = 1; i <= row; i++)
        printf("%d ", i);//打印列坐标1 2 3 4 5 6 7 8 9 10
    printf("\n");
    for (i = 1; i <=row; i++)
    {
        printf("%2d ", i);//打印行坐标1 2 3 4 5 6 7 8 9 10
        for (j = 1; j <=row; j++)
        {
            printf("%c ", board[i][j]);
        }
        printf("\n");
    }
}

void SetBoard(char board[ROWS][COLS], int row, int col,int count)//布雷
{
    int x = 0;
    int y = 0; 
    while (count)
    {
        x = rand() % row + 1;//保证行坐标在1到10之间
        y = rand() % col + 1;//保证列坐标在1到10之间
        if (board[x][y] == '0')//判断该位置是否布过雷
        {
            board[x][y] = '1';//‘1’代表有雷
            count--;//布一次雷,雷数减一
        }
    }

}

int GetCount(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';//将该位置周围八个位置的情况(是否有雷)计数
}

void expend(char board1[ROWS][COLS], char board2[ROWS][COLS], int x, int y, int *num)//扩展式排雷(递归)
{
    int i = 0;
    int j = 0;
    if (board2[x][y]=='*')//如果该位置字符为'*',则该位置是未排过的,进行排雷
    {
        (*num)++;//排雷次数加一
        int count = GetCount(board1,x,y);//统计该位置周围的雷数
        if (count != 0)//如果该位置周围的雷数不为0
        {
            board2[x][y] = count + '0';//显示雷数,则该位置的字符不为'*',可避免下次排雷重复排到该位置
        }
        else//如果该位置的雷数为0,则向它周围八个位置扩展排雷
        {
            board2[x][y] = '0';//该位置的字符显示为‘0’
            for (i = -1; i <= 1; i++)
            {
                for (j = -1; j <= 1; j++)
                {
                    if (x + i >= 1 && x + i <=ROW && y + j >= 1 && y + j <=COL)//耗费我3个小时调试时间的不起眼的小错误!!!!!!!
                    {
                        if (i != 0 || j != 0)//避免重复排到自己
                            expend(board1, board2, x + i, y + j, num);
                    }
                }
            }
        }
    }
}

void sign(char board[ROWS][COLS], int x, int y)//用‘@’标记雷
{
    if (board[x][y] == '*')
    {
        board[x][y] = '@';
    }
}

void unsign(char board[ROWS][COLS], int x, int y)//取消标记
{
    if (board[x][y] == '@')
    {
        board[x][y] = '*';
    }
}

test.c

#include "game.h"

void game()
{
    int x = 0;
    int y = 0;
    int win = 0;//判定输赢的排雷次数
    int select = 0;//决定游戏功能的变量
    char mine[ROWS][COLS] = { 0 };//存雷的数组(雷盘)
    char show[ROWS][COLS] = { 0 };//展示的数组(雷盘)

    InitBoard(show, ROWS, COLS, '*');//展示的数组(雷盘)初始化为'*'
    InitBoard(mine, ROWS, COLS, '0');//存雷的数字(雷盘)初始化为'0'

    SetBoard(mine, ROW, COL, EASY_COUNT);//布雷
    DisplayBoard(show, ROW, COL);//打印雷盘

    while (win<(ROW*COL-EASY_COUNT))//当排雷的次数不少于无雷格数(雷盘格数 减 雷数)时,停止排雷
    {
        printf("请选择功能:\n1 for 排雷;2 for 标记 3 for 取消标记 4 for 再来一局\n");
        scanf("%d", &select);
        fflush(stdin);//清空输入缓冲区,避免多输造成的影响
        if (select == 1)//1 for 排雷
        {
            printf("请输入坐标:\n");
            scanf("%d%d", &x, &y);
            fflush(stdin);//清空输入缓冲区,避免多输造成的影响
            if (x >= 1 && x <= ROW&&y >= 1 && y <= COL)//检验坐标是否合法
            {
                if (mine[x][y] == '1')//如果所选位置有雷,判断是否为第一次排雷
                {
                    if (win != 0)//如果不是第一次排雷,宣布游戏结束
                    {
                        DisplayBoard(mine, ROW, COL);//排雷失败后打印一下雷的分布
                        printf("你被炸死了!\n");
                        return;
                    }
                    else//如果是第一次排雷,将这颗雷转移到其他位置,保证第一次不会排到雷
                    {
                        mine[x][y] = '0';
                        SetBoard(mine, ROW, COL, 1);
                        expend(mine, show, x, y, &win);
                    }
                }
                else//如果所选位置没有雷,进行扩展式排雷
                {
                    expend(mine, show, x, y, &win);
                }
                DisplayBoard(show, ROW, COL);//打印排雷后的雷盘
            }
            else
            {
                printf("错误坐标:\n");
            }
        }
        else if (select==2)//2 for 标记
        {
            printf("请输入坐标:\n");
            scanf("%d%d", &x, &y);
            fflush(stdin);//清空输入缓冲区,避免多输造成的影响
            sign(show,x,y);
            DisplayBoard(show,ROW,COL);
        }
        else if (select==3)//3 for 取消标记
        {
            printf("请输入坐标:\n");
            scanf("%d%d", &x, &y);
            fflush(stdin);//清空输入缓冲区,避免多输造成的影响
            unsign(show, x, y);
            DisplayBoard(show, ROW, COL);
        }
        else if (select==4)//4 for 结束游戏
        {
            return;
        }

    }
    printf("排雷成功!\n");
}

void menu()
{
    int num = 0;
    srand((unsigned int)time(NULL));//产生随机种子,用于随机布雷
    do
    {
        printf("*********1.play           0.exit**********");
        printf("请选择:\n");
        scanf("%d", &num);
        switch (num)
        {
        case 1:
            game();
            break;
        case 0:
            break;
        default:
            printf("选择错误!\n");
            break;
        }
    } while (num);

}

void test()
{
    menu();
}

int main()
{
    test();
    return 0;
}

截图:
程序开始运行
这里写图片描述
这里写图片描述
排雷过程
这里写图片描述
标记雷
这里写图片描述
这里写图片描述
取消标记
这里写图片描述
这里写图片描述
扫雷失败
这里写图片描述
这里写图片描述
重新开始游戏
这里写图片描述
这里写图片描述
扫雷成功
这里写图片描述
这里写图片描述

心得体会:
这个扫雷可以算是个小小小小的项目,对现阶段的我来说还不算难,但过程并不轻松。
我针对其中一个bug进行解释说明,为什么说过程并不轻松?
该程序判定扫雷成功的方式是:
在expend函数中传形参win计数(每排一次雷,win自加一次,当win不小于无雷格数时游戏结束)
代码如下:

//调用排雷的功能函数,将win传给形参num
expend(mine, show, x, y, &win);
//功能函数的具体内容
void expend(char board1[ROWS][COLS], char board2[ROWS][COLS], int x, int y, int *num)//扩展式排雷(递归)
{
    int i = 0;
    int j = 0;
    if (board2[x][y]=='*')//如果该位置字符为'*',则该位置是未排过的,进行排雷
    {
        (*num)++;//排雷次数加一
        int count = GetCount(board1,x,y);//统计该位置周围的雷数
        if (count != 0)//如果该位置周围的雷数不为0
        {
            board2[x][y] = count + '0';//显示雷数,则该位置的字符不为'*',可避免下次排雷重复排到该位置
        }
        else//如果该位置的雷数为0,则向它周围八个位置扩展排雷
        {
            board2[x][y] = '0';//该位置的字符显示为‘0’
            for (i = -1; i <= 1; i++)
            {
                for (j = -1; j <= 1; j++)
                {
                    if (x + i >= 1 && x + i <=ROW && y + j >= 1 && y + j <=COL)//耗费我3个小时调试时间的不起眼的小错误!!!!!!!
                    {
                        if (i != 0 || j != 0)//避免重复排到自己
                            expend(board1, board2, x + i, y + j, num);
                    }
                }
            }
        }
    }
}

我程序中出现的bug是win计数出错(win总是大于实际排雷次数),
这个bug的具体错误耗费了我3个小时才找到,其实就是我把

   if (x + i >= 1 && x + i <=ROW && y + j >= 1 && y + j <=COL)
写成了:
 if (x + i >= 1 && x + i <ROWS && y + j >= 1 && y + j <COLS)

这样就导致排雷排到雷盘行和列的最边缘的位置时会出错
如图:
这里写图片描述
排雷应该在1到10坐标之间(即黑框里)进行,当我们定义i和j小于ROWS的时候,在这个程序中就是小于12,则坐标为0到11(即红框里).
简单来说,就是本应该只在黑框内排雷,但是却把最外层(非雷区)也排了,这样就会增加排雷次数,从而导致win的数值不正常,总是大于90(雷格数10x10-雷数10=90)
在这里看来,您可能会想,“这么简单的bug你竟然花了3个小时!啧啧啧。。。”

其次,我觉得,真的是“当局者迷”,有时候自己真的很难发现自己的微不足道的错误=》小bug

唉。。。
正应了那句话,“程序员20%的时间花在编代码上,80%的时间花在调试上”。

OK!在此,我建议和我一样初学者们,在对一个程序编写之前,首先分析整个程序的流程和功能, 做到有模有块,如果脑海里实在理不出来步骤,不妨在纸上慢慢画出来或者写出来,
莫急于上手编代码!

最后,送给大家一句陈正冲先生的话,“调试代码才是最长水平的!”

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值