扫雷游戏是一款非常经典的小游戏,也能在一定程度上锻炼我们的思维,那么如何利用C语言实现一个扫雷游戏呢,这次我们就用最基本的一些C语言语法来具体的实现。
目录
游戏玩法
主函数的设计
game()函数的设计
棋盘设计
生成棋盘
放置雷
判断游戏状态
如何判断输赢
输出周围雷的个数
代码优化
游戏试玩
源码展示
头文件
创建的函数
主函数
要设计这样一个游戏,首先要了解它的玩法:在一个N *M的格子里一共随机布置了n个雷,当我们把所有的不是雷的地方全部找出来时,就算游戏过关;如果不幸在途中点到了地雷,那么游戏失败。每点到一个不是雷的位置,都会显示一个数字来表示其周围一圈共有几个雷。
首先创建主函数,要包含菜单,进入游戏、选择难度等功能,具体实现与上期大同小异:
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择要进行的操作:>(1/0)");
scanf("%d", &input);
switch (input)
{
case 1:
puts("玩游戏");
game();
break;
case 0:
puts("退出游戏");
break;
default:
puts("操作错误,即将返回菜单");
Sleep(1000);//系统暂停1秒
//这里的Sleep函数需要引入头文件才可使用
}
} while (input);
return 0;
}
在开始之前,首先定义一些后续要用到的宏:
#define ROW 9 //玩家看到的棋盘行数
#define COL 9 //玩家看到的棋盘列数
#define ROWS ROW + 2//实际棋盘行数
#define COLS COL + 2//实际棋盘列数
#define COUNTS 10//设置雷的个数
当我们把菜单等外观功能处理好以后,就可以进入正题,设计game()函数,首先我们可以看一个现成的扫雷游戏:
显然我们首先需要一个棋盘,可以通过遍历一个二维数组来创建,我们可以设计字符0为安全,字符1为雷。这样设计的好处是,后续计算周围有几个雷的时候,直接把这些1加起来就可以了,代码上也会更简便一些。不过这样设计的话可能会产生一个问题,如果该区域周围有一个雷,那么该区域就会显示为1,这个1显然不是雷,不过计算机却会把它当作是一个雷,因此在设计上我们可以直接设计两个棋盘,一个用来存放地雷,另一个用以向用户展示并游戏。也就是一个为原始棋盘init_board(用来存放地雷),另一个为player_board(用来显示)。
首先生成一个9 * 9棋盘:(根据传入的set值的不同输出的初始化棋盘也不同)
void init_board(char board[ROWS][COLS], int rows, int cols, char set)
{
//这里的set为传入的初始化符号,想初始化成什么就传什么符号就好了
//这样函数的复用性就更强
int i = 0;
int j = 0;
for (i = 0; i< rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;//初始化棋盘
}
}
}
初始化后我们可以打印出棋盘来显示结果:
void print_board(char board[ROWS][COLS], int row, int col)
{
system("cls");//每次打印前清理屏幕,更加美观
int i = 0;
int j = 0;
for (i = 0; i <= row; 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");
}
}
结果如下图所示:
用于存放雷的棋盘也可以通过该函数打印。
这里数组行和列均设计为比实际行和列大2,这样设计的原因主要是避免后续排雷操作时数组越界。比如在第0行第0列计算周围雷的个数时就会造成数组越界问题。
接着需要在棋盘中放置雷,假设放置10个雷,需要用到随机数法(随机数法在上期有讲到,感兴趣的可以去了解一下),用到的头文件包括
void set_board(char board[ROWS][COLS], int row, int col, int count)
{
int i = 0;
while (i < count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
//得到的x,y值均为1-9之间的数
if (board[x][y] == '0')
{
board[x][y] = '1';
i++;
}//保证不重复
}
}
此时在打印棋盘可以得到:
当然这个棋盘是不能被看到的,我们所看到的棋盘需要在创建一个新的数组来显示,也就是所谓的表棋盘。
设计好棋盘并且布置好雷以后,就可以设计游戏的具体玩法了。
首先需要我们输入一个合法坐标,再进行后续的判断:
void play_board(char player_board[ROWS][COLS], char hidden_board[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int count = ROW * COL - COUNTS;//判断何时停下
while (count)
{
printf("请输入一个有效坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (player_board[x][y] == '*')//判断该位置是否已经被占用
{
if (hidden_board[x][y] == '1')
{
puts("你被炸死了");//判断该位置是否安全
print_board(hidden_board, ROWS, COLS);//展示原始数组
break;
}
else
{
int ret = find_board(hidden_board, x, y);//得到周围雷的个数
player_board[x][y] = ret + '0';
print_board(player_board, ROWS, COLS);
count--;
}
}
else
puts("该坐标已被占用,请重新输入");
}
else
puts("坐标非法,请重新输入");
}
}
此时如果输入的坐标是安全的,进入find_board()函数计算其周围雷的元素。
其实play_board()函数里面的count判断就可以了,每输入一一次坐标,只要是合法的并且安全的,count--,count的总数为棋盘横×列-雷的个数,当count为0是判断获胜。
由于我们设置的雷均为字符1,所以把周围8个字符加起来再减去8 * '0'就可以得到周围雷的个数,因此函数可以这样实现:
char find_board(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' ;
//由于字符10-字符0 = 数字10
//得到的结果即为该坐标周围的雷的个数
}
此时我们的扫雷小游戏就完成了,其中game()函数如下:
void game()
{
char hidden_board[ROWS][COLS] = { 0 };
char player_board[ROWS][COLS] = { 0 };
//创建棋盘
//存放地雷的棋盘
init_board(hidden_board, ROWS, COLS, '0');
//布置雷
set_board(hidden_board, ROW, COL, COUNTS);
//打印棋盘
/*print_board(hidden_board, ROW, COL);
//这一步是为了后续寻找BUG时更加方便,实际上这一行代码是不需要的*/
//用户游戏的棋盘
init_board(player_board, ROWS, COLS, '*');
print_board(player_board, ROW, COL);
//进入游戏
play_board(player_board, hidden_board, ROW, COL);
}
不过这样和我们所认识的扫雷还不太一样,比如说所排查的位置如果周围8个均没有雷,应该展开一片,而我们的代码并不能满足这样的要求,因此可以进行优化。
当所排查坐标周围有雷的时候,显然是不用更改代码的,当该位置坐标周围雷的个数为0时,就需要单独写一个函数来展开所有的0。代码如下:
void open_board(char hidden_board[ROWS][COLS], char player_board[ROWS][COLS], int row, int col, int x, int y)
{
int ret = find_board(hidden_board, x, y);
if (ret == 0)
{
player_board[x][y] = ' ';
if (x - 1 > 0 && y > 0 && player_board[x - 1][y] == '*')
open_board(hidden_board, player_board, row, col, x - 1, y);
if (x > 0 && y - 1 > 0 && player_board[x][y - 1] == '*')
open_board(hidden_board, player_board, row, col, x, y - 1);
if (x > 0 && y + 1 > 0 && player_board[x][y + 1] == '*')
open_board(hidden_board, player_board, row, col, x, y + 1);
if (x + 1 > 0 && y > 0 && player_board[x + 1][y] == '*')
open_board(hidden_board, player_board, row, col, x + 1, y);
//判断所选位置的上下左右是否可选,可选则在进入递归判断
}
else
player_board[x][y] = ret + '0';
}
如果添加这样一个函数的话,我们的count值就很难有正确的值,这时几乎不可能获胜(可选的坐标全选完了依然未获胜),那么我们的play_board()函数就需要这么修改一下:
void play_board(char player_board[ROWS][COLS], char hidden_board[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int count = ROW * COL - COUNTS;//判断何时停下
while (1)
{
printf("请输入一个有效坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (player_board[x][y] == '*')//判断该位置是否已经被占用
{
if (hidden_board[x][y] == '1')
{
print_board(hidden_board, ROW, COL);//展示原始数组
puts("你被炸死了");
Sleep(3000);
break;
}
else
{
int ret = find_board(hidden_board, x, y);//得到周围雷的个数
player_board[x][y] = ret + '0';
open_board(hidden_board, player_board, row, col, x, y);
print_board(player_board, ROW, COL);
int final_ret = is_win(player_board, row, col);
if (final_ret == COUNTS)
{
puts("恭喜你,获胜了");
break;
}
}
}
else
puts("该坐标已被占用,请重新输入");
}
else
puts("坐标非法,请重新输入");
}
}
同时需要再写入一个函数来判断游戏是否结束:原理是查找player_board数组里面'*'存在的个数
int is_win(char player_board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
int count = 0;
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)
{
if (player_board[i][j] == '*')
count++;
}
}
return count;//返回的值为剩余'*'的个数
//一共有几个雷就就应该有几个'*'剩下时判胜
}
#include
#include
#include
#include
#define ROW 9 //玩家看到的棋盘行数
#define COL 9 //玩家看到的棋盘列数
#define ROWS ROW + 2//实际棋盘行数
#define COLS COL + 2//实际棋盘列数
#define COUNTS 10//设置雷的个数
void init_board(char board[ROWS][COLS], int rows, int cols, char set);
void set_board(char board[ROWS][COLS], int row, int col, int count);
void print_board(char board[ROWS][COLS], int rows, int cols);
void play_board(char player_board[ROWS][COLS], char hidden_board[ROWS][COLS], int row, int col);
#include"check_game.h"
void init_board(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
int j = 0;
for (i = 0; i< rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;//初始化棋盘
}
}
}
void print_board(char board[ROWS][COLS], int row, int col)
{
system("cls");//每次打印前清理屏幕,更加美观
int i = 0;
int j = 0;
for (i = 0; i <= row; 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");
}
}
void set_board(char board[ROWS][COLS], int row, int col, int count)
{
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
//得到的x,y值均为1-9之间的数
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}//保证不重复
}
}
int find_board(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') ;
//由于字符10-字符0 = 数字10
//得到的结果即为该坐标周围的雷的个数
}
void open_board(char hidden_board[ROWS][COLS], char player_board[ROWS][COLS], int row, int col, int x, int y)
{
int ret = find_board(hidden_board, x, y);
if (ret == 0)
{
player_board[x][y] = ' ';
if (x - 1 > 0 && y > 0 && x - 1 <= 9 && y <= 9 && player_board[x - 1][y] == '*')
open_board(hidden_board, player_board, row, col, x - 1, y);
if (x > 0 && y - 1 > 0 && x <= 9 && y <= 9 && player_board[x][y - 1] == '*')
open_board(hidden_board, player_board, row, col, x, y - 1);
if (x > 0 && y + 1 > 0 && x <= 9 && y + 1<= 9 && player_board[x][y + 1] == '*')
open_board(hidden_board, player_board, row, col, x, y + 1);
if (x + 1 > 0 && y > 0 && x + 1 <= 9 && y <= 9 && player_board[x + 1][y] == '*')
open_board(hidden_board, player_board, row, col, x + 1, y);
}
else
player_board[x][y] = ret + '0';
}
int is_win(char player_board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
int count = 0;
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)
{
if (player_board[i][j] == '*')
count++;
}
}
return count;//返回的值为剩余'*'的个数
//一共有几个雷就就应该有几个'*'剩下时判胜
}
void play_board(char player_board[ROWS][COLS], char hidden_board[ROWS][COLS], int row, int 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 (player_board[x][y] == '*')//判断该位置是否已经被占用
{
if (hidden_board[x][y] == '1')
{
print_board(hidden_board, ROW, COL);//展示原始数组
puts("你被炸死了");
Sleep(3000);
break;
}
else
{
int ret = find_board(hidden_board, x, y);//得到周围雷的个数
player_board[x][y] = ret + '0';
open_board(hidden_board, player_board, row, col, x, y);
print_board(player_board, ROW, COL);
int final_ret = is_win(player_board, row, col);
if (final_ret == COUNTS)
{
puts("恭喜你,获胜了");
break;
}
}
}
else
puts("该坐标已被占用,请重新输入");
}
else
puts("坐标非法,请重新输入");
}
}
#include"check_game.h"
void menu()
{
puts("***********************************");
puts("**************1.play***************");
puts("**************0.exit***************");
puts("***********************************");
}
void game()
{
char hidden_board[ROWS][COLS] = { 0 };
char player_board[ROWS][COLS] = { 0 };
//创建棋盘
//存放地雷的棋盘
init_board(hidden_board, ROWS, COLS, '0');
//布置雷
set_board(hidden_board, ROW, COL, COUNTS);
//打印棋盘
/*print_board(hidden_board, ROW, COL);
//这一步是为了后续寻找BUG时更加方便,实际上这一行代码是不需要的*/
//用户游戏的棋盘
init_board(player_board, ROWS, COLS, '*');
print_board(player_board, ROW, COL);
//进入游戏
play_board(player_board, hidden_board, ROW, COL);
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择要进行的操作:>(1/0)");
scanf("%d", &input);
switch (input)
{
case 1:
puts("玩游戏");
game();
break;
case 0:
puts("退出游戏");
break;
default:
puts("操作错误,即将返回菜单");
Sleep(1000);//系统暂停1秒
}
} while (input);
return 0;
}