C语言小游戏扫雷

基于一个游戏的整体逻辑(同前面的三子棋完全一致),所以游戏整体的代码框架如下:

void menu()
{
    printf("****************************************\n");
    printf("*******           1.play         *******\n");
    printf("*******           0.exit         *******\n");
    printf("****************************************\n");
}

int main()
{
    int input = 0;
    srand((unsigned int)time(NULL));
    do
    {
        menu();
        printf("请选择:>");
        scanf("%d",&input);
        switch(input)
        {
        case 1:
            game();
            break;
        case 0:
            printf("游戏结束\n");
            return 0;
        default :
            printf("输入错误!请重新输入\n");
            break;
        }
    }while (input);
}

用一个二维数组来模拟棋盘。
对游戏的整体功能分析发现,在扫雷的时候,我们要对棋盘每个元素的变量产生修改,修改的内容是该位置周围雷的个数,这样不断的修改会破坏原有位置雷的值,让整个游戏一旦开始变无从结束。
所以我们要用两个棋盘来避免以上的问题。
用一个二维数组专门来存放雷阵,后面的操作只是对这个棋盘的判断,将输出结果存放在另一个二维数组中。可以用0来初始化棋盘,用1来表示雷,而某个位置周围元素之和也可以很好的作为返回值输出到状态棋盘。
另一个二维数组便是状态棋盘,如果将棋盘也初始化为0,用雷阵的返回值作为该位置的值会出现一个问题:如果该位置刚好周围没雷,则无法区分没雷和还没有赋值两种情况。
为了解决上面的问题以及便于初始化函数的统一设计,将雷阵初始化为字符0,雷标记为字符1(根据ASCLL码表得知:'0'-0=48 '1'-1=48)。而将状态棋盘初始化为*,将返回值count(count+'0'等价于'count')以其对应的字符存放。

以下是棋盘的初始化和打印:

void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
    memset(board, set, rows*cols*sizeof(board[0][0]));
}

void PrintBoard(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");
    }
}

当然棋盘的初始化也可以通过循环遍历来完成,而库函数memset刚好可以实现此功能,参数分别为:目标数组的起始地址,赋值的内容,赋值的大小(单位为字节)。
在初始化棋盘之前,我们还要考虑数组越界访问的问题,两个棋盘都要对棋盘最外围元素进行操作和判断,如果稍不注意,数组便会越界访问。在这里我们可以初始化两个比所要操作数组外围再大一圈的数组,相应的操作在里面完成,以此来解决数组越界访问问题。
这里写图片描述
这里写图片描述
完成上面操作接下来就是置雷和扫雷两个功能了。
置雷比较简单,通过产生1~9两个随机数,来判断雷阵该位置是否已置雷,没有则将该位置置为字符1,相应的用一个计数器记录置雷的数量。

void SetMine(char board[ROWS][COLS], int row, int col)
{
    int i = 0;
    int j = 0;
    int count = 0;
    while (count<EASY_COUNT)
    {
        i = rand()%row+1;
        j = rand()%col+1;
        if (board[i][j]=='0')
        {
            board[i][j] = '1';
            count++;
        }
    }
}

接下来用递归实现拓展扫雷。
这里写图片描述
对其中一个位置进行分析,中间位置如果周围没雷,则先置该对应位置的状态棋盘为字符0,接着对周围8个元素分别再作为中心元素往外拓展,一旦发现某位置周围有雷,则不再拓展。
用循环来生成这九个棋盘位置:

static void extend(char mine[ROWS][COLS], char show[ROWS][COLS], int i, int j)
{
    int m = 0;
    int n = 0;
    int count = 0;

    if ((count = (mine[i - 1][j] 
    + mine[i - 1][j + 1] 
    + mine[i][j + 1] 
    + mine[i + 1][j + 1] 
    + mine[i + 1][j] 
    + mine[i + 1][j - 1]
    + mine[i][j - 1] 
    + mine[i - 1][j - 1] - 8 * '0')) == 0)
    {
        show[i][j] = '0';

        for (m=-1; m<2; m++)
        {
            for (n=-1; n<2; n++)
            {
                if (mine[i+m][j+n]=='0' && show[i+m][j+n]=='*')
                {
                    extend(mine,show,i+m,j+n);
                }
            }
        }
    }
    else  
    {
        show[i][j] = count + '0';  
    }
} 

这个递归拓展需要注意的是,生成的九个位置中还重复了自身位置,一旦不对此位置进行处理,递归将陷入死循环。所以先置该位置对应状态棋盘为字符0,在递归调用时加上对状态棋盘的判断来避免重复调用中间位置。
最后便是扫雷函数的设计,整个逻辑框架是一个循环,不慎踩中雷跳出循环,游戏结束,或者将棋盘中不是雷的位置排查完,玩家获胜跳出循环,否则一直进行棋盘的排查。

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
    int i = 0;
    int j = 0;
    int flag = 1;

    while (1)
    {
        printf("请输入坐标:>\n");
        scanf("%d%d", &i, &j);

        if (i>0 && i<=ROW && j>0 && j<=COL)
        {
            //保证第一次不被炸死
            if (mine[i][j]=='1')
            {
                if (flag)
                {
                    do
                    {
                        InitBoard(mine,ROWS,COLS,'0');
                        SetMine(mine,ROW,COL);
                    }while (mine[i][j]=='1');
                    flag = 0;
                    goto flag;
                }
                printf("很遗憾!你被炸死了\n");
                PrintBoard(mine,ROW,COL);
                break;
            }

            flag = 0;

            flag:;
            extend(mine,show,i,j);

            if (count_board_mine(show,ROW,COL)==EASY_COUNT)
            {
                printf("恭喜你!排雷成功\n");
                PrintBoard(mine,ROW,COL);
                break;
            }
            PrintBoard(show,ROW,COL);
        }
        else
        {
            printf("输入有误!请重新选择\n");
        }
    }
}

