文章目录
前言
上一篇给大家展示了井字棋的做法,本次则展示扫雷的做法,我们仍然尝试将扫雷的规则拆分化解,并使用C语言进行编程构造。同样,大家也可以尝试用easyX等工具进行GUI的制作,或者借助Unity等游戏引擎会更加方便,但本文章仅使用C语言进行编程与功能实现。
功能拆解
首先,我们对井字棋的功能进行拆分。
- 菜单的创建与Button功能。
- 棋盘以及地雷的创建初始化与可视化。
- 玩家拆弹功能,并实时更新棋盘(暂不实现鼠标检测,以键盘输入模拟过程)
- 实现连锁开格子的功能
- 实时判断游戏是否结束,设计胜利条件。
对功能模块实现拆分后,我们尝试编程实现对应功能。
功能实现
在分解完扫雷的各项功能之后,我们尝试进行分模块功能制作。
1)菜单的创建与Button功能
menu()函数
目前本博客未制作GUI而使用纯文字制作开始页面,而menu(菜单)函数的功能就是完成对游戏页面的构造,实现游戏的可交互功能。示例代码如下:
//菜单
void menu()
{
printf("****************************\n");
printf("********做出您的选择********\n");
printf("**1.开始游戏***0.结束游戏***\n");
}
Button功能
在菜单构建之后,我们的程序需要对玩家的交互做出反馈,这里就牵涉到制作按钮,实现对应的功能。目前实现的是游戏开始以及结束游戏两个按钮。示例代码如下:
//选择模式,让玩家自行输入数字实现模式的选择
printf("请选择>");
scanf("%d", &input);
switch (input)
{
//退出,如果输入0就直接退出
case 0:
printf("游戏退出\n");
Sleep(1000);
break;
//开始游戏,如果输入1就开始游戏
case 1:
game();
Sleep(1000);
break;
//要时刻检测玩家是否不安规则标准进行
default:
printf("选择错误,请重新选择\n");
Sleep(1000);
break;
}
ps:如果使用Unity进行开发,这里可以直接使用Button,并且切换场景使用SceneManager.LoadScene()会更加方便。
持续游玩功能
考虑到玩家可能在游玩一局后不打算结束,而选择再来一次的场景,程序也需要设计反复游玩的功能,而不是直接结束运行。示例代码如下:
int input = 0;
//游戏循环开始
do
{
//菜单
menu();
//选择模式
printf("请选择>");
scanf("%d", &input);
switch (input)
{
//退出
case 0:
printf("游戏退出\n");
Sleep(1000);
break;
//开始游戏
case 1:
game();
Sleep(1000);
break;
default:
printf("选择错误,请重新选择\n");
Sleep(1000);
break;
}
//一轮结束清屏
system("cls");
} while (input != 0);
这样就实现了重复游玩的功能,当然Unity中的Update()功能即可自己实现。
- 菜单的创建与Button功能。
2)棋盘的创建初始化与可视化
在玩家游玩的过程中,应实时更新棋盘情况一边玩家作出反应。因此需要实现棋盘的可视化以及实时更新。
棋盘的初始化
不同于井字棋只需要记录一个字符变量记录棋子,扫雷需要记录该格是否是炸弹,以及周围八格炸弹的数量,因此在制作棋盘时使用了结构体数组来实现。示例代码如下:
typedef struct Ceil
{
int count;//记录周围炸弹数量
bool isBoom;//记录是否是炸弹
bool isAppear;//记录此格子是否已经被开过
}Ceil;
此为结构体的定义,其中bool的变量使用在C语言中需要stdbool.h这个头文件。
在说明了一个棋盘的格子类型之后,我们便可以使用结构体数组来制作棋盘并进行初始化。示例代码如下:
//初始化棋盘
void InitChess(Ceil chess[ROW][COL],int row,int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
chess[i][j].isBoom = false;
chess[i][j].count = 0;
chess[i][j].isAppear = false;
}
}
}
二维数组的传参也可以使用数组指针进行传参,这里即可Ceil chess[ROW] [COL] 改为Ceil (*chess)[COL],可达到相同效果。(之后的传参过程若出现二维数组均可使用数组指针进行代替,不再特意指出)
涉及到的宏定义
以下是整个代码用到的宏定义:
#define ROW 11//行
#define COL 11//列
#define BOOM_COUNT 20//炸弹数量
对棋盘的行数,列数以及整个棋盘中的炸弹个数进行了定义,方便后续的更改与代码的维护。
地雷的生成
如今我们已经拥有了一个棋盘,接下来我们就需要给棋盘上埋炸弹,这次采用最简单的随机数逻辑去生成炸弹,大家也可以选择加一些限制条件让炸弹更倾向于生成在边缘地区。示例代码如下:
//生成地雷
void CreateBoom(Ceil chess[ROW][COL], int row, int col, int boom_count)
{
int x, y;
bool isfull = false;
srand((unsigned int)time(NULL));
for (int i = 0; i < boom_count; i++)
{
while (!isfull)
{
x = rand() % (row - 2) + 1;
y = rand() % (col - 2) + 1;
if (chess[x][y].isBoom == false)
{
isfull = true;
chess[x][y].isBoom = true;
}
}
isfull = false;
}
}
考虑到越界问题,在生成数组时特意将行数跟列数设计的比需要的多了2,以此方便边缘的格子遍历四周算炸弹个数。
棋盘的可视化
在实现了棋盘的初始化之后,我们需要在玩家开了一个格子后能看到棋盘的样子,以及能看到每个格子附近有多少炸弹,所以我们需要将棋盘进行打印。示例代码如下:
//打印棋盘
void PrintChess(Ceil chess[ROW][COL], int row, int col)
{
for (int i = 1; i < row - 1; i++)
{
for (int j = 1; j < col - 1; j++)
{
if (chess[i][j].isAppear == true)
{
if (j < col - 2)
printf(" %d |", chess[i][j].count);
else
printf(" %d ", chess[i][j].count);
}
else
{
if (j < col - 2)
printf(" |");
else
printf(" ");
}
}
printf("\n");
for (int j = 1; j < row - 1; j++)
{
printf("---");
if (j < row - 2)
{
printf("|");
}
}
printf("\n");
}
}
这里选择将打印所有开过的格子的周围炸弹数量,如果周围没有炸弹则会显示0。如图示例:
当然,我们需要将该函数置于game()函数,并用while循环实现棋盘的实时打印。
- 棋盘的创建初始化与可视化。
3)玩家拆弹功能
在拥有一个棋盘之后,我们需要做的就是实现玩家点击格子可以拆的功能。我们用键盘模拟点击格子的功能,当玩家输入他要开的棋盘坐标,我们就展现开后的结果将棋盘打印出来。示例代码如下:
//玩家开格子
int PlayerMove(Ceil chess[ROW][COL], int row, int col)
{
int x, y;
bool flag = false;
while (!flag)
{
printf("请输入想点击位置坐标>");
scanf("%d%d", &x, &y);
system("cls");
if (chess[x][y].isAppear == false)
{
if (chess[x][y].isBoom == false)
{
flag = true;
AppearOne(chess, x, y);
return 1;
}
else
{
printf("炸弹\n");
printf("游戏结束\n");
flag = true;
return 0;
}
}
else
printf("坐标非法,请重新输入\n");
}
}
- 玩家拆弹功能。
4)连锁开格子功能
我将开格子的函数封装单独做了出来,在实际的扫雷游戏过程中,如果这个格子的周围没有炸弹,会实现连锁开格子的直到遇到一个周围有炸弹的格子,这里尝试用递归实现这个功能。示例代码如下:
//开一个
void AppearOne(Ceil chess[ROW][COL], int x, int y)
{
if (chess[x][y].isAppear == false)
{
chess[x][y].isAppear = true;
chess[x][y].count = ArroundCount(chess, x, y);
if ((x > 0 && x < 10) && (y > 0 && y < 10) && chess[x][y].count == 0)
{
AppearArround(chess, x, y);
}
}
}
//开周围
void AppearArround(Ceil chess[ROW][COL], int x, int y)
{
int count = 0;
/*if (chess[x - 1][y - 1].isBoom == false)
{
AppearOne(chess, x - 1, y - 1);
}*/
if (chess[x - 1][y].isBoom == false)
{
AppearOne(chess, x - 1, y);
}
/*if (chess[x - 1][y + 1].isBoom == false)
{
AppearOne(chess, x - 1, y + 1);
}*/
if (chess[x][y - 1].isBoom == false)
{
AppearOne(chess, x, y - 1);
}
if (chess[x][y + 1].isBoom == false)
{
AppearOne(chess, x, y + 1);
}
/*if (chess[x + 1][y - 1].isBoom == false)
{
AppearOne(chess, x + 1, y - 1);
}*/
if (chess[x + 1][y].isBoom == false)
{
AppearOne(chess, x + 1, y);
}
/*if (chess[x + 1][y + 1].isBoom == false)
{
AppearOne(chess, x + 1, y + 1);
}*/
}
开一个之后不断判断周围的格子是否都开过以及周围炸弹数量是否是0,然后去递归实现开格子的过程,这里里面也用到了去获取该格子周围炸弹数量的函数ArroundCount(),该函数代码如下:
//周围炸弹个数
int ArroundCount(Ceil chess[ROW][COL],int x, int y)
{
int count = 0;
if ((x > 0 && x < 10) && (y > 0 && y < 10))
{
if (chess[x - 1][y - 1].isBoom == true)
count++;
if (chess[x - 1][y].isBoom == true)
count++;
if (chess[x - 1][y + 1].isBoom == true)
count++;
if (chess[x][y - 1].isBoom == true)
count++;
if (chess[x][y + 1].isBoom == true)
count++;
if (chess[x + 1][y - 1].isBoom == true)
count++;
if (chess[x + 1][y].isBoom == true)
count++;
if (chess[x + 1][y + 1].isBoom == true)
count++;
}
return count;
}
至此,我们便实现了玩家连锁开格子的功能,当然扫雷游戏实际上还有一个可以给炸弹标点的功能,但我们这次暂不实现,最终的胜利条件也是只要除了炸弹以外的格子都打开就算胜利。代码效果如图:
- 连锁开格子功能。
5)设计胜利条件
最后我们需要设计函数去判断玩家是否胜利,我们设定胜利条件是只要除了炸弹以外的格子都打开就算胜利。示例代码如下:
//胜利
int isWin(Ceil chess[ROW][COL], int row, int col)
{
bool flag = false;
for (int i = 1; i < row - 1; i++)
{
for (int j = 1; j < col - 1; j++)
{
if (chess[i][j].isAppear == false && chess[i][j].isBoom == false)
{
flag = true;
}
}
}
if (flag == false)
{
printf("游戏胜利\n");
return 1;
}
else
return 0;
}
- 设计胜利条件。
完整代码
1)test.c
#include "game.h"
int main()
{
int input = 0;
do
{
//菜单
menu();
printf("模式选择:请输入>");
scanf("%d", &input);
switch (input)
{
//退出
case 0:
break;
//开始游戏
case 1:
game();
break;
default:
printf("输入错误,请重新选择\n");
break;
}
Sleep(1000);
system("cls");
} while (input);
return 0;
}
2)game.h
#pragma once
#include <stdio.h>
#include <windows.h>
#include <stdbool.h>
#include <stdlib.h>
#include <time.h>
#define ROW 11//行
#define COL 11//列
#define BOOM_COUNT 15//炸弹数量
typedef struct Ceil
{
int count;
bool isBoom;
bool isAppear;
}Ceil;
//菜单
void menu();
//初始化棋盘
void InitChess(Ceil chess[ROW][COL], int row, int col);
//打印棋盘
void PrintChess(Ceil chess[ROW][COL], int row, int col);
//打印炸弹位置
void PrintBoom(Ceil chess[ROW][COL], int row, int col);
//生成地雷
void CreateBoom(Ceil chess[ROW][COL], int row, int col);
//玩家开格子
int PlayerMove(Ceil chess[ROW][COL], int row, int col);
//周围炸弹个数
int ArroundCount(Ceil chess[ROW][COL], int x, int y);
//开一个
void AppearOne(Ceil chess[ROW][COL], int x, int y);
//开周围
void AppearArround(Ceil chess[ROW][COL], int x, int y);
//游戏主程序
void game();
//胜利
int isWin(Ceil chess[ROW][COL], int row, int col);
3)game.c
#include "game.h"
//typedef struct Ceil
//{
// int count;//周围数量
// bool isBoom;//是否是炸弹
// bool isAppear;//是否现行
//}Ceil;
//地雷是true,空白是false
//菜单
void menu()
{
printf("****************************\n");
printf("********做出您的选择********\n");
printf("**1.开始游戏***0.结束游戏***\n");
}
//初始化棋盘
void InitChess(Ceil chess[ROW][COL],int row,int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
chess[i][j].isBoom = false;
chess[i][j].count = 0;
chess[i][j].isAppear = false;
}
}
}
//打印棋盘
void PrintChess(Ceil chess[ROW][COL], int row, int col)
{
for (int i = 1; i < row - 1; i++)
{
for (int j = 1; j < col - 1; j++)
{
if (chess[i][j].isAppear == true)
{
if (j < col - 2)
printf(" %d |", chess[i][j].count);
else
printf(" %d ", chess[i][j].count);
}
//显示炸弹测试用
/*if (chess[i][j].isBoom == true)
{
if (j < col - 2)
printf(" 1 |");
else
printf(" 1 ");
}*/
else
{
if (j < col - 2)
printf(" |");
else
printf(" ");
}
}
printf("\n");
for (int j = 1; j < row - 1; j++)
{
printf("---");
if (j < row - 2)
{
printf("|");
}
}
printf("\n");
}
}
//生成地雷
void CreateBoom(Ceil chess[ROW][COL], int row, int col, int boom_count)
{
int x, y;
bool isfull = false;
srand((unsigned int)time(NULL));
for (int i = 0; i < boom_count; i++)
{
while (!isfull)
{
x = rand() % (row - 2) + 1;
y = rand() % (col - 2) + 1;
if (chess[x][y].isBoom == false)
{
isfull = true;
chess[x][y].isBoom = true;
}
}
isfull = false;
}
}
//玩家开格子
int PlayerMove(Ceil chess[ROW][COL], int row, int col)
{
int x, y;
bool flag = false;
while (!flag)
{
printf("请输入想点击位置坐标>");
scanf("%d%d", &x, &y);
system("cls");
if (chess[x][y].isAppear == false)
{
if (chess[x][y].isBoom == false)
{
flag = true;
AppearOne(chess, x, y);
return 1;
}
else
{
printf("炸弹\n");
printf("游戏结束\n");
flag = true;
return 0;
}
}
else
printf("坐标非法,请重新输入\n");
}
}
//开一个
void AppearOne(Ceil chess[ROW][COL], int x, int y)
{
if (chess[x][y].isAppear == false)
{
chess[x][y].isAppear = true;
chess[x][y].count = ArroundCount(chess, x, y);
if ((x > 0 && x < 10) && (y > 0 && y < 10) && chess[x][y].count == 0)
{
AppearArround(chess, x, y);
}
}
}
//开周围
void AppearArround(Ceil chess[ROW][COL], int x, int y)
{
int count = 0;
/*if (chess[x - 1][y - 1].isBoom == false)
{
AppearOne(chess, x - 1, y - 1);
}*/
if (chess[x - 1][y].isBoom == false)
{
AppearOne(chess, x - 1, y);
}
/*if (chess[x - 1][y + 1].isBoom == false)
{
AppearOne(chess, x - 1, y + 1);
}*/
if (chess[x][y - 1].isBoom == false)
{
AppearOne(chess, x, y - 1);
}
if (chess[x][y + 1].isBoom == false)
{
AppearOne(chess, x, y + 1);
}
/*if (chess[x + 1][y - 1].isBoom == false)
{
AppearOne(chess, x + 1, y - 1);
}*/
if (chess[x + 1][y].isBoom == false)
{
AppearOne(chess, x + 1, y);
}
/*if (chess[x + 1][y + 1].isBoom == false)
{
AppearOne(chess, x + 1, y + 1);
}*/
}
//周围炸弹个数
int ArroundCount(Ceil chess[ROW][COL],int x, int y)
{
int count = 0;
if ((x > 0 && x < 10) && (y > 0 && y < 10))
{
if (chess[x - 1][y - 1].isBoom == true)
count++;
if (chess[x - 1][y].isBoom == true)
count++;
if (chess[x - 1][y + 1].isBoom == true)
count++;
if (chess[x][y - 1].isBoom == true)
count++;
if (chess[x][y + 1].isBoom == true)
count++;
if (chess[x + 1][y - 1].isBoom == true)
count++;
if (chess[x + 1][y].isBoom == true)
count++;
if (chess[x + 1][y + 1].isBoom == true)
count++;
}
return count;
}
//打印炸弹位置
void PrintBoom(Ceil chess[ROW][COL], int row, int col)
{
for (int i = 1; i < row - 1; i++)
{
for (int j = 1; j < col - 1; j++)
{
//显示炸弹测试用
if (chess[i][j].isBoom == true)
{
if (j < col - 2)
printf(" 1 |");
else
printf(" 1 ");
}
else
{
if (j < col - 2)
printf(" |");
else
printf(" ");
}
}
printf("\n");
for (int j = 1; j < row - 1; j++)
{
printf("---");
if (j < row - 2)
{
printf("|");
}
}
printf("\n");
}
}
//胜利
int isWin(Ceil chess[ROW][COL], int row, int col)
{
bool flag = false;
for (int i = 1; i < row - 1; i++)
{
for (int j = 1; j < col - 1; j++)
{
if (chess[i][j].isAppear == false && chess[i][j].isBoom == false)
{
flag = true;
}
}
}
if (flag == false)
{
printf("游戏胜利\n");
return 1;
}
else
return 0;
}
//游戏主程序
void game()
{
int boom = 0;
int win = 0;
//建立棋盘
Ceil chess[ROW][COL];
//初始化
InitChess(chess, ROW, COL);
//打印棋盘
//PrintChess(chess, ROW, COL);
//生成地雷
CreateBoom(chess, ROW, COL, BOOM_COUNT);
//打印地雷位置
//PrintBoom(chess, ROW, COL);
while (1)
{
//玩家开格子
boom = PlayerMove(chess, ROW, COL);
if (boom == 0)
break;
PrintChess(chess, ROW, COL);
//胜利
win = isWin(chess, ROW, COL);
if (win == 1)
break;
}
}
这就是井字棋C语言实现的全部内容啦,期待我们的下次相遇,喜欢的可以点个关注再走哦!之后会持续更新更多的内容!