扫雷游戏分析和设计
1.扫雷游戏设计分析
1.1游戏功能说明
- 使用控制台实现经典的扫雷游戏
- 游戏棋盘为9X9
- 默认随即布置十个雷
- 可以通过每个方块对应的坐标排查雷
1.2游戏的设计分析
当我们在扫雷的过程中,布置的雷和排查出的不是雷的的方块的信息都要存储,我们首先想到的就是创建一个9*9的数组来存储信息。
如果这个位置布置雷,我们就存放1,没有布置雷就存放0
假如我们查询坐标(2,2),我们需要访问周围八个格子,统计周围有一个雷
但是假如我们排查坐标(3,8),当我们访问周围八个格子时,最下面三个格子就会越界
为了防止越界我们可以把棋盘扩大一圈,变成11X11,但在布置雷和扫雷时只能在中间9X9的区域进行操作
这样就解决了越界的问题,还能让每个格子对应坐标更准确。
我们继续分析,当我们在棋盘上布置了雷,雷的为1,非雷为0。假设我们排查了一个位置后,这个坐标周围有1个雷,那我需要将排查出雷的信息记录下来,并显示给玩家。那么这个信息应该存储在哪呢?如果我们存储在布置好雷的数组中,如果周围有一个雷,那么雷的信息和周围雷数量的信息就会产生混淆。
当然是有解决办法的啦:我们专门再创建一个棋盘(对应数组show),就用来存放排查出雷的信息,同时这个棋盘也展示给玩家。
而另一个棋盘(对应数组mine),就用来存放布置好雷的信息,这个棋盘不用来展示。
同时为了保持游戏性,show棋盘开始全部初始化为字符’*‘,为了保持数组类型一致,方便后续调用,我们将mine棋盘最开始也全部初始化为字符’0’,布置雷的地方为字符’1’
2.代码部分
2.1代码功能详细介绍
为了保持程序的高内聚低耦合,让当代码逻辑更清晰,我们这里可以设计三个文件:
test.c//文件中写游戏的测试逻辑
game.c//文件中写游戏中函数的实现等
game.h//文件中写游戏需要的数据类型的定义和函数声明等
2.1.1 game.h
首先从头game.h开始
#pragma once
//头文件包含,stdlib.h为随机数需要的文件,time.h为生成随机数时需要的种子的文件
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
//初始化棋盘
void InitBoard(char board[11][11],int rows,int cols,char set);
//展示棋盘
void DisplayBoard(char board[11][11],int row,int col);
//布置雷
void SetMine(char board[11][11],int row,int col);
//排查雷
void FindMine(char mine[11][11],char show[11][11],int row,int col);
.
.
2.1.2 test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include "game.h"
void menu()
{
printf("--------------------\n");
printf("********************\n");
printf("****** 1. paly******\n");
printf("****** 0. exit******\n");
printf("********************\n");
printf("--------------------\n");
}
int main()
{
int input = 0;
//生成随机数rand时需要的调用srand来设置种子,放在main函数中整个程序只需要调用一次
srand((unsigned int)time(NULL));
do
{
//打印菜单
menu();
printf("请选择>:\n" );
scanf("%d", &input);
switch (input)
{
case 1:
//输入1开始游戏
game();
break;
case 0:
//输入0退出游戏
printf("退出游戏\n");
break;
default:
printf("选择错误,重新选择>:\n");
break;
}
} while (input);//当输入0时,意味着选择退出游戏,同时跳出循环
return 0;
}
我们可以先运行一下测试代码逻辑:
输入1,进行游戏(这里game函数还没实现QAQ only test):
输入0,退出游戏:
.
.
test.c中game()函数
void game()
{
char mine[11][11];//存放雷和非雷的信息
char show[11][11];//存放排查出的信息
//初始化棋盘:mine数组最开始全是'0',show数组最开始全是'*'
//这里我们传递4个参:数组名、行、列和数组里最终初始化后的元素
InitBoard(mine, 11, 11, '0');
InitBoard(mine, 11, 11, '*');
//初始化完毕后,我们就要展示我们的棋盘啦;
DisplayBoard(show, 9, 9);//我们只展示中间9X9的区域
//然后布置雷到mine数组
SetMine(mine, 9, 9);
//扫雷开始!(排查雷)
FindMine(mine, show, 9, 9);
}
2.1.3 game.c
终于到了我们上面定义的函数的实现的部分了!接下来我们开始讲解这些函数是怎么实现的
- 初始化棋盘函数InitBoard:
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void InitBoard(char board[11][11], int rows, int cols, char set)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
//遍历每个元素,进行初始化,set接收这个棋盘里将要初始化的元素
//当我们初始化show棋盘时,将'0'传到set里,则初始化mine棋盘时,将'*'传到set
board[i][j] = set;
}
}
}
.
.
-展示棋盘函数DisplayBoard:
我们只需要打印出中间9X9的区域
当然我们也需要将行和列的下标给显示出来,方便后面扫雷
void DisplayBoard(char board[11][11], int row, int col)
{
int i = 0;
printf("-------------------\n");
for (i = 0; i <= 9; i++)
{
//打印行下标
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= 9; i++)
{
printf("%d ", i);//打印列下标
int j = 0;
for (j = 1; j <= 9; j++)
{
//通过遍历打印出中间9x9的元素
printf("%c ",board[i][j]);
}
printf("\n");
}
printf("-------------------\n");
}
测试运行:
-布置雷函数SetMine:
void SetMine(char board[11][11], int row, int col)
{
//同样的,我们也只能在中间9X9的区域布置雷
//布置十个雷
int count = 10;
while (count)
{
//使用rand函数生成随机数(x为行,y为列)
int x = rand() % row + 1;
int y = rand() % col + 1;
//当确保这个坐标没有雷(里面存的是'0')时,布置雷(改为'1')
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;//每布置完一个count--
}
}
}
这里我们可以查看一下布置完雷之后的棋盘:
.
.
-排查雷函数FindMine:
void FindMine(char mine[11][11], char show[11][11], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
//结束游戏的条件就是:每次成功排雷之后win就++当win的值等于整个棋盘减去雷的数量的值,扫雷成功
while (win<9*9-10)
{
printf("请输入要排查的坐标>:");
scanf("%d %d", &y, &x);
//判断输入的坐标是否为合法输入
if (x >= 1 && x <= 9 && y >= 1 && y <= 9)
{
//当输入的坐标对应的mine数组里存的是'1' 说明踩到雷了,游戏结束
if (mine[x][y] == '1')
{
printf("很遗憾,游戏失败QAQ\n");
break;
}
else
{
//当输入的坐标正确且没有踩到雷,我们就要排查这个坐标周围有几个雷,并且将周围雷的数量存在show数组中
//这里我们单独写一个函数来排查周围雷的数量,并返回到count中
int count = GetMine_count(mine, x, y);
//因为最终返回的是数字,我们要将其变为字符才能将其存到show数组中,且表达的数量不能变化
//这时我们就可以让其加上一个字符'0'。例如数字1加上一个字符'0'后变为字符'1'
show[x][y] = count + '0';
DisplayBoard(show, 9, 9);
win++;
}
}
else
{
printf("请输入正确坐标>:\n");
}
}
if (win == 9 * 9 - 10)
{
printf("恭喜你!排雷成功\n");
}
}
-排查周围雷数量的函数GetMine_count(嵌套在FindMine函数中):
如图所示当我们排查坐标(x,y)周围雷的数量时,因为这个是mine数组,此时里面存放的是字符’0’或’1’,又因为字符’0’减去数字0得到数字0,字符’01’减去数字1得到数字1(根据ACSII码表)。
所以我们将周围八个元素全部相加,最后再减去八个字符’0’,就得到周围雷的数量啦
int GetMine_count(char mine[11][11], int x, int y)
{
return mine[x - 1][y - 1] +
mine[x - 1][y] +
mine[x - 1][y + 1] +
mine[x][y + 1] +
mine[x + 1][y + 1] +
mine[x + 1][y] +
mine[x + 1][y - 1] +
mine[x][y - 1] - 8 * '0';
}
2.2 游戏界面
2.3 代码全貌
game.h
#pragma once
//头文件包含,stdlib.h为随机数函数rand需要的文件,time.h为生成随机数时需要的种子的文件
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
//初始化棋盘
void InitBoard(char board[11][11], int rows, int cols, char set);
//展示棋盘
void DisplayBoard(char board[11][11], int row, int col);
//布置雷
void SetMine(char board[11][11], int row, int col);
//排查雷
void FindMine(char mine[11][11], char show[11][11], int row, int col);
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include "game.h"
void menu()
{
printf("--------------------\n");
printf("********************\n");
printf("****** 1. paly******\n");
printf("****** 0. exit******\n");
printf("********************\n");
printf("--------------------\n");
}
void game()
{
char mine[11][11];//存放雷和非雷的信息
char show[11][11];//存放排查出的信息
//初始化棋盘:mine数组最开始全是'0',show数组最开始全是'*'
//这里我们传递4个参:数组名、行、列和数组里最终初始化后的元素
InitBoard(mine, 11, 11, '0');
InitBoard(show, 11, 11, '*');
//初始化完毕后,我们就要展示我们的棋盘啦;
DisplayBoard(show, 9, 9);//我们只展示中间9X9的区域
//然后布置雷到mine数组
SetMine(mine, 9, 9);
//DisplayBoard(mine, 9, 9);
//扫雷开始!(排查雷)
FindMine(mine, show, 9, 9);
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
//打印菜单
menu();
printf("请选择>:\n" );
scanf("%d", &input);
switch (input)
{
case 1:
//输入1开始游戏
printf("开始游戏!\n");
game();
break;
case 0:
//输入0退出游戏
printf("退出游戏\n");
break;
default:
printf("选择错误,重新选择>:\n");
break;
}
} while (input);//当输入0时,意味着选择退出游戏,同时跳出循环
return 0;
}
game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void InitBoard(char board[11][11], int rows, int cols, char set)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
//遍历每个元素,进行初始化,set接收这个棋盘里将要初始化的元素
board[i][j] = set;
}
}
}
void DisplayBoard(char board[11][11], int row, int col)
{
int i = 0;
printf("-------------------\n");
for (i = 0; i <= 9; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= 9; i++)
{
printf("%d ", i);
int j = 0;
for (j = 1; j <= 9; j++)
{
printf("%c ",board[i][j]);
}
printf("\n");
}
printf("-------------------\n");
}
void SetMine(char board[11][11], int row, int col)
{
//同样的,我们也只能在中间9X9的区域布置雷
//布置十个雷
int count = 10;
while (count)
{
//使用rand函数生成随机数(x为行,y为列)
int x = rand() % row + 1;
int y = rand() % col + 1;
//当确保这个坐标没有雷(里面存的是'0')时,布置雷(改为'1')
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;//每布置完一个count--
}
}
}
int GetMine_count(char mine[11][11], int x, int y)
{
return mine[x - 1][y - 1] +
mine[x - 1][y] +
mine[x - 1][y + 1] +
mine[x][y + 1] +
mine[x + 1][y + 1] +
mine[x + 1][y] +
mine[x + 1][y - 1] +
mine[x][y - 1] - 8 * '0';
}
void FindMine(char mine[11][11], char show[11][11], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
//结束游戏的条件就是:每次成功排雷之后win就++当win的值等于整个棋盘减去雷的数量的值,扫雷成功
while (win<9*9-10)
{
printf("请输入要排查的坐标>:");
scanf("%d %d", &y, &x);
//判断输入的坐标是否为合法输入
if (x >= 1 && x <= 9 && y >= 1 && y <= 9)
{
//当输入的坐标对应的mine数组里存的是'1' 说明踩到雷了,游戏结束
if (mine[x][y] == '1')
{
printf("很遗憾,游戏失败QAQ\n");
break;
}
else
{
//当输入的坐标正确且没有踩到雷,我们就要排查这个坐标周围有几个雷,并且将周围雷的数量存在show数组中
//这里我们单独写一个函数来排查周围雷的数量,并返回到count中
int count = GetMine_count(mine, x, y);
//因为最终返回的是数字,我们要将其变为字符才能将其存到show数组中,且表达的数量不能变化
//这时我们就可以让其加上一个字符'0'。例如数字1加上一个字符'0'后变为字符'1'
show[x][y] = count + '0';
DisplayBoard(show, 9, 9);
win++;
}
}
else
{
printf("请输入正确坐标>:\n");
}
}
if (win == 9 * 9 - 10)
{
printf("恭喜你!排雷成功\n");
}
}
谢谢支持!