喜欢扫雷但代码......(今天又死了一遍呢)

恭喜你被标题骗进来了!现在就开始我们通俗易懂的扫雷代码讲解吧!(讲的不好别骂我,我很脆弱的!)

一. 扫雷游戏分析和设计

写扫雷代码前我们需要先将它完整的分析一遍,这样能方便你更流畅的写代码有以及更好的理解代码内涵。

扫雷游戏的功能说明

1. 首先,我们需要使用控制台实现经典的扫雷游戏(以防有同学不知道程序台是什么:↑  这就是程序台)

2. 然后我们需要选择退出游戏还是继续玩,这就导致我们需要在代码中写出一个简易的菜单menu。由于我们已经学过函数的嵌套调用了,menu就可以用嵌套调用来写。

嵌套调用:在长代码中使用嵌套调用能让我们更直观的看懂代码,使代码更简洁,易懂。

3. 继续设计扫雷的棋盘数 R*R,可以是基础版的9*9,也可以进阶(you know的~)

4. 默认随机布置雷的个数 X,想要多少就多少

5. 排查雷

  如果位置不是雷,就显示周围有几个雷
◦   如果位置是雷,就炸死,游戏结束
◦   把除 X 个雷之外的所有非雷都找出来,排雷成功,游戏结束
                                                         ↑                 扫雷初始界面
   
                                          ↑      排雷界面
                    ↑     游戏结束界面
二. 游戏的分析和设计
1. 数据结构的分析
扫雷的基本玩法: 根据屏幕上显示的数字猜测雷的位置以及标记它。在 扫雷的过程中,我们需要找出布置的雷和我们排出的雷的信息并存储它。
因为我们需要在棋盘上布置雷的信息和排查雷,我这里创建⼀个9*9的数组来存放信息。
                                       空棋盘
那如果这个位置布置雷,我们就存放1,没有布置雷就存放0。
                                                            布置雷的棋盘
假设我们排查(2,5)这个坐标时,我们访问周围的一圈8个黄色位置,统计周围雷的个数是1
                             排雷的假设
假设我们排查(8,6)这个坐标时,我们访问周围的⼀圈8个黄色位置,统计周围雷的个数时,最下⾯的三个坐标就会越界,为了防止越界,我们在设计的时候,给数组扩大一圈,雷还是布置在中间的9*9的坐标上,周围⼀圈不去布置雷就行,这样就解决了越界的问题。
所以我们将存放数据的数组创建成11*11 是比较合适。
                          周围加上一圈的棋盘
再继续分析,我们在棋盘上布置了雷,棋盘上雷的信息(1)和非雷的信息(0),假设我们排查了某一个位置后,这个坐标处不是雷,这个坐标的周围有1个雷,那我们需要将排查出的雷的数量信息记录存储,并打印出来,作为排雷的重要参考信息的。那这个雷的个数信息存放在哪里呢?如果存放在布置雷的数组中,这样雷的信息和雷的个数信息就可能或产生混淆和打印上的困难。
一句话就是: 排雷的位置和雷的位置如何分辨呢?
这里我们肯定有办法解决,比如:雷和非雷的信息不要使用数字,使用某些字符就行,这样就避免冲突了,但是这样做棋盘上有雷和非雷的信息,还有排查出的雷的个数信息,就比较混杂,不够方便。
这里我们采用另外⼀种方案,我们专门给一个棋盘(对应⼀个数组mine)存放布置好的雷的信息,再给另外⼀个棋盘(对应另外一个数组show)存放排查出的雷的信息。这样就互不干扰了,把雷布置到mine数组,在mine数组中排查雷,排查出的数据存放在show数组,并且打印show数组的信息给后期排查参考
同时为了保持神秘,show数组开始时初始化为字符 '*',为了保持两个数组的类型⼀致,可以使用同一套函数处理,mine数组最开始也初始化为字符'0'布置雷改成'1'。如下如:
                  mine数组布置雷后的状态    
                   show输出初始化的状态
对应的数组应该是:
                      
