啊我摔倒了..有没有人扶我起来学习....
@TOC
1. 前言
《扫雷》是一款大众类的益智小游戏,于1992年发行。游戏目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。
现用C程序简单实现一下扫雷游戏,对应玩法为:
- 根据行列所标的数字输入坐标,如2 2(中间需间隔一个空格):
- 随后在坐标2 2显示的数字“2”表示周围八个坐标里共有2颗雷(即黄色方框内)。
- 直到把所有不是雷的“*”翻开,即判赢。相反,其中任意一次翻开雷即判负。
- 博主这边就不展示了,怪累的~
2. 游戏实现
2.1 一些建议
这次来个保姆级教学,希望小白都可以学会~所以我们会一步一步实现各个功能,接下来出现在代码中注释掉的部分先不要看,当做我们还没写。代码段也会在下面分块给出。好了,废话不多说,我们直接开干!
2.2 游戏整体框架
- 在主函数内把整体框架写好。
- 于是加入游戏菜单,并运行试试看。
3.为了实现清屏的效果,可以在每次输入后边加上 system(“cls”);
2.3 棋盘的实现
- 首先定义两个数组:
(1)mine数组用来储存雷,以1为雷,0为空。(如果同时用该数组表示所选坐标周围雷的个数,那么就会产生冲突,于是多定义一个数组)
(2)show用来展示该坐标周围的雷个数。
- 但是数组的行列数该怎么定呢?我们打算模仿9×9的扫雷游戏,所以这两数组就应该是9吗?我们来分析一下:
假如数组是9,那么当选中的坐标为棋盘边上的时候,show数组就会产生越界访问的错误,因为图中的“2”就是通过计算该坐标周围八个空格而来。为了不产生越界,我们应该在9×9的基础上,上下左右各增加一层空位,于是数组得是11的。
- 同时,为了方便以后修改棋盘的大小,我们采取如下措施,于是mineROWS就表示mine11。
- 按照1.的设想,我们把棋盘初始化一下:
- 打印一下这两个数组,可见初始化成功。但后面记得把打印mine的那一行删掉,不然就相当于开挂啦,现在只是打印出来检查检查初始化情况而已。
(1)但有一点要特别注意蓝色方框部分,虽然我们的数组是11×11的,但是mine埋雷和show展示雷个数的区域我们希望是9×9,所以传ROW、COL给打印棋盘的函数,用形参row、col接收。
(2)橙色框表示打印出行号列号。
(3)黑色框要注意i是从1开始而不是0,因为数组mine和show是11×11的,而我们要展示的是9×9。
2.4 关于雷
这部分我想分得更细致一点。
- 布置雷
打印出来检查一下,果然是10颗雷。同样的,打印检查之后记得删掉这行代码~
- 排雷
==这里是超级无敌重点部分!!!==
- 先大致浏览一下排雷代码
- 分析排雷函数的大框架
- 咱们拿出黄色方框部分仔细分析排雷过程:
1)先分析非递归部分的实现
2)其中get_mine_count的实现:
把所选坐标(X,Y)附近八个点全加起来就是雷的个数,但是注意红色方框的目的是把字符转换成整形。因为mine数组里面的0和1是字符来的并不是数字。而这是怎么转换的呢?例如‘7’-‘0’=7,不信你翻ASCII表,这就转过来啦。
3)递归部分的实现
递归展开的条件是:1.所选坐标周围没有雷;
2.所选坐标未被排查过。
注:第二点的用意是,不让递归无限循环下去。那么实现这一点,我们需要再定义一个数组负责标记排查过的地方。
2.5 赢的实现
非常简单!具体来看看是怎么实现的吧~
利用总的地雷数等于show所剩的星号“*”的个数相等来实现赢的功能,比如一共10颗雷,排雷排到最后还剩10个星号,可不就都是雷,赢了!
3. 代码段
==Gitee仓库==
3.1 test.c
//扫雷游戏
#include "game.h"
void game()
{
char mine[ROWS][COLS] = { 0 };//储存雷
char show[ROWS][COLS] = { 0 };//展示周围雷个数
char contrast[ROWS][COLS] = { 0 };//把排查过的坐标进行标记
//初始化棋盘
init_board(mine, ROWS, COLS, '0');
init_board(show, ROWS, COLS, '*');
//打印棋盘
display_board(show, ROW, COL);
//布置雷
set_mine(mine, ROW, COL);
//排雷
find_mine(mine, show, contrast, ROW, COL);
}
void menu()
{
printf("****************\n");
printf("****1. play ****\n");
printf("****0. exit ****\n");
printf("****************\n");
//printf("嘤嘤嘤~共有%d颗雷,好害怕!\n", EASY_COUNT);
}
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
printf("小伙子请选择:>");
scanf("%d", &input);
system("cls");
switch (input)
{
case 1:
game();
break;
case 0:
printf("不玩了!回家找妈妈~\n");
break;
default:
printf("笨啊,这都能输错,请重新输入!\n");
break;
}
} while (input);
return 0;
}
3.2 game.c
#include "game.h"
//统计show中'*'的数量,实现游戏赢的功能
int count_mine(char show[ROWS][COLS], int row,int col)
{
int count = 0;
for (int i = 1; i <= row; i++)
{
for (int j = 1; j <= col; j++)
{
if (show[i][j] == '*')
count++;
}
}
return count;
}
//递归扩展排雷
void Extend_board(char mine[ROWS][COLS], char show[ROWS][COLS], char contrast[ROWS][COLS], int x, int y)
{
if (get_mine_count(mine, x, y) == 0)
//get_mine_count函数返回存储地雷信息的mine数组中x y位置周围一圈(8个位置)的地雷总和
{
for (int i = x - 1; i <= x + 1; i++) //不得超出棋盘大小
{
for (int j = y - 1; j <= y + 1; j++) //不得超出棋盘大小
{
show[i][j] = (get_mine_count(mine, i, j)+'0'); //将该位置一圈地雷总和信息传递到show()函数
if (show[i][j] == '0' && contrast[i][j] != '$') //如果该位置周围没有地雷且没有被标记
{
contrast[i][j] = '$'; //将该位置标记
Extend_board(mine, show, contrast, i, j); //调用递归函数再次进行判断
}
}
}
}
else
show[x][y] = (get_mine_count(mine, x, y) + '0');
return;
}
//统计雷的数量
int get_mine_count(char mine[ROWS][COLS], int x, int y)
{
return (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] +
mine[x - 1][y + 1] - 8 * '0');
}
//排雷
void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS],char contrast[ROWS][COLS], int row, int col)
{
int x, y;
int win = 0;
while (win != EASY_COUNT)
{
printf("请输入排雷的坐标:>");
scanf("%d %d", &x, &y);
system("cls");
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] == '*')
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸毛了!\n");
break;
}
else
{
contrast[x][y] = '$';
Extend_board(mine, show, contrast, x, y);
display_board(show, ROW, COL);
win = count_mine(show, ROW, COL);
}
}
else
{
printf("该位置坐标已经排查过啦,请重新输入!\n");
display_board(show, ROW, COL);
}
}
else
{
printf("无效的坐标哦~请重新输入!\n");
display_board(show, ROW, COL);
}
}
if (win == EASY_COUNT)
printf("恭喜您,扫雷成功!奖励一拖鞋~\n");
}
//布置雷
void set_mine(char mine[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
//打印棋盘
void display_board(char board[ROWS][COLS], int row, int col)
{
for (int j = 0; j <= col; j++)
printf("%d ", j);
printf("\n");
for (int i = 1; i <= row; i++)
{
printf("%d ", i);
for (int j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
//初始化棋盘
void init_board(char board[ROWS][COLS], int row, int col, char set)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
board[i][j] = set;
}
}
3.3 game.h
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EASY_COUNT 10
//初始化棋盘
void init_board(char board[ROWS][COLS], int row, int col, char set);
//打印棋盘
void display_board(char board[ROWS][COLS], int row, int col);
//布置雷
void set_mine(char mine[ROWS][COLS], int row, int col);
//排雷
void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], char contrast[ROWS][COLS], int row, int col);
//统计雷的数量
int get_mine_count(char mine[ROWS][COLS], int x, int y);
//自动扩展排雷
void Extend_board(char mine[ROWS][COLS], char show[ROWS][COLS], char contrast[ROWS][COLS], int x, int y, int* win);
//统计show中'*'的数量,实现游戏赢的功能
int count_mine(char show[ROWS][COLS], int row, int col);