※※※大家好!我是同学〖森〗,一名计算机爱好者,今天让我们进入小游戏开发学习模式。若有错误,请多多指教。小主使用的是VS2019编译器。
👍 点赞 ⭐ 收藏 📝留言 都是我创作的最大的动力!
目录
※※※大家好!我是同学〖森〗,一名计算机爱好者,今天让我们进入小游戏开发学习模式。若有错误,请多多指教。小主使用的是VS2019编译器。
扫雷
简介:
《扫雷》是一款大众类的益智小游戏,于1992年发行。游戏目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。
那今天让我们用已有的c语言知识来自己写一个扫雷小游戏
那么首先来了解一下扫雷游戏的原理;
设计效果:
游戏前的准备
1)游戏显示盘,用来提供给玩家。
2)随机产生规定数量的雷。
3)玩家输入一个坐标后的判断(即是否有雷)
4)第一次不能让玩家点到雷
5)若有雷,游戏结束界面,
6)若没雷,返回周围8格
7)若返回值为0,展开周围位置。
知道以上几点,让我们一起来写属于自己的扫雷游戏吧!
第一步:编写主函数:
int main()
{
system("COLOR 0A"); //将代码换成黑底绿色
int input = 0;
srand((unsigned int)time(NULL)); // 用来产生随机位置的雷
//用循环来使玩家多次玩
do
{
system("cls"); //清空屏幕
menu(); //选择页面
gotoxy(54, 25); //将光标定位
printf("请选择-> ");
scanf("%d", &input);
switch (input) //用Switch来判断玩家选择
{
case 1:
game(); //进入游戏
break;
case 2:
printf("退出游戏\n"); //退出游戏
break;
default: //输入错误
system("cls"); //清屏
fault();
Sleep(1500);
break;
}
} while (input != 2);
return 0;
}
1)system("COLOR 0A");
system头文件<stdlib.h>
其中COLOR后面的0是背景色代号,A是前景色代号
0 | 黑色 |
1 | 蓝色 |
2 | 绿色 |
3 | 湖蓝色 |
4 | 红色 |
5 | 紫色 |
6 | 黄色 |
7 | 白色 |
8 | 灰色 |
9 | 淡蓝色 |
A | 淡绿色 |
B | 淡浅绿色 |
C | 淡红色 |
D | 淡紫色 |
E | 淡黄色 |
F | 亮白色 |
2)menu();
选择页面
void menu() {
printf("\n########################################################################################################################\n");
printf("\n▓ * 欢迎进入扫雷小游戏 * ▓\n");
printf("\n########################################################################################################################\n");
printf("\n ◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆\n");
printf(" |◆|******|◆| |◆|******|◆|\n");
printf(" |◆|******|◆| ☆ 开始游戏 请按 1 |◆|******|◆|\n");
printf(" |◆|******|◆| ☆ 退出游戏 请按 2 |◆|******|◆|\n");
printf(" |◆|******|◆| |◆|******|◆|\n");
printf(" ◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆\n");
printf("\n########################################################################################################################\n");
printf("\n▓ ************************* ▓\n");
printf("\n########################################################################################################################\n");
}
运行起来的效果
3)gotoxy(54, 25);
作用:光标定位第25行第54列
1、在 Turbo C 或 Borland C 中可以使用 gotoxy 函数,包含在头文件 conio.h中。
2、而在 Visual C++ 或 GCC 中使用 gotoxy 函数,就需要把上面自定义的 gotoxy 函数写在自己的程序中。
下面这个 C 程序将在屏幕中第5行10列(横向为X,纵向为Y轴)中央输出“你好 陌生人”
#include <stdio.h>
#include <windows.h>
void gotoxy(short x, short y)
{
COORD coord = {x, y};
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
int main(void) {
gotoxy(10, 5);
printf("你好 陌生人\n");
return 0;
}
4)switch
用input接收玩家的选择结果
如果 input为1 进入game游戏
如果input为2 退出游戏,注意while(input != 2)
如果input为其他值 输入错误,重新输入。
void fault()
{
printf("\n\n *********************************************** \n");
printf(" ▓ $#$ 输入错误,重新输入 $#$ ▓\n");
printf("\n *********************************************** \n");
}
5)Sleep(1500);
程序停留1.5s,即提示信息1.5s后消失
不然直接接入循环看不见提示信息
主函数编写完毕,接下来让我们编写game();函数
第二步:游戏函数game();
void game()
{
system("cls"); //清空屏幕上menu()的内容
//存放雷
char mine[ROWS][COLS] = { 0 };
//展示给玩家
char show[ROWS][COLS] = { 0 };
//初始化雷盘
init_board(mine, ROWS, COLS, '0');
init_board(show, ROWS, COLS, '*');
//布雷
set_mine(mine, ROW, COL);
//排雷
find_mine(mine, show, ROW, COL);
}
我们先一起先做一个9*9的10个雷的简单版本。
1)雷盘 :
先定义两个一样大小二维字符数组一个用来存放随机产生的雷,另一个用来展示给玩家;
说明一下:我要想定义9*9的雷盘,就先定义11*11的二维数组,因为返回周围雷的个数时比较好写代码。
ROW和COL是宏定义,用来代表9;方便后期修改。
#define ROW 9
#define COL 9
#define ROWS ROW + 2
#define COLS COL + 2
#define EASY_COUNT 10
第三步:初始化雷盘、init_board()
void init_board(char a[ROWS][COLS], int row, int col, char c)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++) //注意:row是11,col是11
{
for (j = 0; j < col; j++)
{
a[i][j] = c;
}
}
}
注意:形参设置的量,加一个char c,可以自己在函数调用时设置初始化的内容
如:game()函数里的初始化一个是 ‘ 0 ’ 另一个是‘ * ’;
//初始化棋盘
init_board(mine, ROWS, COLS, '0');
init_board(show, ROWS, COLS, '*');
第四步: 打印雷盘
//打印棋盘
void show_board(char a[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("====================扫雷====================\n");
printf(" ");
for (i = 1; i <= row; i++) //打印列的坐标
{
printf(" %d ", i);
}
printf("\n");
printf(" "); //空格是为了对齐位置。
for (j = 1; j <= col; j++) //打印分割行
{
if (j == 1)
printf("|---");
else
printf("---");
printf("|");
}
printf("\n"); //换行符不要忘记
for (i = 1; i <= row; i++)
{
printf("%d ", i);
for (j = 1; j <= col; j++)
{
if (j == 1)
printf("| %c ", a[i][j]);
else
printf(" %c ", a[i][j]);
printf("|");
}
printf("\n");
printf(" ");
//打印分割行
for (j = 1; j <= col; j++)
{
if (j == 1)
printf("|---");
else
printf("---");
printf("|");
}
printf("\n");
}
printf("====================扫雷====================\n");
gotoxy(55, 9);
printf("能标记雷数 #:%d",(EASY_COUNT - flag_mine)); //显示能标记雷数
}
让我们先看一下成品图
1) 先用循环打印标记列的第一行的1 2 3 ……
2)然后打印一行分割行由 |- - -组成,别忘记前后都有 ' | '
3)第一个先打印显示行的i值。再循环打印‘ | %c | ’
4)这里注意打印出来的9*9是原数组的第2行到第10行的内容,不要打印错,即上下左右都要空出一行。
这里之所以一个元素一个元素的打印,不是一整行地打印就是为了要如果想打印规模大的棋盘只需要传入相对应规格的行列数值就行。
第五步:随机布雷
void set_mine(char mine[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT; //用宏值赋给雷数,方便修改雷数
int x = 0;
int y = 0;
while (count)
{
x = rand() % row + 1; //随机产生横坐标值1到10
y = rand() % col + 1; //随机产生纵坐标值1到10
if (mine[x][y] == '0') //判断是否已经布雷,避免重复
{
mine[x][y] = '1'; //雷 //字符‘1’为雷
count--;
}
}
}
这一步比较简单,就是给随机产生的x,y坐标赋值为字符‘1’;
但要注意随机数的产生条件,和范围。
第六步:玩家排雷
这一步最为复杂,前面所准备的问题都要在这一步解决
例如:返回周围8格的雷数,判断是否踩雷,怎么何时扩展其他格的坐标,如何标记,怎么避免首步踩雷。
void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int k = 0; //记录是否为首次踩雷
int win = 0;
int ch = 0;
time_t start, end; //用来判断玩家用了多长时间
flag_mine = 0; //初始化标记雷数。
start = time(NULL); //记录开始时间
while (1)
{
system("cls");
menu1();
show_board(show, ROW, COL);
gotoxy(55, 11);
printf("请选择(1, 2, 3):> ");
scanf("%d", &ch);
if (1 == ch)
{
gotoxy(55, 13);
printf("请输入要排查的目标:> ");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
if (k == 0)
{
Movemine(mine, show, x, y);
k++;
}
else
{
system("cls");
fail();
show_board(mine, ROW, COL);
end = time(NULL);
gotoxy(55, 15);
printf("用时:%.2lf s", difftime(end, start));
int a;
gotoxy(55, 17);
printf("返回主菜单请按 1");
scanf("%d", &a);
if (a == 1)
break;
}
}
else
{
expand_mine(mine, show, x, y);
k++;
}
}
else
{
system("cls"); //清屏
fault();
Sleep(1500);
}
win = Iswin(show, ROW, COL);
if (win == EASY_COUNT)
break;
}
else if (2 == ch)
{
gotoxy(55, 13);
Flagmine(mine, show, ROW, COL);
}
else if (3 == ch)
{
gotoxy(55, 13);
Cancelflag(mine, show, ROW, COL);
}
else
{
system("cls"); //清屏
fault();
Sleep(1500);
}
}
if (win == EASY_COUNT)
{
system("cls");
wins ();
show_board(mine, ROW, COL);
end = time(NULL);
gotoxy(55, 15);
printf("用时:%.2lf s", difftime(end, start));
int a;
gotoxy(55, 17);
printf("返回主菜单请按 1");
scanf("%d", &a);
}
}
menu1()子函数
void menu1()
{
printf("\n ◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆\n");
printf(" |◆|******|◆| * 欢迎进入扫雷游戏 * |◆|******|◆|\n");
printf(" |◆|******|◆| |◆|******|◆|\n");
printf(" |◆|******|◆| ☆ 开始排雷 请按 1 |◆|******|◆|\n");
printf(" |◆|******|◆| ☆ 标记网格 请按 2 |◆|******|◆|\n");
printf(" |◆|******|◆| ☆ 取消标记 请按 3 |◆|******|◆|\n");
printf(" ◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆\n");
}
打印效果
再打印出雷盘让玩家做参考,
让玩家输入选择,用scanf(“%c”,ch);接收根据ch的值,分四种情况,分别是排雷, 标记, 取消标记, 输入错误,用if语句来实现。由于左侧是雷盘,需要gotoxy函数来指定光标的位置。做好后的情况如下
那我们接下来开始写排雷的代码
情况一:开始排雷
if (1 == ch)
{
gotoxy(55, 13); //光标定位
printf("请输入要排查的目标:> ");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col) //防止玩家输入坐标错误
{
//若踩雷
if (mine[x][y] == '1')
{
if (k == 0) //判断是否为第一次踩雷。
{
Movemine(mine, show, x, y); //第一次防爆
k++;
}
else //不是第一次踩雷,游戏结束
{
system("cls");
fail(); //失败页面
show_board(mine, ROW, COL); //展示雷盘,让玩家知道他怎么死的
end = time(NULL);
gotoxy(55, 15);
printf("用时:%.2lf s", difftime(end, start)); //打印玩家耗时
int a;
gotoxy(55, 17);
printf("返回主菜单请按 1"); //用来玩家返回
scanf("%d", &a);
if (a == 1)
break;
}
}
else
{
expand_mine(mine, show, x, y); // 扩展雷
k++;
}
}
else
{
system("cls"); //清屏
fault();
Sleep(1500);
}
win = Iswin(show, ROW, COL);
if (win == EASY_COUNT) //胜利
break;
}
先让玩家输入坐标,分别用x,y接收横纵坐标值,用if语句来判断玩家输入的坐标是否在显示雷盘内,做不在提醒错误,请重新输入,若在这进行下一步判断即判断该坐标是否有雷,若有雷再分别讨论是否为第一步,若为第一步,则将该坐标的雷移到另一个位置,若不是第一步则结束游戏,若该坐标没有雷,则判断周围8格是否有雷,若有雷则返回周围雷数,若没雷则再向其他8格依次判断。最后判断游戏是否胜利。
第一次踩雷,移雷函数
void Movemine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
if (mine[x][y] == '1')
{
mine[x][y] = '0'; //将该处的雷取消
int m, n;
while (1)
{
m = rand() % ROW + 1;
n = rand() % COL + 1;
if (m == x && n == y) //避免又移到该位置
{
continue;
}
if (mine[m][n] == '0')
{
mine[m][n] = '1';
break;
}
}
expand_mine(mine, show, x, y);
}
}
游戏失败设置
fail()函数
void fail()
{
printf(" \t\t\t$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n");
printf(" \t\t\t|#| |#|\n");
printf(" \t\t\t|#| ☆☆☆☆☆☆☆☆☆☆ |#|\n");
printf(" \t\t\t|#| ☆ ☆ |#|\n");
printf(" \t\t\t|#| ☆☆ 挑战失败! ☆☆ |#|\n");
printf(" \t\t\t|#| ☆ ☆ |#|\n");
printf(" \t\t\t|#| ☆☆☆☆☆☆☆☆☆☆ |#|\n");
printf("\t\t\t|#| |#|\n");
printf("\t\t\t$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n");
}
运行效果:
游戏结束代码
system("cls");
fail(); //失败页面
show_board(mine, ROW, COL); //展示雷盘,让玩家知道他怎么死的
end = time(NULL);
gotoxy(55, 15);
printf("用时:%.2lf s", difftime(end, start)); //打印玩家耗时
int a;
gotoxy(55, 17);
printf("返回主菜单请按 1"); //用来玩家返回
scanf("%d", &a);
if (a == 1)
break;
展示效果:
没雷,返回周围雷数和扩展周围坐标
//返回周围雷数
static int show_mine_count(char mine[ROWS][COLS], int x, int y)
{
int sum = 0;
//求(x,y)周围8个格的ASCII码之和
sum = 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];
return sum - 8 * '0';
}
//扩展周围不是雷的区域
void expand_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
//判断坐标是否越界
if (x == 0 || y == 0 || x == ROWS - 1 || y == COLS - 1)
return;
//判断是否已经被排除
if (show[x][y] != '*'&&show[x][y] !='#')
return;
int count = show_mine_count(mine, x, y) + '0'; //接收周围雷数
if(count > '0')
{
show[x][y] = count;
return;
}
//递归拓展地图
else if (count == '0') //运用递归方法
{
show[x][y] = ' ';
expand_mine(mine, show, x - 1, y);
expand_mine(mine, show, x - 1, y - 1);
expand_mine(mine, show, x - 1, y + 1);
expand_mine(mine, show, x, y - 1);
expand_mine(mine, show, x, y + 1);
expand_mine(mine, show, x + 1, y - 1);
expand_mine(mine, show, x + 1, y);
expand_mine(mine, show, x + 1, y + 1);
}
}
显示周围雷数,由ACSII码可知,周围8格的ACSII码和减去8个字符‘0’的ACSII码和就是周围的雷数。
扩展周围雷区:
一、要确定坐标是不是在规定位置内。不然很容易扩展过多
二、要注意我们要返回show【x】【y】的坐标是字符数字而不是整数,因为我们定义的是字符数组,且打印函数用的是%c,
三、判断递归的条件:count==‘0’,不是数字0。
实现效果:
游戏成功
while(1)
{
……
win = Iswin(show, ROW, COL);
if (win == EASY_COUNT) //胜利就跳出循环
break;
}
if (win == EASY_COUNT) //与失败过程类似。
{
system("cls");
wins ();
show_board(mine, ROW, COL);
end = time(NULL);
gotoxy(55, 15);
printf("用时:%.2lf s", difftime(end, start));
int a;
gotoxy(55, 17);
printf("返回主菜单请按 1:> ");
scanf("%d", &a);
}
Iswin()函数
//判断是否胜利
int Iswin(char show[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
int win = 0;
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)
{
if (show[i][j] == '#' || show[i][j] == '*')
win++;
}
}
return win;
}
效果:
情况二:标记
//标记雷
void Flagmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
if (flag_mine == EASY_COUNT)
{
system("cls");
printf("\n\n *********************************************** \n\n");
printf(" ▓ $#$ 标记雷数已达最大值 $#$ ▓\n");
printf("\n *********************************************** \n");
Sleep(2000);
return ;
}
int x = 0;
int y = 0;
printf("请输入要标记坐标:> ");
scanf("%d%d", &x, &y);
//判断输入坐标是否在雷区
if (x > 0 && x <= row && y > 0 && y <= col)
{
//判断坐标是否显示
if (show[x][y] == '*')
{
show[x][y] = '#';
flag_mine++;
}
else
{
system("cls");
printf("\n\n *********************************************** \n\n");
printf(" ▓ $#$ (%d,%d)坐标已展示,无法标记 $#$ ▓\n", x, y);
printf("\n *********************************************** \n");
Sleep(2000);
//gotoxy(55, 19);
//printf("(%d,%d)坐标已展示,无法标记", x, y);
//Sleep(1500);
}
}
else
{
system("cls"); //清屏
fault();
Sleep(1500);
}
}
标记函数相对来说较简单:
首先判断标记雷数是否达到最大值,如果是提醒玩家无法标记,
其次让玩家输入要标记的坐标。要标记的坐标,并判断是否在显示的范围内,没有提醒玩家输入错误,
然后判断该坐标只有为‘ * ’即没有被翻开才能标记为' # '否则不能标记
情况三:取消标记
//取消标记
void Cancelflag(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
if (flag_mine == 0)
{
system("cls");
printf("\n\n *********************************************** \n\n");
printf(" ▓ $#$ 你未标记无法取消 $#$ ▓\n");
printf("\n *********************************************** \n");
Sleep(2000);
return;
}
printf("请输入要取消标记坐标:> ");
int x = 0;
int y = 0;
scanf("%d%d", &x, &y);
if (x > 0 && x <= row && y > 0 && y <= col)
{
//判断坐标是否显示
if (show[x][y] == '#')
{
show[x][y] = '*';
flag_mine--;
}
else
{
system("cls");
printf("\n\n ****************************************************\n\n");
printf(" ▓ $#$ (%d,%d)坐标未标记,无法取消标记 $#$ ▓\n", x, y);
printf("\n ****************************************************\n");
Sleep(2000);
}
}
else
{
system("cls"); //清屏
fault();
Sleep(1500);
}
}
情况入表记所示:
完整代码记更多有趣的代码请移步小森的Gitee仓库Gitee
到这里又要和大家说再见的时候了,若有什么问题或者有什么修改建议可在博客下面评论或者私信小主,若小主看见定第一时间回复。