开始先放代码
#include <stdio.h>
#include <graphics.h>
#include <conio.h>
#include <Windows.h>
#define BLONG 50
void InitData();
void JudgeAnd();
void Input();
void WithoutInput();
void execute();
int plat[BLONG][BLONG];
int SnakePosH;
int SnakePosL;
int direction;//上下左右 5 2 1 3
int SnakeLong;
int Grade;
int Foodi;
int Foodj;
void InitData()
{
Grade = 0;
plat[BLONG][BLONG] = { 0 };
SnakePosH = BLONG / 2;
SnakePosL = BLONG / 2;
direction = 3;
SnakeLong = 4;
for (int i = 0; i < BLONG; i++)
{
for (int j = 0; j < BLONG; j++)
{
if (plat[i][j] > 0)//将蛇消失
plat[i][j] = 0;
else if (plat[i][j] == -2)//将食物消失
plat[i][j] = 0;
}
}
for (int i = 0; i < BLONG; i++)//定义边界
{
plat[i][0] = -1;
plat[i][BLONG - 1] = -1;
}
for (int j = 0; j < BLONG; j++)
{
plat[0][j] = -1;
plat[BLONG - 1][j] = -1;
}
for (int i = 0; i < SnakeLong; i++)//定义蛇
plat[SnakePosH][SnakePosL - i] = 1 + i;
Foodi = rand() % (BLONG - 2) + 2;//理论上 -2 +2可以是food出现的范围最大
Foodj = rand() % (BLONG - 2) + 2;//因为随机表示从2到lie-2 因为1和lie-1都是边框 避免food出现在边框上
plat[Foodi][Foodj] = -2;
}
void JudgeAnd()
{
BeginBatchDraw();
for (int i = 0; i < BLONG * 10; i+=10)
{
for (int j = 0; j < BLONG * 10; j+=10)
{
if (plat[i / 10][j / 10] >= 1)
solidrectangle(j, i, j + 10, i + 10);
else if (plat[i / 10][j / 10] == -1)//墙
solidrectangle(j, i, j + 10, i + 10);//这里用的是计算机坐标 和数组是相反的
else if(plat[i / 10][j / 10] == -2)
solidrectangle(j, i, j + 10, i + 10);
//FlushBatchDraw(j, i, j + 10, i + 10);
}
//printf("\n");
}
Sleep(25);
EndBatchDraw();
}
void Input()
{
char input;
if (_kbhit())
{
input = _getch();
if (input == '5' && direction != 2)//上
direction = 5;
else if (input == '2' && direction != 5)//下
direction = 2;
else if (input == '1' && direction != 3)//左
direction = 1;
else if (input == '3' && direction != 1)//右
direction = 3;
}
}
void WithoutInput()
{
int OldHeadi, OldHeadj;
int OldTaili, OldTailj;
int NewHeadi, NewHeadj;
int max = 0;
for (int i = 0; i < BLONG; i++)
{
for (int j = 0; j < BLONG; j++)
{
if (plat[i][j] > 0)
{
plat[i][j]++;
if (max < plat[i][j])
{
max = plat[i][j];//记录旧的蛇尾巴
OldTaili = i;
OldTailj = j;
}
if (plat[i][j] == 2)
{
OldHeadi = i;//记录旧的蛇头
OldHeadj = j;
}
}
}
}
if (direction == 5)//
{
NewHeadi = OldHeadi - 1;
NewHeadj = OldHeadj;
}
else if (direction == 2)//
{
NewHeadi = OldHeadi + 1;
NewHeadj = OldHeadj;
}
else if (direction == 1)
{
NewHeadi = OldHeadi;
NewHeadj = OldHeadj - 1;
}
else if (direction == 3)
{
NewHeadi = OldHeadi;
NewHeadj = OldHeadj + 1;
}
if (plat[NewHeadi][NewHeadj] == -2)//当前的food消失,产生新的食物
{
plat[Foodi][Foodj] = 0;
Foodi = rand() % (BLONG - 5) + 2;
Foodj = rand() % (BLONG - 5) + 2;
plat[Foodi][Foodj] = -2;
Grade++;
}
else
{
plat[OldTaili][OldTailj] = 0;//让尾巴为0,实现转向时不改变长度
}
if (plat[NewHeadi][NewHeadj] > 0 || plat[NewHeadi][NewHeadj] == -1)
{
char str[100];
sprintf_s(str, "您输了,是否继续。您的分数是:%d", Grade);
if (IDYES == MessageBox(NULL, str, "贪吃蛇", MB_YESNO))
execute();
else
exit(0);
}
else
{
plat[NewHeadi][NewHeadj] = 1;
}
}
void execute()
{
InitData();
while (1)
{
JudgeAnd();
WithoutInput();
Input();
cleardevice();
}
}
int main()
{
initgraph(BLONG * 10, BLONG * 10);
if (IDNO == MessageBox(NULL, "是否开始", "贪吃蛇", MB_YESNO))
exit(0);
execute();
_getch();
closegraph();
return 0;
}
代码也就两百多行
本次使用C语言
代码目标是利用easyx图形库做一个简单的贪吃蛇
先放一下最终代码图:
MessageBox函数
这个图片首先是初始化了窗口
定义的数组是长宽为50。做成画布大概是50*10的边长
这里第一个弹出的窗口就是最简单的API
利用了MessageBox函数
这个函数一共有四个形参
第一个是窗口句柄
第二个是窗口内信息
第三个是窗口名字
第四个是按钮类型
1.对话框的按钮类型
MB_OK //"确定"
MB_OKCANCEL //"确定" + "取消"
MB_ABORTRETRYIGNORE //"终止" + "重试" + "忽略"
MB_YESNOCANCEL //"是" + "否" + "取消"
MB_YESNO //"是" + "否"
MB_RETRYCANCEL //"重试" + "取消"
2.对话框的图标类型
MB_ICONHAND //带有红X的错误/停止图标
MB_ICONQUESTION //问号的询问图标
MB_ICONEXCLAMATION //黄色感叹号的警告图标
MB_ICONASTERISK //带有蓝i的信息提示图标
这是资料整理来的
这是对贪吃蛇开始的和结束的准备工作
sprintf函数
然后就是sprintf函数
这是一个类型转换函数
在vs2019的环境下
char str[100];
sprintf_s(str, "您输了,是否继续。您的分数是:%d", Grade);
分数是全局变量 在代码开头就已经定义了
利用sprintf函数 转换了分数的类型 把sprintf的函数中的第二个参数上的内容放到str[100]数组中
这是游戏结束时的图片
准备工作基本完成
接下来就是代码:
头文件
#include <stdio.h>
#include <graphics.h>
#include <conio.h>
#include <Windows.h>
四个头文件
第一个在图形库中其实用不到
但是我写习惯了 便加上了
第二个是图形库头文件
第三个也用不到
但是在写代码的时候需要_getch()函数方便调试
第四个就是需要调用API 所以加上的
宏定义
#define BLONG 50
就是数组的边长
也便是画布的边长
函数申明
void InitData();
void JudgeAnd();
void Input();
void WithoutInput();
void execute();
这里是所有函数的申明
这里是避免函数在定义的时候顺序不当
因为在用到void execute();这个函数时需要在某些函数之前
但是我嫌麻烦就一次性把函数都申明了。
全局变量的申明
int plat[BLONG][BLONG];//地图
int SnakePosH;//蛇的位置
int SnakePosL;//蛇的位置
int direction;//上下左右 5 2 1 3
int SnakeLong;//蛇的长度
int Grade;//分数
int Foodi;//食物的位置
int Foodj;//食物的位置
这些变量必须是全局变量 因为在下面函数中需要改变变量的值 所以只有全局变量的作用域的范围广
全局变量是可以被本程序所有对象或函数引用。
代码中整数的代表
1便是蛇头
大于1的地方代码蛇身
-1代表地图的边界
-2代表食物
数据的初始化
void InitData()
{
Grade = 0;
plat[BLONG][BLONG] = { 0 };
SnakePosH = BLONG / 2;
SnakePosL = BLONG / 2;
direction = 3;
SnakeLong = 4;
for (int i = 0; i < BLONG; i++)
{
for (int j = 0; j < BLONG; j++)
{
if (plat[i][j] > 0)//将蛇消失
plat[i][j] = 0;
else if (plat[i][j] == -2)//将食物消失
plat[i][j] = 0;
}
}
for (int i = 0; i < BLONG; i++)//定义边界
{
plat[i][0] = -1;
plat[i][BLONG - 1] = -1;
}
for (int j = 0; j < BLONG; j++)
{
plat[0][j] = -1;
plat[BLONG - 1][j] = -1;
}
for (int i = 0; i < SnakeLong; i++)//定义蛇
plat[SnakePosH][SnakePosL - i] = 1 + i;
Foodi = rand() % (BLONG - 2) + 2;//理论上 -2 +2可以是food出现的范围最大
Foodj = rand() % (BLONG - 2) + 2;//因为随机表示从2到lie-2 因为1和lie-1都是边框 避免food出现在边框上
plat[Foodi][Foodj] = -2;
}
- 函数第一把所有全局变量需要初始化的地方都初始化。
- 因为游戏结束后需要继续时。就要把所有的数据都都变成原来的数据。
for (int i = 0; i < BLONG; i++)
{
for (int j = 0; j < BLONG; j++)
{
if (plat[i][j] > 0)//将蛇消失
plat[i][j] = 0;
else if (plat[i][j] == -2)//将食物消失
plat[i][j] = 0;
}
}
这里就是遍历数组 把数组不是0的地方都变成0,除了边界不处理 其实处理了也没有什么大不了的 因为代码下面都有对边界的初始化
- 接下来对地图的边界 食物刚开始的位置 蛇刚开始的长度和位置进行初始化
for (int i = 0; i < BLONG; i++)//定义边界
{
plat[i][0] = -1;
plat[i][BLONG - 1] = -1;
}
for (int j = 0; j < BLONG; j++)
{
plat[0][j] = -1;
plat[BLONG - 1][j] = -1;
}
for (int i = 0; i < SnakeLong; i++)//定义蛇
plat[SnakePosH][SnakePosL - i] = 1 + i;
Foodi = rand() % (BLONG - 2) + 2;//理论上 -2 +2可以是food出现的范围最大
Foodj = rand() % (BLONG - 2) + 2;//因为随机表示从2到lie-2 因为1和lie-1都是边框 避免food出现在边框上
plat[Foodi][Foodj] = -2;
随机函数
rand() rand()%(变量1) + 变量2
表示函数的返回值随机从变量1到变量2 包括变量1到变量2
easyx绘图函数(来自easyx帮助文档)
solidrectangle
这个函数用于画填充矩形(无边框)。
void solidrectangle(
int left,
int top,
int right,
int bottom
);参数:
left
矩形左部 x 坐标。
top
矩形上部 y 坐标。
right
矩形右部 x 坐标。
bottom
矩形下部 y 坐标。
返回值:
(无)
示例:
(无)
这个函数用于绘制边界 蛇头 蛇身 这样看起来比较单一 但是这个只是绘图 因为边界 蛇头 蛇身 的数值都不一样 所以想要改变样式只要更改绘图函数就行了。
批量绘制函数
BeginBatchDraw
这个函数用于开始批量绘图。执行后,任何绘图操作都将暂时不输出到屏幕上,直到执行 FlushBatchDraw 或 EndBatchDraw 才将之前的绘图输出。
void BeginBatchDraw();参数:
(无)
返回值:
(无)
示例:
以下代码实现一个圆从左向右移动,会有比较明显的闪烁。
请取消 main 函数中的三个注释,以实现批绘图功能,可以消除闪烁。
#include <graphics.h>
int main()
{
initgraph(640,480);
setlinecolor(WHITE);
setfillcolor(RED);
// BeginBatchDraw();
for(int i=50; i<600; i++)
{
circle(i, 100, 40);
floodfill(i, 100, WHITE);
// FlushBatchDraw();
Sleep(10);
cleardevice();
}
// EndBatchDraw();
closegraph();
}
EndBatchDraw
这个函数用于结束批量绘制,并执行未完成的绘制任务。
// 结束批量绘制,并执行未完成的绘制任务
void EndBatchDraw();// 结束批量绘制,并执行指定区域内未完成的绘制任务
void EndBatchDraw(
int left,
int top,
int right,
int bottom
);参数:
left
指定区域的左部 x 坐标。
top
指定区域的上部 y 坐标。
right
指定区域的右部 x 坐标。
bottom
指定区域的下部 y 坐标
返回值:
(无)
示例:
请参见 BeginBatchDraw 的示例。
这两个函数分别放在绘图函数的开头和结尾
FlushBatchDraw
这个函数用于执行未完成的绘制任务。
// 执行未完成的绘制任务
void FlushBatchDraw();// 执行指定区域内未完成的绘制任务
void FlushBatchDraw(
int left,
int top,
int right,
int bottom
); 参数:
left
指定区域的左部 x 坐标。
top
指定区域的上部 y 坐标。
right
指定区域的右部 x 坐标。
bottom
指定区域的下部 y 坐标。
返回值:
(无)
示例:
请参见 BeginBatchDraw 的示例。
这个函数可以画出没有画出的图
但是加上这个会使频闪更加严重
所以没有加上
输出地图
void JudgeAnd()
{
BeginBatchDraw();
for (int i = 0; i < BLONG * 10; i+=10)
{
for (int j = 0; j < BLONG * 10; j+=10)
{
if (plat[i / 10][j / 10] >= 1)
solidrectangle(j, i, j + 10, i + 10);
else if (plat[i / 10][j / 10] == -1)//墙
solidrectangle(j, i, j + 10, i + 10);//这里用的是计算机坐标 和数组是相反的
else if(plat[i / 10][j / 10] == -2)
solidrectangle(j, i, j + 10, i + 10);
//FlushBatchDraw(j, i, j + 10, i + 10);
}
//printf("\n");
}
Sleep(25);
EndBatchDraw();
}
在画矩形的时候数据有些不一样 但是因为屏幕的坐标是从左上角开始的 但是数组和坐标是相反的 所以这个画矩形的函数的实参是和数组的参数相反的
因为地图是500*500 所以i 和 j 都是以10为一个单位增加的
所以在数组的时候 i和j都除以了10;
这就保证了地图的输出
刚开始这样只是输出了一个食物和蛇 但是它们是没有动的
那么就需要另外一个函数
输入函数
因为代码需要while(1)死循环 不断更新数据来输出地图
所以如果要输入 那么程序就必须暂停来获取用户输入
那么就不能达到我们的目的
所以我们需要一个非阻塞函数
_kbhit()
这个函数如果有输入就会返回真
所以代码如下:
void Input()
{
char input;
if (_kbhit())
{
input = _getch();
if (input == '5' && direction != 2)//上
direction = 5;
else if (input == '2' && direction != 5)//下
direction = 2;
else if (input == '1' && direction != 3)//左
direction = 1;
else if (input == '3' && direction != 1)//右
direction = 3;
}
}
这个direction是表示方向
为了方便我把方向的 上下左右 定为小键盘里的 1 2 3 5
只要有输入就改变方向 当然保证方向不能与当前方向相反
与用户输入无关的函数(核心)
这个函数不断的更改数据
可以说是贪吃蛇里面最核心的函数
void WithoutInput()
{
int OldHeadi, OldHeadj;
int OldTaili, OldTailj;
int NewHeadi, NewHeadj;
int max = 0;
for (int i = 0; i < BLONG; i++)
{
for (int j = 0; j < BLONG; j++)
{
if (plat[i][j] > 0)
{
plat[i][j]++;
if (max < plat[i][j])
{
max = plat[i][j];//记录旧的蛇尾巴
OldTaili = i;
OldTailj = j;
}
if (plat[i][j] == 2)
{
OldHeadi = i;//记录旧的蛇头
OldHeadj = j;
}
}
}
}
if (direction == 5)//
{
NewHeadi = OldHeadi - 1;
NewHeadj = OldHeadj;
}
else if (direction == 2)//
{
NewHeadi = OldHeadi + 1;
NewHeadj = OldHeadj;
}
else if (direction == 1)
{
NewHeadi = OldHeadi;
NewHeadj = OldHeadj - 1;
}
else if (direction == 3)
{
NewHeadi = OldHeadi;
NewHeadj = OldHeadj + 1;
}
if (plat[NewHeadi][NewHeadj] == -2)//当前的food消失,产生新的食物
{
plat[Foodi][Foodj] = 0;
Foodi = rand() % (BLONG - 5) + 2;
Foodj = rand() % (BLONG - 5) + 2;
plat[Foodi][Foodj] = -2;
Grade++;
}
else
{
plat[OldTaili][OldTailj] = 0;//让尾巴为0,实现转向时不改变长度
}
if (plat[NewHeadi][NewHeadj] > 0 || plat[NewHeadi][NewHeadj] == -1)
{
char str[100];
sprintf_s(str, "您输了,是否继续。您的分数是:%d", Grade);
if (IDYES == MessageBox(NULL, str, "贪吃蛇", MB_YESNO))
execute();
else
exit(0);
}
else
{
plat[NewHeadi][NewHeadj] = 1;
}
}
int OldHeadi, OldHeadj;
int OldTaili, OldTailj;
int NewHeadi, NewHeadj;
int max = 0;
定义 旧的蛇头 旧的蛇尾 新的蛇头
这个蛇移动的原理是这样的:
图中没有画出边界
1 2 3 表示蛇头 蛇身
刚开始遍历数组 大于0的地方就是蛇 所以我们把大于0的地方都+1
于是就变成了这样:
然后遍历数组中 如果数组值为2 就记录这个数组的i和j 这个i
和j就放到旧的蛇头地方
新的蛇头需要输入来改变
其次的是蛇尾 因为移动的时候 蛇头会在2的周围增加
那么蛇的长度就增加了 那么蛇尾就需要消除
因此就需要记录蛇尾的位置 放到旧的蛇尾这个变量中
但是蛇尾其实是最大的数 那么遍历中 用max
如果max小于这个数组值 那么就让这个max变成这个数组值
同时 我们把数组值的i j都记录下来 那么并且这个循环只有在max最大的时候回停止 那么结束后max就是最大值 记录数组的i j
那么这个地方就记录好了
if (direction == 5)//
{
NewHeadi = OldHeadi - 1;
NewHeadj = OldHeadj;
}
else if (direction == 2)//
{
NewHeadi = OldHeadi + 1;
NewHeadj = OldHeadj;
}
else if (direction == 1)
{
NewHeadi = OldHeadi;
NewHeadj = OldHeadj - 1;
}
else if (direction == 3)
{
NewHeadi = OldHeadi;
NewHeadj = OldHeadj + 1;
}
这是输入 判断上下左右
并把旧的蛇头修改 如果上 那么新的蛇头的i就-1;
以此类推
这样就记录了新的蛇头
到这个地方为止 都只是记录
if (plat[NewHeadi][NewHeadj] == -2)//当前的food消失,产生新的食物
{
plat[Foodi][Foodj] = 0;
Foodi = rand() % (BLONG - 5) + 2;
Foodj = rand() % (BLONG - 5) + 2;
plat[Foodi][Foodj] = -2;
Grade++;
}
else
{
plat[OldTaili][OldTailj] = 0;//让尾巴为0,实现转向时不改变长度
}
判断新的蛇头的位置是不是在-2的地方 就是表示蛇头遇见了食物 那么蛇尾就没有再消失的必要 所以不把蛇尾消失 但是如果没有遇见食物 所以就让蛇尾消失 就保证了蛇的长度不变
最后如果食物吃到了 那么食物消失 就随机产生新的食物
接下在我们新的蛇头还没有产生
但是我们先判断有没有遇见边界 如果遇见了 游戏就输了
如果没有遇见 那么就让新的蛇头产生 便是赋值为1
那么整个代码基本上都完成了。
接下来就只是需要用另一个函数把这些函数都封装起来
那么就是我们的execute()函数
void execute()
{
InitData();
while (1)
{
JudgeAnd();
WithoutInput();
Input();
cleardevice();
}
}
那么回到上一个函数
在判断蛇遇见边界的时候 我们就利用API转换char类型值 输入分数 以及 告诉你输了 而且要不要继续
那么如果继续 我们就把函数execute()放上去
这个API的函数的返回值很特殊
Windows.h头文件自带一些宏定义
IDABORT:Abort 按钮被选中。
IDCANCEL:Cancel按钮被选中。
IDIGNORE:Ignore按钮被选中。
IDNO:NO按钮被选中。
IDOK:OK按钮被选中。
IDRETRY:RETRY按钮被选中。
IDYES:YES按钮被选中。
我们用IDYES代表选中了yes
那么我们就继续游戏
因为画图已经初始化了 不用再画 所以这个execute()函数中没有初始化画布的函数 把这个函数放到了主函数
如果取消 没那么exit(0)直接结束程序
这就是为什么全局变量没有直接初始化的原因
而且数据的初始化只需要一次就够了 所以没有放在while循环里面。
主函数部分
int main()
{
initgraph(BLONG * 10, BLONG * 10);
if (IDNO == MessageBox(NULL, "是否开始", "贪吃蛇", MB_YESNO))
exit(0);
execute();
_getch();
closegraph();
return 0;
}
利用简单的API告诉我们是不是要开始
然后把execute();放上
就关闭画布
结束…
最后我们小店的镇店之图 :