前言
作为一个小作业,实现起来难度并不是很高,但是编程的手跟不上脑子想的思路,于是想将自己的顺着思路写下来。一步一步地实现扫雷界面的布局,放置雷,判断一个点周围雷的个数,判断胜负,如何标记这个坐标上有雷,如果一点周围都没有雷,进行扩散,把周边周围没有雷的点一起露出。
进入扫雷
选择是否进入游戏
套用了上一次的三子棋,输入零或一来判断是否进入游戏。
#include"Saolei.h" int main() { int input = 0; do { menu();//菜单 printf("请选择(0或1):"); scanf("%d", &input); srand((unsigned int)time(NULL));//为后续随机放雷做铺垫 switch (input) { case 1: system("cls");//清屏 Game();//游戏的主体函数 break; case 0: printf("退出游戏\n"); break; default: printf("输入错误,请重新输入\n"); break; } } while (input); return 0; }
菜单(menu)
void menu()
{
printf("******************************\n");
printf("************1.play************\n");
printf("************0.exit************\n");
printf("******************************\n");
}
游戏开始(Game)
游戏场地
首先是定义场地的大小,通过修改ROW和COLUMN来改变扫雷场地的大小,即设定行和列。
#define ROW 9 #define COLUMN 9 #define ROWS ROW+2 #define COLUMNS COLUMN+2
ROWS和COLUMNS的设定是为了方便后续的判断。
在假设在3*3的场地上判断一个点的周围。
也就是判断红线经过的地方是否有雷。
如果雷判断点在边上
若是数组只有3行3列,那么就会越界,虽然可以限制各种条件来进行判断,如,在棋盘的边上用另一套判断方法,但是会显得比较麻烦。
所以想到了将原本3*3的场地扩展一圈,也就是5*5,这样子所有点都可以用同一套判断方法。
初始化场地(Initialize_area)
首先是定义了两个二维数组作为游戏的场地,一个用来记录雷的地点,一个是玩家看到的界面。
一开始没有雷则雷的场地都用字符' '填满,界面则用'*'填满。
(此处有bug!!!在揭示部分的第一个bug修改!!!)
char Lei[ROWS][COLUMNS]; char Show[ROWS][COLUMNS]; Initialize_area(Lei, ' '); Initialize_area(Show, '*');
用传进来的字符变量来填充数组。
代码展示:
void Initialize_area(char area[ROWS][COLUMNS], char c) { int i, j; for (i = 0; i < ROWS; i++) { for (j = 0; j < COLUMNS; j++) { area[i][j] = c; } } }
展示场地(Display_area)
这条函数的使用及其效果:
将场地的行号列号以及场地中的内容打印出来。
Display_area(Lei); Display_area(Show);
这段代码较为直白,就不赘述了。
代码展示:
void Display_area(char area[ROWS][COLUMNS]) { int i, j; for (j = 0; j <= COLUMN; j++)//打印分割线 printf("--"); printf("\n"); for (j = 0; j <= COLUMN; j++)//打印列号 printf("%d ", j); printf("\n"); for (i = 1; i <= ROW; i++) { printf("%d ", i);//打印行号 for (j = 1; j <=COLUMN; j++) { printf("%c ", area[i][j]);//打印游戏场地内的元素 } printf("\n"); } for (j = 0; j <= COLUMN; j++)//打印分割线 printf("--"); printf("\n"); }
玩家排查点(Player_search)
用x,y来记录玩家排查的点的坐标:
int x, y; Player_search(Show,&x,&y);
控制玩家输入的点在场地内,且没有被排查过。
代码展示:
void Player_search(char area[ROWS][COLUMNS], int* x,int* y) { printf("请输入要排查的坐标(第几行第几列,如:1 1):"); scanf("%d %d", x, y); if (*x<1 || *x>ROW || *y<1 || *y>COLUMN) { printf("输入错误,重新输入\n"); Player_search(area,x,y); } else if(area[*x][*y]!='*') { printf("该位置已被排查,重新输入\n"); Player_search(area,x,y); } }
地雷放置
我的想法是玩家先下一步在放置地雷,防止玩家第一步就踩到地雷,所以地雷的放置会放在玩家第一次下棋之后。
int x, y; Player_search(Show,&x,&y); Set(Lei, x, y);
效果如下 :
首先定义雷的数量,用'#'来当地雷,随机放置在不是玩家第一步的位置,且没有地雷。
代码展示:
#define LEI 10 //注意不要重名! void Set(char area[ROWS][COLUMNS], int x, int y) { int count = LEI; while (count) { int x1, y1; x1 = rand() % ROW + 1;//让随机数x1和y1控制在1~ROW和1~COLUMN y1 = rand() % COLUMN + 1; if ((x1 != x || y1 != y)&&area[x1][y1]!='#') { count--; area[x1][y1] = '#';//下地雷 } } }
同时我也希望能在地雷放置时候,在地雷场地中把周围有多少雷的数字直接表现出来,方便之后的判断。
效果如下(地雷界面):
代码实现:
void Set(char area[ROWS][COLUMNS], int x, int y) { int count = LEI; while (count) { int x1, y1; x1 = rand() % ROW + 1; y1 = rand() % COLUMN + 1; if (x1 != x || y1 != y) { count--; area[x1][y1] = '#'; } } int i, j; for (i = 1; i <= ROW; i++) { for (j = 1; j <= COLUMN; j++) { if(area[i][j]!='#') { int x2, y2; int t = 0; for (x2 = -1; x2 <= 1; x2++)//查询以自身为中点的3*3范围有没有地雷 { for (y2 = -1; y2 <= 1; y2++) { if (area[i + x2][j + y2] == '#')t++;//有则+1 } } if (t > 0)area[i][j] = '0' + t; } } } }
揭示(Discover)
根据输入的坐标来揭示这个点的周围有多少个地雷。
效果如下(上方显示雷的场地,下方显示玩家的场地):
int x, y; Player_search(Show,&x,&y); Set(Lei, x, y); Display_area(Lei); Discover(Show, Lei, x, y); Display_area(Show);
第一个bug(Initialize)
这还只是第一步,首先要做到当周围没有地雷的时候把周围没有地雷的地方都显示出来,也就是在地雷场地中相邻的' '(空格)都一起显现。
效果如下(bug):
出bug了!
它揭示了全部的空格!
为什么!
看看代码:
void Discover(char show[ROWS][COLUMNS],char lei[ROWS][COLUMNS], int x, int y) { if (lei[x][y] == ' ') { show[x][y] = lei[x][y]; int x1, y1; for (x1 = -1; x1 <= 1; x1++) { for (y1 = -1; y1 <= 1; y1++) { if (show[x+x1][y+y1] == '*') { if(lei[x+x1][y+y1]==' ')Discover(show, lei, x + x1, y + y1); } } } } }
也就是如果这个地方是空格,向揭示周围八个还没有显示的空格,进行递归,知道不是空格为止。
但是!我忽略了为了方便判断棋盘我设置了更大一圈的棋盘,这更大一圈的上全是空格,所以代码就沿着空格揭示了整个棋盘的空格。
找到了问题,应该把最外圈的设置成非空格。
也就是修改Initiative_area的代码,如下:
void Initialize_area(char area[ROWS][COLUMNS], char c) { int i, j; for (i = 0; i < ROWS; i++) { for (j = 0; j < COLUMNS; j++) { if (i >= 1 && i <= ROW && j >= 1 && j <= COLUMN) area[i][j] = c; else area[i][j] = '%'; } } }
也就是只初始化游戏场地,将最外圈的改成其他字符。
改完之后效果如下(上方显示雷的场地,下方显示玩家的场地):
那么继续,接下来就是显示非空格的元素了
效果如下:
代码如下:
void Discover(char show[ROWS][COLUMNS],char lei[ROWS][COLUMNS], int x, int y) { if (lei[x][y] == ' ') { show[x][y] = lei[x][y]; int x1, y1; for (x1 = -1; x1 <= 1; x1++) { for (y1 = -1; y1 <= 1; y1++) { if (show[x+x1][y+y1] == '*') { Discover(show, lei, x + x1, y + y1); } } } } else if (lei[x][y] != '#') { show[x][y] = lei[x][y]; } }
但是并不全面,若是没有包死,也就是斜着的地方有空格则会延申出去
显然这不是想要的效果需要更改判断的条件:
将探测周围八格改成探测上下左右四个,就不会出现斜着延申出去的情况了:
效果如下:
代码如下:
void Discover(char show[ROWS][COLUMNS],char lei[ROWS][COLUMNS], int x, int y) { if (lei[x][y] == ' ') { show[x][y] = lei[x][y]; int x1, y1; for (x1 = -1; x1 <= 1; x1+=2) { if (show[x + x1][y] == '*') Discover(show, lei, x + x1, y); } for (y1 = -1; y1 <= 1; y1 += 2) { if (show[x][y+y1] == '*') Discover(show, lei, x, y+y1); } } else if (lei[x][y] != '#') { show[x][y] = lei[x][y]; } }
最后,除开踩到地雷场景空格和数字的情况,便是踩到雷了,显示雷的场地,展示雷在哪里。
效果如下:
我们先无限循环,踩到雷Show[0][0]变成'0'跳出循环即代码如下:
void Discover(char show[ROWS][COLUMNS],char lei[ROWS][COLUMNS], int x, int y) { if (lei[x][y] == ' ') { show[x][y] = lei[x][y]; int x1, y1; for (x1 = -1; x1 <= 1; x1+=2) { if (show[x + x1][y] == '*') Discover(show, lei, x + x1, y); } for (y1 = -1; y1 <= 1; y1 += 2) { if (show[x][y+y1] == '*') Discover(show, lei, x, y+y1); } } else if (lei[x][y] != '#') { show[x][y] = lei[x][y]; } else { show[0][0] = '0'; } }
while (1) { Player_search(Show, &x, &y); Discover(Show, Lei, x, y); Display_area(Show); if (Show[0][0] == '0') { printf("踩到雷了爆炸了!!\n"); Display_area(Lei); break; } }
成功条件
将DIscover的返回值改成整数型,添加计数器count,显示出来的数量为场地格子的总数减去地雷数则胜利。
效果如下:
代码如下(演示count):
int x, y; Player_search(Show,&x,&y); Set(Lei, x, y); int count = 0; count+=Discover(Show, Lei, x, y); Display_area(Show); printf("count=%d\n", count);
修改后的DIscover如下:
int Discover(char show[ROWS][COLUMNS],char lei[ROWS][COLUMNS], int x, int y) { int count1 = 0; if (lei[x][y] == ' ') { show[x][y] = lei[x][y]; count1++; int x1, y1; for (x1 = -1; x1 <= 1; x1+=2) { if (show[x + x1][y] == '*') count1+=Discover(show, lei, x + x1, y); } for (y1 = -1; y1 <= 1; y1 += 2) { if (show[x][y+y1] == '*') count1 +=Discover(show, lei, x, y+y1); } } else if (lei[x][y] != '#') { show[x][y] = lei[x][y]; count1++; } else { show[x][y] = lei[x][y]; show[0][0] = '0'; count1++; } return count1; }
为了方便测试,先将地雷数改成80,测试是否能成功跳出。
效果如下:
代码如下:
void Game() { char Lei[ROWS][COLUMNS]; char Show[ROWS][COLUMNS]; Initialize_area(Lei, ' '); Initialize_area(Show, '*'); int x, y; Player_search(Show,&x,&y); Set(Lei, x, y); int count = 0; count+=Discover(Show, Lei, x, y); Display_area(Show); while (count!=ROW*COLUMN-LEI) { Player_search(Show, &x, &y); count+=Discover(Show, Lei, x, y); Display_area(Show); if (Show[0][0] == '0') { printf("踩到雷了爆炸了!!\n"); Display_area(Lei); break; } } Display_area(Lei); if (count == ROW * COLUMN - LEI) printf("你赢了,找出了所有地雷\n"); }
标记地雷
至此扫雷的基本功能都已经实现,最后就是标记自己认为的地雷,有个初步的想法:
就是在玩家输入的时候若是输入问号则进入标记模式,将该坐标标记为地雷,但是每次输入都要判断是否是问号对于游玩体验又很麻烦,希望有大神能给个好的解决方法,如,在坐标后输入'?'代表标记,但是scanf似乎不能这样子。
我希望的是默认输入坐标,后面有?就标记,但是以现在的水平似乎想不到有什么方法可以这样子,只能每次输入的时候判断是否要标记,救救。
全部代码展示
#define _CRT_SECURE_NO_WARNINGS 1
#include"Saolei.h"
int main()
{
int input = 0;
do
{
menu();//菜单
printf("请选择(0或1):");
scanf("%d", &input);
srand((unsigned int)time(NULL));
switch (input)
{
case 1:
system("cls");//清屏
Game();//游戏的主体函数
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9
#define COLUMN 9
#define ROWS ROW+2
#define COLUMNS COLUMN+2
#define LEI 10
void menu();
void Game();
void Initialize_area(char area[ROWS][COLUMNS], char c);
void Display_area(char area[ROWS][COLUMNS]);
void Player_search(char area[ROWS][COLUMNS], int* x, int* y);
void Set(char area[ROWS][COLUMNS],int x,int y);
int Discover(char area1[ROWS][COLUMNS],char area2[ROWS][COLUMNS], int x, int y);
#define _CRT_SECURE_NO_WARNINGS 1
#include"Saolei.h"
void menu()
{
printf("******************************\n");
printf("************1.play************\n");
printf("************0.exit************\n");
printf("******************************\n");
}
void Initialize_area(char area[ROWS][COLUMNS], char c)
{
int i, j;
for (i = 0; i < ROWS; i++)
{
for (j = 0; j < COLUMNS; j++)
{
if (i >= 1 && i <= ROW && j >= 1 && j <= COLUMN)
area[i][j] = c;
else
area[i][j] = '%';
}
}
}
void Display_area(char area[ROWS][COLUMNS])
{
int i, j;
for (j = 0; j <= COLUMN; j++)//打印分割线
printf("--");
printf("\n");
for (j = 0; j <= COLUMN; j++)//打印列号
printf("%d ", j);
printf("\n");
for (i = 1; i <= ROW; i++)
{
printf("%d ", i);//打印行号
for (j = 1; j <=COLUMN; j++)
{
printf("%c ", area[i][j]);
}
printf("\n");
}
for (j = 0; j <= COLUMN; j++)//打印分割线
printf("--");
printf("\n");
}
void Player_search(char area[ROWS][COLUMNS], int* x,int* y)
{
printf("请输入要排查的坐标(第几行第几列,如:1 1):");
scanf("%d %d", x, y);
if (*x<1 || *x>ROW || *y<1 || *y>COLUMN)
{
printf("输入错误,重新输入\n");
Player_search(area,x,y);
}
else if(area[*x][*y]!='*')
{
printf("该位置已被排查,重新输入\n");
Player_search(area,x,y);
}
}
void Set(char area[ROWS][COLUMNS], int x, int y)
{
int count = LEI;
while (count)
{
int x1, y1;
x1 = rand() % ROW + 1;
y1 = rand() % COLUMN + 1;
if ((x1 != x || y1 != y)&&area[x1][y1]!='#')
{
count--;
area[x1][y1] = '#';
}
}
int i, j;
for (i = 1; i <= ROW; i++)
{
for (j = 1; j <= COLUMN; j++)
{
if(area[i][j]!='#')
{
int x2, y2;
int t = 0;
for (x2 = -1; x2 <= 1; x2++)
{
for (y2 = -1; y2 <= 1; y2++)
{
if (area[i + x2][j + y2] == '#')t++;
}
}
if (t > 0)area[i][j] = '0' + t;
}
}
}
}
int Discover(char show[ROWS][COLUMNS],char lei[ROWS][COLUMNS], int x, int y)
{
int count1 = 0;
if (lei[x][y] == ' ')
{
show[x][y] = lei[x][y];
count1++;
int x1, y1;
for (x1 = -1; x1 <= 1; x1+=2)
{
if (show[x + x1][y] == '*')
count1+=Discover(show, lei, x + x1, y);
}
for (y1 = -1; y1 <= 1; y1 += 2)
{
if (show[x][y+y1] == '*')
count1 +=Discover(show, lei, x, y+y1);
}
}
else if (lei[x][y] != '#')
{
show[x][y] = lei[x][y];
count1++;
}
else
{
show[x][y] = lei[x][y];
show[0][0] = '0';
count1++;
}
return count1;
}
void Game()
{
char Lei[ROWS][COLUMNS];
char Show[ROWS][COLUMNS];
Initialize_area(Lei, ' ');
Initialize_area(Show, '*');
int x, y;
Player_search(Show,&x,&y);
Set(Lei, x, y);
int count = 0;
count+=Discover(Show, Lei, x, y);
Display_area(Show);
while (count!=ROW*COLUMN-LEI)
{
Player_search(Show, &x, &y);
count+=Discover(Show, Lei, x, y);
Display_area(Show);
if (Show[0][0] == '0')
{
printf("踩到雷了爆炸了!!\n");
Display_area(Lei);
break;
}
}
if (count == ROW * COLUMN - LEI)
printf("你赢了,找出了所有地雷\n");
}
结语
对于此次练习,扫雷的基本功能都以实现,可能有没测出来的bug,以及未完成的标识功能,还是水平不够哇,欢迎大家指出错误!