上面调用的count_board_mine()是对状态棋盘未排查位置的计数:

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

由于本函数和上面的拓展函数均只在本源文件中调用,所以可以加上static进行保护。

FindMine()中为了避免玩家一上手就凉凉,所以在碰到第一次选择就中奖的情况对雷阵偷偷进行了重置,在本次判断语句未执行完,通过goto语句跳过后面不必要的操作,并用状态标记的方法对此次操作作相应记录,保证在偷偷帮你一次下次一次你依然中奖时为你默哀。

完整代码展示:

  • game.h
#ifndef __GAME_H__
#define __GAME_H__

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

#define EASY_COUNT 10

#define ROW 9
#define COL 9

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

void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
void SetMine(char board[ROWS][COLS], int row, int col);
void PrintBoard(char board[ROWS][COLS], int row, int col);
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

#endif //__GAME_H__
  • game.c
#include <string.h>
#include "game.h"

void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
    memset(board, set, rows*cols*sizeof(board[0][0]));
}

void PrintBoard(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");
    }
}

void SetMine(char board[ROWS][COLS], int row, int col)
{
    int i = 0;
    int j = 0;
    int count = 0;
    while (count<EASY_COUNT)
    {
        i = rand()%row+1;
        j = rand()%col+1;
        if (board[i][j]=='0')
        {
            board[i][j] = '1';
            count++;
        }
    }
}

//未排出个数
static int count_board_mine(char show[ROWS][COLS], int row, int col)
{
    int total = 0;
    int i = 0;
    int j = 0;
    for (i = 1; i <= row; i++)
    {
        for (j = 1; j <= col; j++)
        {
            if (show[i][j] == '*')
            {
                total++;
            }
        }
    }
    return total;
}

static void extend(char mine[ROWS][COLS], char show[ROWS][COLS], int i, int j)
{
    int m = 0;
    int n = 0;
    int count = 0;

    if ((count = (mine[i - 1][j] 
    + mine[i - 1][j + 1] 
    + mine[i][j + 1] 
    + mine[i + 1][j + 1] 
    + mine[i + 1][j] 
    + mine[i + 1][j - 1]
    + mine[i][j - 1] 
    + mine[i - 1][j - 1] - 8 * '0')) == 0)
    {
        show[i][j] = '0';

        for (m=-1; m<2; m++)
        {
            for (n=-1; n<2; n++)
            {
                if (mine[i+m][j+n]=='0' && show[i+m][j+n]=='*')
                {
                    extend(mine,show,i+m,j+n);
                }
            }
        }
    }
    else  
    {
        show[i][j] = count + '0';  
    }
} 



void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
    int i = 0;
    int j = 0;
    int flag = 1;

    while (1)
    {
        printf("请输入坐标:>\n");
        scanf("%d%d", &i, &j);

        if (i>0 && i<=ROW && j>0 && j<=COL)
        {
            //保证第一次不被炸死
            if (mine[i][j]=='1')
            {
                if (flag)
                {
                    do
                    {
                        InitBoard(mine,ROWS,COLS,'0');
                        SetMine(mine,ROW,COL);
                    }while (mine[i][j]=='1');
                    flag = 0;
                    goto flag;
                }
                printf("很遗憾!你被炸死了\n");
                PrintBoard(mine,ROW,COL);
                break;
            }

            flag = 0;

            flag:;
            extend(mine,show,i,j);

            if (count_board_mine(show,ROW,COL)==EASY_COUNT)
            {
                printf("恭喜你!排雷成功\n");
                PrintBoard(mine,ROW,COL);
                break;
            }
            PrintBoard(show,ROW,COL);
        }
        else
        {
            printf("输入有误!请重新选择\n");
        }
    }
}
  • test.c
#include <stdio.h>
#include "game.h"

void menu()
{
    printf("****************************************\n");
    printf("*******           1.play         *******\n");
    printf("*******           0.exit         *******\n");
    printf("****************************************\n");
}

void game()
{
    char mine[ROWS][COLS];
    char show[ROWS][COLS];

    InitBoard(mine,ROWS,COLS,'0');
    InitBoard(show,ROWS,COLS,'*');

    SetMine(mine,ROW,COL);

    PrintBoard(mine,ROW,COL);
    PrintBoard(show,ROW,COL);

    FindMine(mine,show,ROW,COL);

}

int main()
{
    int input = 0;
    srand((unsigned int)time(NULL));
    do
    {
        menu();
        printf("请选择:>");
        scanf("%d",&input);
        switch(input)
        {
        case 1:
            game();
            break;
        case 0:
            printf("游戏结束\n");
            return 0;
        default :
            printf("输入错误!请重新输入\n");
            break;
        }
    }while (input);
}

效果展示

置雷为80,保证第一下不炸死,利用拓展扫雷的方法一击必胜!
这里写图片描述

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值