C语言学习记录04- 第一个“项目”:扫雷游戏

01程序设计:需要的功能

第一步:进入菜单,玩家选择进行游戏或者退出程序

第二步:选择难度,不同难度对应着不同大小的扫雷界面与地雷的个数*

第三步:进行游戏

        1️⃣程序根据难度生成一个棋盘,并根据难度在其中随机放置不同数量的地雷。

        简单:9*9,10个雷;困难:16*16,40个雷

        2️⃣玩家选择位置进行排查。若选择的位置不是雷,显示出周围有几个雷,若周围一定范围内都没有雷,则像windows扫雷一样展开周围的一片区域*。若该位置是雷,则游戏结束。

        3️⃣玩家也可以选择位置进行标记,即标记出雷所在的位置*。

        4️⃣玩家排查出所有的雷,游戏结束。显示出玩家从游戏开始到游戏结束一共花费的时间*。

第四步:返回菜单

*挑战:即笔者不知道实现方法,或对笔者来说有实现的难度TT

02实现过程

2.1项目新增

新建项目mine_game,在项目中新建game.c以及头文件game.h。

2.2第一个功能:menu()函数

        menu()需要实现的功能有:

                在程序开始时,打印出菜单界面,询问玩家进行游戏或者退出游戏。

                在玩家选择了进行游戏后,询问玩家要玩的游戏的难度。

遇到问题:

在编写好初步的程序框架后,准备进行调试,编译器报错。

                在查找问题,并询问chatgpt后,发现问题所在:

               我习惯性地将将两个.c分开编译了,由此让编译器无法找到函数的定义部分。将他们放在同一个add_executable中,问题解决。

       至此,初步完成了menu()函数,可以做到选择进行或退出游戏。

//run.c

#include "game.h"

int main(){
    int MenuCommand;
    srand((unsigned int)time(NULL));
//之后用于生成随机数。其所需要的头文件,放在game.h中。
    do {
        MenuCommand = -1;
        menu(MenuCommand);
        scanf("%d",&MenuCommand);
        switch(MenuCommand) {
            case 1:
                game();
//game()即是扫雷游戏的主体函数,将函数声明和函数原型放在game.h和game.c中
                break;
            case 0:
                break;
            default:
                printf("输入错误,请重新输入\n");
                break;

        }
    }while(MenuCommand);

    return 0;
}
//game.c
#include"game.h"

void menu(int n)
{
    if (n==-1)
    {
        printf("*************************\n");
        printf("******    1.play    ********\n");
        printf("******    0.exit    ********\n");
        printf("*************************\n");
        printf("请选择>");

    }
}

2.3 DifChoose()函数

根据选择的难度对棋盘的长宽进行赋值,对mine的个数进行赋值
void DifChoose(void){
    int level;
    printf("***************************\n");
    printf("*****请选择游戏难度**********\n");
    printf("*****    11.easy     ******\n");
    printf("*****    22.hard     ******\n");
    printf("***************************\n");
    printf("请选择>");
    scanf("%d",&level);
    if (level==11)
    {
        ROWS = 11;
        COLS = 11;
        row=9;
        col=9;
        MineNum=10;
        r = 9;
        c = 9;
    }
    if(level==22)
    {
        ROWS = 18;
        COLS = 18;
        row=16;
        col=16;
        MineNum=40;
        r = 16;
        c = 14;
    }
}

2.4 InitBoard()函数

        扫雷游戏的主要实现过程是创建两个数组,下层数组用以记录生成的地雷的位置,上层数组用以展示给用户,并且不能直接告诉用户哪里有地雷,因此,两个数组应该相互独立。

        由于后面涉及到相邻八个地址是否有雷的计算,因此,下层埋放地雷的数组,应当比上层展示数组多一圈,即是在row和col上都要多2。

Initboard()函数用以初始化数组(后称为棋盘),在下层放雷的棋盘初始化每个都是字符‘0’,方便后续随机布置地雷,上层初始化地址则是每个位置都是初始的‘*’字符。

void InitBoard(char board[ROWS][COLS],int i,int j,char set)
{
    for(int x=0;x<i;x++)
    {
        for(int y=0;y<j;y++)

            board[x][y]=set;

    }
}

2.5 PlaceMine()函数

PlaceMine()函数大致思路即是在底层棋盘中利用rand()函数生成的伪随机值,用与选择的Mine Num个数一致的‘1’替换掉棋盘上的‘0’。

void PlaceMine(char board[ROWS][COLS],int i,int j )//board是放雷的,i和j是小的排和列
{
    int count= MineNum;
   while (count)
   {
       // printf("%d ",count);
        int x = rand()%i+1;
        int y=rand()%j+1;
        if(board[x][y]=='0')
        {
            printf("%d %d\n",x,y);
            board[x][y] = '1';
            count--;
        }
    }
}

遇到问题

        在这里,我本来写的是board[r][c],并且r和c只在头文件game.h中声明但未初始化,也并未赋值,在实际调试时发现PlaceMine函数在count为1时开始无限循环,本人百思不得其解,求助老师后,老师告知是r和c引起的。因为在c语言中对未初始化的变量执行操作可能会导致无法预知的结果。

        在改为board[ROWS][COLS]后,函数正常执行。