char mine[ 11 ][ 11 ] = { 0 }; // ⽤来存放布置好的雷的信息
char show[ 11 ][ 11 ] = { 0 }; // ⽤来存放排查出的雷的个数信息
2. 文件结构设计
之前学习了多文件的形式对函数的声明和定义,这里我们实践一下,我们设计三个文件:
1. test.c       //文 件中写游戏的 测试逻辑
2. game.c   //文 件中 写游戏中函数的实现
3. game.h   //文 件中 写游戏需要的数据类型和函数声明
首先,当然是创建test.c函数
(test.c)

#define _CRT_SECURE_NO_WARNINGS 1

int main()
{
    test();
    //整个游戏的逻辑
    return 0;
}

在这里我们直接将扫雷需要的所有代码存放到test()里,方便后续理解,因为扫雷代码也挺多的。

(test.c)

int input=0;
//定义input为雷

玩扫雷我们肯定需要游戏的选择面板:那么我们就从最基础的进入或退出开始。

创建一个菜单menu来储存进入或退出选项,如下。

(test.c)

void menu()
{
    printf("*******************************\n");
    printf("*****        0.exit        *****\n");
    printf("*****        1.play        *****\n");
    printf("*******************************\n");
}
    //选择0退出,选择1进入

接着使用do while()循环控制菜单的选择,配合switch语句搭配结果。

(test.c)

do
{
    menu();
    printf("请选择:");
    scanf("%d", &input);
    switch (input)
    {
    case 0:
        printf("游戏结束,退出游戏\n");
        break;
    case 1:
        printf("扫雷开始\n");
        break;
    }
} while (input);//input为0跳出

我们在输入input值时可能输入错误,输成了别的数字或什么,我们需要考虑到这层情况,面对这种情况我们可以在case 1:后增加选项。

(test.c)  

do
  {
      menu();
      printf("请选择:");
      scanf("%d", &input);
      switch (input)
      {
      case 0:
          printf("游戏结束,退出游戏\n");
          break;
      case 1:
          printf("扫雷开始\n");
          break;
      default :
          printf("选择错误,请重新输入:\n");
          break;
      }
  } while (input);//input为0跳出

这样就解决了。

既然是扫雷游戏,我们就需要创造最关键的game.c()代码,将game()植入到case 1:中。

(test.c)

  do
  {
      menu();
      printf("请选择:");
      scanf("%d", &input);
      switch (input)
      {
      case 0:
          printf("游戏结束,退出游戏\n");
          break;
      case 1:
          printf("扫雷开始\n");
          game();
          break;
      default :
          printf("选择错误,请重新输入:\n");
          break;
      }
  } while (input);//input为0跳出

我们需要布置的雷是随机的,在这里我们使用    srand((unsigned int)time(NULL));      来生成随机数(至于为什么用这个代码,同学们可以去专门的博客那去看,我这里就不赘述了。)

(test.c)

void test()
{
    int input = 0;
    //随机放置雷
    srand((unsigned int)time(NULL));
    do
    {
        menu();
        printf("请选择:");
        scanf("%d", &input);
        switch (input)
        {
        case 0:
            printf("游戏结束,退出游戏\n");
            break;
        case 1:
            printf("扫雷开始\n");
            game();
            break;
        default :
            printf("选择错误,请重新输入:\n");
            break;
        }
    } while (input);//input为0跳出
}


 有了test.c文件后,我们需要创建test.c代码中适配的库函数,这些库函数就存放在game.h头文件中。

(game.h)

#pragma once
#include <stdio.h>
//printf需要的头文件
#include <stdlib.h>
//rand需要的头文件(rand生成随机值)
#include <time.h>
//time需要的头文件

扫雷代码需要格子,也就存在行和列,因此我们在头文件中定义他们。

(game.h)​

#pragma once
#include <stdio.h>
//printf需要的头文件
#include <stdlib.h>
//rand需要的头文件(rand生成随机值)
#include <time.h>
//time需要的头文件

#define ROW 9  
// 行
#define COL  9
// 列

