目录
一、什么是扫雷游戏?
下面是一个网页版的9*9的扫雷游戏:
所以这个扫雷游戏其实就是在一个9*9的格子里排找出10个雷,同时每当在找了一个空格时,都会统计在这个空格的周围(这个空格的一周)有几个雷,并将数字储存在这个空格中,若没有,则什么都不显示。
下面我们来看一下使用编译器来实现的扫雷游戏:
可以看到,我们是通过键盘一步步输入坐标来排查雷的。在上述图中,雷是用字符‘1’来表示的,不是雷则是用字符‘0’来表示的。
二、游戏主逻辑
每实现一个游戏,都离不开实现游戏的逻辑:
1.打印菜单
2.输入选择是否开始游戏(可以运用switch语句)
(1)开始游戏
(2)退出游戏
(3)输入的选项是否准确,考虑重新输入。
3.要考虑游戏可以反复玩(do...while循环)。
#include <stdio.h>
//menu为实现菜单打印的函数
void menu()
{
printf("******************************\n");
printf("********** 1.play ********\n");
printf("********** 0.eixt ********\n");
printf("******************************\n");
}
int main()
{
int input = 0;
do
{
//打印菜单
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("玩游戏\n");
game();//这里用来存放扫雷游戏的过程
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
这里我们用一个game函数(自己写的函数)来实现扫雷游戏的整个逻辑。
下面就让我们来看看这个函数(game)是怎么实现的。
三、扫雷游戏的过程实现
首先,我们来仔细看扫雷游戏的规则:
在一个9*9的棋盘中,存放有10个雷,玩家需要从键盘输入坐标来逐步排查整个9*9棋盘的每一个棋子。直到将这整个9*9的棋盘中的71个空格全排完,则玩家胜利,游戏结束;若在中途减排输入的坐标所对应格子是雷,则玩家就输了,游戏结束。当排查到的不是雷时,则需要提示排查雷的信息,即会统计出这个坐标周围一圈的雷的数目,并将该数字存放在该坐标中,同时显示这个数字。
所以这个游戏就是要先打印棋盘,然后输入坐标,再打印棋盘(已显示排查坐标周围雷的数目),后面继续重复上述操作(即:输入坐标,再打印棋盘),直到排到雷或者将空格全排完,才游戏结束。
1. 棋盘实现
知道了扫雷游戏的过程,那么要怎么实现这个棋盘呢?
首先,必须要用一个二位数组来存放雷,也要用一个二维数组来存放排查雷的信息(排查雷的信息即在这个坐标周围的雷的个数。这个数组当作棋盘,需要打印)。
但是,是要用9*9的数组吗?
这时我们就要考虑统计雷数目的操作了。
当输入的坐标再棋盘内部时:
但是当输入的坐标再棋盘的边缘时呢?
这时实际需要统计的并未满足周围8个的,但是当我们统计时却需要统计8个,这样才好便于编写,也可以保证数组不越界。既然要这样,那我们就可以使用一个(9+2)*(9+2)的二维数组了,使用这样的数组,就可以当输入再这个9*9棋盘的边缘的坐标时,也可以统计8个数据(除了再9*9棋盘内的数据,其余都表示没有雷)。
使用宏定义,便于操作:
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
所以在后续操作中都应用11*11的数组(除了打印9*9数组时),那么需要的数组就为:
char mine[ROWS][COLS] = { 0 };//用来存放布置雷的信息
char show[ROWS][COLS] = { 0 };//用来存放排查雷的信息,要打印
2. 初始化棋盘
这里我们先规定:
字符:‘ 1 ’ 表示雷
字符:‘ 0 ’ 表示没有雷
字符:‘ * ’ 表示空格,用来隐藏雷
在没有开始排雷时,需要把9*9的棋盘打印出来。
所以,用来存放雷的数组就可以先初始化为 ‘ 0 ’ (后面再布置雷);
而用来存放雷的信息数组就可以先初始化为 ‘ * ’ 。
声明初始化数组的函数:
//声明初始化棋盘的函数
void InitBoard(char board[ROWS][COLS], int row, int col, char set);//set表示要初始化的数据
使用这样的函数就可以将两个数组分别初始化为‘0’和‘*’了;
//分别初始化棋盘
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
则函数实现:
void InitBoard(char board[ROWS][COLS], int row, int col, char set)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
board[i][j] = set;
}
}
}
3. 打印棋盘
虽然所用的数组是11*11的二维数组,但是所打印的棋盘需要以9*9的二维数组呈现出来。
所以这个函数为:
//声明打印棋盘的函数
void PrintBoard(char board[ROWS][COLS], int row, int col);
函数实现:
void PrintBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("----------扫雷----------\n");//这句代码是为了美观
for (i = 0; i <= col; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("----------扫雷----------\n");
}
4. 布置雷
在9*9棋盘中一般是只布置10个雷,但是也可以布置其他数目的雷,所以可以用宏定义表示雷数目,更加便于修改。
运用宏定义(雷的数目):
//雷的数量
#define COUNT 20
那怎样布置雷呢?下面以10个雷为例:
首先,需要产生10个不同的随机坐标,在这些坐标上布置雷。
当然,这就要用到产生随机数的方法了:
首先需要调用以下代码放在主函数中(要保证整个过程它只调用一次):
srand((unsigned int)time(NULL));
同时需要包含头文件(stdlib.h、time.h)然后才能调用rand函数参数随机值,就像这样:
int x = rand() % row + 1;
int y = rand() % col + 1;
此时的x与y坐标的范围要在1~9内,其原因如下图所示:
当然,产生的随机坐标对应的位置是否已经布置了雷,若是,则要循环重新生成,直到此产生的不同的坐标有10个,即完成操作。
所以这个函数为:
//数目布置雷的函数
void SetMine(char board[ROWS][COLS], int row, int col);
函数实现:
void SetMine(char board[ROWS][COLS], int row, int col)
{
int count = COUNT;
while (count)
{
//1.产生随机的坐标1~9
int x = rand() % row + 1;
int y = rand() % col + 1;
//2.在坐标中放置雷
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
5.排查雷
当雷布置完了之后,就要开始排雷了。那么排雷有要怎么操作呢?
其操作如下:
a. 输入要排的坐标。
b. 判断坐标合理性(即是否在1~9范围内)。
c. 判断坐标是否被排查过(即重复排查)。
d. 判断坐标是否是雷,若是则被炸死,若不是,则需统计周围雷的个数,然后继续排雷。
e. 打印含有排查坐标中存放数字信息的棋盘。
f. 重复a~e的操作,直到雷被排完,则游戏胜利,同时将整个棋盘雷的位置信息打印出来。
其中,统计周围雷的个数的操作也可以用一个函数来表示:
int get_mine_count(char mine[ROWS][COLS], int x, int y);
由于雷是用字符1表示的,不是雷使用字符0表示的,并且函数返回的是整型,所以可以通过排查坐标周围的8个字符相加后,再减去8个字符0的值 就是周围雷的个数。
这是通过ASCLL码值来计算的:
技巧:因为字符0的ASCLL值为48,字符1的ASCLL码值为49,字符2的ASCLL码值为50,......,所以字符1减去字符0就是整型1,字符2减去字符0就是整型2,......
故函数实现:
int get_mine_count(char mine[ROWS][COLS], 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 board[ROWS][COLS], char show[ROWS][COLS], int row, int col);
函数实现:
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
//通过成功排查的次数来控制循环
while (win < (row * col - COUNT))
{
printf("请输入要排查的坐标:> ");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] == '*')
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了\n");
PrintBoard(mine, ROW, COL);
break;
}
else
{
int n = get_mine_count(mine, x, y);
//由于数组中的都是字符,所以这里需要加上字符0
show[x][y] = n + '0';
PrintBoard(show, ROW, COL);
win++;
}
}
else
{
printf("该坐标被排查过了\n");
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
//当 所排雷的数目 等于 棋盘数减去雷数的数目 时,游戏才结束
if (win == (row * col - COUNT))
{
printf("恭喜你,排雷成功\n");
PrintBoard(mine, ROW, COL);
}
}
四、游戏代码
经过上面分析可以发现,扫雷游戏时一个比较多的代码项目,所以,使用分文件写,才比较方便,易懂。
game.h 用于声明游戏的函数
game.c 实现游戏过程的代码
time.c 用于测试游戏的逻辑
game.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
//雷的数量
#define COUNT 20
//实现扫雷游戏中的函数声明
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int row, int col,char set);
//打印棋盘
void PrintBoard(char board[ROWS][COLS], int row, int col);
//布置雷
void SetMine(char board[ROWS][COLS], int row, int col);
//排查雷
void FindMine(char board[ROWS][COLS], char show[ROWS][COLS], int row, int col);
time.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void menu();
void test();
void game();
int main()
{
//实现游戏的主逻辑
test();
return 0;
}
void menu()
{
printf("**********************************\n");
printf("********** 1.play *********\n");
printf("********** 0.exit *********\n");
printf("**********************************\n");
}
void test()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{ //打印菜单
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("扫雷游戏\n");
//实现扫雷游戏的逻辑
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
}
void game()
{
//9*9棋盘
//需要存放布置雷的信息,需要存放排查雷的信息--2个数组
//排查时,防止坐标越界,要给数组行,列各加2
char mine[ROWS][COLS] = { 0 };//用来存放布置雷的信息
char show[ROWS][COLS] = { 0 };//用来存放排查雷的信息
//1.初始化棋盘
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//2.打印棋盘
PrintBoard(show, ROW, COL);
//3.布置雷
SetMine(mine, ROW, COL);
//4.排查雷
FindMine(mine, show, ROW, COL);
}
game.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void InitBoard(char board[ROWS][COLS], int row, int col, char set)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
board[i][j] = set;
}
}
}
void PrintBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("----------扫雷----------\n");
for (i = 0; i <= col; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("----------扫雷----------\n");
}
void SetMine(char board[ROWS][COLS], int row, int col)
{
int count = COUNT;
while (count)
{
//1.产生随机的坐标1~9
int x = rand() % row + 1;
int y = rand() % col + 1;
//2.在坐标中放置雷
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
int get_mine_count(char mine[ROWS][COLS], 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[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
while (win < (row * col - COUNT))
{
printf("请输入要排查的坐标:> ");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] == '*')
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了\n");
PrintBoard(mine, ROW, COL);
break;
}
else
{
int n = get_mine_count(mine, x, y);
show[x][y] = n + '0';
PrintBoard(show, ROW, COL);
win++;
}
}
else
{
printf("该坐标被排查过了\n");
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
if (win == (row * col - COUNT))
{
printf("恭喜你,排雷成功\n");
PrintBoard(mine, ROW, COL);
}
}