一、游戏前的准备
第一步,不管三七二十一,创建main()函数,没有main()函数,程序找不到运行的入口,所以代码一开始可以写成这样:
int main()
{
return 0;
}
第二步,我们玩大部分游戏,进去的第一个页面都是“开始游戏”or“退出游戏”这种菜单页面。我们能不能也整一个呢?于是代码变成了这样(用了printf()别忘了添加头文件stdio.h):
#include<stdio.h>
int main()
{
printf("********************\n");
printf("*** 1.继续游戏 ***\n");
printf("*** 2.退出游戏 ***\n");
printf("********************\n");
return 0;
}
运行一下看看:
第三步,这时候界面已经出来了,但是你发现不能控制,你希望输入1——开始游戏,输入2——退出游戏。有输入?你马上就想到了scanf()函数,输入1——开始游戏,输入2——退出游戏,输入其他数字——提醒输入有误,你马上想到用switch语句来实现多情况问题。这时候代码变成:
#include<stdio.h>
int main()
{
printf("********************\n");
printf("*** 1.继续游戏 ***\n");
printf("*** 2.退出游戏 ***\n");
printf("********************\n");
scanf("%d", &input);
switch (input)
{
case 1:
{
//游戏;
}
case 2:
{
//????
}
default:
{
printf("输入有误,请重新输入:");
}
return 0;
}
第四步,写着写着问题来了,输入为2的时候退出游戏?这一步要怎么实现,聪明的你又想到了——用循环,这个时候用do-while循环特别合适,因为你想进来就开玩,每玩玩一把后,你还可以选择是否继续游玩。于是代码变成这样:
#include<stdio.h>
int main()
{
printf("********************\n");
printf("*** 1.继续游戏 ***\n");
printf("*** 2.退出游戏 ***\n");
printf("********************\n");
scanf("%d", &input);
do
{
scanf("%d", &input);
switch (input)
{
case 1:
{
//游戏
}
case 2:
{
break;
}
default:
{
printf("输入有误,请重新输入:");
}
}
} while (input != 2);
return 0;
}
第五步,这还没开始,代码就开始冗长了,这时候能不能用函数来模块化?这时候你想把界面放到一个函数里面,游戏放到一个函数里面,会不会看起来要简便很多,答案是:是的,现在代码变成了这样:
main函数部分:
int main()
{
int input = 0;
print();
do
{
scanf("%d", &input);
switch (input)
{
case 1:
{
game();
again();
}
case 2:
{
break;
}
default:
{
printf("输入有误,请重新输入:");
}
}
} while (input != 2);
return 0;
}
开始菜单部分:
void print()
{
printf("********************\n");
printf("*** 1.开始游戏 ***\n");
printf("*** 2.退出游戏 ***\n");
printf("********************\n");
return;
}
继续游玩界面部分:
void again()
{
printf("********************\n");
printf("*** 1.继续游戏 ***\n");
printf("*** 2.退出游戏 ***\n");
printf("********************\n");
return;
}
游戏内容暂时写成这样,方便调试对不对,后续将着力开发这个:
void game()
{
printf("扫雷...\n");
}
别忘了头文件:
#include<stdio.h>
总的就是这样:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
//开始菜单
void print()
{
printf("********************\n");
printf("*** 1.开始游戏 ***\n");
printf("*** 2.退出游戏 ***\n");
printf("********************\n");
return;
}
//继续游玩?
void again()
{
printf("********************\n");
printf("*** 1.继续游戏 ***\n");
printf("*** 2.退出游戏 ***\n");
printf("********************\n");
return;
}
//游戏内容
void game()
{
printf("扫雷...\n");
}
int main()
{
int input = 0;
print();
do
{
scanf("%d", &input);
switch (input)
{
case 1:
{
game();
again();
}
case 2:
{
break;
}
default:
{
printf("输入有误,请重新输入:");
}
}
} while (input != 2);
return 0;
}
运行一下看看:
没问题,输入1、2以外的数字提示输入有误,输入1开始游玩,输入2退出游戏。
二、游戏分析
我们分析一下扫雷游戏,
以简单版的扫雷游戏为例,在9*9的表格中埋了有10个雷,我们点开后会出现各种数字,数字代表周围一圈雷的个数。
以下图为例,数字1代表红圈内雷的个数,这里只剩一个没翻开,所以这个位置一定是雷。
如果翻到雷,就结束游戏,显示所有的雷,同时上图的黄色表情发生改变,提醒游戏失败。
如果成功排除所有的雷,上图的黄色表情也会发生改变,不过这时候小黄人戴了墨镜,提醒游戏成功。
这些分析对我们有啥帮助呢?
1.扫雷游戏是在9*9的表格上进行的,所以我们可以直接创建二维数组。
2.每局游戏雷的分布都是随机的,所以我们还要随机进行埋雷。
3.如果点到雷,就提示失败。
4.完成全部排雷过程,提示成功。
三、游戏编程
3.1 创建雷盘
雷盘内肯定有两种情况,有雷和无雷,这里我们可以用1和0表示,1代表有雷,0代表无雷
但是,如果我们翻开一个棋盘后,出现了个数字1,这个时候就犯难了,这个1到底是表示这里是个雷还是说附近有一个雷?
最好的处理办法是用两个二维数组表示,一个数组只有0和1,表示埋雷的情况,同时便于我们调试程序,这里不妨用A数组表示;另一个数组用B数组,用*表示还没翻开,当我们输入行号和列号的时候,在俩程序同时进行比对,A数组中该行该列元素是0,则计算附近雷的个数,在B数组打印,如果是1,则将A数组中数字1在B数组同位置显示,并显示游戏失败。
以输入(3,3)为例,A(3,3)=0,表示不是雷,而A(3,3)附近元素有三个雷,所以在B(3,3)中打印3,图中红色仅便于观察到变化,无其他用意。
以输入(4,4)为例,A(4,4)=1,表示该位置有雷,这里可以直接用printf函数输出:你踩中雷了,游戏失败!!
如果输入(1,4),A(1,4)=0,表示不是雷,而计算机在检索A(1,4)附近元素时,由于上侧没有元素,所以还须考虑上下左右四个边界问题,非常麻烦,在这里我们可以将数组变成11*11的二维数组,埋雷只在A[1][1]~A[9][9]区间完成,其余部分没有雷,所以全部赋值为0,在B数组扫雷过程中,也只在B[1][1]~ B[9][9]区间完成,再以输入(1,4)为例,A[1][4]=A[0][3]+A[0][4]+A[0][5]+ A[1][3]+A[1][4]+A[1][5]+ A[2][3]+A[2][4]+A[2][5], A[0][3]、A[0][4]、A[0][5]恒为0, A[1][3]+A[1][4]+A[1][5]+ A[2][3]+A[2][4]+A[2][5]=1,所以B[1][4]打印为1,没问题。
这一方法既解决了边界问题,又简化了代码编程。
上述长篇分析只为证明11*11的二维数组能解决边界问题,由于B数组的*要创建字符型二维数组,在这里将A数组也创建成字符型,用字符型的‘0’和‘1’表示是否有雷,便于创建函数将A和B模块化处理。
此时游戏部分的代码为:
//游戏内容
void game()
{
char show[11][11] ;//埋雷的位置
char saolei[11][11] ;//游戏界面
//初始化数组
//随机埋雷
//打印雷盘
//判断是否有雷
}
3.2初始化数组
//初始化
void init(char arr[11][11], int x, int y, char z)
{
for (int i = 0; i < x; i++)
{
for (int j = 0; j < y; j++)
{
arr[i][j] = z;
}
}
}
这里新建一个函数,初始化过程两个循环就能完成,每次运行到一个位置,都将要赋值的字符输入进去,这时候游戏内容部分变成了
//游戏内容
void game()
{
char show[11][11] ;
char saolei[11][11] ;
//初始化数组
init(show, 11, 11, '0');
init(saolei, 11, 11, '*');
//随机埋雷
//打印雷盘
//判断是否有雷
}
3.3 随机埋雷
//随机埋雷
void mine(char arr[11][11],int x,int y )
{
srand((unsigned int)time(NULL));
int z = 0;
while (z < 10)
{
int i = rand() % x+1;
int j = rand() % y+1;
if (arr[i][j] == '0')
{
arr[i][j] = '1';
z++;
}
}
}
既然随机埋雷了,当然少不了rand()函数,用了rand()函数,记得加上头文件:
#include<stdlib.h>
同时,由于rand()输出的数是个伪随机数,所以记得加上时间戳,而用了时间记得加上头文件:
#include<time.h>
既然是随机数,当然也可能取到重复的值,所以这里用了个循环,z表示埋雷个数,埋到有效雷才计数,当埋完10个雷跳出循环。
3.4 打印雷盘
//打印雷盘
void print_mine(char arr[11][11],int x,int y)
{
printf("-----------------\n");
for (int i = 0; i <= x; i++)
{
for (int j = 0; j <= y; j++)
{
if (i == 0)
{
printf("%c ", j+'0');
}
else if (j == 0)
{
printf("%c ", i + '0');
}
else
{
printf("%c ", arr[i][j]);
}
}
printf("\n");
}
}
虽然创建的数组是11*11,但实际埋雷和找雷的区域是9*9的,所以这里只要打印最中间9*9的数组,同时为了寻找方便,特意在打印后的数组的上区域和左区域各加一行(列),表上数字0~9。
到这里代码运行后为:
3.5判断是否有雷
void find(char arr1[11][11], char arr2[11][11], int x, int y)
{
int z = 71;
while (z)
{
printf("输入你翻开的区域:");
scanf("%d %d", &x, &y);
if (x > 0 && x < 10 && y>0 && y < 10)
{
if (arr1[x][y] == '1')
{
printf("boom!你踩中雷了,游戏失败!\n");
break;
}
else
{
arr2[x][y] = arr1[x - 1][y - 1] + arr1[x - 1][y] + arr1[x - 1][y + 1] + arr1[x][y - 1] +
arr1[x][y] + arr1[x][y + 1] + arr1[x + 1][y - 1] + arr1[x + 1][y] + arr1[x + 1][y + 1] - 8 * '0';
print_mine(arr1, 9, 9);
print_mine(arr2, 9, 9);
}
}
else
{
printf("你输入有误哦~请重新输入\n");
}
z--;
}
if (z == 0)
{
printf("游戏成功,你真棒");
}
}
通过输入行号和列号,判埋show数组内,该位置是‘1’还是‘0’,是‘1’则提醒游戏失败,是‘0’,则计算附近一圈‘1’的个数,并表示在数组saolei上,输入其他值则表示输入有误,当找到所有不是雷的区域后结束游戏,提醒游戏成功。
四、代码整理
到这里,代码初稿就算出来了,如下所示:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
//开始菜单
void print()
{
printf("********************\n");
printf("*** 1.开始游戏 ***\n");
printf("*** 2.退出游戏 ***\n");
printf("********************\n");
return;
}
//继续游玩?
void again()
{
printf("********************\n");
printf("*** 1.再来一把 ***\n");
printf("*** 2.退出游戏 ***\n");
printf("********************\n");
return;
}
//初始化
void init(char arr[11][11], int x, int y, char z)
{
for (int i = 0; i < x; i++)
{
for (int j = 0; j < y; j++)
{
arr[i][j] = z;
}
}
}
//随机埋雷
void mine(char arr[11][11],int x,int y )
{
srand((unsigned int)time(NULL));
int z = 0;
while (z < 10)
{
int i = rand() % x+1;
int j = rand() % y+1;
if (arr[i][j] == '0')
{
arr[i][j] = '1';
z++;
}
}
}
//打印雷盘
void print_mine(char arr[11][11],int x,int y)
{
printf("-----------------\n");
for (int i = 0; i <= x; i++)
{
for (int j = 0; j <= y; j++)
{
if (i == 0)
{
printf("%c ", j+'0');
}
else if (j == 0)
{
printf("%c ", i + '0');
}
else
{
printf("%c ", arr[i][j]);
}
}
printf("\n");
}
}
void find(char arr1[11][11], char arr2[11][11], int x, int y)
{
int z = 71;
while (z)
{
printf("输入你翻开的区域:");
scanf("%d %d", &x, &y);
if (x > 0 && x < 10 && y>0 && y < 10)
{
if (arr1[x][y] == '1')
{
printf("boom!你踩中雷了,游戏失败!\n");
break;
}
else
{
arr2[x][y] = arr1[x - 1][y - 1] + arr1[x - 1][y] + arr1[x - 1][y + 1] + arr1[x][y - 1] +
arr1[x][y] + arr1[x][y + 1] + arr1[x + 1][y - 1] + arr1[x + 1][y] + arr1[x + 1][y + 1] - 8 * '0';
print_mine(arr1, 9, 9);
print_mine(arr2, 9, 9);
}
}
else
{
printf("你输入有误哦~请重新输入\n");
}
z--;
}
if (z == 0)
{
printf("游戏成功,你真棒");
}
}
//游戏内容
void game()
{
char show[11][11] ;
char saolei[11][11] ;
int x = 0;
int y = 0;
//初始化数组
init(show, 11, 11, '0');
init(saolei, 11, 11, '*');
//随机埋雷
mine(show,9,9);
//打印雷盘
print_mine(show, 9, 9);
print_mine(saolei, 9, 9);
//判断是否有雷
find(show, saolei, x, y);
}
int main()
{
int input = 0;
print();
do
{
scanf("%d", &input);
switch (input)
{
case 1:
{
game();
again();
}
case 2:
{
break;
}
default:
{
printf("输入有误,请重新输入:");
}
}
} while (input != 2);
return 0;
}
可以看到,代码过于冗长,同时该代码只能输出9*9的雷盘,如果我想增加雷的个数和更改雷盘的大小,需要修改代码的很多地方,非常的麻烦。
所以对于冗长部分,我们可以把自定义函数放到其他文件中,再自定义个头文件,包含自定义函数的声明和头文件。
对于后一个问题,则可以把行号和列好用同一的定义表示,后续更改直接更改定义的值就好。
整理后的代码如下
自定义函数部分:
function.h :函数声明部分
function.c : 自定义函数部分
test_1.c : 游戏主体部分
function.h
#pragma once
#define rows 9
#define cols 9
#define row rows+2
#define col cols+2
#define boom 10
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
void print();
//继续游玩?
void again();
//初始化
void init(char arr[row][col], int x, int y, char z);
//随机埋雷
void mine(char arr[row][col], int x, int y);
//打印雷盘
void print_mine(char arr[row][col], int x, int y);
// 判断是否有雷
void find(char arr1[row][col], char arr2[row][col], int x, int y);
//游戏内容
void game();
function.c
#define _CRT_SECURE_NO_WARNINGS
#include"function.h"
void print()
{
printf("********************\n");
printf("*** 1.开始游戏 ***\n");
printf("*** 2.退出游戏 ***\n");
printf("********************\n");
return;
}
//继续游玩?
void again()
{
printf("********************\n");
printf("*** 1.再来一把 ***\n");
printf("*** 2.退出游戏 ***\n");
printf("********************\n");
return;
}
//初始化
void init(char arr[row][col], int x, int y, char z)
{
for (int i = 0; i < x; i++)
{
for (int j = 0; j < y; j++)
{
arr[i][j] = z;
}
}
}
//随机埋雷
void mine(char arr[row][col], int x, int y)
{
srand((unsigned int)time(NULL));
int z = 0;
while (z < boom)
{
int i = rand() % x + 1;
int j = rand() % y + 1;
if (arr[i][j] == '0')
{
arr[i][j] = '1';
z++;
}
}
}
//打印雷盘
void print_mine(char arr[row][col], int x, int y)
{
printf("-----------------\n");
for (int i = 0; i <= x; i++)
{
for (int j = 0; j <= y; j++)
{
if (i == 0)
{
printf("%d ", j);
}
else if (j == 0)
{
printf("%d ", i);
}
else
{
printf("%c ", arr[i][j]);
}
}
printf("\n");
}
}
//判断是否有雷
void find(char arr1[row][col], char arr2[row][col], int x, int y)
{
int z = x * y - boom;
while (z)
{
printf("输入你翻开的区域:");
scanf("%d %d", &x, &y);
if (x > 0 && x < 10 && y>0 && y < 10)
{
if (arr1[x][y] == '1')
{
printf("boom!你踩中雷了,游戏失败!\n");
break;
}
else
{
arr2[x][y] = arr1[x - 1][y - 1] + arr1[x - 1][y] + arr1[x - 1][y + 1] + arr1[x][y - 1] +
arr1[x][y] + arr1[x][y + 1] + arr1[x + 1][y - 1] + arr1[x + 1][y] + arr1[x + 1][y + 1] - 8 * '0';
print_mine(arr1, 9, 9);
print_mine(arr2, 9, 9);
}
}
else
{
printf("你输入有误哦~请重新输入\n");
}
z--;
}
if (z == 0)
{
printf("游戏成功,你真棒");
}
}
//游戏内容
void game()
{
char show[row][col];//埋雷的位置
char saolei[row][col];//游戏界面
int x = 0;
int y = 0;
//初始化数组
init(show, row, col, '0');
init(saolei, row, col, '*');
//随机埋雷
mine(show, rows, cols);
//打印雷盘
print_mine(show, rows, cols);
print_mine(saolei, rows, cols);
//判断是否有雷
find(show, saolei, x, y);
}
test_1.c
#define _CRT_SECURE_NO_WARNINGS
#include"function.h"
//开始菜单
int main()
{
int input = 0;
print();
do
{
scanf("%d", &input);
switch (input)
{
case 1:
{
game();
again();
}
case 2:
{
break;
}
default:
{
printf("输入有误,请重新输入:");
}
}
} while (input != 2);
return 0;
}
四、后计
学完这个,我个人感觉有两个很头疼的问题
1.正常游戏中,如果排查位置不是雷,周围也没有雷,可以展开周围的一片,但是目前这个无法实现,好像要学完递归才能完成这个功能。
2.正常游戏中不是输入行号和列号,而是直接点击某个位置,就能识别出你要翻开的位置,但这个好像涉及前端?
我还是小菜鸡,继续学吧。