1.前言
1.1扫雷游戏的介绍
《扫雷》是一款大众类的益智小游戏,于1992年发行。游戏目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。如下图是我们现实版所见的扫雷游戏。
1.2模块化编程
模块化概念:模块化是指解决一个复杂问题时自顶向下逐层把系统划分成若干模块的过程,有多种属性,分别反映其内部特性。
模块化编程:
软件模块是一套一致而互相有紧密关连的软件组织。它分别包含了程序和数据结构两部份。 现代软件开发往往利用模块作合成的单位。
模块的接口表达了由该模块提供的功能和调用它时所需的元素。
模块是可能分开地被编写的单位。这使他们可再用和允许广泛人员同时协作、编写及研究不同的模块。
1.3用模块化编程实现扫雷游戏
1.用.h文件来放头文件和需要声明的函数和宏定义。
2.用两个.c文件,一个游戏如何运行逻辑的实现,另一个实现游戏里的规则和逻辑的实现以及其他相关函数的实现。
2. 游戏运行逻辑和游戏内部逻辑的分析理解
步骤一: 设置游戏菜单,颜色背景的设置。菜单内容为开始游戏,退出游戏,游戏玩法的介绍。这里便是游戏的运行逻辑。
步骤二: 棋盘的初始化和打印
步骤三: 布置雷
步骤四: 排雷
3.实现游戏逻辑运行的过程和步骤
3.1步骤一:
这里先介绍case2,case0,default子句来介绍游戏运行逻辑,游戏内部实现放在case1中,下面步骤实现。
使用字符a来控制游戏的逻辑运行。
void text()
{
int a; //a用来控制进入的游戏选项。
srand((unsigned int)time(NULL)); //生成随机数,排雷设雷用
do
{
menu(); //菜单定义到里面以便每次都执行,
printf("请选择选项:\n");
scanf_s("%d", &a);
switch (a)
{
case 1:
game();
break;
case 0:
printf("您已退出游戏!\n");
break;
case 2:
instruction();
printf("\n");
printf("\n");
printf("请稍等几秒钟。");
Sleep(20000);
system("cls"); //清屏,将上一次的数据消失。
printf("请重新选择游戏选项:\n");
break;
default:
printf("输入错误,请重新选择:\n");
Sleep(1000);
system("cls");
break;
}
} while (a);
}
//这里a要设置为全局变量,不然一次游戏玩完后就不能继续玩了。menu菜单也要进入到循环内部,不然下次不知道按啥了,这里要经常注意,设置游戏都要这样写菜单。
3.1.1菜单的设置,游戏运行逻辑的实现。
游戏菜单的实现
void menu()
{
color(10);
printf("*********************************************\n");
printf("****** 欢迎进入扫雷游戏 ******\n");
printf("*********************************************\n");
printf("****** 1.开 始 游 戏 ******\n");
printf("****** 0.退 出 游 戏 ******\n");
printf("****** 2.游 戏 说 明 ******\n");
printf("****** 其他键将重新选择 ******\n");
printf("*********************************************\n");
}
颜色函数。
使我们的数据看起来更好看。
void color(short x)
{
if (x >= 0 && x <= 15)
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), x);
else
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 3);
}
这里使用的函数来自#include<Windows.h>
颜色使用介绍:
0=黑色 8=灰色
1=蓝色 9=淡蓝色
2=绿色 10=淡绿色
3=湖蓝色 11=淡浅绿色
4=红色 12=淡红色
5=紫色 13=淡紫色
6=黄色 14=淡黄色
7=白色 15=亮白色
case 2 :中设置游戏玩法介绍 使用sleep(100000),让我们有充足的时间去看游戏玩法的说明,等待几秒后进入游戏选项界面。
case 2:
instruction();
printf("\n");
printf("\n");
printf("请稍等几秒钟。");
Sleep(20000);
system("cls"); //清屏,将上面的数据内容消失。
printf("请重新选择游戏选项:\n");
break;
Sleep(200000);使用了#include<Windows.h>库函数。
void instruction() //游戏玩法说明
{
printf(" 1 2 3 4 5 6\n");
printf("1 * * * * * *\n");
printf("2 * * * * * *\n");
printf("3 * * * * * *\n");
printf("4 * * * * * *\n");
printf("5 * * * * * *\n");
printf("6 * * * * * *\n");
printf("如上图,输入'*'的横纵坐标,\n如果显示出是0则周围八个格子没有雷,输入的这个位置也没有雷;\n如果是其他数字,则周围八个位置共有这些数字的雷数,输入数字的位置也没有雷,如果再输入,可能会碰到雷。我们所要做的就是找出不是雷的格子。\n");
}
case 0:实现退出游戏
case 0:
printf("您已退出游戏!\n");
break;
default:如果你不小心按错了,它会报输入错误,让你重新输入,增强了代码的健壮性。
default:
printf("输入错误,请重新选择:\n");
Sleep(1000);
system("cls");
break;
Sleep函数让你有时间看到你输入错误了,system清除没用的信息,使界面保持干净。之前没用过的小伙伴可以试一下。
3.2步骤二:棋盘的初始化和打印
雷和其他信息都保存到二维数组中。所以用二维数组进行棋盘的设置和打印。
定义棋盘的大小
#define ROWS 11;
#defines COLS 11;
棋盘数组的初始化
//这里的棋盘初始化比实际大,游戏内逻辑需要
void game() //游戏内部的实现
{
char mine[ROWS][COLS];//定义棋盘数组 '0'
char show[ROWS][COLS];//定义棋盘数组 '*'
mineboard(mine,ROWS,COLS,'0');
showboard(show,ROWS, COLS, '*');
}
void mineboard(char arr[ROWS][COLS],int rows, int cols, char ret)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
arr[i][j] = ret;
}
}
}
棋盘的打印
void printboard(char arr[ROWS][COLS], int row, int col)
{
printf("----------------- 扫雷游戏-----------------\n");
printf(" ");//控制坐标对齐,更改数字。用这个进行调整
for (int i = 1; i <= row; i++)
{
printf("%3d ", i); //横坐标目录
}
printf("\n");
for (int i = 1; i <= row; i++)
{
printf("%2d ", i); //纵坐标目录
for (int j = 1; j <= col; j++)
{
printf("%3c ", arr[i][j]);
}
printf("\n");
}
printf("----------------- 扫雷游戏-----------------\n");
}
这里用到了宏定义,确定棋盘大小。
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
3.3步骤三 布置雷
// printboard(mine, COL, ROW);真正游戏不需要这个函数,我们用它只是能看见雷在哪。
srand((unsigned int)time(NULL)); //生成随机数,排雷设雷用
这里调用了
#include<stdlib.h>
#include<time.h>
#define MINE_COUNT 10
布置雷
void setmine(char arr[ROWS][COLS], int row, int col)
{
int count = MINE_COUNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (arr[x][y] == '0')
{
arr[x][y] == '1';
count--;
}
}
}
3.4步骤四:排雷
如果没触碰雷,用到下面的函数
int getmine(char mine[ROWS][COLS], 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][y + 1] + mine[x + 1][y - 1] +
mine[x + 1][y] + mine[x + 1][y + 1] - 8 * '0';
}
排雷的逻辑运行
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- MINE_COUNT)
{
printf("请输入两个操作数:\n");
scanf_s("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <=col)
{
if (mine[x][y] =='1')
{
printf("很遗憾,你被炸死了!\n你的棋盘中雷分布如图:\n");
printboard(mine, ROW, COL);
printf("由于你失败了,罚你多等几秒钟,请稍等,正在为你清屏!");
Sleep(10000);
system("cls");
break;
}
else
{
int count = getmine(mine, x, y);
show[x][y] = count + '0';
system("cls");
printboard(show, ROW, COL);
win++;
}
}
else
{
printf("非法输入,请输入正确的坐标。\n");
}
}
if (win == row * col - MINE_COUNT)
{
printf("恭喜你,排雷成功\n");
printf("请稍等,正在为你清屏!");
Sleep(3000);
system("cls");
}
}
4.各个文件
text.c
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include"game.h"
void menu()
{
color(10);
printf("*********************************************\n");
printf("****** 欢迎进入扫雷游戏 ******\n");
printf("*********************************************\n");
printf("****** 1.开 始 游 戏 ******\n");
printf("****** 0.退 出 游 戏 ******\n");
printf("****** 2.游 戏 说 明 ******\n");
printf("****** 其他键将重新选择 ******\n");
printf("*********************************************\n");
}
void game() //游戏内部的实现
{
char mine[ROWS][COLS] = { 0 };//定义棋盘数组 '0'
char show[ROWS][COLS] = { 0 };//定义棋盘数组 '*'
mineboard(mine, ROWS, COLS, '0');
mineboard(show, ROWS, COLS, '*');
//printboard(mine, ROW, COL);//真正游戏不需要
printboard(show, ROW, COL);
setmine(mine, ROW, COL);
//printboard(mine, ROW, COL);//真正游戏不需要
findmine(mine, show, ROW, COL);
}
void text()
{
int a; //a用来控制进入的游戏选项。
srand((unsigned int)time(NULL)); //生成随机数,排雷设雷用
do
{
menu(); //菜单定义到里面以便每次都执行,
printf("请选择选项:\n");
scanf_s("%d", &a);
switch (a)
{
case 1:
game();
break;
case 0:
printf("您已退出游戏!\n");
break;
case 2:
instruction();
printf("\n");
printf("\n");
printf("请稍等几秒钟。");
Sleep(20000);
system("cls"); //清屏,将上一次的数据消失。
printf("请重新选择游戏选项:\n");
break;
default:
printf("输入错误,请重新选择:\n");
Sleep(1000);
system("cls");
break;
}
} while (a);
}
int main()
{
text();
return 0;
}
text.c主要放游戏的逻辑运行和游戏菜单需要的函数及玩法的介绍,还有game函数实现游戏内函数的调用。
这里调用了"game.h",里面包含了游戏内部的使用,这使得运行逻辑中case1 可以顺利进行。
还有一些系统的函数也在”game.h“里,使用使得游戏内容更加贴切。
game.c
//游戏内部逻辑实现
#define _CRT_SECURE_NO_WARNINGS
#include"game.h"
void color(short x) //期盼背景设置
{
if (x >= 0 && x <= 15)
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), x);
else
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 3);
}
void instruction() //游戏玩法说明
{
printf(" 1 2 3 4 5 6\n");
printf("1 * * * * * *\n");
printf("2 * * * * * *\n");
printf("3 * * * * * *\n");
printf("4 * * * * * *\n");
printf("5 * * * * * *\n");
printf("6 * * * * * *\n");
printf("如上图,输入'*'的横纵坐标,\n如果显示出是0则周围八个格子没有雷,输入的这个位置也没有雷;\n如果是其他数字,则周围八个位置共有这些数字的雷数,输入数字的位置也没有雷,如果再输入,可能会碰到雷。我们所要做的就是找出不是雷的格子。\n");
}
void mineboard(char arr[ROWS][COLS], int rows, int cols, char ret)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
arr[i][j] = ret;
}
}
}
void printboard(char arr[ROWS][COLS], int row, int col)
{
printf("----------------- 扫雷游戏-----------------\n");
printf(" ");//控制坐标对齐,更改数字。用这个进行调整
for (int i = 1; i <= row; i++)
{
printf("%3d ", i); //横坐标目录
}
printf("\n");
for (int i = 1; i <= row; i++)
{
printf("%2d ", i); //纵坐标目录
for (int j = 1; j <= col; j++)
{
printf("%3c ", arr[i][j]);
}
printf("\n");
}
printf("----------------- 扫雷游戏-----------------\n");
}
void setmine(char arr[ROWS][COLS], int row, int col)
{
int count = MINE_COUNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (arr[x][y] == '0')
{
arr[x][y] = '1';
count--;
}
}
}
int getmine(char mine[ROWS][COLS], 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][y + 1] + mine[x + 1][y - 1] +
mine[x + 1][y] + mine[x + 1][y + 1] - 8 * '0';
}
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- MINE_COUNT)
{
printf("请输入两个操作数:\n");
scanf_s("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <=col)
{
if (mine[x][y] =='1')
{
printf("很遗憾,你被炸死了!\n你的棋盘中雷分布如图:\n");
printboard(mine, ROW, COL);
printf("由于你失败了,罚你多等几秒钟,请稍等,正在为你清屏!");
Sleep(10000);
system("cls");
break;
}
else
{
int count = getmine(mine, x, y);
show[x][y] = count + '0';
system("cls");
printboard(show, ROW, COL);
win++;
}
}
else
{
printf("非法输入,请输入正确的坐标。\n");
}
}
if (win == row * col - MINE_COUNT)
{
printf("恭喜你,排雷成功\n");
printf("请稍等,正在为你清屏!");
Sleep(3000);
system("cls");
}
}
game.c这里包含了游戏内部的函数的实现。以及游戏内函数之间的调用。
game.h
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<Windows.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define MINE_COUNT 10
void color(short x); //期盼背景设置
void instruction();
void mineboard(char arr[ROWS][COLS], int rows, int cols, char ret);
void printboard(char arr[ROWS][COLS], int row, int col);
void setmine(char arr[ROWS][COLS], int row, int col);
int getmine(char mine[ROWS][COLS], int x, int y);
void findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
这里包含了游戏所用的头文件和宏定义,以及游戏内的函数名,使得text.c调用game.h便可以使用game.c里的函数。
总结:扫雷游戏的实现要理清楚思路,理解各个文件的调用关系,这是最主要的。否则即使一些内部逻辑实现了,程序是运行不起来的,我们学习这些项目就是要理清楚他们之间的调用关系,使自己的脑回路更加清晰,另外也可以接触到新的知识点从其他的学者身上学习他们的知识。我写的内容可能存在一些错误。如果还有一些错误的话希望伙计们指出来。多多指正,点点赞,支持我一下!