定义ROW为行,COL为列。因为我们创建的是9*9的扫雷形式,就定义ROW为9,COL为9,则ROW和COL是摆放数据的格数。但是由于我们先前因为越界问题将它的总格数改为了11*11,因此还需要再定义总格数。

(game.h)​

#pragma once
#include <stdio.h>
//printf需要的头文件
#include <stdlib.h>
//rand需要的头文件(rand生成随机值)
#include <time.h>
//time需要的头文件

#define ROW 9  
// 行
#define COL  9
// 列

#define ROWS ROW+2
//总行数
#define COLS COL+2
//总列数

我们不需要再定义,在原代码的基础上修改就可以了。

(game.h)​

#pragma once
#include <stdio.h>
//printf需要的头文件
#include <stdlib.h>
//rand需要的头文件(rand生成随机值)
#include <time.h>
//time需要的头文件

#define ROW 9  
// 行
#define COL  9
// 列

#define ROWS ROW+2
//总行数
#define COLS COL+2
//总列数

//雷数量
#define MineCount 10

现在定义雷的数量,我们这边选择定义10个雷。

在test.c中的game函数定义mine和show,因为我在show和mine中存放的是字符,则使用char定义。

(test.c)

void game()
{
    //完成扫雷游戏
    //mine数组中存放布置好的雷的信息
    char mine[ROWS][COLS] = { 0 };//数组全部初始化为'0'

    //show数组中存放排查出的雷的信息
    char show[ROWS][COLS] = { 0 }; //数组全部初始化为'* '
}

我们需要将mine和show中的函数全部进行初始化为对应的'0'和'*',则我们需要先定义对应的代码,这个代码我们可以在game.h()文件中定义,再运用在test.c()和game.c()中。则test.c()和game.c()都需要调用我自创的头文件。如上图所示。在C语言中如果需要调用自创的头文件,需要使用"头文件"的格式。

我们就可以将我们所需要的头文件都写在game.h()中。

(game.h)​

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

#define ROW 9  // 行
#define COL  9//   列

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

//雷数量
#define MineCount 10

以上就是我们所需的全部头文件。

我们在test.c()中的game()处写出初始化棋盘并在game.h()中进行声明,再到game.c()实现运用。

在我们对棋盘进行初始化的时候,我们需要用到数组传参,假设棋盘初始化函数是 InitBoard,将mine,show放入初始化函数里,因为这俩都是二维数组,所以行和列也需要表示,所以再依次放入 ROWSCOLS,最后放入的'0'和'*'我们在函数测试时会使用到。

(test.c)

void game()
{
    //完成扫雷游戏
    //mine数组中存放布置好的雷的信息
    char mine[ROWS][COLS] = { 0 };//数组全部初始化为'0'

    //show数组中存放排查出的雷的信息
    char show[ROWS][COLS] = { 0 }; //数组全部初始化为'* '
    // 初始化棋盘
    InitBoard(mine, ROWS, COLS, '0');
    InitBoard(show, ROWS, COLS, '*');
 }

接下来在game.h()中对 InitBoard 进行声明

(game.h)

//声明初始化棋盘
void InitBoard(char arr[ROWS][COLS], int rows, int cols,char set);
           //        mine               ROWS      COLS     '0'
           //        show               ROWS      COLS     '*'

在game.c()写出棋盘初始化所需的全部代码。

(game.c)

//初始化棋盘
void InitBoard(char arr[ROWS][COLS], int rows, int cols, char set)
{
	int i = 0;
	for (i = 0; i < rows; i++)
	{
		int j = 0;
		for (j = 0; j < cols; j++)
		{
			arr[i][j] = set;
		}
	}
}

现在再假设打印函数为  DisplayBoard ,同上。

在test.c()中打印 DisplayBoard

(test.c)

//打印棋盘  
DisplayBoard(mine, ROW, COL);
DisplayBoard(show, ROW, COL);

在game.h()中声明。

(game.h)

//打印棋盘
void DisplayBoard(char arr[ROWS][COLS], int row, int col);
               //     mine               ROW      COL 
               //     show               ROW      COL         