2.6 展示棋盘程序 DisplayBoard()

用户必须要有一个可视化的棋盘才能进行游戏,由此,编写DisplayBoard()函数。

void DisplayBoard(char board[ROWS][COLS], int i, int j)
{
    printf("*********MineGame************\n");
    int x ;
    for (x = 0; x <= i; x++)
    {
        printf("%d", x);//打印列号
    }
    printf("\n");
    for ( x = 1; x < i; x++)
    {
        printf("%d", x ); // 打印行号
        for (int y = 1; y < j; y++)
        {
            printf("%c", board[x][y]);
        }
        printf("\n");
    }
}

        此函数用以打印上层棋盘,并且有从1开始的列号与行号。由于下层棋盘比上层棋盘多一圈,所以上层棋盘的从1开始的坐标刚好与下层棋盘一一对应,十分精妙。

        另外,我还写了一个不打印行号和列号的函数,用以检查PlaceMine()之后的下层棋盘,方便调试。

void No_serialDisplay(char board[ROWS][COLS],int i,int j)//调试用函数,不在头文件中声明
{
    printf("Inside No_serialDisplay\n");
    for (int x=0;x < i;x++) {
        for (int y = 0; y < j; y++) {
            printf("%c ", board[x][y]);
        }
        printf("\n");
    }
}

   2.7 排查雷程序GetMineCount()与FindMine()

        用户在选择了地址进行排查后,如果该位置不是雷,应该展示该位置周围的雷的个数,即是FineMine()函数中会调用GetMineCount()函数,因此,我将两者放在一起。

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int i, int j){
    int x=0;
    int y=0;
    int win=0;
    while(win<row*col-MineNum)
    {
        printf("输入排查的坐标(x y)");
        scanf("%d %d",&x,&y);
        if (x >= 1 && x <= row && y >= 1 && y <= col)
        {
            if (mine[x][y]=='1') {
                printf("您输了。\n");
                DisplayBoard(mine,row,col);
                printf("再玩一把吗?\n");
                break;
            }
            else
            {
                int count = GetMineCount(mine,x,y);
                show[x][y]=count+'0';
                DisplayBoard(show,row,col);
                win++;
            }
        }
        else
            printf("非法坐标,请重新输入>\n");


    }
    if(win==row*col-MineNum)
    {
        printf("你赢了!\n");
        DisplayBoard(mine,row,col);
        printf("再玩一把吗?\n");

    }
}

        至此,主要函数均已完成,接下来,只需要在game()中正确的调用函数,正确的传参,即可完成。

2.8 game()

void game(void){

    DifChoose();//难度选择程序 DifChoose() 并通过此程序选择的难度设置COL等变量的值
    char mine_board[ROWS][COLS];
    char display_board[ROWS][COLS];

    InitBoard(mine_board,ROWS,COLS,'0');
    InitBoard(display_board,ROWS,COLS,'*');//InitBoard():棋盘初始化程序:一个底层棋盘记录地雷的坐标,上层棋盘展示给用户
    No_serialDisplay(mine_board,ROWS,COLS);
    No_serialDisplay(display_board,row,col);

    PlaceMine(mine_board,row,col);
    //PlaceMine():埋雷程序,地雷放置在底层棋盘中

    No_serialDisplay(mine_board,ROWS,COLS);
    //写一个No_serialDisplay()函数,用以在编写程序时观察。

    DisplayBoard(display_board,row,col);
    FindMine(mine_board,display_board,row,col);
    //GetMineCount():排查雷时,数周围的雷的个数的程序
    //FindMine():找雷程序,中间使用GetMineCount()程序。游戏成功失败由此函数判断
}

03 所有函数代码

        至此,程序已经完成,解决打印的小问题后,程序调试完毕。

所有的函数代码:

头文件game.h


#ifndef MINE_GAME_H
#define MINE_GAME_H

#endif //MINE_GAME_H
#include "stdlib.h"
#include "stdio.h"
#include "time.h"

int ROWS;
int COLS;
//代表下层棋盘
int row;
int col;
//代表上层的棋盘
int MineNum;//雷的个数
int r,c;//用以函数传参


void menu(int n);

void game(void);

void DifChoose(void);
void InitBoard(char board[ROWS][COLS],int i,int j,char set);
void PlaceMine(char board[ROWS][COLS],int i,int j);
void DisplayBoard(char board[ROWS][COLS], int i, int j);

int GetMineCount(char board[ROWS][COLS],int x,int y);

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

源文件game.c:


#include "game.h"

