引言
在这个数字化的时代,游戏已经成为我们生活中不可或缺的一部分。无论是复杂的3D大作,还是简单的桌面小游戏,它们都能带给我们无尽的乐趣和挑战。今天,我们要一起回到那个经典的桌面游戏时代,探索如何用C语言编写一个充满怀旧气息的扫雷小游戏。
一、游戏规则
游戏目标: 盘面上随机分布着一定数量的地雷。你的目标是避开这些地雷,打开其他所有格子。 地雷数量: 选择相应的难度后,电脑会在雷区随机布置一定数量的地雷。 排雷:输入需要排查的坐标。如果点击的是地雷,则游戏失败;如果点击的是非雷方格,会显示周围八个方格内地雷的数。 标记:在怀疑的方格上放置旗帜(本游戏中用$符号代替),标记为地雷。 游戏结束: 当所有非雷方格都被揭开,且所有地雷都被正确标记时,游戏胜利。 如果揭开了一个地雷,游戏失败。
二、设计思路
1. 游戏概述
首先,明确游戏的基本框架和玩法。扫雷游戏主要包括一个雷区、地雷的随机分布、玩家的点击操作以及游戏胜负的判定。
2. 数据结构设计
雷区表示:使用一个二维数组来表示雷区,每个元素对应一个方格。棋盘可操作的区域是9*9的二维字符数组,实际的棋盘要多出两行两列(防止越界,简化设计操作)。 棋盘有两个,一个用来埋雷,一个用来显示排查雷的情况 埋雷数组(逻辑层):这个数组存储了游戏的真实状态,即哪些位置埋有地雷,哪些位置是安全的。这个数组对玩家是不可见的,它用于游戏的内部逻辑处理。其中用‘1’表示有雷,‘0’表示无雷。 展示数组(视图层):这个数组用于向玩家展示游戏当前的状态。它包含了玩家已经点击的方格、标记的地雷以及显示的数字提示。其中‘*’表示待排查的地理,可进行标记操作和排雷操作,‘$’表示已标记的地理,盘面上的数字表示该位置周围一圈格子雷的数量。
3. 游戏流程设计
难度选择:开始游戏前可以选择难度,简单,一般,困难三个级别难度 初始化:初始化两个二维数组 生成雷区:随机布置地雷 显示盘面:打印展示数组供玩家操作 玩家操作:玩家可以选择排雷,标记或是删除标记 逻辑判断:根据玩家的点击,更新显示数组,并进行游戏胜负的判断。 游戏结束:当玩家触发地雷或成功标记所有地雷时,游戏结束。
4. 功能模块划分
难度模块:供玩家选择对应难度。 初始化模块:负责初始化雷区和显示数组。 布雷模块:随机在雷区布置地雷。 显示模块:根据玩家的操作更新显示数组,并打印当前雷区的状态。 标记模块:玩家可以在怀疑的地方做说标记。 胜负判定模块:判断游戏是否结束,并给出相应的提示。
5. 主要算法设计
布雷算法:使用随机数生成器来确定地雷的位置。 计算周围地雷数量:对于每个非雷方格,计算其周围八个方格内地雷的数量。 递归扫雷:当一个格子显示‘0’即周围没有雷时,进行递归扫雷,展开一片区域
三、游戏设计
1.菜单函数
首先,我们需要制作一个简易的游戏菜单,代码如下:
代码语言:javascript复制void Menu()
{
printf("****************************n");
printf("******* 1.play *******n");
printf("******* 0.exit *******n");
printf("****************************n");
//玩家按1开始游戏,按0则结束游戏
}
2.主函数
主函数实现代码框架,用来控制按1开始游戏/按0退出游戏,并且多次进行直到玩家退出。
这里我们用switch来实现玩家的选择,用do...while循环语句保证游戏的多次进行。代码如下:
其中的srand函数功能以及解释请看上一篇博客中的介绍。代码如下:
代码语言:javascript复制int main()
{
srand((unsigned int)time(NULL));//随机种子
int option;
do
{
system("cls");//用于清除缓冲区,后一次玩的时候清除前面记录
Menu();
printf("请做出你的选择:");
scanf("%d", &option);
switch (option)
{
case 1:
system("cls");//清除缓冲区
game();
break;
case 0:
printf("游戏结束n");
break;
default:
printf("输入有误,请重新输入n");
system("pause");//用来暂停程序,按下后继续运行,即运行下面的清除缓冲区
break;
}
} while (option);
}
现在完成了框架,接下来是各个模块代码的实现
3.选择难度函数
返回值为地理的数量,简单模式8个地雷,正常模式12个地雷,困难模式16个地雷。代码如下:
代码语言:javascript复制int SelectDiff() {//难度选择
int difficulty;
printf("请选择难度:n");
printf("1. 简单 8个雷n");
printf("2. 正常(默认) 12个雷n");
printf("3. 困难 16个雷n");
scanf("%d", &difficulty);
switch (difficulty) {
case 1:
return MINE - 4;//简单模式8个雷
break;
case 2:
return MINE;//正常模式12个雷
break;
case 3:
return MINE 4;//困难模式16个雷
break;
case 0:
break;
default:
printf("输入有误,请重新选择n");
break;
}
return MINE;
}
4.初始化函数
该函数用来初始化mine数组和show数组。代码如下:
代码语言:javascript复制void InitBoard(char arr[ROWS][COLS], char set)//初始化棋盘
{
int i, j;
for (i = 0; i < ROWS; i )
{
for (j = 0; j < ROWS; j )
{
arr[i][j] = set;//初始化棋盘,mine数组中全部初始化为'0',show数组中全部初始化为'*'
}
}
}
5.布置地雷函数
利用生成的随机数,在棋盘上随机位置布置地雷。代码如下:
代码语言:javascript复制void SetMine(char arr[ROWS][COLS], int count)//布置地雷
{
while (count)
{
int x = rand() % ROW 1;//产生1~9的随机数
int y = rand() % COL 1;//产生1~9的随机数
if (arr[x][y] == '0')
{
arr[x][y] = '1';
count--;
}
}
}
6.打印函数
用来打印show数组供玩家在盘面上进行操作以及游戏结束时打印mine数组供玩家查看雷区位。代码如下:
代码语言:javascript复制void PrintBoard(char arr[ROWS][COLS])//打印棋盘
{
int i, j;
printf("n=====Minesweeper=====n");
for (j = 0; j <= ROW; j )
{
printf("-", j);//打印列标
}
printf("n");
for (i = 1; i <= ROW; i )
{
printf("-", i);//打印行标
for (j = 1; j <= COL; j )
{
printf(",", arr[i][j]);
}
printf("n");
}
printf("=====================n");
}
7.计算雷数函数
玩家在该位置排雷后,若该位置没有雷,则计算周围地雷个数,展示在show数组的该位置上。代码如下:
代码语言:javascript复制int GetMineCount(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');
}
8.递归排雷函数
当排查的地方周围无雷时,进行递归,自动深度排查周围区域。代码如下:
代码语言:javascript复制void DeepSweep(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)//递归连续排雷
{
if (x == 0 || y == 0 || x == ROW 1 || y == ROW 1)
return;
if (show[x][y] != '*' && show[x][y] != '$')
return;
int count = GetMineCount(mine, x, y);
if (count == 0)
{
show[x][y] = '0';
DeepSweep(mine, show, x - 1, y - 1);
DeepSweep(mine, show, x - 1, y);
DeepSweep(mine, show, x - 1, y 1);
DeepSweep(mine, show, x, y - 1);
DeepSweep(mine, show, x, y 1);
DeepSweep(mine, show, x 1, y - 1);
DeepSweep(mine, show, x 1, y);
DeepSweep(mine, show, x 1, y 1);
}
else
{
show[x][y] = count '0';
}
}
9.标记(删除标记)函数
供玩家在怀疑的地方标记为地雷,或者删除某一位置的标记,其中标记符号为$。代码如下:
代码语言:javascript复制void MarkMine(char show[ROWS][COLS], int x, int y) {
if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
if (show[x][y] == '*')
{
show[x][y] = '$';
}
}
}
void UnmarkMine(char show[ROWS][COLS], int x, int y) {
if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
if (show[x][y] == '$')
{
show[x][y] = '*';
}
}
}
10.操作函数
游戏中可供玩家进行排雷、标记、删除标记的选择,在玩家操作后更新展示show数组,并且根据玩家的一系列操作判断来玩家是胜利还是失败。代码如下:
代码语言:javascript复制void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS],int count)
{
int x, y;
int win = 0;
int flag = count;
while (win < ROW * COL - MINE) {//排查的安全地方个数
// 显示菜单
printf("1. 排雷n2. 标记雷(当前还可标记%d处)n3. 删除标记n0. 退出n请选择操作:",flag);
int choice;
scanf("%d", &choice);
switch (choice) {
case 1: // 排雷
printf("输入你要排查的位置(输入坐标:行 列):");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
{
if (show[x][y] == '*' || show[x][y] == '$')
{
if (mine[x][y] == '1')
{
printf("很遗憾,你踩到雷了!n");
PrintBoard(mine);
system("pause");//用来暂停程序,按下后继续运行
break;
}
else
{
DeepSweep(mine, show, x, y);//递归排雷
int count = GetMineCount(mine, x, y);
show[x][y] = count '0';
system("cls");//清除缓冲区
PrintBoard(show);
win ;
}
}
else
{
printf("该位置已被排除过,请重新输入!n");
}
}
break;
case 2: // 标记雷
if(flag>0)
{
printf("输入你要标记的位置(输入坐标:行 列):");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
if (show[x][y] == '*')
{
system("cls");//清除缓冲区
MarkMine(show, x, y);
PrintBoard(show);
flag--;
}
else
{
printf("该位置已被排查,无法标记!n");
}
}
else {
printf("输入位置不合法,请重新输入n");
}
}
else
{
printf("标记达上限,无法再标记!");
}
break;
case 3: // 删除标记
printf("输入你要删除标记的位置(输入坐标:行 列):");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
if (show[x][y] == '$')
{
system("cls");//清除缓冲区
UnmarkMine(show, x, y);
PrintBoard(show);
}
else {
printf("该位置未被标记,无法删除!n");
}
}
else {
printf("输入位置不合法,请重新输入n");
}
break;
case 0: // 退出
return;
default:
printf("输入有误,请重新输入n");
break;
}
}
if (win == ROW * COL - MINE) {//排查完所有非雷区游戏胜利
printf("我嘞个雷!n");
printf("恭喜你已经排完了所有的雷!n");
PrintBoard(mine);
}
}
11.游戏函数
即整合实现游戏运行的分模块。代码如下:
代码语言:javascript复制void game()
{
int minecount = SelectDiff();//难度选择
char mine[ROWS][COLS] = { 0 };//mine数组全部初始化为'0'
char show[ROWS][COLS] = { 0 };//show数组中全部初始化为'*'
InitBoard(mine, '0');
InitBoard(show, '*');
SetMine(mine, minecount);
//PrintBoard(mine);雷区,取消注释可以作弊查看
PrintBoard(show);
FindMine(mine, show,minecount);
}
四、完整代码及运行效果图
完整源代码:
代码语言:javascript复制#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9//9*9的棋盘
#define COL 9
#define ROWS ROW 2
#define COLS COL 2
#define MINE 12//正常难度雷的数量
void Menu()
{
printf("****************************n");
printf("******* 1.play *******n");
printf("******* 0.exit *******n");
printf("****************************n");
//玩家按1开始游戏,按0则结束游戏
}
int SelectDiff() {//难度选择
int difficulty;
printf("请选择难度:n");
printf("1. 简单 8个雷n");
printf("2. 正常(默认) 12个雷n");
printf("3. 困难 16个雷n");
scanf("%d", &difficulty);
switch (difficulty) {
case 1:
return MINE - 4;//简单模式8个雷
break;
case 2:
return MINE;//正常模式12个雷
break;
case 3:
return MINE 4;//困难模式16个雷
break;
case 0:
break;
default:
printf("输入有误,请重新选择n");
break;
}
return MINE;
}
void InitBoard(char arr[ROWS][COLS], char set)//初始化棋盘
{
int i, j;
for (i = 0; i < ROWS; i )
{
for (j = 0; j < ROWS; j )
{
arr[i][j] = set;//初始化棋盘,mine数组中全部初始化为'0',show数组中全部初始化为'*'
}
}
}
void PrintBoard(char arr[ROWS][COLS])//打印棋盘
{
int i, j;
printf("n=====Minesweeper=====n");
for (j = 0; j <= ROW; j )
{
printf("-", j);//打印列标
}
printf("n");
for (i = 1; i <= ROW; i )
{
printf("-", i);//打印行标
for (j = 1; j <= COL; j )
{
printf(",", arr[i][j]);
}
printf("n");
}
printf("=====================n");
}
void SetMine(char arr[ROWS][COLS], int count)//布置地雷
{
while (count)
{
int x = rand() % ROW 1;//产生1~9的随机数
int y = rand() % COL 1;//产生1~9的随机数
if (arr[x][y] == '0')
{
arr[x][y] = '1';
count--;
}
}
}
int GetMineCount(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 DeepSweep(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)//递归连续排雷
{
if (x == 0 || y == 0 || x == ROW 1 || y == ROW 1)
return;
if (show[x][y] != '*' && show[x][y] != '$')
return;
int count = GetMineCount(mine, x, y);
if (count == 0)
{
show[x][y] = '0';
DeepSweep(mine, show, x - 1, y - 1);
DeepSweep(mine, show, x - 1, y);
DeepSweep(mine, show, x - 1, y 1);
DeepSweep(mine, show, x, y - 1);
DeepSweep(mine, show, x, y 1);
DeepSweep(mine, show, x 1, y - 1);
DeepSweep(mine, show, x 1, y);
DeepSweep(mine, show, x 1, y 1);
}
else
{
show[x][y] = count '0';
}
}
void MarkMine(char show[ROWS][COLS], int x, int y) {
if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
if (show[x][y] == '*')
{
show[x][y] = '$';
}
}
}
void UnmarkMine(char show[ROWS][COLS], int x, int y) {
if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
if (show[x][y] == '$')
{
show[x][y] = '*';
}
}
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS],int count)
{
int x, y;
int win = 0;
int flag = count;
while (win < ROW * COL - MINE) {//排查的安全地方个数
// 显示菜单
printf("1. 排雷n2. 标记雷(当前还可标记%d处)n3. 删除标记n0. 退出n请选择操作:",flag);
int choice;
scanf("%d", &choice);
switch (choice) {
case 1: // 排雷
printf("输入你要排查的位置(输入坐标:行 列):");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
{
if (show[x][y] == '*' || show[x][y] == '$')
{
if (mine[x][y] == '1')
{
printf("很遗憾,你踩到雷了!n");
PrintBoard(mine);
system("pause");//用来暂停程序,按下后继续运行
break;
}
else
{
DeepSweep(mine, show, x, y);//递归排雷
int count = GetMineCount(mine, x, y);
show[x][y] = count '0';
system("cls");//清除缓冲区
PrintBoard(show);
win ;
}
}
else
{
printf("该位置已被排除过,请重新输入!n");
}
}
break;
case 2: // 标记雷
if(flag>0)
{
printf("输入你要标记的位置(输入坐标:行 列):");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
if (show[x][y] == '*')
{
system("cls");//清除缓冲区
MarkMine(show, x, y);
PrintBoard(show);
flag--;
}
else
{
printf("该位置已被排查,无法标记!n");
}
}
else {
printf("输入位置不合法,请重新输入n");
}
}
else
{
printf("标记达上限,无法再标记!");
}
break;
case 3: // 删除标记
printf("输入你要删除标记的位置(输入坐标:行 列):");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
if (show[x][y] == '$')
{
system("cls");//清除缓冲区
UnmarkMine(show, x, y);
PrintBoard(show);
}
else {
printf("该位置未被标记,无法删除!n");
}
}
else {
printf("输入位置不合法,请重新输入n");
}
break;
case 0: // 退出
return;
default:
printf("输入有误,请重新输入n");
break;
}
}
if (win == ROW * COL - MINE) {//排查完所有非雷区游戏胜利
printf("我嘞个雷!n");
printf("恭喜你已经排完了所有的雷!n");
PrintBoard(mine);
}
}
void game()
{
int minecount = SelectDiff();//难度选择
char mine[ROWS][COLS] = { 0 };//mine数组全部初始化为'0'
char show[ROWS][COLS] = { 0 };//show数组中全部初始化为'*'
InitBoard(mine, '0');
InitBoard(show, '*');
SetMine(mine, minecount);
//PrintBoard(mine);雷区,取消注释可以作弊查看
PrintBoard(show);
FindMine(mine, show,minecount);
}
int main()
{
srand((unsigned int)time(NULL));//随机种子
int option;
do
{
system("cls");//用于清除缓冲区,后一次玩的时候清除前面记录
Menu();
printf("请做出你的选择:");
scanf("%d", &option);
switch (option)
{
case 1:
system("cls");//清除缓冲区
game();
break;
case 0:
printf("游戏结束n");
break;
default:
printf("输入有误,请重新输入n");
system("pause");//用来暂停程序,按下后继续运行,即运行下面的清除缓冲区
break;
}
} while (option);
}
运行效果图:
总结
通过这次C语言扫雷小游戏的开发,我们不仅重温了一个经典的桌面游戏,而且在实践中加深了对C语言编程的理解。从设计思路到具体实现,每一步都是对逻辑思维和编程技能的锻炼。在这个过程中,我们学到了如何利用二维数组管理复杂的游戏状态,如何处理用户输入,以及如何在游戏中实现递归和条件判断等高级功能。这次实践不仅让我们体验了从零到一构建游戏的成就感,也为未来的编程学习奠定了坚实的基础。扫雷游戏虽小,但它背后的编程智慧无穷,让我们继续探索,创造更多有趣的作品。