目录
游戏分析
游戏模块
?1、打印菜单
2、创建两个棋盘(字符数组)
3、初始化棋盘(字符数组)
4、打印棋盘与行列序号
5、布置雷
6、排雷
?完整代码
总结
扫雷这个游戏相信很多人都玩过,但深入去研究这个游戏的朋友应该很少。大部分人玩这个游戏只是用来打发一下时间,但当你真正去研究这个游戏时,就会发现这个游戏其实很有趣。
对这个游戏感兴趣的朋友或者对编程感兴趣的初学者肯定也想自己动手使用自己所学的知识来实现这个游戏。
进入主题-------使用C语言简单实现这个游戏
游戏分析
在实现这个游戏之前先分析一下游戏的布局。
扫雷初级是一个9*9的布局(后文中称这个布局为棋盘),使用二维数组来创建棋盘。
扫雷游戏玩法的简单说明
在点击一个格子后,如果没有雷就会显示出一个数字或者是空白的格子。如果显示出的是一个数字3,就表示在这个数字周围的8个格子中有3个雷。根据这个信息就可以分析雷的大致位置。
由于本人实力有限,还无法实现使用鼠标点击排雷。只能够实现输入坐标排雷。
?
- 创建两个字符数组,一个用来存放布置好雷的信息,另一个用来存放排查出雷的信息。
- 使用字符 '0' 表示不是雷,使用字符 '1' 表示是雷,使用字符 '*' 表示未排的雷。
- 在输入坐标排雷时如果附近有雷,在输入的坐标位置显示附近雷的个数。
- 为了保持神秘感将雷的信息隐藏起来,棋盘只显示字符 '*' 。
- 输入坐标如果该位置不是雷就在该位置显示周围雷的信息,如果该坐标周围没有雷就显示空格。如果该位置有雷,则输出你被炸死了并将雷信息的棋盘显示出来,游戏结束。如果将所有雷都排查出来,就输出你赢了,游戏结束。
- 在排查棋盘周围的雷时,统计雷的个数会造成数组越界。所以将两个数组扩大一圈,统计9*9的棋盘,就将两个数组设置为11*11,这样在计算棋盘周围的雷时也不会造成数组越界。
- 在输入坐标时无法快速确定该坐标在棋盘中的位置,所以在棋盘周围打印出棋盘的行列序号。因为将数组的大小设置为11*11,棋盘的大小只是9*9,所以在打印棋盘的时候可以在数组多出来的位置打印出行列序号。
代码规划 : 创建三个源文件
game.h? :存放的是库函数的引用、宏定义、函数的声明等代码
game.c :存放函数实现的代码
main.c :存放函数调用的代码
游戏模块
?1、打印菜单
#include"game.h"
void menu()
{
printf("*********************************\n");
printf("********** 1. play **********\n");
printf("********** 0. exit **********\n");
printf("*********************************\n");
}
void game()
{
printf("扫雷游戏\n");
}
void test()
{
int input = 0;
do {
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
测试结果
2、创建两个棋盘(字符数组)
- mine :存放布置好雷的信息
- show:存放排查出雷的信息
使用宏定义设置数组的大小
//表示实际操作的数组 9*9
#define ROW 9
#define COL 9
//表示要创建的数组 11*11
#define ROWS ROW+2
#define COLS COL+2
使用宏定义来设置数组的大小,方便后期更改数组的大小。如果想要将棋盘中实际操作的大小改为16*16,就可以直接修改宏定义的参数。
3、初始化棋盘(字符数组)
存放布置雷信息的棋盘初始化为'0',存放排查出雷信息的棋盘初始化为 '*'
创建 InitBoard 函数实现棋盘(数组)初始化。
//初始化棋盘
void InitBoard(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;
}
}
}
4、打印棋盘与行列序号
创建 DisplayBoard 函数实现棋盘的打印。因为实现玩游戏的区域是9*9,所以只需要打印9*9的棋盘。
//打印棋盘,因为实参传递的数组大小是 ROWS,COLS,所以接收数组形参大小要与其保持一致
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
//打印列号
for (i = 0; i <= col; i++)
{
printf("%d ", i);
}
printf("\n");
//1~9
for (i = 1; i <= row; i++)
{
//打印行号
printf("%d ", i);
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
测试结果
本质上应该打印的是show数组,因为在玩游戏的时候看到的应该全部都是 '*' ,
5、布置雷
使用rand函数生成10个1~9的随机数布置在9*9的棋盘中。将生成随机数的位置赋值为'1'。
//布置雷
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int count = 10;
while (count)
{
int x = rand() % row + 1;//1~9的随机数
int y = rand() % col + 1;//1~9的随机数
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
测试结果(这里的图是优化后的棋盘,后面会有全部的代码)
6、排雷
排雷是在布置雷信息的棋盘中找雷,找到后再将雷的信息显示在排查雷的棋盘中。排雷这个过程会涉及两个数组,而且操作的都是9*9的格子。
排雷(方法一)
//排雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
while (win<row*col- COUNT)
{
printf("请输入要排查的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("你踩到雷了,游戏结束!\n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
//计算x,y坐标周围雷的个数
int count = get_mine_count(mine, x, y);
show[x][y] = count + '0';//将计算出雷的个数转换为字符
DisplayBoard(show, ROW, COL);
win++;
}
}
else
{
printf("你输入的坐标非法,无法排雷!\n");
}
}
if (win == row * col - COUNT)
{
printf("恭喜你,排雷成功!\n");
DisplayBoard(mine, ROW, COL);
}
}
这种写法不能实一个坐标现周围8个格子都没有雷直接展开的效果。
补充:数字加字符0('0')可以转成对应的数字
例如:2+'0'='2';
原因:'0'的ASCII码为48。2+'0'其实是2与字符0的ASCII码值进行计算---->48+2=50,而字符'2'的ASCII码值为50。
排雷(二)
使用递归,实现如果周围8个格子都没有雷直接展开的效果
递归的条件:
(1)该坐标处不是雷
(2)该坐标周围也没有雷
(如果该坐标处不是雷,该坐标周围也没有雷就将该坐标设置为空格。)
(3)该坐标没有被排查过
如果满足这三个条件,使用递归展开排查。
//实现如果周围8个格子都没有雷直接展开的效果
void blank(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
int i = 0;
int j = 0;
//统计雷的个数
int count = get_mine_count(mine, x, y);
if (count == 0)
{
show[x][y] = ' ';
for (i = x-1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (i>0 && i<ROWS && j>0 && j<COLS && mine[i][j]!='1' && show[i][j]=='*')
{
blank(mine, show, i, j);
}
}
}
}
else
{
show[x][y]=count+'0';
}
}
如果使用递归展开,排查雷的函数也会有一些变化
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int i = 0;
int j = 0;
while (1)
{
printf("\n请输入要排查的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("你踩到雷了,游戏结束!\n");
printf("\n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
//计算x,y坐标周围雷的个数
int count = get_mine_count(mine, x, y);
show[x][y] = count + '0';//将计算出雷的个数转换为字符
blank(mine, show, x, y);
DisplayBoard(show, ROW, COL);//将雷的个数在show棋盘中显示出来
}
}
else
{
printf("你输入的坐标非法,无法排雷!\n");
}
//统计雷('*')的个数
int win = 0;
for (i = 1; i <= ROW; i++)
{
for (j = 1; j <= COL; j++)
{
if (show[i][j] == '*')
{
win++;
}
}
}
if (win == COUNT)
{
printf("\n恭喜你,排雷成功!\n");
break;
}
}
}
测试结果
扫雷游戏一些基本的功能已经实现了,但是这个代码还可以改进。
比如:确保在第一次排雷时排查出的一定不是雷。
?完整代码
game.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<windows.h>
//表示实际操作的数组 9*9
#define ROW 9
#define COL 9
//表示要创建的数组 11*11
#define ROWS ROW+2
#define COLS COL+2
//雷的个数
#define COUNT 10
//声明初始化棋盘函数
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
//打印棋盘
void DisplayBoard(char board[ROWS][COLS],int row,int col);
//布置雷
void SetMine(char mine[ROWS][COLS], int row, int col);
//排雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//递归实现展开空白格
void blank(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y);
game.c
#include"game.h"
//初始化棋盘
void InitBoard(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;
}
}
}
//打印棋盘,因为实参传递的数组大小是 ROWS,COLS,所以接收数组形参大小要与其保持一致
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
//打印列号
for (i = 0; i <= col; i++)
{
printf("%-4d", i);
}
printf("\n");
//打印棋盘上面的边框线
printf(" ");
for (j = 1; j <= col; j++)
{
printf("|---");
}
printf("|\n");
//打印棋盘(1~9 --> 9*9)
for (i = 1; i <= row; i++)
{
//打印行号
printf("%d", i);
for (j = 1; j <= col; j++)
{
printf(" |");//打印棋盘中的竖线
printf(" %c", board[i][j]);
}
printf(" |");//打印最后一行的竖线
printf("\n");
//打印棋盘中的横线
printf(" ");
for (j = 1; j <= col; j++)
{
printf("|---");
}
printf("|\n");
}
}
//布置雷
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int count = COUNT;
while (count)
{
int x = rand() % row + 1;//1~9的随机数
int y = rand() % col + 1;//1~9的随机数
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
//统计雷的个数
int get_mine_count(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 FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int i = 0;
int j = 0;
while (1)
{
printf("\n请输入要排查的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("你踩到雷了,游戏结束!\n");
printf("\n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
//计算x,y坐标周围雷的个数
int count = get_mine_count(mine, x, y);
show[x][y] = count + '0';//将计算出雷的个数转换为字符
blank(mine, show, x, y);
//system("cls");//清屏
DisplayBoard(show, ROW, COL);//将雷的个数在show棋盘中显示出来
}
}
else
{
printf("你输入的坐标非法,无法排雷!\n");
}
//统计雷('*')的个数
int win = 0;
for (i = 1; i <= ROW; i++)
{
for (j = 1; j <= COL; j++)
{
if (show[i][j] == '*')
{
win++;
}
}
}
if (win == COUNT)
{
printf("\n恭喜你,排雷成功!\n");
break;
}
}
}
//实现如果周围8个格子都没有雷直接展开的效果
void blank(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
int i = 0;
int j = 0;
int count = get_mine_count(mine, x, y);
if (count == 0)
{
show[x][y] = ' ';
for (i = x-1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (i>0 && i<ROWS && j>0 && j<COLS && mine[i][j]!='1' && show[i][j]=='*')
{
blank(mine, show, i, j);
}
}
}
}
else
{
show[x][y]=count+'0';
}
}
main.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void menu()
{
printf("\n");
printf("*********************************\n");
printf("********** 1. play **********\n");
printf("********** 0. exit **********\n");
printf("*********************************\n");
printf("\n");
}
void game()
{
//创建数组
char mine[ROWS][COLS];//存放布置好雷的信息
char show[ROWS][COLS];//存放排查出雷的信息
//初始化mine数组为全字符'0'
InitBoard(mine,ROWS,COLS,'0');
//初始化show数组为全字符'*'
InitBoard(show,ROWS,COLS,'*');
//布置雷
SetMine(mine,ROW, COL);
//打印棋盘
DisplayBoard(show, ROW, COL);
//布置雷以后可以看一下雷的位置
//打印雷信息的棋盘
//DisplayBoard(mine, ROW, COL);
//排雷
FindMine(mine,show, ROW, COL);
}
void test()
{
int input = 0;
srand((unsigned int)time(NULL));
do {
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
总结
游戏可以在头文件中更改宏定义参数的大小来改变棋盘的大小或者修改雷的个数,使游戏玩起来更加有挑战性。也可以在玩的时候将雷信息的棋盘打印出来,玩一玩破解版的扫雷。
使用C语言实现扫雷游戏,主要运用了C语言中数组、函数、选择语句、循环等知识。
|