您现在的位置是:首页 >其他 >扫雷游戏(C语言)网站首页其他
扫雷游戏(C语言)
C语言实现扫雷
一、多文件整体概括
game.h
#pragma once
#include <stdio.h>//scanf和printf
#include <stdlib.h>//系统的清屏和随机数函数
#include <time.h>//时间函数,主要为了产生随机数
#define ROW 9 //要操作的行
#define COL 9 //要操作的列
#define ROWS ROW+2 //实际的行
#define COLS COL+2 //实际的列
#define Easy_Count 80
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 MineBoard[ROWS][COLS], char playBoard[ROWS][COLS], int row, int col);//排查雷
int GetMineCount(char Board[ROWS][COLS], int row, int col);//计算该位置周围的八个位置有几个雷
void ExtendBoard(char MineBoard[ROWS][COLS], char playBoard[ROWS][COLS], int row, int col, int x, int y);//爆炸展开
void MarkBrard(char MineBoard[ROWS][COLS], char playBoard[ROWS][COLS], int row, int col);//标记雷
void UnMarkBoard(char MineBoard[ROWS][COLS], char playBoard[ROWS][COLS], int row, int col);//取消标记
int isWin(char playBoard[ROWS][COLS], int row, int col);//判断输赢
game.h主要是对要用到的函数,头文件进行引入和声明
扫雷.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
int count1 = 0;//判断是否标记正确
int count2 = Easy_Count;//最多标记次数
int win = 0;
//初始化棋盘
void InitBoard(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;
}
}
}
//打印棋盘
void PrintBoard(char Board[ROWS][COLS], int row, int col)
{
printf("------扫雷游戏--------
");
for (int i = 0;i <=row;i++)
{
printf("%d ", i);
}
printf("
");
for (int i = 1;i <= row;i++)
{
printf("%d ", i);
for (int j = 1;j <= col;j++)
{
printf("%c ",Board[i][j]);
}
printf("
");
}
}
//设置雷
void SetMine(char Board[ROWS][COLS], int row, int col)
{
int count = Easy_Count;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (Board[x][y] == '0')
{
Board[x][y] = '1';
count--;
}
}
}
//排查雷
void FindMine(char MineBoard[ROWS][COLS], char playBoard[ROWS][COLS], int row, int col)
{
PrintBoard(playBoard, row, col);
int x = 0;
int y = 0;
while (1)
{
printf("请输入要排查的位置->(用空格隔开)
");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (playBoard[x][y] != '*')
{
printf("该位置已经排查过!!!请重新输入->
");
}
else
{
if (MineBoard[x][y] == '1')
{
//排查的位置是雷
printf("很遗憾,你被炸死了!
");
PrintBoard(MineBoard, row, col);
break;
}
else
{
//排查的位置不是雷,显示周围八个有几个雷
ExtendBoard(MineBoard, playBoard, row, col, x, y);
break;
}
}
}
else
{
printf("坐标不合法!!!请重新输入->
");
}
}
}
//计算周围有多少个雷
int GetMineCount(char Board[ROWS][COLS], int x, int y)
{
return (Board[x - 1][y - 1] + Board[x - 1][y] + Board[x - 1][y + 1] + Board[x][y - 1] + Board[x][y + 1] + Board[x + 1][y - 1] + Board[x + 1][y] + Board[x + 1][y + 1] - 8 * '0');
}
//展开一片
void ExtendBoard(char MineBoard[ROWS][COLS], char playBoard[ROWS][COLS], int row, int col, int x, int y)
{
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
int count = GetMineCount(MineBoard, x, y);
if (!count)
{
playBoard[x][y] = ' ';
for (int i = x - 1;i <= x + 1;i++)
{
for (int j = y - 1;j <= y + 1;j++)
{
if (playBoard[i][j] == '*')
ExtendBoard(MineBoard, playBoard, ROW, COL, i, j);
}
}
}
else
{
playBoard[x][y] = count + '0';
}
}
}
//标记雷
void MarkBrard(char MineBoard[ROWS][COLS], char playBoard[ROWS][COLS], int row, int col)
{
PrintBoard(playBoard, row, col);
int x = 0;
int y = 0;
printf("请选择要标记的坐标->(用空格隔开)
");
scanf("%d %d", &x, &y);
while (1)
{
if(x >= 1 && x <= row && y >= 1 && y <= col)//坐标合法
{
if (playBoard[x][y] == '*')//可以标记
{
playBoard[x][y] = '#';
count2--;
if (MineBoard[x][y] == '1')
{
count1++;
}
break;
}
else
{
printf("该位置已被标记过,请重新输入!!!
");
}
}
else//坐标不合法
{
printf("非法坐标!!!请重新输入!!!
");
}
}
}
//取消标记
void UnMarkBoard(char MineBoard[ROWS][COLS], char playBoard[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
PrintBoard(playBoard, row, col);
printf("请输入要取消标记的坐标->(中间用空格隔开)
");
scanf("%d %d", &x, &y);
while (1)
{
if (x >= 1 && x <= row && y >= 1 && y <= col)//坐标合法
{
if (playBoard[x][y] == '#')
{
playBoard[x][y] = '*';
count2++;
if (MineBoard[x][y] == '1')
{
count1--;
}
break;
}
else
{
printf("该位置未曾标记,无法取消,请重新输入->
");
}
}
else//坐标非法
{
printf("非法坐标!!!请重新输入!!!
");
}
}
}
int isWin(char playBoard[ROWS][COLS], int row, int col)
{
for (int i = 1;i <= row;i++)
{
for (int j = 1;j <= col;j++)
{
if (playBoard[i][j] == '*')
{
win++;
}
}
}
if (win == Easy_Count)//剩余未排查的坐标等于雷的个数,获胜
{
win = 0;
return 1;
}
else if (count1 == Easy_Count)//将雷的位置全部标记,获胜
{
win = 0;
return 1;
}
else return 0;
}
扫雷.c主要是对该游戏要用到的函数的实现
test.c (main函数实现逻辑)
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
printf("***************************
");
printf("*********1. 排查雷 ********
");
printf("*********2. 标记雷 ********
");
printf("*********3. 取消标记 ********
");
printf("*********0. 退出游戏********
");
}
int main()
{
regame:
{
int input = 0;
srand((unsigned int)time(NULL));
char mineBoard[ROWS][COLS];//存放布置好的雷
char playBoard[ROWS][COLS];//存放排查出的雷
//初始化棋盘
//1.mineBoard数组最开始全是'0'
//2.playBoard数组最开始全是'*'
InitBoard(mineBoard, ROWS, COLS, '0');
InitBoard(playBoard, ROWS, COLS, '*');
//打印棋盘
//PrintBoard(mineBoard,ROW,COL);
//PrintBoard(playBoard, ROW, COL);
//布置雷
SetMine(mineBoard, ROW, COL);
PrintBoard(mineBoard, ROW, COL);
do
{
menu();
printf("请选择->
");
scanf("%d", &input);
switch (input)
{
case 0:
{
goto exit;
break;
}
case 1:
{
FindMine(mineBoard, playBoard, ROW, COL);
if (isWin(playBoard, ROW, COL))
{
PrintBoard(playBoard, ROW, COL);
PrintBoard(mineBoard, ROW, COL);
printf("恭喜你,排雷成功^ ^
");
printf("本轮游戏结束,是否想再玩一局->(Y/N)
");
while (getchar() != '
')//吸收多余回车
{
}
char s = 0;
while (1)
{
scanf("%c", &s);
if (s == 'Y')
{
system("cls");//清空屏幕
goto regame;
}
else if (s == 'N')
{
goto exit;
}
else
{
printf("输入非法,请重新输入
");
}
}
break;
}
PrintBoard(playBoard, ROW, COL);
break;
}
case 2:
{
MarkBrard(mineBoard, playBoard, ROW, COL);
if (isWin(playBoard, ROW, COL))
{
PrintBoard(playBoard, ROW, COL);
PrintBoard(mineBoard, ROW, COL);
printf("恭喜你,排雷成功^ ^
");
printf("本轮游戏结束,是否想再玩一局->(Y/N)
");
while (getchar() != '
')//吸收多余回车
{
}
char s = 0;
while (1)
{
scanf("%c", &s);
if (s == 'Y')
{
system("cls");//清空屏幕
goto regame;
}
else if (s == 'N')
{
goto exit;
}
else
{
printf("输入非法,请重新输入
");
}
}
break;
}
PrintBoard(playBoard, ROW, COL);
break;
}
case 3:
{
UnMarkBoard(mineBoard, playBoard, ROW, COL);
if (isWin(playBoard, ROW, COL))
{
PrintBoard(playBoard, ROW, COL);
PrintBoard(mineBoard, ROW, COL);
printf("恭喜你,排雷成功^ ^
");
printf("本轮游戏结束,是否想再玩一局->(Y/N)
");
while (getchar() != '
')//吸收多余回车
{
}
char s = 0;
while (1)
{
scanf("%c", &s);
if (s == 'Y')
{
system("cls");//清空屏幕
goto regame;
}
else if (s == 'N')
{
goto exit;
}
else
{
printf("输入非法,请重新输入
");
}
}
break;
}
PrintBoard(playBoard, ROW, COL);
break;
}
default:
printf("非法输入!!!请重新输入->
");
break;
}
} while (input);
exit: printf("游戏退出
");
}
}
test.c主要是对整个游戏的逻辑进行实现,当中用到了goto语句和system(“cls”)清屏函数,主要是想对之前学过的进行运用,也可以通过其他方法实现;当玩完一局,会让用户输入Y/N决定是否在玩一局
二、子函数实现
(1)初始化棋盘
//初始化棋盘
void InitBoard(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;
}
}
}
因为用到了两个字符二维数组,一个是给用户看的(playBoard[ROWS][COLS]),另一个是我们设置雷用的(mineBoard[ROWS][COLS]),playBoard[ROWS][COLS]初始值为*,mineBoard[ROWS][COLS]初始值为’0’,数组长度定义为11*11,但实际上我们用到的只是1-9这个9**9的二维数组,这样设计的好处在于我们在计算周围八个位置的时候保证不会越界;
(2)打印棋盘
//打印棋盘
void PrintBoard(char Board[ROWS][COLS], int row, int col)
{
printf("------扫雷游戏--------
");
for (int i = 0;i <=row;i++)
{
printf("%d ", i);//第一行加上0-9
}
printf("
");
for (int i = 1;i <= row;i++)
{
printf("%d ", i);//每一列开头分别加上1-9
for (int j = 1;j <= col;j++)
{
printf("%c ",Board[i][j]);
}
printf("
");
}
}
为了用户输入方便,我们在行和列前加上数字;
(3)设置雷
/设置雷
void SetMine(char Board[ROWS][COLS], int row, int col)
{
int count = Easy_Count;
while (count)
{
int x = rand() % row + 1;//产生1-9的随机数
int y = rand() % col + 1;//产生1-9的随机数
if (Board[x][y] == '0')
{
Board[x][y] = '1';
count--;
}
}
}
定义局部变量count初始值为雷的个数,每成功布置一个雷,count减1,直到雷全部布置好 count==0;布置雷的过程是利用随机数产生对应的坐标,所以不用担心坐标不合法;
(4)排查雷
//排查雷
void FindMine(char MineBoard[ROWS][COLS], char playBoard[ROWS][COLS], int row, int col)
{
PrintBoard(playBoard, row, col);
int x = 0;
int y = 0;
while (1)
{
printf("请输入要排查的位置->(用空格隔开)
");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)//判断坐标是否合法
{
if (playBoard[x][y] != '*')//判断该位置是否被排查过
{
printf("该位置已经排查过!!!请重新输入->
");
}
else
{
if (MineBoard[x][y] == '1')//判断该位置是不是雷,是雷炸死
{
//排查的位置是雷
printf("很遗憾,你被炸死了!
");
PrintBoard(MineBoard, row, col);
break;
}
else
{
//排查的位置不是雷,显示周围八个有几个雷
ExtendBoard(MineBoard, playBoard, row, col, x, y);
break;
}
}
}
else
{
printf("坐标不合法!!!请重新输入->
");
}
}
}
大概的过程说明在上面的代码注释中;
(5)计算该位置周围有几个雷
//计算周围有多少个雷
int GetMineCount(char Board[ROWS][COLS], int x, int y)
{
return (Board[x - 1][y - 1] + Board[x - 1][y] + Board[x - 1][y + 1] + Board[x][y - 1] + Board[x][y + 1] + Board[x + 1][y - 1] + Board[x + 1][y] + Board[x + 1][y + 1] - 8 * '0');
}
可以用这种方式,也可以用循环,这里我用这种方式,下面展开时我用循环;
(6)爆炸展开一片
//展开一片
void ExtendBoard(char MineBoard[ROWS][COLS], char playBoard[ROWS][COLS], int row, int col, int x, int y)
{
//当我能调用到这个函数时,说明该位置一定不是雷,
//其他八个位置递归时也能保证他们不是雷(因为上一层递归时坐标周围没有雷才能继续递归)
if (x >= 1 && x <= row && y >= 1 && y <= col)//递归位置合法
{
int count = GetMineCount(MineBoard, x, y);
if (!count)//该坐标周围没有雷
{
playBoard[x][y] = ' ';
for (int i = x - 1;i <= x + 1;i++)
{
for (int j = y - 1;j <= y + 1;j++)
{
if (playBoard[i][j] == '*')//该位置没有被递归过
ExtendBoard(MineBoard, playBoard, ROW, COL, i, j);
}
}
}
else//该位置周围有雷,结束递归
{
playBoard[x][y] = count + '0';
}
}
}
要清楚展开的条件(递归可以进行的条件)
(1)该坐标不是雷 MineBoard[x][y]!=‘1’
(2)该坐标周围没有雷 count==0
(3)该坐标没有被递归过 playBoard[x][y]=‘*’
(4)坐标合法 (x >= 1 && x <= row && y >= 1 && y <= col)
(7)标记雷
//标记雷
void MarkBrard(char MineBoard[ROWS][COLS], char playBoard[ROWS][COLS], int row, int col)
{
PrintBoard(playBoard, row, col);
int x = 0;
int y = 0;
printf("请选择要标记的坐标->(用空格隔开)
");
scanf("%d %d", &x, &y);
while (1)
{
if(x >= 1 && x <= row && y >= 1 && y <= col)//坐标合法
{
if (playBoard[x][y] == '*')//可以标记
{
playBoard[x][y] = '#';
count2--;
if (MineBoard[x][y] == '1')
{
count1++;
}
break;
}
else
{
printf("该位置已被标记过,请重新输入!!!
");
}
}
else//坐标不合法
{
printf("非法坐标!!!请重新输入!!!
");
}
}
}
count1是用来确认标记是否正确,也就是说标记的是否是雷,后面用来判断胜利
count2是最多标记次数,等于雷的个数
(8)取消标记
//取消标记
void UnMarkBoard(char MineBoard[ROWS][COLS], char playBoard[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
PrintBoard(playBoard, row, col);
printf("请输入要取消标记的坐标->(中间用空格隔开)
");
scanf("%d %d", &x, &y);
while (1)
{
if (x >= 1 && x <= row && y >= 1 && y <= col)//坐标合法
{
if (playBoard[x][y] == '#')
{
playBoard[x][y] = '*';
count2++;
if (MineBoard[x][y] == '1')
{
count1--;
}
break;
}
else
{
printf("该位置未曾标记,无法取消,请重新输入->
");
}
}
else//坐标非法
{
printf("非法坐标!!!请重新输入!!!
");
}
}
}
取消标记主要用来让用户在标记错误的时候可以取消,增加了容错率,与标记的思想刚好相反
(9)判断胜利
int isWin(char playBoard[ROWS][COLS], int row, int col)
{
for (int i = 1;i <= row;i++)
{
for (int j = 1;j <= col;j++)
{
if (playBoard[i][j] == '*')
{
win++;
}
}
}
if (win == Easy_Count)//剩余未排查的个数等于雷的个数,获胜
{
win = 0;
return 1;
}
else if (count1 == Easy_Count)//将雷的位置全部标记,获胜
{
win = 0;
return 1;
}
else return 0;
}
判断胜利的条件
(1)若剩余为排查的个数等于雷的个数,则排雷成功
(2)若将所有是雷的位置标记,则排雷成功
(3)以上条件不满足,游戏继续进行
三、运行结果
(1)正常排查
为了方便设置了80个雷,只有一个位置不是雷
(2)标记雷获胜
四、总结
该程序仍然存在许多不足,如下
(1)程序可玩性不高,不能让用户选择难度,要想改变棋盘大小和雷的个数,要通过修改程序中的宏定义完成,由于时间原因,后续会进行修改;
(2)主函数中有许多冗余代码,待改进
优点:除了无法选择难度,游戏功能实现较完善,可供初学者参考;