目录
前言
一、基本实现逻辑
二、实现步骤
1. 我们希望在进入游戏时有一个菜单让我们选择
2. 我们希望可以重复的玩(一把玩完了还可以接着玩)
3. 采用多文件形式编程
4.要扫雷先得有棋盘(创建棋盘R*N)
5.初始化棋盘
6.打印棋盘
7.设置雷
8.排查雷
三、全部源码:
前言
上期我们介绍了三子棋小游戏,玩法也比较简单。这一期我们讨论的是扫雷小游戏。什么是扫雷小游戏?简而言之,就是在一个的棋盘上布置n个雷,把棋盘不显示让你来找雷的过程!OK,来一张图片看一看:
就是这样的一个游戏!下面我们就来用C语言简单实现一扫雷小游戏吧!
一、基本实现逻辑
还是和上期一样,在开始前我们先理清楚整个工程的思路然后在开始敲,不然一开始就狂敲到最后又是一堆的Bug那就不太好了! OK,我们下来梳理一下完成这个小游戏的基本逻辑:
(1)进入游戏时有一个菜单让我们选择。
(2)可以重复的玩(一把玩完了还可以接着玩)。
(3)采用多文件形式编程
(4)要扫雷先得有棋盘(创建棋盘9*9)
(5)初始化棋盘
(6)打印棋盘
(7)设置雷
(8)排雷
二、实现步骤
1. 我们希望在进入游戏时有一个菜单让我们选择
#include"game.h"
void menu()
{
printf("***********************\n");
printf("*******1. play ********\n");
printf("*******0. exit ********\n");
printf("***********************\n");
}
int main()
{
menu();
return 0;
}
2. 我们希望可以重复的玩(一把玩完了还可以接着玩)
这个我们已经在上一期中介绍过用do...while循环实现!
#include"game.h"
void menu()
{
printf("***********************\n");
printf("*******1. play ********\n");
printf("*******0. exit ********\n");
printf("***********************\n");
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择操作数:> ");
scanf("%d", &input);
switch (input)
{
case 1:
printf("扫雷!\n");
break;
case 0:
printf("退出游戏!\n");
break;
default :
printf("操作数非法!请重新输入!\n");
break;
}
} while (input);
return 0;
}
3. 采用多文件形式编程
首先为什么要采用多文件编程?
让文件功能相对简洁,不同文件进行执行不同的功能!也可减少代码的冗余!例如:#include<stdio.h>这个头文件和一些函数声明可能在多个不同的文件中使用,那么就得反复声明!为了减少这种情况,我们采用多文件形式编程,把相应的功能都封装好,谁用谁调即可!这大大的提高了简洁性和代码的复用性!
采用多文件编程的好处:
让对应的代码块的功能独立,后期调试的时候也相对容易!例如:game.h就专门放一些头文件和申明。game.c游戏实现的各种代码块。test.c用于测试!
4.要扫雷先得有棋盘(创建棋盘R*N)
我们分析,我们得要有两个棋盘(二维数组)----》一个用于存储雷!一个用于给用户展示用于排雷!另外,我们还要考虑到不能把数组的行和列给写死,不然后面改的话就会有很多的地方要改会极其麻烦!(例如,目前可能需要的是9*9,后期要是变需求要5*5,那改的地方可就很多了)因此,我们不能把写死,我们的建议是,用#define 定义的字符常量,后期即使需求改了,我们只需要改一个地方即可!还有就是扫雷的时候,实际是遍历数组,但如果遍历到最边上的话就没有8个位置空间让其遍历了,只时候就会越界(前面有一期“函数栈的帧创建与销毁”中可知道C语言中数组存在栈区,而数组开辟的空间实际在main函数的栈帧上,我们知道在没有初始化的栈帧是0hcccccc,所以一旦越界,它的值是不可控的),为了不让越界,我们把设置类的数组Mine设置为比展示数组show多一行一列就可以完美的解决这个问题了!
void game()
{
//创建数组
char mine[RS][CS] = { 0 };
char show[RS][CS] = { 0 };
}
#define R 9
#define C 9
#define RS R+2
#define CS C+2
5.初始化棋盘
我们想让 mine数组初始化为字符 0 , 让show 数组初始化为 *(给用户神秘感),为什么给mine数组是字符0而不是#等符号呢?后面在排雷的时候会解释清楚字符0的好处!另外,我们进行初始化的时候都采用RS行和CS列,打印的时候(show数组)让他打印R行C列就OK了。我么初始化两个棋盘要初始化两次,我们直接写一个函数专门用来初始化!
void InitBoard(char board[RS][CS], int rs, int cs, char c)
{
for (int i = 0; i < rs; i++)
{
for (int j = 0; j < cs; j++)
{
board[i][j] = c;
}
}
}
6.打印棋盘
和上面的初始化一样后期可能会有很多次的打印,我们如果每次都去实现就会造成代码的冗余!因此我们还是封装成函数,用的时候直接调即可!
void DisplayBoard(char board[RS][CS], int r, int c)
{
for (int i = 1; i <= r; i++)
{
for (int j = 1; j <= c; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
OK来看一下效果:
两个数组已经都创建好了!但似乎有点不太好,让人顶不住某个元素在几行几列,这就会让我们后期排雷产生影响!因此我么对此进行优化(版本2):
void DisplayBoard(char board[RS][CS], int r, int c)
{
for (int i = 0; i <= r; i++)
{
printf("%d ", i);
}
printf("\n");
printf(" -----------------\n");
for (int i = 1; i <= r; i++)
{
printf("%d|", i);
for (int j = 1; j <= c; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
这样的确比第一次好多了,但还是没有棋盘的好,我们直接打印成棋盘(版本3):
void DisplayBoard(char board[RS][CS], int r, int c)
{
printf("----------------扫雷游戏---------------\n");
//列号
for (int i = 0; i <= r; i++)
{
if (i == 0)
printf(" %d ", i);
else
printf(" %d ", i);
}
printf("\n");
printf("\n");
for (int i = 1; i <= r; i++)
{
printf(" %d ", i);//行号
for (int j = 1; j <= c; j++)
{
printf(" %c ", board[i][j]);
if (j <= c - 1)
{
printf("|");
}
}
printf("\n");
if (i <= r - 1)
{
printf(" ");
for (int j = 1; j <= c; j++)
{
printf("---");
if (j <= c - 1)
{
printf("|");
}
}
printf("\n");
}
}
printf("\n");
}
看效果:
这样就看起来好一点了!
7.设置雷
随机生成符合的坐标,然后在随机坐标处设置雷(字符1)
void SetMine(char mine[RS][CS], int r, int c)
{
int x = 0;
int y = 0;
x = rand() % r + 1;
y = rand() % c + 1;
int count = NUM;
while (count)
{
x = rand() % r + 1;
y = rand() % c + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
8.排查雷
我们等输入合理的坐标后进行排查,如果该位置是‘1’则显示被炸死!否则,对周围八个位置排查,我们一开始初始化的时候对mine是进行的初始化为'0',而我们知道一个数字 + 一个字符0就是对应的字符,反之一个字符 - '0', 就是对应的数字!因此我们可以直接对x,y周围的八个坐标进行相加然后 - 8*'0';便是其周围雷的个数!把这个数要放到show数组里面显示给用户(玩家)就得再上‘0’即可!如何判断排雷成功?我们采用计数器与总数之间的差值进行比对!设置一个变量计数器win 当成功排雷一次++,如果r*c-NUM(雷的数) 不大于win 后就说明把所有的空位全部排完了剩下的全部都是雷!此时说明排雷成功!
int GetCount(char mine[RS][CS], 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[RS][CS], char show[RS][CS], int r, int c)
{
int x = 0;
int y = 0;
int win = 0;
while (r * c- NUM > win )
{
printf("请输入要排查的坐标:> ");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= r && y >= 1 && y <= c)
{
if (mine[x][y] == '1')
{
printf("很遗憾你被炸死了!\n");
DisplayBoard(mine, R, C);
break;
}
else
{
int n = GetCount(mine, x, y);
show[x][y] = n + '0';
DisplayBoard(show, R, C);
win++;
}
}
else
{
printf("坐标非法,请重新输入!\n");
}
}
if (r * c - NUM == win)
{
printf("恭喜你,排雷成功!\n");
DisplayBoard(mine, R, C);
}
}
三、全部源码:
game.h
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<windows.h>
#define R 9
#define C 9
#define RS R+2
#define CS C+2
#define NUM 10
//初始化
void InitBoard(char mine[RS][CS], int rs, int cs, char c);
//打印
void DisplayBoard(char board[RS][CS], int r, int c);
//设置雷
void SetMine(char mine[RS][CS], int r, int c);
//排查雷
void FindMine(char mine[RS][CS], char show[RS][CS] ,int r, int c);
game.c
#include"game.h"
void InitBoard(char board[RS][CS], int rs, int cs, char c)
{
for (int i = 0; i < rs; i++)
{
for (int j = 0; j < cs; j++)
{
board[i][j] = c;
}
}
}
//void DisplayBoard(char board[RS][CS], int r, int c)
//{
// for (int i = 0; i <= r; i++)
// {
// printf("%d ", i);
// }
// printf("\n");
// printf(" -----------------\n");
// for (int i = 1; i <= r; i++)
// {
// printf("%d|", i);
// for (int j = 1; j <= c; j++)
// {
// printf("%c ", board[i][j]);
// }
// printf("\n");
// }
//}
void DisplayBoard(char board[RS][CS], int r, int c)
{
printf("----------------扫雷游戏---------------\n");
//列号
for (int i = 0; i <= r; i++)
{
if (i == 0)
printf(" %d ", i);
else
printf(" %d ", i);
}
printf("\n");
printf("\n");
for (int i = 1; i <= r; i++)
{
printf(" %d ", i);//行号
for (int j = 1; j <= c; j++)
{
printf(" %c ", board[i][j]);
if (j <= c - 1)
{
printf("|");
}
}
printf("\n");
if (i <= r - 1)
{
printf(" ");
for (int j = 1; j <= c; j++)
{
printf("---");
if (j <= c - 1)
{
printf("|");
}
}
printf("\n");
}
}
printf("\n");
}
void SetMine(char mine[RS][CS], int r, int c)
{
int x = 0;
int y = 0;
x = rand() % r + 1;
y = rand() % c + 1;
int count = NUM;
while (count)
{
x = rand() % r + 1;
y = rand() % c + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
int GetCount(char mine[RS][CS], 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[RS][CS], char show[RS][CS], int r, int c)
{
int x = 0;
int y = 0;
int win = 0;
while (r * c- NUM > win )
{
printf("请输入要排查的坐标:> ");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= r && y >= 1 && y <= c)
{
if (mine[x][y] == '1')
{
printf("很遗憾你被炸死了!\n");
DisplayBoard(mine, R, C);
break;
}
else
{
int n = GetCount(mine, x, y);
show[x][y] = n + '0';
DisplayBoard(show, R, C);
win++;
}
}
else
{
printf("坐标非法,请重新输入!\n");
}
}
if (r * c - NUM == win)
{
printf("恭喜你,排雷成功!\n");
DisplayBoard(mine, R, C);
}
}
test.c
#include"game.h"
void menu()
{
printf("***********************\n");
printf("*******1. play ********\n");
printf("*******0. exit ********\n");
printf("***********************\n");
}
void game()
{
//创建数组
char mine[RS][CS] = { 0 };
char show[RS][CS] = { 0 };
//初始化数组
InitBoard(mine, RS, CS, '0');
InitBoard(show, RS, CS, '*');
//打印数组
//DisplayBoard(mine, R, C);
DisplayBoard(show, R, C);
//设置雷
SetMine(mine, R, C);
//DisplayBoard(mine, R, C);
//排查雷
FindMine(mine,show, R, C);
}
int main()
{
system("color 3");
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
printf("请选择操作数:> ");
scanf("%d", &input);
switch (input)
{
case 1:
game();
//printf("扫雷!\n");
break;
case 0:
printf("退出游戏!\n");
break;
default :
printf("操作数非法!请重新输入!\n");
break;
}
} while (input);
return 0;
}
OK !好兄弟我们下期再见!