在game.c()中写出打印棋盘所需的全部代码。

(game.c)

//打印棋盘
void DisplayBoard(char arr[ROWS][COLS], int row, int col)
{
	int i = 0;
	printf("————扫雷游戏————\n");
	for (i = 0; i <= col; i++)//行
	{
		printf("%d ", i);
	}
	printf("\n");
	for (i = 1; i <= row; i++)
	{
		int j = 0;
		printf("%d ", i);
		for (j = 1; j <= col; j++)
		{
			printf("%c ", arr[i][j]);
		}
		printf("\n");
	}
}

打印出来长这样。

上面是可以看见的棋盘,下面是我们需要猜测的棋盘。

接下来假设 SetMine 为布置雷的函数。

(test.c)

 //布置雷
 //在9*9的棋盘上布置10个雷
SetMine(mine, ROW, COL);

继续在game.h()中声明函数。

(game.h)

//布置雷的信息 
void SetMine(char arr[ROWS][COLS], int row, int col);
          //          mine            ROW     COL

为什么我们只在mine函数里布置雷呢,而不在show里面布置雷呢?因为我们在mine函数里布置雷是为了让我们知道我们的扫雷代码有没有写对,我们后面会把mine函数和show函数写在一起,这样就不需要再在show里面布置雷了。

在game.c()中写出布置雷所需的全部代码。

(test.c)

void SetMine(char arr[ROWS][COLS], int row, int col)
{
	int count =MineCount ;
    //         雷的数量 10
	while(count)
	{
		int x =  rand () % row + 1;(数字范围1-9)
		int y =  rand () % col  + 1;
              // rand表示随机布置雷
		//不是雷,则可以布置雷
		if (arr[x][y] == '0')
		{
			arr[x][y] = '1';
			count--;
		}
	}
}

打印雷如下。

如图所示,取图中的一个九宫格,创建X,Y。X为行,Y为列。

在test.c()中创造一个 GetMineCount 函数,此函数用于分辨每个九宫格中的数旁有几个雷。

(test.c)

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

{
	int i = 0;
          //行
	int count = 0;
	for (i = x - 1; i <= x + 1; i++)
	{
		int j = 0;
		for (j = y - 1; j <= y + 1; j++)
		{
			count+=(mine[i][j]-'0');
      //'1'的ASCII码值为49,'0'的ASCII码值为48,则'1'-'0'=1;
      //这样就可以表达出mine[i][j]周围的雷的个数
		}
	}
	return count;
}

在test.c()中假设 FindMine 为排查雷的函数。

  FindMine(mine, show, ROW, COL);
  // 找雷的信息

在game.h()中声明函数。

(game.h)

//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
(game.c)

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = 0;
	// 判断坐标的有效性
	while (win<row*col- MineCount)
	{
		printf("请输入要排查的坐标:\n");
		scanf("%d %d", &x, &y);
		int count=0;
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (show[x][y] == '*')
			{
				if (mine[x][y] == '1')
				{
					printf("很遗憾,你被炸死了\n");
					DisplayBoard(mine, ROW, COL);
					//打印雷的位置
					break;
				}
				else
					//该坐标不是0,统计坐标周围有几个0
				{
					count = GetMineCount(mine, x, y);
					show[x][y] = count + '0';
					DisplayBoard(show, ROW, COL);
					win++;
				}
			}
			else
			{
				printf("该坐标已被排查,请重新输入\n");
                //考虑输入重复坐标
			}
		}
		else
		{
			printf("坐标非法,请重新输入:\n");
		}
	}
	if (win == row * col - MineCount)
	{
		printf("恭喜你,获胜了\n");
		DisplayBoard(mine, ROW, COL);
	}
}

则总函数如下。

test.c()中总函数。

#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"



