目录
前言:扫雷想必大家都有玩过,是非常经典的电脑小游戏,给儿时的我们带来了许多快乐,如今工作之余摸摸鱼时,也不失为一个不错的益智游戏,下面我们使用C语言来实现这个有趣的小游戏。
扫雷简介:扫雷(1992年发布的小游戏)_百度百科 (baidu.com)
一、扫雷游戏分析
1、扫雷游戏功能
1.游戏界面
本次扫雷游戏的代码实现并不包括经典扫雷游戏中的游戏界面的设计,更主要是进行游戏功能的实现。
2.游戏需要游戏菜单来引导玩家进行游戏
需要一个开始界面菜单,让玩家选择是游戏 /退出。在游戏过程中,经典扫雷游戏是有插旗排雷的操作,本次代码实现同样拥有,所以应该提供一个排雷/插旗的游戏功能选项菜单。
3.插旗排雷
当通过游戏界面提供的信息可以知道某个位置一定或者可疑为雷时,对其进行插旗,当插旗数和排雷数相等且一一对应时,获得游戏胜利。
4.炸花
棋盘较大而埋雷量较小时,应该实现当一片区域几乎没雷时,自动排除,使游戏可玩性更高。
5.扫雷的游戏棋盘为9×9大小,可以随机埋10个雷
当然,这里我们应该可以设置棋盘大小,埋雷个数来增加游戏难度。
6.可以对雷进行排查
a.如果位置不是雷,就显示周围有几个雷。
b.如果位置是雷,就炸死游戏结束。
c.把除10个雷之外的所有没雷的位置都找出来,排雷成功,游戏结束。
7.成品效果展示
OK,已经把大家胃口都吊起来了,那么本次扫雷代码实现成品是啥样啊?下面看看成品效果图:
2、扫雷游戏分析和设计
1.数据结构的分析
扫雷的过程中,雷的分布和是否埋雷的信息都需要存储,所以我们需要一定的数据结构来存储这些信息。
那么应该选取怎样的数据结构呢?扫雷游戏是在一个棋盘上进行的,通过对应坐标来操作棋盘,这里我们就可以想到使用二维数组来构成数据结构,二维数组可以很好的储存这类方阵类型数据,同时二维数组的两个访问下标就很好的对应了棋盘坐标。
那么,二维数组的大小到底该有多大呢?以9*9棋盘大小为例,我们知道当选中坐标不为雷时,要将此坐标周围一圈含雷个数显示在棋盘上,如下:
对于(3,6)坐标处我们可以很好的处理(假设数组大小为9*9),但若遇到(9,7)坐标情况就不太行了,若按(3,6)坐标处记数方式明显会造成访问越界,那么如何是好呢?重新写一个函数,还要对应判断?其实我们只要将棋盘改为10*10大小(比原先大上一圈),问题就迎刃而解了,9*9大小棋盘作为显示,10*10大小棋盘作为数据存储。
同时,当我们进行排除时,周围雷数的信息应该显示在哪里呢,若和埋雷数组放一起,肯定会造成雷与雷数的信息冲突,那么我们应该新建一个数组来专门存放雷数的信息,其大小当然也为10*10。方案:我们专门给一个棋盘(对应一个数组mine)存放布置好的雷的信息,再给另外⼀个棋盘(对应另外⼀个数组show)存放排查出的雷的信息。这样就互不干扰了,把雷布置到mine数组,在mine数组中排查雷,排查出的数据存放在show数组,并且打印show数组的信息给后期排查参考。
考虑到,我们要使用‘*’来遮挡雷,二维数组show应该使用 char[ ][10] 类型数组存‘*’,而二维数组mine为了保证一致性,也应该用 char[ ][10] 类型数组,那么此时雷用 ‘1’ 表示,非雷用 ‘0’ 表示。
2.文件结构设计
我们应该创建三个文件:
a.游戏功能函数(.c)
b.游戏逻辑框架(.c)
c.函数汇众引用(.h)
使用头文件(.h)来声明函数和新建源文件来汇集函数(.c),如此定义声明分离方便对函数的引用和维护。
二、扫雷游戏的代码实现
注:具体编写细节请看代码注释。
1.函数汇总引用.h:
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
//雷数
#define easy_Mine 10
#define hard_Mine (easy_Mine*3)
#define master_Mine (easy_Mine*6)
//游戏棋盘区域
#define ROW 9
#define COL 9
//实际棋盘区域
#define ROWS ROW+2
#define COLS COL+2
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
//打印棋盘
void PrintfBoard(char board[ROWS][COLS],int row,int col);
//埋雷
void SetMine(char board[ROWS][COLS], int row, int col ,int mine);
//周围雷数
int Find(char board[ROWS][COLS],int x,int y);
//炸花
void Blank(char board1[ROWS][COLS], char board2[ROWS][COLS], int row, int col);
//插旗
void Flag(char board[ROWS][COLS]);
//插旗判断
int IsMine_Flag(char board1[ROWS][COLS], char board2[ROWS][COLS], int row, int col);
2.逻辑框架.c:
#include"函数汇总引用.h"
void menu1()
{
printf("********************\n");
printf("***** 1.play *****\n");
printf("***** 0.exit *****\n");
printf("********************\n");
}
void menu2()
{
printf("-----> 1.排雷\n");
printf("-----> 0.插旗\n");
}
void Find_Mine()
{
char MineBoard[ROWS][COLS] = { 0 };
char ShowBoard[ROWS][COLS] = { 0 };
int x = 0;
int y = 0;
int win = 0;//扫雷成功次数
int ok = 0;//插旗正确判断
//初始化棋盘
InitBoard(MineBoard,ROWS,COLS,'0');
InitBoard(ShowBoard,ROWS,COLS,'*');
//埋雷
SetMine(MineBoard,ROW,COL,easy_Mine);
//炸花
Blank(MineBoard,ShowBoard,ROW,COL);
//打印棋盘
PrintfBoard(ShowBoard, ROW, COL);
//PrintfBoard(MineBoard, ROW, COL);
//玩家输入坐标进行排雷
while (win < ROW * COL - easy_Mine )
{
//插旗胜利跳出循环
if (ok == 1)
{
printf("大吉大利,扫雷成功!\n");
PrintfBoard(MineBoard, ROW, COL);//结局反馈
break;
}
//判断是进行插旗还是排雷
menu2();
int chos = 0;
printf("选择:>");
scanf("%d", &chos);
switch (chos)
{
case 1:
printf("请输入坐标:>");
scanf("%d %d", &x, &y);
//判断坐标合法性
if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
{
//判断是否为雷
if (MineBoard[x][y] == '1')
{
printf("you are die !\n");
PrintfBoard(MineBoard, ROW, COL);//死的瞑目
return;
}
//判断是否被标识了
else if (ShowBoard[x][y] != '*')
{
printf("坐标已被占用,请重新输入!\n");
}
else
{
//周围雷数标识
int num = Find(MineBoard, x, y);
ShowBoard[x][y] = num + '0';
//打印Show棋盘
PrintfBoard(ShowBoard, ROW, COL);
win++;
//获胜结算
if (win == ROW * COL - easy_Mine)
{
printf("大吉大利,扫雷成功!\n");
PrintfBoard(MineBoard, ROW, COL);//结局反馈
}
}
}
else
{
printf("输入不合法,请重新输入!\n");
}
break;
case 0:
//插旗功能的实现
Flag(ShowBoard);
PrintfBoard(ShowBoard, ROW, COL);
//判断是否通过插棋排完所有雷
ok = IsMine_Flag(MineBoard,ShowBoard,ROW,COL);
break;
default:
printf("未知选项,请重新选择!\n");
break;
}
}
}
int main()
{
int input = 0;
srand((unsigned)time(NULL));
do
{
//打开游戏菜单
menu1();
//输入玩家选择
printf("选择:>");
scanf("%d", &input);
//选择判断
switch (input)
{
case 1:
//开始运行游戏功能
Find_Mine();
break;
case 0:
printf("baby!\n");
break;
default:
printf("输入错误,重新输入!\n");
break;
}
} while (input);//以input进行判断方便于跳出循环
return 0;
}
3.游戏功能函数.c:
#include"函数汇总引用.h"
int IsMine_Flag(char board1[ROWS][COLS], char board2[ROWS][COLS], int row, int col)
{
//判断插旗数是否为雷数
int f = 0;
int i = 0;
for (i = 1; i <= row; i++)
{
int j = 0;
for (j = 1; j <= col; j++)
{
if (board2[i][j] == '#')
{
f++;
}
}
}
//当旗数为雷数,再判断是否一一对应
if (f == easy_Mine)
{
int y = 0;
int u = 0;
for (u = 1; u <= row; u++)
{
int h = 0;
for (h = 1; h <= col; h++)
{
if (board1[u][h] == '1' && board2[u][h] == '#')//判断一一对应
{
y++;
if (y == easy_Mine)
{
return 1;
}
}
}
}
}
}
void Flag( char board[ROWS][COLS])
{
int x = 0;
int y = 0;
while (1)
{
printf("请输入插旗坐标:>");
scanf("%d %d", &x, &y);
//判断坐标合法性
if (board[x][y] == '*')
{
board[x][y] = '#';
break;
}
else
{
printf("无法插旗,重输坐标!\n");
}
}
}
void Blank(char board1[ROWS][COLS], char board2[ROWS][COLS], int row, int col)
{
int i = 0;
for (i = 1; i <= row; i++)
{
int j = 0;
for (j = 1; j <= col; j++)
{
int dig = 0;
//利用先前写下的周围雷数函数进行判断
dig = Find(board1, i, j) + board1[i][j] - '0';//board1[i][j]-'0'注意花心
if (dig == 0)
{
board2[i][j] = '0';//花的中心也要改
board2[i - 1][j - 1] = '0';
board2[i][j - 1] = '0';
board2[i + 1][j - 1] = '0';
board2[i + 1][j] = '0';
board2[i + 1][j + 1] = '0';
board2[i][j + 1] = '0';
board2[i - 1][j + 1] = '0';
board2[i - 1][j] = '0';
}
}
}
}
int Find(char board[ROWS][COLS],int x,int y)
{
return board[x - 1][y - 1] +
board[x][y - 1] +
board[x + 1][y - 1] +
board[x + 1][y] +
board[x + 1][y + 1] +
board[x][y + 1] +
board[x - 1][y + 1] +
board[x - 1][y] - 8 * '0';
}
void SetMine(char board[ROWS][COLS], int row, int col,int mine)
{
while (mine)//埋完循环结束
{
//取随机坐标
int x = rand() % row + 1;
int y = rand() % col + 1;
//判断是否可以埋雷
if (board[x][y] == '0')
{
board[x][y] = '1';
mine--;//埋一次少一个
}
}
}
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
//将棋盘中每个格子进行初始化
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void PrintfBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
//横坐标打印
for (i = 0; i <= col; i++)
{
printf("%d ", i);
}
printf("\n");
//将棋盘游戏区域打印显示
for (i = 1; i <= row; i++)
{
//纵坐标打印
printf("%d ", i);
int j = 0;
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);//"%c "可以让棋盘散一些,看起来舒适
}
printf("\n");//每打印一行注意换行
}
}