欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C语言小游戏扫雷

程序员文章站 2024-03-18 11:26:22
...

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

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刚好可以实现此功能,参数分别为:目标数组的起始地址,赋值的内容,赋值的大小(单位为字节)。
在初始化棋盘之前,我们还要考虑数组越界访问的问题,两个棋盘都要对棋盘最外围元素进行操作和判断,如果稍不注意,数组便会越界访问。在这里我们可以初始化两个比所要操作数组外围再大一圈的数组,相应的操作在里面完成,以此来解决数组越界访问问题。
C语言小游戏扫雷
C语言小游戏扫雷
完成上面操作接下来就是置雷和扫雷两个功能了。
置雷比较简单,通过产生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++;
        }
    }
}

接下来用递归实现拓展扫雷。
C语言小游戏扫雷
对其中一个位置进行分析,中间位置如果周围没雷,则先置该对应位置的状态棋盘为字符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,保证第一下不炸死,利用拓展扫雷的方法一击必胜!
C语言小游戏扫雷