目录
前言
🎈🎈🎈扫雷小游戏,既然讲写游戏那么我们就先构思一下游戏框架嘛。
1、我们玩游戏的时候,首先看到肯定是游戏界面吧,在游戏界面里我们就简单弄个”开始游戏“和退出游戏吧,所以我们需要先弄个游戏界面。
2、弄好界面后其次就是初始化游戏了,就类似于打王者荣耀开局进去后都会是从一级开始的。
3、初始化好后就可以开始弄游戏雷盘或布置雷了,可以先布置雷再打印游戏棋盘,当然反之也可以。
4、当前面的一切都弄好了之后,就开始到玩家输入了,不然弄前面那么多拿来干看着嘛,现在就让玩家输入自己想要排查的坐标。
5、判断输赢,当玩家输入坐标后,判断玩家所排查的坐标是否有雷,如果有那么玩家就被炸死了,反之没有就可以继续排查,直到排查出全部的雷或被炸死为止。
1、游戏界面
我们就简单的用 printf() 来进行界面的设计就好了,其功能是让玩家选择开始游戏还是退出游戏。
void menu() //游戏界面
{
printf("\n");
printf("*******************************\n");
printf("****** 1. Play ******\n");
printf("****** 0. Exit ******\n");
printf("*******************************\n");
}
2、初始化游戏
在初始化游戏之前,我们需要创建两个二维数组mine和show。
mine数组作为内部雷盘是用来存放隐藏的地雷和方便观察调式使用的。
show数组作为外部雷盘是用来向玩家展示使用的。
char mine[ROWS][COLS] = { 0 }; //内部棋盘
char show[ROWS][COLS] = { 0 }; //外部棋盘
初始化时,mine和show两个雷盘都要进行初始化。
InitBoard( mine, ROWS, COLS, '0' ); //初始化为'0'
InitBoard( show, ROWS, COLS, '*' ); //初始化为'*'
我们通过调用把我们想要初始化的数据和内容给传过来。
下面为初始代码:
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;
}
}
}
代码中的set储存了我想要初始化的内容,所以我们直接把set赋值个棋盘就可以了。
3、布置雷、打印游戏棋盘
3.1、布置雷
布置雷时我们不可能每次都把雷布置在相同的位置,我们希望它能够具备随机性,每局游戏雷所在的位置都不一样,这时我们就需要用到rand函数,rand函数具备足够随机性。用rand函数模上我们的行和列,所得到的坐标就我们布置雷的坐标。 至于为什么要在后面+1呢,那是因为比如当我们打印9x9的雷盘时,rand取余后所得到范围只有0~8,当+1后其范围才是0~9。
下面为布置雷的代码,用@表示雷:
//srand((unsigned int)time(NULL)); //需要放到主函数里面
void SetMine(char board[ROWS][COLS], int row, int col, int conti) //布置雷
{
int count = conti;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '@';
count--;
}
}
}
3.1.1、rand
rand函数的主要作用就是产生一个随机值,它本身是需要配合srand来进行使用的。
rand每次生成随机数时,其随机数的种子为1,这时我们就需要srand来随机生成随机数的种子,以此来提高rand的随机性。
由以下图片我们可以看到,rand和srand的头文件都是<stdlib.h>,并且srand随机种子数为unsigned int 无字符型整形
3.1.2、time
为了能够让rand函数每秒产生的随机值的不同,我们需要用到time函数引入一个时间戳的概念。
time()的头文件为<time.h>,NULL表示为空,time(NULL)即将空指针传递给time()函数。
time()的作用为返回一个值,即格林尼治时间1970年1月1日00:00:00到当前时刻的时长,时长单位是秒。
3.1.3、扩展:模式选择
我们可以通过改变雷的数量来提升其游戏的难度,当然这是比较简单的一种。
我们在选择开始游戏后,进入游戏难度选择模式。
其页面代码如下:
void menu1() //游戏模式选择
{
printf("\n");
printf("*******************************\n");
printf("****** 请选择模式 ******\n");
printf("****** 1. 简单 ******\n");
printf("****** 2. 一般 ******\n");
printf("****** 3. 困难 ******\n");
printf("*******************************\n");
}
其选择代码如下:
printf("请选择模式->");
scanf("%d", &count);
switch (count)
{
case 1:
printf("简单模式\n");
conti = EASY_COUNT + 0;
break;
case 2:
printf("一般模式\n");
conti = EASY_COUNT + 20;
break;
case 3:
printf("困难模式\n");
conti = EASY_COUNT + 49;
break;
default:
printf("未检出选项,返回主页重新选择.\n");
break;
}
3.2、打印游戏雷盘
我们根据自己给定的行和列来打印棋盘,为方便的排查我们还打印了行号和列号以此玩家可以直观的看到所要排查雷的坐标。
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
由以上代码我们可以看到,其打印的行和列都为9,但为什么其下面又要让其+2呢?
那是为了后面排查雷是所准备的。当我们排查一个坐标时此坐标没有雷的话,那么会以此坐标为中心检测周围一格的坐标内是否有雷,如1号坐标位置。
但当我们排查2号坐标位置时,我们会发现其排查时会发生越界现象,当然为了避免越界我们可以添加一系列的限制,但这样的话无疑会增加其难度和复杂度。此时我们多增加的行数和列数就发挥了作用,由于我们在其周围多给了一行一列,又让其一起初始化为0,那么我们检测时其内容都会被判定为非雷,这样就可以简单的解决越界的问题。
虽然创建了11x11的棋盘,但实际上我们打印的还是9x9的棋盘,其布置雷和排查雷等指令内容也都是在9x9的棋盘内做的。
mine雷盘:
show雷盘:
以下是打印棋盘的代码:
void DisplayBoard(char board[ROWS][COLS], int row, int col) //打印棋盘
{
int i = 0;
int j = 0;
printf("\n -------------扫雷小游戏----------- \n");
printf(" ");
for (j = 1; j <= 9; j++)
{
printf(" %d ", j); //列号
}
printf("\n");
printf("\n");
printf(" ");
for (j = 0; j <= 8; j++)
{
if (j >= 1)
{
printf("-");
}
printf("---");
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf(" %d |", i); //行号
for (j = 1; j <= col; j++)
{
printf(" %c ", board[i][j]);
if (j <= col)
{
printf("|");
}
}
printf("\n");
if (i < row )
{
printf(" ");
printf("|");
for (j = 1; j <= col; j++)
{
printf("---");
if (j <= col)
{
printf("|");
}
}
printf("\n");
}
}
printf(" ");
for (j = 0; j <= 8; j++)
{
if (j >= 1)
{
printf("-");
}
printf("---");
}
printf("\n");
printf("\n -------------扫雷小游戏----------- \n");
}
4、玩家输入
在玩家输入坐标前,我们可以做个选择功能,之后再输入坐标进行其排查/标记/取消标记。
注意:当我们输入坐标时,要先开始判断此坐标是否在雷盘内,如在雷盘内才会开始下面的指令,否则让玩家重新输入。
排查坐标时,它会判断mine雷盘的内此坐标的内容是否为雷。
标记坐标时,它会直接改变show雷盘坐标内的内容。
取消标记坐标时,它会将show雷盘内被标记的坐标内容变为原样。
其代码如下:
void sige(char show[ROWS][COLS], int row, int col) //标记坐标
{
int x = 0;
int y = 0;
while (1)
{
printf("请输入标记的坐标->");
shop:scanf("%d %d", &x, &y);
if ((x >= 1 && x <= row) && (y >= 1 && y <= col) && show[x][y] == '*')
{
show[x][y] = '?';
break;
}
else
{
printf("坐标不可标记,请重新输入->");
goto shop;
}
}
system("cls");
DisplayBoard(show, row, col);
}
void cancel(char show[ROWS][COLS], int row, int col) //取消标记
{
int x = 0;
int y = 0;
while (1)
{
printf("请输入取消标记的坐标->");
shop:scanf("%d %d", &x, &y);
if ((x >= 1 && x <= row) && (y >= 1 && y <= col) && show[x][y] == '?')
{
show[x][y] = '*';
break;
}
else
{
printf("该坐标没有被标记,请重新输入->");
goto shop;
}
}
system("cls");
DisplayBoard(show, row, col);
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) //排查雷
{
int x = 0;
int y = 0;
char ch = 0;
int win = 0; //非雷的个数
while (win < row*col-EASY_COUNT)
{
printf("\n");
printf("请选择->\n");
printf("1排查的坐标\n");
printf("2.标记坐标\n");
printf("3.取消标记坐标\n\n");
int choice = 0;
scanf("%d", &choice);
switch (choice)
{
case 1:
printf("请输入排查坐标->");
shop:scanf("%d %d", &x, &y);
if ((x >= 1 && x <= row) && (y >= 1 && y <= col)) //判断坐标是否在雷盘内
{
if ((show[x][y] != '*') && (show[x][y] != '?'))
{
printf("\n该坐标已被排查,不能重复进行\n");
}
else
{
if (mine[x][y] == '@')
{
printf("很遗憾你被炸死了!\n");
DisplayBoard(mine, ROW, COL);
goto stop;
}
else
{
win++;
clear(mine, show, row, col, x, y);
system("cls");
DisplayBoard(show, row, col);
}
}
}
else
{
printf("输入坐标不在范围,请重新输入->");
goto shop;
}
break;
case 2:
sige(show, row, col); //跳转到标记坐标
break;
case 3:
cancel(show, row, col); //跳转到取消标记坐标
break;
default:
printf("未检测出选项,请重新选择->\n");
break;
}
if (!1)
{
stop: break;
}
}
if (win == row * col - EASY_COUNT)
{
printf("\n恭喜你,排雷成功\n");
DisplayBoard(mine, ROW, COL);
}
}
5、判断输赢
判断输赢时,它会直接判断mine雷盘内的此坐标里的内容是否是雷,如果是雷则游戏结束,打印内部雷盘,否则会继续让玩家输入坐标排查,直到所有的不是雷被找出为止。
其代码如下:
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) //排查雷
{
int x = 0;
int y = 0;
char ch = 0;
int win = 0; //非雷的个数
while (win < row*col-EASY_COUNT)
{
printf("\n");
printf("请选择->\n");
printf("1排查的坐标\n");
printf("2.标记坐标\n");
printf("3.取消标记坐标\n\n");
int choice = 0;
scanf("%d", &choice);
switch (choice)
{
case 1:
printf("请输入排查坐标->");
shop:scanf("%d %d", &x, &y);
if ((x >= 1 && x <= row) && (y >= 1 && y <= col))
{
if ((show[x][y] != '*') && (show[x][y] != '?'))
{
printf("\n该坐标已被排查,不能重复进行\n");
}
else
{
if (mine[x][y] == '@')
{
printf("很遗憾你被炸死了!\n");
DisplayBoard(mine, ROW, COL);
goto stop;
}
else
{
win++;
clear(mine, show, row, col, x, y);
system("cls");
DisplayBoard(show, row, col);
}
}
}
else
{
printf("输入坐标不在范围,请重新输入->");
goto shop;
}
break;
case 2:
sige(show, row, col); //跳转到标记坐标
break;
case 3:
cancel(show, row, col); //跳转到取消标记坐标
break;
default:
printf("未检测出选项,请重新选择->\n");
break;
}
if (!1)
{
stop: break;
}
}
if (win == row * col - EASY_COUNT)
{
printf("\n恭喜你,排雷成功\n");
DisplayBoard(mine, ROW, COL);
}
}
5.1、扩展
我们都知道一般扫雷时,如果周围都没有时会呈现出一片空白,附近有雷时则会再其周围显示雷的个数。
当我们输入坐标时,会先开始判断此坐标是否在雷盘内。
当坐标在雷盘时,则开始判断此坐标内容是否为初始化内容。如果是初始化内容则把其变成空格,并检测周围一格内的坐标内容,如果周围一格内的坐标内容都为初始化的内容的话,就会将其都变为空格。一直循环检测直到遇到雷为止。
如果周围有雷,则会以此空格为中心检测周围一格内有多少个雷后,在此空格内显示雷的个数。
其代码如下:
void clear(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y) //把没有雷的地方改为空格
{
int i = 0;
int j = 0;
if ((x >= 1 && x <= row) && (y >= 1 && y <= col))
{
int conti = get_mine_conti(mine, x, y);
if (conti == 0)
{
show[x][y] = ' ';
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (show[i][j] == '*')
{
clear(mine, show, row, col, i, j);
}
}
}
}
else
{
return show[x][y] = conti + '0';
}
}
}
6、game.h、game.c、test.c
我们发现当我们把全部的代码写完并都放在一个源文件里的时候,会发现其代码行数非常多,而且当你把函数的声明放最上面时写完几百行后想补个函数声明又得翻回最上头来,这样显得很麻烦,而且调试代码时不方便。所有我们把代码分成三个部分,game.h用来存放各类函数的声明,game.c用来存放游戏代码,test.c用来进行游戏代码的测试。
全部的游戏代码如下:
game.h
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EASY_COUNT 10
void menu(); //游戏界面
void menu1(); //游戏难度选择
void game(conti); //开始游戏
void InitBoard( char board[ROWS][COLS], int rows, int cols, char set ); //初始化游戏
void SetMine(char board[ROWS][COLS], int row, int col, int conti); //布置雷
void DisplayBoard(char board[ROWS][COLS], int row, int col); //打印棋盘
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col); //排查雷
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;
}
}
}
void SetMine(char board[ROWS][COLS], int row, int col, int conti) //布置雷
{
int count = conti;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '@';
count--;
}
}
}
void DisplayBoard(char board[ROWS][COLS], int row, int col) //打印棋盘
{
int i = 0;
int j = 0;
printf("\n -------------扫雷小游戏----------- \n");
printf(" ");
for (j = 1; j <= 9; j++)
{
printf(" %d ", j); //列号
}
printf("\n");
printf("\n");
printf(" ");
for (j = 0; j <= 8; j++)
{
if (j >= 1)
{
printf("-");
}
printf("---");
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf(" %d |", i); //行号
for (j = 1; j <= col; j++)
{
printf(" %c ", board[i][j]);
if (j <= col)
{
printf("|");
}
}
printf("\n");
if (i < row )
{
printf(" ");
printf("|");
for (j = 1; j <= col; j++)
{
printf("---");
if (j <= col)
{
printf("|");
}
}
printf("\n");
}
}
printf(" ");
for (j = 0; j <= 8; j++)
{
if (j >= 1)
{
printf("-");
}
printf("---");
}
printf("\n");
printf("\n -------------扫雷小游戏----------- \n");
}
int get_mine_conti(char mine[ROWS][COLS], int x, int y)
{
int i = 0;
int j = 0;
int conti = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (mine[i][j] == '@')
{
conti++;
}
}
}
return conti;
}
void clear(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y) //优化,把没有雷的地方改为空格
{
int i = 0;
int j = 0;
if ((x >= 1 && x <= row) && (y >= 1 && y <= col))
{
int conti = get_mine_conti(mine, x, y);
if (conti == 0)
{
show[x][y] = ' ';
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (show[i][j] == '*')
{
clear(mine, show, row, col, i, j);
}
}
}
}
else
{
return show[x][y] = conti + '0';
}
}
}
void sige(char show[ROWS][COLS], int row, int col) //标记坐标
{
int x = 0;
int y = 0;
while (1)
{
printf("请输入标记的坐标->");
shop:scanf("%d %d", &x, &y);
if ((x >= 1 && x <= row) && (y >= 1 && y <= col) && show[x][y] == '*')
{
show[x][y] = '?';
break;
}
else
{
printf("坐标不可标记,请重新输入->");
goto shop;
}
}
system("cls");
DisplayBoard(show, row, col);
}
void cancel(char show[ROWS][COLS], int row, int col) //取消标记
{
int x = 0;
int y = 0;
while (1)
{
printf("请输入取消标记的坐标->");
shop:scanf("%d %d", &x, &y);
if ((x >= 1 && x <= row) && (y >= 1 && y <= col) && show[x][y] == '?')
{
show[x][y] = '*';
break;
}
else
{
printf("该坐标没有被标记,请重新输入->");
goto shop;
}
}
system("cls");
DisplayBoard(show, row, col);
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) //排查雷
{
int x = 0;
int y = 0;
char ch = 0;
int win = 0; //非雷的个数
while (win < row*col-EASY_COUNT)
{
printf("\n");
printf("请选择->\n");
printf("1排查的坐标\n");
printf("2.标记坐标\n");
printf("3.取消标记坐标\n\n");
int choice = 0;
scanf("%d", &choice);
switch (choice)
{
case 1:
printf("请输入排查坐标->");
shop:scanf("%d %d", &x, &y);
if ((x >= 1 && x <= row) && (y >= 1 && y <= col))
{
if ((show[x][y] != '*') && (show[x][y] != '?'))
{
printf("\n该坐标已被排查,不能重复进行\n");
}
else
{
if (mine[x][y] == '@')
{
printf("很遗憾你被炸死了!\n");
DisplayBoard(mine, ROW, COL);
goto stop;
}
else
{
win++;
clear(mine, show, row, col, x, y);
system("cls");
DisplayBoard(show, row, col);
}
}
}
else
{
printf("输入坐标不在范围,请重新输入->");
goto shop;
}
break;
case 2:
sige(show, row, col); //跳转到标记坐标
break;
case 3:
cancel(show, row, col); //跳转到取消标记坐标
break;
default:
printf("未检测出选项,请重新选择->\n");
break;
}
if (!1)
{
stop: break;
}
}
if (win == row * col - EASY_COUNT)
{
printf("\n恭喜你,排雷成功\n");
DisplayBoard(mine, ROW, COL);
}
}
test.c
#include "game.h"
//游戏主页
//初始化扫雷
// 放入雷
//打印扫雷
//玩家输入 -> 标记/排查
//判断输赢
int main()
{
int input = 0;
int count = 0;
int conti = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请玩家选择->");
scanf("%d", &input);
switch (input)
{
case 1:
menu1();
printf("请选择模式->");
scanf("%d", &count);
switch (count)
{
case 1:
printf("简单模式\n");
conti = EASY_COUNT + 0;
break;
case 2:
printf("一般模式\n");
conti = EASY_COUNT + 20;
break;
case 3:
printf("困难模式\n");
conti = EASY_COUNT + 49;
break;
default:
printf("未检出选项,返回主页重新选择.\n");
break;
}
game(conti);
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("未检出选项,返回主页重新选择.\n");
break;
}
} while (input);
return 0;
}
void menu() //游戏界面
{
printf("\n");
printf("*******************************\n");
printf("****** 1. Play ******\n");
printf("****** 0. Exit ******\n");
printf("*******************************\n");
}
void menu1() //游戏模式选择
{
printf("\n");
printf("*******************************\n");
printf("****** 请选择模式 ******\n");
printf("****** 1. 简单 ******\n");
printf("****** 2. 一般 ******\n");
printf("****** 3. 困难 ******\n");
printf("*******************************\n");
}
void game(conti)
{
char mine[ROWS][COLS] = { 0 }; //内部棋盘
char show[ROWS][COLS] = { 0 }; //外部棋盘
InitBoard( mine, ROWS, COLS, '0' ); //初始化为'0'
InitBoard( show, ROWS, COLS, '*' ); //初始化为'*'
SetMine(mine, ROW, COL, conti); //布置雷
//DisplayBoard(mine, ROW, COL); //打印内部棋盘
DisplayBoard(show, ROW, COL); //打印外部棋盘
FindMine(mine, show, ROW, COL); //排查雷
}