要用C语言实现一个扫雷小游戏,扫雷中有一个棋盘和若干个地雷。而棋盘上要有初始状态,要是还要在棋盘上设置地雷的话就要求棋盘上一个位置能同时存在两种状态或信息。这是很难做到的,所以我们要有一个棋盘,一个雷区。这样的话我们就定义两个数组,一个数组就是棋盘数组,一个数组就是雷区数组。
扫雷游戏中有些情况下需要统计雷数,所以我们就将地雷在雷区上的表示设置为1,没有地雷的地方则设置为0。这样雷区数组就可以确定为int型数组了。棋盘数组的内容要能够表示周围雷的数量,所以棋盘的数组也应该是int型数组。但是我们发现当棋盘初始状态全为0时,不利于游戏的观察和状态区分。那么我们就想让棋盘的初始状态全部为char‘*’,但是数组后面又会出现int型元素,这是不符合数组规定的。那么,我们就将数组设置为char型数组,后面的int型元素我们将它们换位数字字符。
这样,扫雷游戏最重要的雷和棋盘的问题我们就解决了。接下来,我们先对扫雷这个游戏做一个基本的架构。
我们首先要打印一个扫雷游戏菜单供玩家选择。
在打印这个菜单之后我们要对雷区和棋盘进行初始化。我们先将雷区全部初始化为无雷,即char‘0’。将棋盘全部初始化为初始状态,即char‘*’。
接着进行布雷。我们用取随机数的方法得到到坐标,雷区该坐标若无雷,则将其变为一颗雷,即char‘1’。
接下来玩家选择菜单选项。选择1正式开始游戏,选择0退出程序,选错提示重新选择。当游戏结束后重复上面的操作。
我们来实现这个架构:
//扫雷游戏的基本架构
#include "removemine.h"
void test(char mine[ROWB][COLB], char board[ROWB][COLB])
{
srand((unsigned int)time(NULL));
//打印游戏菜单
Menu();
//初始化雷区和棋盘
InitBoard(mine,'0');
InitBoard(board,'*');
/*打印一下棋盘(验证初始化是否成功)
PrintForTest(mine);
PrintForTest(board);*/
//布雷
SetMine(mine,NUMMINE);
//打印一下棋盘(验证布雷是否成功)
PrintForTest(mine);
//开始扫雷
do
{
int select = -1;
printf("请根据菜单进行选择>");
scanf("%d", &select);
switch (select)
{
case 1:
//正式进入扫雷游戏
Game(mine,board);
break;
case 0:
return;
break;
default:
printf("选择错误,重新选择\n");
break;
}
} while (1);
}
int main()
{
char mine[ROWB][COLB] = { 0 };
char board[ROWB][COLB] = { 0 };
test(mine,board);
return 0;
}
接下来我们对这个架构进行填充:
选择菜单函数 void Menu():
菜单选项:选择1进入游戏,选择0退出程序。
//选择菜单
void Menu()//菜单
{
printf("***************************\n");
printf("***** 扫雷 *****\n");
printf("***** 1.play 0.exit *****\n");
printf("***************************\n");
return;
}
初始化雷区和棋盘函数 void InitBoard(char board[ROWB][COLB],char set):
输入要初始化的数组和要初始化的内容即可完成初始化。
//初始化雷区和棋盘
void InitBoard(char board[ROWB][COLB],char set)//雷区和棋盘的初始化
{
int i = 0;
int j = 0;
for (i = 0; i < ROWB; i++)
{
for (j = 0; j < COLB; j++)
{
board[i][j] = set;
}
}
return;
}
布雷函数 void SetMine(char board[ROWB][COLB],int nummine):
选取随机数字得到左边,并判断这个坐标内容不是雷。则将这个坐标的内容设置为雷,同时要设置雷的数量减一。如此循环知道要设置的雷数量为0。
//布雷
void SetMine(char board[ROWB][COLB],int nummine)//雷区地雷的设置
{
int i = nummine;
while (i)
{
int x = -1;
int y = -1;
x = rand() % ROW + 1;
y = rand() % COL + 1;
if ('0' == board[x][y])
{
board[x][y] = '1';
i--;
}
}
return;
}
游戏体函数 void Game(char mine[ROWB][COLB],char board[ROWB][COLB]):
我们要求玩家在第一次选择时不能被炸死,并且要求这个坐标进行展开提供更多信息以增加游戏可玩性。完成这些要求后,玩家每次选择一个坐标时要判断其是否为雷。为雷则游戏失败,不为雷则要求棋盘上这个坐标打印周围雷的个数。最后还要判断棋盘上是否还有雷以外的坐标未被玩家选中,有则继续游戏,没有则游戏胜利。
我们可以看到这个游戏体函数比较复杂,所以我们先实现它的架构:
//游戏体函数架构
void Game(char mine[ROWB][COLB], char board[ROWB][COLB])//扫雷游戏
{
int hollow_num = GetNumHollow(board);//得到棋盘上'*'的数目
int x = -1;
int y = -1;
/*printf("%d\n", hollow_num);*/
//打印棋盘
PrintBoard(board);
while (hollow_num > NUMMINE)
{
//玩家选择
printf("请选择坐标>\n");
scanf("%d,%d", &x, &y);
//判断:第一步踩雷就将雷挪一下,否则正常进行
if (100 == hollow_num)
{
if ('1' == mine[x][y])
{
SetMine(mine, 1);
mine[x][y] = '0';
}
}
//后面进行正常游戏,当'*'剩下的数量和雷的数量相等时,玩家胜利
if ('1' == mine[x][y])
{
PrintBoard(mine);
printf("踩到雷了,游戏失败。\n");
return;
}
else
{
//当棋盘上有100个'*'时,进行展开,减小游戏难度
if (100 == hollow_num)
{
int z = NUMSIMPLE;
Simple(mine, board, x, y, z);
}
else
{
int minenumaround = GetNumMine(mine, x, y);
board[x][y] = minenumaround + '0';
}
}
PrintBoard(board);
hollow_num = GetNumHollow(board);
}
printf("游戏胜利,恭喜你!\n");
return;
}
接下来我们来填充这个函数:
打印棋盘函数 void PrintBoard(char board[ROWB][COLB]):
将棋盘进行打印,此处为了玩家更加便捷的得到坐标,我们给其加上行列号。
//打印棋盘
void PrintBoard(char board[ROWB][COLB]) //打印棋盘,最后在玩家踩到雷后将雷区打印一遍
{
int i = 0;
int j = 0;
for (i = 0; i <= 4*(ROWB-1); i++)
{
printf("-");
}
printf("\n");
printf("|X&Y");
for (i = 1; i <= 10; i++)
{
printf("|%3d", i);
}
printf("|\n");
for (i = 0; i <= 4 * (ROWB - 1); i++)
{
printf("-");
}
printf("\n");
for (i = 1; i <= 10; i++)
{
printf("|%3d|",i);
for (j = 1; j <= 10; j++)
{
printf(" %c |", board[i][j]);
}
printf("\n");
for (j = 0; j <= 4 * (ROWB - 1); j++)
{
printf("-");
}
printf("\n");
}
}
得到棋盘上未被玩家选中过的坐标数目函数 int GetNumHollow (char board[ROWB][COLB]):
将棋盘数组遍历一遍,得到未被玩家选中过的坐标数目numhollow。有了它我们就可以判断游戏是否胜利。
//得到棋盘上未被玩家选中过的坐标数目
int GetNumHollow(char board[ROWB][COLB])//得到棋盘上'*'的数目
{
int ret = 0;
int i = 0;
int j = 0;
for (i = 1; i <= ROW; i++)
{
for (j = 1; j <= COL; j++)
{
if ('*' == board[i][j])
{
ret++;
}
}
}
return ret;
}
信息扩展函数 void Simple(char mine[ROWB][COLB],char board[ROWB][COLB],int x,int y,int z):
这个函数是一个递归函数,我们会对先对当前坐标进行正常处理,接着在对它周围的8个元素分别进行对它做过的处理。每次函数调用计数器会减一,直到计数器为0,递归结束。
//信息扩展
void Simple(char mine[ROWB][COLB], char board[ROWB][COLB], int x, int y,int z)
//在游戏初始对玩家第一次选的坐标进行信息扩展,用以降低游戏难度
{
if (((x <= 10 && x >= 1) && (y <= 10 && y >= 1))&&(board[x][y]=='*'))
{
while (z>0)
{
if ('1' == mine[x][y])
{
return;
}
else
{
int numminearound = GetNumMine(mine, x, y);
board[x][y] = numminearound + '0';
}
int a = --z;
Simple(mine, board, x - 1, y - 1, a);
Simple(mine, board, x - 1, y, a);
Simple(mine, board, x - 1, y + 1, a);
Simple(mine, board, x, y - 1, a);
Simple(mine, board, x, y + 1, a);
Simple(mine, board, x + 1, y - 1, a);
Simple(mine, board, x + 1, y, a);
Simple(mine, board, x + 1, y + 1, a);
}
}
return;
}
这样游戏体函数的架构就填充完了。
由于这些函数分别放在不同的源文件中,我们要用一个头文件将它们联系起来:
//头文件
#ifndef __REMOVEMINE_H__
#define __REMOVEMINE_H__
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#define ROW 10
#define COL 10
#define ROWB ROW+2
#define COLB COL+2
#define NUMMINE 20
#define NUMSIMPLE 10
void Menu();
void InitBoard(char board[ROWB][COLB],char set);
void PrintForTest(char board[ROWB][COLB]);
void SetMine(char board[ROWB][COLB],int nummine);
void Game(char mine[ROWB][COLB],char board[ROWB][COLB]);
int GetNumHollow (char board[ROWB][COLB]);
void PrintBoard(char board[ROWB][COLB]);
int GetNumMine(char mine[ROWB][COLB],int x,int y);
void Simple(char mine[ROWB][COLB],char board[ROWB][COLB],int x,int y,int z);
#endif //__REMOVEMINE_H__
最后,我们将它们整合起来:
//头文件
#ifndef __REMOVEMINE_H__
#define __REMOVEMINE_H__
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#define ROW 10
#define COL 10
#define ROWB ROW+2
#define COLB COL+2
#define NUMMINE 20
#define NUMSIMPLE 10
void Menu();
void InitBoard(char board[ROWB][COLB],char set);
void PrintForTest(char board[ROWB][COLB]);
void SetMine(char board[ROWB][COLB],int nummine);
void Game(char mine[ROWB][COLB],char board[ROWB][COLB]);
int GetNumHollow (char board[ROWB][COLB]);
void PrintBoard(char board[ROWB][COLB]);
int GetNumMine(char mine[ROWB][COLB],int x,int y);
void Simple(char mine[ROWB][COLB],char board[ROWB][COLB],int x,int y,int z);
#endif //__REMOVEMINE_H__
//游戏基本架构
#include "removemine.h"
void test(char mine[ROWB][COLB], char board[ROWB][COLB])
{
srand((unsigned int)time(NULL));
//打印游戏菜单
Menu();
//初始化棋盘
InitBoard(mine,'0');
InitBoard(board,'*');
/*打印一下棋盘(验证初始化是否成功)
PrintForTest(mine);
PrintForTest(board);*/
//布雷
SetMine(mine,NUMMINE);
/*打印一下棋盘(验证布雷是否成功)
PrintForTest(mine);*/
//开始扫雷
do
{
int select = -1;
printf("请根据菜单进行选择>");
scanf("%d", &select);
switch (select)
{
case 1:
//正式进入扫雷游戏
Game(mine,board);
break;
case 0:
return;
break;
default:
printf("选择错误,重新选择\n");
break;
}
} while (1);
}
int main()
{
char mine[ROWB][COLB] = { 0 };
char board[ROWB][COLB] = { 0 };
test(mine,board);
return 0;
}
//游戏体即各个函数
#include "removemine.h"
void Menu()//菜单
{
printf("***************************\n");
printf("***** 扫雷 *****\n");
printf("***** 1.play 0.exit *****\n");
printf("***************************\n");
return;
}
void InitBoard(char board[ROWB][COLB],char set)//雷区和棋盘的初始化
{
int i = 0;
int j = 0;
for (i = 0; i < ROWB; i++)
{
for (j = 0; j < COLB; j++)
{
board[i][j] = set;
}
}
return;
}
void PrintForTest(char board[ROWB][COLB])//打印验证初始化的正确性
{
int i = 0;
int j = 0;
for (i = 0; i < ROWB; i++)
{
for (j = 0; j < COLB; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
return;
}
void SetMine(char board[ROWB][COLB],int nummine)//雷区地雷的设置
{
int i = nummine;
while (i)
{
int x = -1;
int y = -1;
x = rand() % ROW + 1;
y = rand() % COL + 1;
if ('0' == board[x][y])
{
board[x][y] = '1';
i--;
}
}
return;
}
void Game(char mine[ROWB][COLB], char board[ROWB][COLB])//扫雷游戏
{
int hollow_num = GetNumHollow(board);//得到棋盘上'*'的数目
int x = -1;
int y = -1;
/*printf("%d\n", hollow_num);*/
//打印棋盘
PrintBoard(board);
while (hollow_num > NUMMINE)
{
//玩家选择
printf("请选择坐标>\n");
scanf("%d,%d", &x, &y);
//判断:第一步踩雷就将雷挪一下,否则正常进行
if (100 == hollow_num)
{
if ('1' == mine[x][y])
{
SetMine(mine, 1);
mine[x][y] = '0';
}
}
//后面进行正常游戏,当'*'剩下的数量和雷的数量相等时,玩家胜利
if ('1' == mine[x][y])
{
PrintBoard(mine);
printf("踩到雷了,游戏失败。\n");
return;
}
else
{
//当棋盘上有100个'*'时,进行展开,减小游戏难度
if (100 == hollow_num)
{
int z = NUMSIMPLE;
Simple(mine, board, x, y, z);
}
else
{
int minenumaround = GetNumMine(mine, x, y);
board[x][y] = minenumaround + '0';
}
}
PrintBoard(board);
hollow_num = GetNumHollow(board);
}
printf("游戏胜利,恭喜你!\n");
return;
}
int GetNumHollow(char board[ROWB][COLB])//得到棋盘上'*'的数目
{
int ret = 0;
int i = 0;
int j = 0;
for (i = 1; i <= ROW; i++)
{
for (j = 1; j <= COL; j++)
{
if ('*' == board[i][j])
{
ret++;
}
}
}
return ret;
}
void PrintBoard(char board[ROWB][COLB]) //打印棋盘,最后在玩家踩到雷后将雷区打印一遍
{
int i = 0;
int j = 0;
for (i = 0; i <= 4*(ROWB-1); i++)
{
printf("-");
}
printf("\n");
printf("|X&Y");
for (i = 1; i <= 10; i++)
{
printf("|%3d", i);
}
printf("|\n");
for (i = 0; i <= 4 * (ROWB - 1); i++)
{
printf("-");
}
printf("\n");
for (i = 1; i <= 10; i++)
{
printf("|%3d|",i);
for (j = 1; j <= 10; j++)
{
printf(" %c |", board[i][j]);
}
printf("\n");
for (j = 0; j <= 4 * (ROWB - 1); j++)
{
printf("-");
}
printf("\n");
}
}
int GetNumMine(char mine[ROWB][COLB],int x, int y) //得到此坐标周围的雷的个数,返回数目
{
return ((mine[x - 1][y - 1] - '0')
+ (mine[x - 1][y] - '0')
+ (mine[x - 1][y + 1] - '0')
+ (mine[x][y - 1] - '0')
+ (mine[x][y + 1] - '0')
+ (mine[x + 1][y - 1] - '0')
+ (mine[x + 1][y] - '0')
+ (mine[x + 1][y + 1] - '0'));
}
void Simple(char mine[ROWB][COLB], char board[ROWB][COLB], int x, int y,int z)
//在游戏初始对玩家第一次选的坐标进行信息扩展,用以降低游戏难度
{
if (((x <= 10 && x >= 1) && (y <= 10 && y >= 1))&&(board[x][y]=='*'))
{
while (z>0)
{
if ('1' == mine[x][y])
{
return;
}
else
{
int numminearound = GetNumMine(mine, x, y);
board[x][y] = numminearound + '0';
}
int a = --z;
Simple(mine, board, x - 1, y - 1, a);
Simple(mine, board, x - 1, y, a);
Simple(mine, board, x - 1, y + 1, a);
Simple(mine, board, x, y - 1, a);
Simple(mine, board, x, y + 1, a);
Simple(mine, board, x + 1, y - 1, a);
Simple(mine, board, x + 1, y, a);
Simple(mine, board, x + 1, y + 1, a);
}
}
return;
}
我们来运行程序验证一下:
程序正确。