目录
前言
扫雷最原始的版本可以追溯到1973年一款名为“方块”的游戏。
不久,“方块”被改写成了游戏“Rlogic”。在“Rlogic”里,玩家的任务是作为美国海军陆战队队员,为指挥中心探出一条没有地雷的安全路线,如果路全被地雷堵死就算输。两年后,汤姆·安德森在“Rlogic”的基础上又编写出了游戏“地雷”,由此奠定了现代扫雷游戏的雏形。
1981年,微软公司的罗伯特·杜尔和卡特·约翰逊两位工程师在Windows3.1系统上加载了该游戏,扫雷游戏才正式在全世界推广开来。
这款游戏的玩法是在一个9*9(初级),16*16(中级),16*30(高级),或自定义大小的方块矩阵中随机布置一定量的地雷(初级为10个,中级为40个,高级为99个)。由玩家逐个翻开方块,以找出所有地雷为最终游戏目标。如果玩家翻开的方块有地雷,则游戏结束。
实现思路
我们在实现扫雷游戏时,首先应该掌握他的游戏规则。
以下图为例,我们选择红圈所在的位置,如果他不是雷,则会在该位置显示以他为中心的九宫格中所有雷的个数。如果他是雷,则游戏结束。如果这个位置显示n则表明在他周围的八个位置中存在n颗地雷。
以下图为例
小红旗处一定为雷,但数字2右边三块位置右下角那块不是雷,但上方两块位置谁是雷无法判断,我们可以先排查(4,3)这个位置,然后再进一步推理。
在掌握游戏规则之后,我们来实现这个程序。
游戏框架的设计
程序运行之后,我们要有一个主菜单,在游戏结束之后我们能够返回主菜单选择开始游戏或是退出游戏。
游戏界面的设计
游戏开始时,我们应该有一个雷区的界面(这里我采用9*9行的雷区,如果要改可以在头文件中修改ROW和COL的值),然后有一个选择操作的提示。为了方便玩家找到对应的位置,我在这里做了一个行和列号的输出。每次玩家操作完之后使用了 system(“cls”) 清屏函数。
在输出雷区界面之前,使用两个二维数组分别存贮地雷位置的信息mine[ROWS][COLS]和输出界面的字符show[ROWS][COLS]。(这里的ROWS = ROW + 2, COLS = COL + 2,目的是为了防止边界数组元素计算雷数时越界)
结束界面,扫雷成功或是失败都需要显示布雷图给玩家看,并给出相应提示,这里我用到了Sleep(3000)函数,让结束界面停留三秒。(Sleep()函数的参数在VC环境下单位是毫秒,而在Linux环境下单位是秒,这里应该注意一下)
游戏操作的实现
布置雷
在进行排查雷之前,首先应该随机在雷区内布置雷。(雷数不能超过雷区内的空间)这里使用到了时间戳来进行随机生成雷。通过time()获取时间戳,再通过srand()和rand()的配合使用产生伪随机数序列,进行雷的布置。
排查雷
首先应该明确只有未被排查过和未被标记过的位置可以排查。
如果排查到的位置为雷,则游戏结束。
若排查到的位置不是雷,则在该位置显示周围一圈雷的个数。
但在扫雷游戏中实际会有一个展开一片的功能,当要排查的位置周围一圈雷的个数为零时,会继续排查周围一圈八个位置,并显示那些位置周围的雷数。
标记雷
我们在判断出一个位置是地雷后,应该对其进行标记,防止下次排雷时踩到他。
所以设计出一个标记雷的函数。
1.我们只能对未被排查过的位置进行标记
2.标记后的位置无法排查
3.标记标记过的位置可以取消标记
游戏状态的判断
我们在进行扫雷的时候只有两种情况能够结束游戏:
1.玩家排查的位置是雷,则玩家被炸死,游戏结束
2.玩家将所有非雷的区域排查完,玩家获胜,游戏结束,即未排查的位置和标记过的位置总数等于所布置的地雷的总数。
功能函数
初始化函数
//多一个参数set便于布置雷和打印棋盘
void InitBoard(char arr[ROWS][COLS], int rows, int cols, char set)
{
int i = 0, j = 0;
for (i = 0; i < rows;i++)
{
for (j = 0;j < cols;j++)
{
arr[i][j] = set;
}
}
}
打印棋盘
void ShowBoard(char show[ROWS][COLS], int row, int col)
{
printf("------------------扫雷------------------\n");
int i = 0, j = 0;
printf(" |");
//打印行号
for (i = 1;i <= row;i++)
{
printf(" %d ", i);
if (i<row)
{
printf("|");
}
}
printf("\n");
for (i = 1;i <= row;i++)
{
for (j = 0;j <= col;j++)
{
printf("---");
if (j < col)
{
printf("|");
}
}
//打印列号
printf("\n %d |", i);
//雷区
for (j = 1;j <= col;j++)
{
printf(" %c ", show[i][j]);
if (j < col)
{
printf("|");
}
}
printf("\n");
}
printf("------------------扫雷------------------\n");
}
布置雷
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int count = MINES;
int x = 0, y = 0;
//每布置一个雷count自减1,直到雷布置完
while (count)
{
x = rand() % row + 1;
y = rand() % row + 1;
//只有该位置未布置雷才能布置
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
判断游戏是否结束
//玩家获胜,游戏结束返回0,游戏未结束返回1
int IsWin(char show[ROWS][COLS], int row, int col)
{
int i = 0, j = 0;
int count = 0;
for (i = 1;i <= row;i++)
{
for (j = 1;j <= col;j++)
{
//只有该位置为*或者#,才记为剩余的可操作区域,
//当剩余的可操作区域等于雷数时则游戏获胜
if (show[i][j] == '*' || show[i][j] == '#')
{
count++;
}
}
}
//若未打开区域和标记区域等于雷数则返回0,否则返回1
if (count == MINES)
return 0;
else
return 1;
}
计算九宫格内雷的个数
int GetMines(char mine[ROWS][COLS], int x, int y)
{
//若为雷则该位置为字符1,减去八个字符0即可获得雷的个数
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 ExpandMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
//如果该位置周围8快没雷,则对周围8块检索,展开
if (GetMines(mine, x, y) == 0)
{
//展开的位置都置为0
show[x][y] = '0';
int i = 0;
int j = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
//只有该位置未被打开才能检索,并且检索位置应在棋盘之内
if (show[i][j] == '*' && i > 0 && i <= ROW && j > 0 && j <= COL)
{
ExpandMine(mine, show, i, j);
}
}
}
}
else
{
//不需要展开则显示附近雷的个数
show[x][y] = GetMines(mine, x, y) + '0';
}
}
标记雷
void SignMine(char show[ROWS][COLS], int row, int col)
{
int x = 0, y = 0;
printf("请输入坐标;>");
scanf("%d %d", &x, &y);
//标记雷
if (show[x][y] == '*')
{
show[x][y] = '#';
}
//取消标记
else if (show[x][y] == '#')
{
show[x][y] = '*';
}
//若该位置被打开则无法标记
else
printf("标记失败");
}
排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0, y = 0;
while (IsWin(show, row, col))
{
int input = 0;
//1.排查雷
//2.标记雷
//每次操作可以选择是排查雷还是标记雷
do
{
printf("请选择操作1.排查雷 2.标记雷:>");
scanf("%d", &input);
switch (input)
{
//排查雷
case 1:
printf("请输入坐标;>");
scanf("%d %d", &x, &y);
if (x > 0 && x <= row && y > 0 && y <= col && show[x][y] != '#')
{
if (mine[x][y] == '1')
{
system("cls");
printf("很遗憾你被炸死了\n");
ShowBoard(mine, ROW, COL);
Sleep(3000);
//被炸死直接退出
return ;
}
else
{
ExpandMine(mine, show, x, y);
system("cls");
ShowBoard(show, ROW, COL);
}
}
else
{
//如果该位置被标记则无法排查
if (show[x][y] == '#')
{
printf("该位置被标记为雷,请重新输入\n");
}
//如果排查位置越界则报错
else
printf("坐标非法,请重新输入\n");
}
break;
//标记雷
case 2:
SignMine(show, row, col);
system("cls");
ShowBoard(show, ROW, COL);
break;
default:
break;
}
} while (IsWin(show, row, col));//当玩家获胜或是踩雷时循环结束
}
//如果玩家扫雷成功,则显示布雷图
if (!IsWin(show, row, col))
{
system("cls");
printf("恭喜您扫雷成功!:>\n");
ShowBoard(mine, ROW, COL);
Sleep(3000);
}
}
源文件
game.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<windows.h>
#define ROW 9
#define COL 9
#define ROWS ROW + 2
#define COLS COL + 2
//雷数需小于等于ROW*COL
#define MINES 80
//1.标记
//2.展开一片
//初始化棋盘
void InitBoard(char arr[ROWS][COLS], int row, int col, char set);
//打印棋盘
void ShowBoard(char show[ROWS][COLS], int row, int col);
//布置雷
void SetMine(char show[ROWS][COLS], int row, int col);
//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//标记雷
void SignMine(char show[ROWS][COLS], int row, int col);
game.c
#include"game.h"
//初始化棋盘
//多一个参数set便于布置雷和打印棋盘
void InitBoard(char arr[ROWS][COLS], int rows, int cols, char set)
{
int i = 0, j = 0;
for (i = 0; i < rows;i++)
{
for (j = 0;j < cols;j++)
{
arr[i][j] = set;
}
}
}
//打印棋盘
void ShowBoard(char show[ROWS][COLS], int row, int col)
{
printf("------------------扫雷------------------\n");
int i = 0, j = 0;
printf(" |");
for (i = 1;i <= row;i++)
{
printf(" %d ", i);
if (i<row)
{
printf("|");
}
}
printf("\n");
for (i = 1;i <= row;i++)
{
for (j = 0;j <= col;j++)
{
printf("---");
if (j < col)
{
printf("|");
}
}
printf("\n %d |", i);
for (j = 1;j <= col;j++)
{
printf(" %c ", show[i][j]);
if (j < col)
{
printf("|");
}
}
printf("\n");
}
printf("------------------扫雷------------------\n");
}
//布置雷
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int count = MINES;
int x = 0, y = 0;
//每布置一个雷count自减1,直到雷布置完
while (count)
{
x = rand() % row + 1;
y = rand() % row + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
//判断游戏是否结束
int IsWin(char show[ROWS][COLS], int row, int col)
{
int i = 0, j = 0;
int count = 0;
for (i = 1;i <= row;i++)
{
for (j = 1;j <= col;j++)
{
//只有该位置为*或者#,才记为剩余的可操作区域,
//当剩余的可操作区域等于雷数时则游戏获胜
if (show[i][j] == '*' || show[i][j] == '#')
{
count++;
}
}
}
//若未打开区域和标记区域等于雷数则返回0,否则返回1
if (count == MINES)
return 0;
else
return 1;
}
//计算周围雷的个数
int GetMines(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 ExpandMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
//如果该位置周围8快没雷,则对周围8块检索,展开
if (GetMines(mine, x, y) == 0)
{
//展开的位置都置为0
show[x][y] = '0';
int i = 0;
int j = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
//只有该位置未被打开才能检索,并且检索位置应在棋盘之内
if (show[i][j] == '*' && i > 0 && i <= ROW && j > 0 && j <= COL)
{
ExpandMine(mine, show, i, j);
}
}
}
}
else
{
//不需要展开则显示附近雷的个数
show[x][y] = GetMines(mine, x, y) + '0';
}
}
//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0, y = 0;
while (IsWin(show, row, col))
{
int input = 0;
//1.排查雷
//2.标记雷
//每次操作可以选择是排查雷还是标记雷
do
{
printf("请选择操作1.排查雷 2.标记雷:>");
scanf("%d", &input);
switch (input)
{
//排查雷
case 1:
printf("请输入坐标;>");
scanf("%d %d", &x, &y);
if (x > 0 && x <= row && y > 0 && y <= col && show[x][y] != '#')
{
if (mine[x][y] == '1')
{
system("cls");
printf("很遗憾你被炸死了\n");
ShowBoard(mine, ROW, COL);
Sleep(3000);
//被炸死直接退出
return ;
}
else
{
ExpandMine(mine, show, x, y);
system("cls");
ShowBoard(show, ROW, COL);
}
}
else
{
//如果该位置被标记则无法排查
if (show[x][y] == '#')
{
printf("该位置被标记为雷,请重新输入\n");
}
//如果排查位置越界则报错
else
printf("坐标非法,请重新输入\n");
}
break;
//标记雷
case 2:
SignMine(show, row, col);
system("cls");
ShowBoard(show, ROW, COL);
break;
default:
break;
}
} while (IsWin(show, row, col));//当玩家获胜或是踩雷时循环结束
}
//如果玩家扫雷成功,则显示布雷图
if (!IsWin(show, row, col))
{
system("cls");
printf("恭喜您扫雷成功!:>\n");
ShowBoard(mine, ROW, COL);
Sleep(3000);
}
}
//标记雷
void SignMine(char show[ROWS][COLS], int row, int col)
{
int x = 0, y = 0;
printf("请输入坐标;>");
scanf("%d %d", &x, &y);
//标记雷
if (show[x][y] == '*')
{
show[x][y] = '#';
}
//取消标记
else if (show[x][y] == '#')
{
show[x][y] = '*';
}
//若该位置被打开则无法标记
else
printf("标记失败");
}
test.c
#include"game.h"
void game()
{
char Mine[ROWS][COLS] = { 0 };
char Show[ROWS][COLS] = { 0 };
//先初始化雷区和游戏界面
InitBoard(Mine, ROWS, COLS, '0');
InitBoard(Show, ROWS, COLS, '*');
//布置雷
SetMine(Mine, ROW, COL);
//展示隐藏的雷区
ShowBoard(Show, ROW, COL);
//若需作弊,可以直接显示布置后的雷区
/*ShowBoard(Mine, ROW, COL);*/
//游戏操作函数
FindMine(Mine, Show, ROW, COL);
system("cls");
}
//主菜单
void menu()
{
printf("+---------------------------+\n");
printf("| 1. play |\n");
printf("| 0. exit |\n");
printf("+---------------------------+\n");
}
//测试函数
void test()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择:>\n");
scanf("%d", &input);
switch (input)
{
case 1:
system("cls");
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
文章到此就结束了,感谢大家的阅读。