void menu(int n)
{
    if (n==-1)
    {
        printf("*************************\n");
        printf("******    1.play    ********\n");
        printf("******    0.exit    ********\n");
        printf("*************************\n");
        printf("请选择>");

    }
}
void DifChoose(void){
    int level;
    printf("*************************\n");
    printf("*****请选择游戏难度*****\n");
    printf("*****    11.easy     ******\n");
    printf("*****    22.hard     ******\n");
    printf("*************************\n");
    printf("请选择>");
    scanf("%d",&level);
    if (level==11)
    {
        ROWS = 11;
        COLS = 11;
        row=9;
        col=9;
        MineNum=10;
        r = 9;
        c = 9;
    }
    if(level==22)
    {
        ROWS = 18;
        COLS = 18;
        row=16;
        col=16;
        MineNum=40;
        r = 16;
        c = 14;
    }
}

void InitBoard(char board[ROWS][COLS],int i,int j,char set)
{
    for(int x=0;x<i;x++)
    {
        for(int y=0;y<j;y++)

            board[x][y]=set;

    }
}
void No_serialDisplay(char board[ROWS][COLS],int i,int j)//调试用函数,不在头文件中声明
{
    printf("Inside No_serialDisplay\n");
    for (int x=0;x < i;x++) {
        for (int y = 0; y < j; y++) {
            printf("%c ", board[x][y]);
        }
        printf("\n");
    }
}
void PlaceMine(char board[ROWS][COLS],int i,int j )//board是放雷的,i和j是小的排和列
{
    int count= MineNum;
   while (count)
   {
       // printf("%d ",count);
        int x = rand()%i+1;
        int y=rand()%j+1;
        if(board[x][y]=='0')
        {
            printf("%d %d\n",x,y);
            board[x][y] = '1';
            count--;
        }
    }
}
void DisplayBoard(char board[ROWS][COLS], int i, int j)
{
    printf("*********MineGame************\n");
    int x ;
    for (x = 0; x <= i; x++)
    {
        printf("%d", x);
    }
    printf("\n");
    for ( x = 1; x < i; x++)
    {
        printf("%d", x ); // 打印行号
        for (int y = 1; y < j; y++)
        {
            printf("%c", board[x][y]);
        }
        printf("\n");
    }
}
int GetMineCount(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 FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int i, int j){
    int x=0;
    int y=0;
    int win=0;
    while(win<row*col-MineNum)
    {
        printf("输入排查的坐标(x y)");
        scanf("%d %d",&x,&y);
        if (x >= 1 && x <= row && y >= 1 && y <= col)
        {
            if (mine[x][y]=='1') {
                printf("您输了。\n");
                DisplayBoard(mine,row,col);
                printf("再玩一把吗?\n");
                break;
            }
            else
            {
                int count = GetMineCount(mine,x,y);
                show[x][y]=count+'0';
                DisplayBoard(show,row,col);
                win++;
            }
        }
        else
            printf("非法坐标,请重新输入>\n");


    }
    if(win==row*col-MineNum)
    {
        printf("你赢了!\n");
        DisplayBoard(mine,row,col);
        printf("再玩一把吗?\n");

    }
}

//++++++++++++++++++++++++++++++++++++++++++++++
void game(void){

    DifChoose();//难度选择程序 DifChoose() 并通过此程序选择的难度设置COL等变量的值
    char mine_board[ROWS][COLS];
    char display_board[ROWS][COLS];

    InitBoard(mine_board,ROWS,COLS,'0');
    InitBoard(display_board,ROWS,COLS,'*');//InitBoard():棋盘初始化程序:一个底层棋盘记录地雷的坐标,上层棋盘展示给用户
    No_serialDisplay(mine_board,ROWS,COLS);
    No_serialDisplay(display_board,row,col);

    PlaceMine(mine_board,row,col);
    //PlaceMine():埋雷程序,地雷放置在底层棋盘中

    No_serialDisplay(mine_board,ROWS,COLS);
    //写一个No_serialDisplay()函数,用以在编写程序时观察。

    DisplayBoard(display_board,row,col);
    FindMine(mine_board,display_board,row,col);
    //GetMineCount():排查雷时,数周围的雷的个数的程序
    //FindMine():找雷程序,中间使用GetMineCount()程序。游戏成功失败由此函数判断
}

源文件run.c:


#include "game.h"

int main(){
    int MenuCommand;
    srand((unsigned int)time(NULL));
    do {
        MenuCommand = -1;
        menu(MenuCommand);
        scanf("%d",&MenuCommand);
        switch(MenuCommand) {
            case 1:
                game();
                break;
            case 0:
                break;
            default:
                printf("输入错误,请重新输入\n");
                break;

        }
    }while(MenuCommand);

    return 0;
}

04 其他

        总体而言,扫雷游戏对于初学者而言,是一个难度较大的编程项目,我觉得难度主要在于大量的参数传递,在这个过程中,很容易出现问题,特别是如果要区分游戏难度的话。因为区分游戏难度会让ROW、CO L等参数的值无法在一开始就通过define定义,在后续传参容易出现差错。

        另外,展开一片的功能我思考了一下,可能需要通过递归或者遍历来实现。但我尝试着写了一下,导致程序卡死TT。现在我开学了,时间较紧,我会在之后慢慢把展开一片、标记雷、计时的功能补上。

        总之,完成了一个对现在的我来说有难度的项目,让我十分满足。

        难免有疏漏之处,欢迎大家探讨、指正。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值