扫雷小游戏——C语言实现(超详细版本)
前言
扫雷游戏的玩法是在一个9* 9(初级),16* 16(中级),16*30(高级),或自定义大小的方块矩阵中随机布置一定量的地雷(初级为10个,中级为40个,高级为99个)。由玩家逐个翻开方块,以找出所有地雷为最终游戏目标。如果玩家翻开的方块有地雷,则游戏结束。
一,游戏玩法简介
1.布置雷。
2.扫雷
**注:**假定该扫雷棋盘为9*9的棋盘,如下图:
则该棋盘的每一个小格子都可以用(x,y)的二维坐标表达出来。
(1)玩家输入坐标(x,y)-x为行,y为列。
(2)判断该坐标小是否为雷,若为雷,则玩家被炸死,游戏结束,并且显示棋盘上所有雷的分布情况以及在该坐标上显示该左边周围八个坐标的雷的个数;若不是雷,则在其坐标上显示该坐标周围的八个坐标中有几个雷。其过程如下图:
二,游戏实现的基本思想:
写在前面:
1.test.c -主函数
2.game.c-游戏的实现文件,游戏的各个功能的都在这个函数中实现。
3.game.h-游戏的头文件。
--------------------------------------------分割线-----------------------------------------
2.1 棋盘表示
如上图:假定把扫雷的棋盘看作是一个9*9的方块格子,则每个小格子都有其固定的坐标,因此在这里我们可以用二维数组来表示这个棋盘。即char [9][9]
2.2 棋盘的分类
在这里我们需要在棋盘上显示布置好的雷的信息(左图),同时也需要显示排查雷的信息(右图),因此如果只在一张棋盘上显示这两种信息的话可能会造成信息的混乱。因此,在这里我使用两张棋盘,分别显示以上对应的两种信息。
char bomb[9][9]; //布置好的雷的信息
char show[9][9]; //排查出的雷的信息
注:在bomb棋盘中,我们设定小格子内容-“1”为雷,“0”非雷;在show棋盘中,我们设定小方格的内容-“*”为未排查,数组字符为已排查。
2.3 (排查方式导致)棋盘的扩展
如上图,若玩家选定的坐标为(4,3)不是雷时,这时我们就需要排查其周围八个坐标雷的个数,并可以将雷的个数显示出来;但是,但玩家输入坐标为(8,8)时,这个时候排查该坐标周围的八个坐标时就产生了越界行为,因此我们不妨在将9* 9的棋盘改为11* 11的棋盘,对应的9* 9的二维数组就改成11* 11的二维数组。
注:11* 11的棋盘只用于排查选定的坐标周围雷的个数,实际上游戏运行使用的还是9* 9的棋盘。
三,游戏代码实现
写在前面:因为扫雷游戏涉及到的函数较多,在这里我们要采用c语言的模块化程序设计的思想。
注:模块化编程是指将一个庞大的程序划分为若干个功能独立的模块,对各个模块进行独立开发,然后再将这些模块统一合并为一个完整的程序。这是C语言面向过程的编程方法,可以缩短开发周期,提高程序的可读性和可维护性。
3.1 主函数和菜单函数的创建
主函数–
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
int input = 0;
do
{
menu();
printf("请选择:\n");
scanf("%d", &input);
switch (input)
{
case 1:
printf("开始游戏!\n");
break;
case 0:
printf("退出游戏!\n");
break;
default:
printf("输入数字错误,请重新输入:\n");
}
} while (input);
return 0;
}
这里注意菜单函数-menu()的位置。
菜单函数–menu()
void menu()
{
printf("******************************\n");
printf("********* 1. play ********\n");
printf("********* 0. exit ********\n");
printf("******************************\n");
}
运行结果–
这里运行结果没有问题,即可进行游戏函数(game();)的实现。
3.2 游戏函数创建
3.2.1 头文件-game.h的创建
头文件创建完毕,在这里我们可以将所有涉及到的头文件函数都放在这个头文件中。
例如:在这里我们将主函数中的头文件函数#include<stdio.h>放在game.h中,在主函数中我们直接调用这个即可。
#pragma once
#include<stdio.h>
在这里注意调用格式为 #include “game.h”
#include "game.h"
3.2.2 游戏函数-game.c的创建
game.c的创建与函数创建相同,只需把文件名称修改成game.c即可。
注意game()函数在主函数中的位置。
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("输入数字错误,请重新输入:");
}
} while (input);
return 0;
}
3.3 数组和棋盘的初始化
3.3.1-test.c
void game() //扫雷游戏的实现
{
//数组的创建与初始化
char bomb[ROWS][COLS] = { 0 }; // 表示布置好雷的信息
char show[ROWS][COLS] = { 0 }; // 表示排查出雷的信息
//棋盘的初始化
Init_board(bomb, ROWS,COLS,'0'); //雷未放置前,将棋盘初始化,即每个小格子都是'0' 注:'0'表示不是雷,'1'表示是雷。
Init_board(show, ROWS,COLS,'*'); //这里将棋盘每个小格子初始为'*'
}
3.3.2-game.h
#pragma once
#include<stdio.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
这里引用#define是为了是可以随意修改成任意大小的棋盘,提高游游戏难度。
3.3.3-game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
//初始化棋盘
void Init_board(char arr[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++)
{
arr[i][j] = set;
}
}
}
3.4 打印棋盘
3.4.1-test.c
void game() //扫雷游戏的实现
{
//数组的创建与初始化
char bomb[ROWS][COLS] = { 0 }; // 用来存放布置好雷的信息
char show[ROWS][COLS] = { 0 }; // 用来存放排查出雷的信息
//棋盘的初始化
Init_board(bomb, ROWS,COLS,'0'); //雷未放置前,将棋盘初始化,即每个小格子都是'0' 注:'0'表示不是雷,'1'表示是雷。
Init_board(show, ROWS,COLS,'*'); //这里将棋盘每个小格子初始为'*'
//打印棋盘
Print_board(bomb, ROW, COL); //打印布置雷的棋盘
Print_board(show, ROW, COL); //打印排查雷的棋盘
}
注:在这里我们只需要打印我们所需要的9*9的棋盘即可,但是棋盘传参还是11 *11。
3.4.2-game.h
#pragma once
#include<stdio.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
void Init_board(char arr[ROWS][COLS],int rows ,int cols,char set);//初始化棋盘
void Print_board(char arr[ROWS][COLS], int row, int col); //打印棋盘
3.4.3-game.c
void Print_board(char arr[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("\n");
for (i = 1; i <=row; i++)
{
for (j = 1; j <=col; j++)
{
printf("%c ", arr[i][j]);
}
printf("\n");
}
打印效果:
该扫雷游戏需要我们输入对应的左边来进行游戏,因此我们为了更好的确定坐标,我们加上行和列的标号。
修改后的代码:
//打印棋盘
void Print_board(char arr[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 ", arr[i][j]);
}
printf("\n");
}
printf("--------扫雷--------------\n");
}
运行效果:
3.5 布置雷
3.5.1-test.c
1.查看雷的是否排布
void game() //扫雷游戏的实现
{
//数组的创建与初始化
char bomb[ROWS][COLS] = { 0 }; // 用来存放布置好雷的信息
char show[ROWS][COLS] = { 0 }; // 用来存放排查出雷的信息
//棋盘的初始化
Init_board(bomb, ROWS,COLS,'0'); //雷未放置前,将棋盘初始化,即每个小格子都是'0' 注:'0'表示不是雷,'1'表示是雷。
Init_board(show, ROWS,COLS,'*'); //这里将棋盘每个小格子初始为'*'
//打印棋盘
//Print_board(bomb, ROW, COL); //打印布置雷的棋盘
//Print_board(show, ROW, COL); //打印排查雷的棋盘
//布置雷
set_bomb(bomb, ROW, COL); //布置雷的函数
Print_board(bomb, ROW, COL); //打印布置雷的棋盘,查看雷是否布置完成
}
运行结果:
2.将雷用’*'覆盖,使得玩家不可见
void game() //扫雷游戏的实现
{
//数组的创建与初始化
char bomb[ROWS][COLS] = { 0 }; // 用来存放布置好雷的信息
char show[ROWS][COLS] = { 0 }; // 用来存放排查出雷的信息
//棋盘的初始化
Init_board(bomb, ROWS,COLS,'0'); //雷未放置前,将棋盘初始化,即每个小格子都是'0' 注:'0'表示不是雷,'1'表示是雷。
Init_board(show, ROWS,COLS,'*'); //这里将棋盘每个小格子初始为'*'
//打印棋盘
//Print_board(bomb, ROW, COL); //打印布置雷的棋盘
//Print_board(show, ROW, COL); //打印排查雷的棋盘
//布置雷
set_bomb(bomb, ROW, COL); //布置雷的函数
Print_board(show, ROW, COL); //打印布置雷信息的棋盘,目的是为了把布置的雷给用*号覆盖,不能让玩家看到
}
运行结果:
3.5.2-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 EASY_COUNT 10 //雷的个数
void Init_board(char arr[ROWS][COLS],int rows ,int cols,char set);//初始化棋盘
void Print_board(char arr[ROWS][COLS], int row, int col); //打印棋盘
void set_bomb(char bomb[ROWS][COLS], int row, int col); //布置雷,这里传入的依然是11*11的棋盘,但是我们只在9*9的棋盘棋盘中布雷
3.5.3-game.c
//布置雷
void set_bomb(char bomb[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT; //设置雷的个数,这里将雷的个数设为可修改的值,方便后续游戏进行修改。
int x = 0;
int y = 0; //因为棋盘为9*9,所以x的范围为1~9,y的范围为1~9
while (count)
{
x = (rand() % row) + 1;
y = (rand() % col) + 1; //由srand((unsigned int)time(NULL)),生成在1~9之间,x和y的随机值
if (bomb[x][y] == '0')
{
bomb[x][y] ='1'; //布置雷
count--;
}
}
}
这里需要在引用rand()函数来生成随机数,在主函数中要引入srand((unsigned int)time(NULL));
3.6 排查雷
3.6.1 排雷的原理(重点):
如何排查出雷的个数:
我们初始化布置雷的棋盘是字符’0’,它对应的ASCII值为48,雷则是是用字符’1’表示,它对应的ASCII值为49,因此我们要想知道周围八个坐标有几个雷,就可以把周围八个坐标内的字符依次相加,后再减去8*‘0’,即可。
坐标的表示:
因此1~8的坐标依次为(x-1,y),(x-1,y-1),(x,y-1),(x+1,y-1),(x+1,y),(x+1,y+1),(x,y+1),(x-1,y+1)
代码实现:
test.c-
void game() //扫雷游戏的实现
{
//数组的创建与初始化
char bomb[ROWS][COLS] = { 0 }; // 用来存放布置好雷的信息
char show[ROWS][COLS] = { 0 }; // 用来存放排查出雷的信息
//棋盘的初始化
Init_board(bomb, ROWS,COLS,'0'); //雷未放置前,将棋盘初始化,即每个小格子都是'0' 注:'0'表示不是雷,'1'表示是雷。
Init_board(show, ROWS,COLS,'*'); //这里将棋盘每个小格子初始为'*'
//打印棋盘
//Print_board(bomb, ROW, COL); //打印布置雷的棋盘
//Print_board(show, ROW, COL); //打印排查雷的棋盘
//布置雷
set_bomb(bomb, ROW, COL); //布置雷的函数
Print_board(show, ROW, COL); //打印布置雷的棋盘,查看雷是否布置完成
//排查雷
find_bomb(bomb, show, ROW, COL);
}
game.c-
//排查雷
int get_bomb_count(char bomb[ROWS][COLS], int x, int y)
{
return bomb[x - 1][y] + bomb[x - 1][y - 1] + bomb[x][y - 1] +
bomb[x + 1][y - 1] + bomb[x + 1][y] + bomb[x + 1][y + 1] +
bomb[x][y + 1] + bomb[x - 1][y + 1] - 8 * '0';
}
void find_bomb(char bomb[ROWS][COLS], char show[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 (bomb[x][y] == '1') //玩家输入的坐标是雷
{
printf("很遗憾,是炸弹,你被炸飞了!\n");
Print_board(bomb, ROW, COL); //玩家失败后显示雷的位置
break;
}
else
{
int count = get_bomb_count(bomb, x, y); //玩家输入的坐标不是雷,显示周围8个坐标雷的个数之和
show[x][y] = count + '0'; //将数值转化成字符打印在棋盘上
Print_board(show, ROW, COL);
}
}
else
{
printf("输入坐标非法,请重新输入:\n");
}
}
}
show[x][y] = count + ‘0’; 是为了将count的数值转换为字符数据并打印在棋盘上,即坐标周围八个坐标雷的个数。
运行效果:
1-踩雷,游戏结束,并显示雷分布的情况:
2-未踩雷,只显示周围八个坐标雷的总和
3.7 判断输赢
3.7.1-game.c
void find_bomb(char bomb[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0; //win表示玩家已下的且不是雷的坐标之和
while (win<row*col-EASY_COUNT) //row*col在这里表示9*9=81个小格子,减去10个雷,即玩家最多可以下71次
{
printf("请输入坐标:");
scanf("%d %d", &x, &y);
if (x >= 1 && x < row && y >= 1 && y < col)
{
if (bomb[x][y] == '1') //玩家输入的坐标是雷
{
printf("很遗憾,是炸弹,你被炸飞了!\n");
Print_board(bomb, ROW, COL); //玩家失败后显示雷的位置
break;
}
else
{
int count = get_bomb_count(bomb, x, y); //玩家输入的坐标不是雷,显示周围8个坐标雷的个数之和
show[x][y] = count + '0'; //将数值转化成字符打印在棋盘上
Print_board(show, ROW, COL);
win++;
}
}
else
{
printf("输入坐标非法,请重新输入:\n");
break;
}
if (win == row * col - EASY_COUNT) //在雷的个数设为10个,9*9的棋盘中,当玩家输入不是雷的坐标总数为81-10=71时,代表棋盘上所有的格子都已占完,玩家胜利
{
printf("恭喜你,扫雷成功!\n");
}
}
}
3.7.2-game.h
#include<stdlib.h>
#include<time.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EASY_COUNT 10
void Init_board(char arr[ROWS][COLS],int rows ,int cols,char set);//初始化棋盘
void Print_board(char arr[ROWS][COLS], int row, int col); //打印棋盘
void set_bomb(char bomb[ROWS][COLS], int row, int col); //布置雷,这里传入的依然是11*11的棋盘,但是我们只在9*9的棋盘棋盘中布雷
void find_bomb(char bomb[ROWS],char show[COLS], int row, int col);//排查雷
玩家赢的判别方法采用的数学思想是(玩家输入不是雷的坐标=总的格子-雷的个数)。
3.8 游戏测试
测试该游戏的机制是否完整,即当所有的格子都被占用,玩家没踩雷的情况下,玩家是否能赢。可能有的人说把玩一遍就知道行不行了,但是这样做虽然可行,但是费事费力,稍有不慎,游戏就得重开。
因此在这里我们采用反向思维,将雷设置成80个,且雷分布情况的棋盘打印出来,即仅有一个坐标不是雷,我们输入对应的坐标,即可测试游戏结果。
代码修改(测试):
#define EASY_COUNT 80 //将雷的个数设为80个
//布置雷
set_bomb(bomb, ROW, COL); //布置雷的函数
Print_board(bomb, ROW, COL); //打印雷分布的情况
运行结果-
四,完整代码
4.1-test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
printf("************************\n");
printf("****** 1.开始游戏 ******\n");
printf("****** 0.退出游戏 ******\n");
printf("************************\n");
}
void game() //扫雷游戏的实现
{
//数组的创建与初始化
char bomb[ROWS][COLS] = { 0 }; // 用来存放布置好雷的信息
char show[ROWS][COLS] = { 0 }; // 用来存放排查出雷的信息
//棋盘的初始化
Init_board(bomb, ROWS,COLS,'0'); //雷未放置前,将棋盘初始化,即每个小格子都是'0' 注:'0'表示不是雷,'1'表示是雷。
Init_board(show, ROWS,COLS,'*'); //这里将棋盘每个小格子初始为'*'
//打印棋盘
//Print_board(bomb, ROW, COL); //打印布置雷的棋盘
//Print_board(show, ROW, COL); //打印排查雷的棋盘
//布置雷
set_bomb(bomb, ROW, COL); //布置雷的函数
Print_board(show, ROW, COL); //打印雷分布的情况
//排查雷
find_bomb(bomb, show, ROW, COL);
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL)); //生成随机数
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("开始游戏!\n");
game();
break;
case 0:
printf("退出游戏!\n");
break;
default:
printf("输入数字错误,请重新输入:");
}
} while (input);
return 0;
}
4.2 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 EASY_COUNT 10
void Init_board(char arr[ROWS][COLS],int rows ,int cols,char set);//初始化棋盘
void Print_board(char arr[ROWS][COLS], int row, int col); //打印棋盘
void set_bomb(char bomb[ROWS][COLS], int row, int col); //布置雷,这里传入的依然是11*11的棋盘,但是我们只在9*9的棋盘棋盘中布雷
void find_bomb(char bomb[ROWS],char show[COLS], int row, int col);//排查雷
4.3 game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
//初始化棋盘
void Init_board(char arr[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++)
{
arr[i][j] = set;
}
}
}
//打印棋盘
void Print_board(char arr[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 ", arr[i][j]);
}
printf("\n");
}
printf("--------扫雷--------------\n");
}
//布置雷
void set_bomb(char bomb[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT; //设置雷的个数,这里将雷的个数设为可修改的值,方便后续游戏进行修改。
int x = 0;
int y = 0; //因为棋盘为9*9,所以x的范围为1~9,y的范围为1~9
while (count)
{
x = (rand() % row ) + 1;
y = (rand() % col ) + 1; //由srand((unsigned int)time(NULL)),生成在1~9之间,x和y的随机值
if (bomb[x][y] == '0')
{
bomb[x][y] ='1'; //布置雷
count--;
}
}
}
//排查雷
int get_bomb_count(char bomb[ROWS][COLS], int x, int y)
{
return bomb[x - 1][y] + bomb[x - 1][y - 1] + bomb[x][y - 1] +
bomb[x + 1][y - 1] + bomb[x + 1][y] + bomb[x + 1][y + 1] +
bomb[x][y + 1] + bomb[x - 1][y + 1] - 8 * '0';
}
void find_bomb(char bomb[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0; //win表示玩家已下的且不是雷的坐标之和
while (win<row*col-EASY_COUNT) //row*col在这里表示9*9=81个小格子,减去10个雷,即玩家最多可以下71次
{
printf("请输入坐标:");
scanf("%d %d", &x, &y);
if (x >= 1 && x < row && y >= 1 && y < col)
{
if (bomb[x][y] == '1') //玩家输入的坐标是雷
{
printf("很遗憾,是炸弹,你被炸飞了!\n");
Print_board(bomb, ROW, COL); //玩家失败后显示雷的位置
break;
}
else
{
int count = get_bomb_count(bomb, x, y); //玩家输入的坐标不是雷,显示周围8个坐标雷的个数之和
show[x][y] = count + '0'; //将数值转化成字符打印在棋盘上
Print_board(show, ROW, COL);
win++;
}
}
else
{
printf("输入坐标非法,请重新输入:\n");
break;
}
if (win == row * col - EASY_COUNT) //在雷的个数设为10个,9*9的棋盘中,当玩家输入不是雷的坐标总数为81-10=71时,代表棋盘上所有的格子都已占完,玩家胜利
{
printf("恭喜你,扫雷成功!\n");
}
}
}