void menu()
{
    printf("*******************************\n");
    printf("*****                0.exit              *****\n");
    printf("*****                1.play              *****\n");
    printf("*******************************\n");
}
void game()
{
    //完成扫雷游戏
    //mine数组中存放布置好的雷的信息
    char mine[ROWS][COLS] = { 0 };//数组全部初始化为'0'

    //show数组中存放排查出的雷的信息
    char show[ROWS][COLS] = { 0 }; //数组全部初始化为'* '
    // 初始化棋盘
    InitBoard(mine, ROWS, COLS, '0');
    InitBoard(show, ROWS, COLS, '*');
   
    //布置雷
    //在9*9的棋盘上布置10个雷
   SetMine(mine, ROW, COL);
    //先布置再打印
    DisplayBoard(mine, ROW, COL);

    //打印棋盘
    DisplayBoard(show, ROW, COL);

    FindMine(mine, show, ROW, COL);
}
    //初始化mine数组
void test()
{
    int input = 0;
    //随机放置雷
    srand((unsigned int)time(NULL));
    do
    {
        menu();
        printf("请选择:");
        scanf("%d", &input);
        switch (input)
        {
        case 0:
            printf("游戏结束,退出游戏\n");
            break;
        case 1:
            printf("扫雷开始\n");
            game();
            break;
        default :
            printf("选择错误,请重新输入:\n");
            break;
        }
    } while (input);//input为0跳出
}
int main()
{
    test();
    //整个游戏的逻辑
    return 0;
}

game.c()中总代码。

#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"


//初始化棋盘
void InitBoard(char arr[ROWS][COLS], int rows, int cols, char set)
{
	int i = 0;
	for (i = 0; i < rows; i++)
	{
		int j = 0;
		for (j = 0; j < cols; j++)
		{
			arr[i][j] = set;
		}
	}
}

//打印棋盘
void DisplayBoard(char arr[ROWS][COLS], int row, int col)
{
	int i = 0;
	printf("————扫雷游戏————\n");
	for (i = 0; i <= col; i++)//行
	{
		printf("%d ", i);
	}
	printf("\n");
	for (i = 1; i <= row; i++)
	{
		int j = 0;
		printf("%d ", i);
		for (j = 1; j <= col; j++)
		{
			printf("%c ", arr[i][j]);
		}
		printf("\n");
	}
}

void SetMine(char arr[ROWS][COLS], int row, int col)
{
	int count =MineCount ;
	while(count)
	{
		int x =  rand () % row + 1;
		int y =  rand () % col  + 1;
		//不是雷,则可以布置雷
		if (arr[x][y] == '0')
		{
			arr[x][y] = '1';
			count--;
		}
	}
}
 int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
	int i = 0;
	int count = 0;
	for (i = x - 1; i <= x + 1; i++)
	{
		int j = 0;
		for (j = y - 1; j <= y + 1; j++)
		{
			count+=(mine[i][j]-'0');
		}
	}
	return count;
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = 0;
	// 判断坐标的有效性
	while (win<row*col- MineCount)
	{
		printf("请输入要排查的坐标:\n");
		scanf("%d %d", &x, &y);
		int count=0;
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (show[x][y] == '*')
			{
				if (mine[x][y] == '1')
				{
					printf("很遗憾,你被炸死了\n");
					DisplayBoard(mine, ROW, COL);
					//打印雷的位置
					break;
				}
				else
					//该坐标不是0,统计坐标周围有几个0
				{
					count = GetMineCount(mine, x, y);
					show[x][y] = count + '0';
					DisplayBoard(show, ROW, COL);
					win++;
				}
			}
			else
			{
				printf("该坐标已被排查,请重新输入\n");
			}
		}
		else
		{
			printf("坐标非法,请重新输入:\n");
		}
	}
	if (win == row * col - MineCount)
	{
		printf("恭喜你,获胜了\n");
		DisplayBoard(mine, ROW, COL);
	}
}

game.h()中总代码。

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

#define ROW 9  // 行
#define COL  9//   列

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

//雷数量
#define MineCount 10

//声明函数
//棋盘初始化的函数

void InitBoard(char arr[ROWS][COLS], int rows, int cols,char set);

//打印棋盘
void DisplayBoard(char arr[ROWS][COLS], int row, int col);

//布置雷的信息 
void SetMine(char arr[ROWS][COLS], int row, int col);

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值