前面的话:
首次写这个,如有不足,欢迎指正。
2022/11/2更新:
贪吃蛇C++_2021: 大一作业,使用EasyX图形库制作的贪吃蛇游戏 (gitee.com)
我在Gitee上传了自己的代码,大家喜欢可以支持一下啊!
EasyX库官网https://easyx.cn/
需要先下好哦!
图像音频资源在这里找的 爱给网(现在也不好了,下载资源需要什么币,虽然可以免费获得但是很麻烦,有好的资源网站麻烦推荐一波)☺
作为c语言的练手项目算是非常经典的了,在完成的过程中也回忆了一波童年,所有代码可以运行。图片,音乐使用自己的就可以了。
有人要过游戏图片音频资源,我就在这里放出来了。
图片音频资源https://download.csdn.net/download/m0_63288666/86506592?spm=1001.2014.3001.5503PS:很抱歉没有看消息的习惯(主要是关注的人略微多些,每次都99+,随后就不再去管了)
那个朋友被晾了3,4个月,说声抱歉哈。
运行实例
头文件 MYHEAD.h
包裹了依赖的库和全局定义的变量,全局的函数。
1.首先完成基本功能的搭建。
2.实现变色功能。
3.计分版功能。
4.插入音乐。
大概按这个顺序做的,但是实话实说也不是很严格。
#pragma once
#include
#include
#include
#include
#include
#pragma comment(lib,"winmm.lib")//引用Windows Multimedia
#pragma warning(disable:4996)
/*
开发日志:
1.首先完成基本功能的搭建。
2.实现变色功能。
3.计分版功能。
4.插入音乐。
*/
#define WIDTH 1200//UNIT*600
#define HIGH 660//UNIT*33
#define UNIT 20//这是每个蛇身矩形的单位长度
IMAGE img_bk;//设置背景图片
IMAGE img_frame, img_pause, img_manual, img_bkimg_rank;//边框图片,暂停图片,说明图片,排行榜背景
enum game//枚举设置空地,蛇身,蛇头,墙,食物所处的状态
{
SPACE, BODY, HEAD, WALL, FOOD//0,1,2,3,4
};
struct SNAKE//蛇身的结构体
{
int x;
int y;
};
struct SNAKE snake[1024];//[0][0]储存蛇头的位置
int snakelong;//蛇身的长度
struct _FOOD//食物
{
int color;
int x;
int y;
}food;
struct record//这是记录排行榜的数组
{
int score = 0;//分数
int hour;
int min;
int sec;
int year;
int mon;
int day;
}rec[10];
/*————————————————定义全局变量————————*/
int h_num;//纵横的边框个数,只在绘制边框时有用
int w_num;
int canvas[HIGH][WIDTH - 120];//设置画布,用来存储当前的状态
int state;//状态,用来标识选项:选择的是 1 OR 2 OR 3 OR 4的选项状态
int snakeDir;//方向,用来标识蛇的移动方向
int snakecolor;//蛇的颜色
int score;//得分
int sleeptime;//调节速度用
int mode;//模式选择//普通,无敌
/*———————————————————————————————————*/
/*———————————————定义函数—————————————*/
void startup();//初始化函数
void chose();//选择函数
int setColor(int a);//设置填充颜色
void menu();//菜单函数
void pauseMenu();//暂停游戏菜单
void show();//展示函数
void updateWithoutuser();//与用户输入无关的更新
void updateWithuser();//与用户输入有关的更新
void gameover();//游戏结束
void regame();//重开游戏
void addFood();//随机设置食物
void speed_mode_menu();//速度,模式选择菜单。四种速度,三种模式
int writerank();
int showrank();
void autofind();//自动寻路
/*——————————————————————————————————————*/
源文件 TCS.cpp
#include"MYHEAD.h" 使用双引号是在当前目录下先找,之后再到其它目录下找,我想这对于速度也许有一点的提升吧。实际的话尖括号也行。
#include"MYHEAD.h"
int main()
{
startup();//初始化函数
LABLE: menu();//菜单函数
chose();//选择函数
if (state == 1)//开始游戏
{
speed_mode_menu();//速度,模式选择菜单。四种速度,2种模式
while (1)//游戏循环主题
{
show();//展示函数
updateWithoutuser();//与用户输入无关的更新
updateWithuser();//与用户输入有关的更新
}
}
else if (state == 2)//游戏说明
{
cleardevice();
putimage(0, 0, &img_manual);//输出说明图片
settextstyle(80, 0, L"楷体");
outtextxy(5 * UNIT, 2 * UNIT, L"游戏说明");
settextstyle(30, 0, L"隶书");
outtextxy(5 * UNIT, 7 * UNIT, L"1.游戏有4种速度,2种模式。");
outtextxy(5 * UNIT, 9 * UNIT, L"2.速度决定难度和分数,模式分有“普通”和“无敌”,无敌模式下");
outtextxy(5 * UNIT, 11 * UNIT, L"不会死亡,且可以穿墙。");
outtextxy(5 * UNIT, 13 * UNIT, L"3.由“W”“S”“A”“D”分别控制上、下、左、右的方向。");
outtextxy(5 * UNIT, 15 * UNIT, L"4.空格键暂停游戏。");
outtextxy(5 * UNIT, 17 * UNIT, L"5.Esc键游戏退出,返回主页面。");
outtextxy(5 * UNIT, 19* UNIT, L"6.当蛇首撞墙或咬到蛇身时游戏结束,自动存档并返回主页面。");
outtextxy(5 * UNIT, 21 * UNIT, L"7.小蛇每吃到一个食物,分数就会增加。初始分数为零。");
outtextxy(5 * UNIT, 23 * UNIT, L"8.游戏目标是尽可能多的得分,打破记录!");
outtextxy(5 * UNIT, 25 * UNIT, L"按任意键返回……");
system("pause");
goto LABLE;
}
else if (state == 3)//排行榜
{
showrank();//排行榜显示
system("pause");
goto LABLE;
}
else if(state == 4)//游戏结束
{
gameover();
exit(1);
}
return 0;
}
void startup()//初始化函数
{
srand(GetTickCount());//随机数种子
mciSendString(L"open .\\background_music.mp3 alias bkmusic",NULL,0,NULL);
mciSendString(L"play bkmusic repeat", NULL, 0, NULL);
initgraph(WIDTH, HIGH);//创建画布
HWND hwnd = GetHWnd();//获取窗口句柄
SetWindowText(hwnd, L"我的贪吃蛇 v1.0");//设置窗口标题文字
loadimage(&img_bk, L".\\img_bk (2).png", WIDTH, HIGH);//加载背景图片
loadimage(&img_frame, L".\\walls.gif");//加载边框图片
loadimage(&img_pause, L".\\img_pause.JPG",WIDTH,HIGH);//加载暂停图片
loadimage(&img_manual, L".\\manual.png", WIDTH, HIGH);//加载说明图片
loadimage(&img_bkimg_rank, L".\\bkimg.JPG", WIDTH, HIGH);
h_num = HIGH / 60;//高WALL图片有几个
w_num = (WIDTH-120) / 60;//宽WALL图片有几个
//====================================分割线=====================================//
memset(canvas, SPACE, sizeof(canvas));//全部初始化为空地
for (int i = 0; i < 60; i++)//初始化墙壁所在的位置,四周 60 像素的边框
{
for (int j = 0; j < WIDTH-120; j++)
{
canvas[i][j] = canvas[HIGH - i - 1][j] = WALL;
}
}
for (int i = 0; i < HIGH; i++)//初始化墙壁所在的位置,四周 60 像素的边框
{
for (int j = 0; j < 60; j++)
{
canvas[i][j] = canvas[i][WIDTH-120 - j - 1] = WALL;
}
}
canvas[60 + UNIT*5][60 + UNIT*5] = HEAD;//初始化头
canvas[60 + UNIT * 5][60 + UNIT * 4] = canvas[60 + UNIT * 5][60 + UNIT * 3] = BODY;//初始化身体
addFood();//随机设置食物
state = 1;//菜单选择状态
snakelong = 3;//蛇长3
snake[0].x = 60 + UNIT * 5; snake[0].y = 60 + UNIT * 5;
snake[1].x = 60 + UNIT * 4; snake[1].y = 60 + UNIT * 5;
snake[2].x = 60 + UNIT * 3; snake[2].y = 60 + UNIT * 5;
snakeDir = 0x44;//D方向,虚拟键码,是默认的移动方向
snakecolor = 1;//蛇初始颜色 1 —— 蓝色
score = 0;
sleeptime = 100;
mode = 0;
}
void chose()//选择函数
{
while (1)
{
switch (_getch())
{
case '1':
menu();
outtextxy(WIDTH / 2 - 150, HIGH / 2 - 60, L"->");
state = 1;
break;
case '2':
menu();
outtextxy(WIDTH / 2 - 150, HIGH / 2 - 15, L"->");
state = 2;
break;
case '3':
menu();
outtextxy(WIDTH / 2 - 150, HIGH / 2 + 30, L"->");
state = 3;
break;
case '4':
menu();
outtextxy(WIDTH / 2 - 150, HIGH / 2 + 75, L"->");
state = 4;
break;
case VK_RETURN:
return;
}
}
}
void menu()//菜单函数
{
putimage(0, 0, &img_bk);//显示背景
setbkmode(TRANSPARENT);//设置背景格式为透明
settextcolor(BLACK);//字体颜色是黑色
//输出
settextstyle(80, 0, _T("隶书"));
outtextxy(WIDTH / 2 - 300, HIGH / 6, _T("贪吃蛇小游戏"));
settextstyle(30, 0, _T("隶书"),0,0,0,0,0,0);
outtextxy(WIDTH / 2 -120, HIGH / 2 - 60, _T("1:开始游戏"));
outtextxy(WIDTH / 2 - 120, HIGH / 2 -15, _T("2:游戏说明"));
outtextxy(WIDTH / 2 - 120, HIGH / 2 + 30, _T("3:排行榜"));
outtextxy(WIDTH / 2 - 120, HIGH / 2 + 75, _T("4:退出游戏"));
outtextxy(WIDTH / 2 - 300, HIGH / 2 + 125, L"数字键 1,2,3,4 选择,Enter键进入 “->”标识当前选中的位置");
}
void pauseMenu()//暂停游戏菜单
{
system("pause");
}
void show()//展示函数
{
BeginBatchDraw();
setbkcolor(RGB(130,130,130));//设置当前背景色为 黑
cleardevice();//用该背景色清空屏幕
settextstyle(35, 0, L"华文彩云",0,0,FW_LIGHT,0,0,0);
setcolor(YELLOW);
TCHAR sco[5],snlong[6];
_stprintf_s(sco, _T("%d"), score); // _stprintf_s 函数
_stprintf_s(snlong, _T("%d"), snakelong);
outtextxy(WIDTH - 100, 2 * UNIT, L"得分:");
outtextxy(WIDTH - 100, 4 * UNIT, sco);
outtextxy(WIDTH - 100, 6 * UNIT, L"蛇长:");
outtextxy(WIDTH - 100, 8 * UNIT, snlong);
int i = 0,j = 0;
for (i = 0,j = 0; i < h_num || j < w_num; i++,j++)//输出边框
{
putimage(0, i * 60, &img_frame);
putimage(WIDTH -120- 60, i * 60, &img_frame);
putimage(j * 60, 0, &img_frame);
putimage(j * 60, HIGH - 60, &img_frame);
}
setColor(rand()%7);//炫彩蛇头
fillrectangle(snake[0].x, snake[0].y, snake[0].x + UNIT, snake[0].y + UNIT);
for (i = 1; i < snakelong; i++)//输出蛇的矩形
{
setColor(snakecolor);
fillrectangle(snake[i].x, snake[i].y, snake[i].x + UNIT, snake[i].y + UNIT);
}
setColor(food.color);
fillrectangle(food.x, food.y, food.x + UNIT, food.y + UNIT);
FlushBatchDraw();
}
void updateWithoutuser()//与用户输入无关的更新
{
SNAKE temp{};//用来暂存蛇头的下一个位置
//autofind();
switch (snakeDir)
{
case 0x57://w
temp.x = snake[0].x;
temp.y = snake[0].y -UNIT;
break;
case 0x41://a
temp.x = snake[0].x - UNIT;
temp.y = snake[0].y;
break;
case 0x53://s
temp.x = snake[0].x;
temp.y = snake[0].y + UNIT;
break;
case 0x44://d
temp.x = snake[0].x + UNIT;
temp.y = snake[0].y;
break;
}
switch (canvas[temp.y][temp.x])
{
case SPACE://是空地,可以继续前进
canvas[snake[snakelong - 1].y][snake[snakelong - 1].x] = SPACE;//蛇尾消除
for (int i = snakelong - 1; i > 0; i--)//蛇整体做个移动
{
snake[i] = snake[i-1];
}
canvas[snake[0].y][snake[0].x] = BODY;//地图上老蛇头的位置赋成蛇身
snake[0] = temp;//重新赋值蛇头的坐标
canvas[snake[0].y][snake[0].x] = HEAD;//地图标识蛇头位置
break;
case FOOD://食物,增长身体,重新生成
mciSendString(L"close eatmusic", NULL, 0, NULL);
mciSendString(L"open .\\eat.mp3 alias eatmusic", NULL, 0, NULL);
mciSendString(L"play eatmusic", NULL, 0, NULL);
fillrectangle(food.x, food.y, food.x + UNIT, food.y + UNIT);
for (int i = snakelong; i > 0; i--)//蛇整体做个移动
{
snake[i] = snake[i - 1];
}
canvas[snake[0].y][snake[0].x] = BODY;//地图上老蛇头的位置赋成蛇身
snake[0] = temp;//重新赋值蛇头的坐标
canvas[snake[0].y][snake[0].x] = HEAD;//地图标识蛇头位置
snakelong++;//长度 + 1
snakecolor = setColor(food.color);//颜色更该为原来食物的颜色
addFood();//重新随机一个食物的颜色
if (sleeptime == 200)
score++;//得分加1
else if (sleeptime == 100)
score += 2;
else if (sleeptime == 60)
score += 3;
else if (sleeptime == 40)
score += 5;
break;
case BODY://身体,普通模式下死亡
if (mode)//是模式2:无敌
break;
writerank();
Sleep(800);
regame();
break;
case WALL://墙,普通模式下死亡
if (mode)//是模式2:无敌穿墙
{
canvas[snake[snakelong - 1].y][snake[snakelong - 1].x] = SPACE;//蛇尾消除
for (int i = snakelong - 1; i > 0; i--)//蛇整体做个移动
{
snake[i] = snake[i - 1];
}
canvas[snake[0].y][snake[0].x] = BODY;//地图上老蛇头的位置赋成蛇身
switch (snakeDir)
{
case 0x57://w
temp.y = HIGH -20 - snake[0].y;
break;
case 0x41://a
temp.x = WIDTH - 140 - snake[0].x;
break;
case 0x53://s
temp.y = HIGH - 20 - snake[0].y;
break;
case 0x44://d
temp.x = WIDTH - 140 - snake[0].x;
break;
}
snake[0] = temp;//重新赋值蛇头的坐标
canvas[snake[0].y][snake[0].x] = HEAD;//地图标识蛇头位置
break;
}
writerank();
Sleep(800);
regame();
break;
}
Sleep(sleeptime);
}
void updateWithuser()//与用户输入有关的更新
{
if (GetAsyncKeyState(0x41) && snakeDir != 0x44)//a
snakeDir = 0x41;
if (GetAsyncKeyState(0x44) && snakeDir != 0x41)//d
snakeDir = 0x44;
if (GetAsyncKeyState(0x57) && snakeDir != 0x53)//w
snakeDir = 0x57;
if (GetAsyncKeyState(0x53) && snakeDir != 0x57)//s
snakeDir = 0x53;
if (GetAsyncKeyState(VK_SPACE))//空格建暂停
pauseMenu();
if (GetAsyncKeyState(VK_ESCAPE))//esc键退出游戏返回主菜单
regame();
}
void gameover()//游戏结束
{
EndBatchDraw();
closegraph();
}
void addFood()//随机设置食物
{
int x = 0, y = 0;
do
{
x = (rand() % (int)((WIDTH-120 - 120) / UNIT)) * UNIT + 60;
y = (rand() % (int)((HIGH - 120) / UNIT)) * UNIT + 60;
} while (canvas[y][x] != SPACE);//随机的位置只能是空地
canvas[y][x] = FOOD;//位置是食物
food.x = x;
food.y = y;
food.color = rand() % 3;
}
int setColor(int a)//设置颜色
{
switch (a)
{
case 0:
setfillcolor(RGB(255, 0, 0)); //红色
break;
case 1:
setfillcolor(RGB(0, 0, 255)); //蓝色
break;
case 2:
setfillcolor(RGB(0, 255, 0)); //绿色
break;
case 3:
setfillcolor(RGB(255, 165, 0)); //橙色
break;
case 4:
setfillcolor(RGB(255, 255, 0)); //黄色
break;
case 5:
setfillcolor(RGB(0, 255, 255)); //青色
break;
case 6:
setfillcolor(RGB(160, 32, 240)); //紫色
break;
}
return a;
}
void speed_mode_menu()//速度,模式选择菜单。四种速度,三种模式
{
T1:cleardevice();
wchar_t wspeed[5];
InputBox(wspeed, 5, L"+请输入速度值+(输入数字1,2,3,4)\n\n1-超简单速度(一个食物得一分)\n2-普通速度(一个食物得两分)\n3-困难速度(一个食物三分)\n4-地狱速度(一个食物五分)\n\n★默认为普通速度★",L"这是一个选择速度等级的框框q(≧▽≦q)",L"输入吧( ̄︶ ̄)↗");
int speed = _wtoi(wspeed);
if (speed == 1)//超简单速度
sleeptime = 200;
else if (speed == 2)//普通模式
sleeptime = 100;
else if (speed == 3)//困难模式
sleeptime = 60;
else if (speed == 4)//地狱模式
sleeptime = 40;
else
goto T1;
T2:wchar_t tmode[2];
InputBox(tmode, 2, L"+请输入1,2选择模式+\n\n1-普通模式\n2-无敌模式\n\n\n★默认为普通模式★", L"这是一个选择游戏模式的框框q(≧▽≦q)", L"输入吧( ̄︶ ̄)↗");
int _mode = _wtoi(tmode);
mode = _mode -1;//设置模式
if (mode != 0 && mode != 1)
goto T2;
}
int writerank()//输入存储排名
{
while (_kbhit())//清空键盘缓冲区
{
_getch();
}
time_t timep;
struct tm* ti;
time(&timep);
ti = localtime(&timep);
record* temp = (record*)malloc(sizeof(record)*100);//申请一个空间
int i = 0, n = 0;
FILE* fp = NULL;
errno_t err = fopen_s(&fp, ".\\rank.txt", "ab");//保存成绩用追加读写
if (err)
{
printf("cannot open the file\n");
system("pause");
return -1;//如果文件出现错误返回-1
}
temp->score = score;//分数
temp->year = ti->tm_year+1900;//年
temp->mon = ti->tm_mon+1; //月
temp->day = ti->tm_mday; //日
temp->hour = ti->tm_hour;//时
temp->min = ti->tm_min; //分
temp->sec = ti->tm_sec; //秒
fwrite(temp, sizeof(record), 1, fp);
fclose(fp);
free(temp);
return 0;
}
int showrank()//展示排名
{
record _temp;
int j = 0, i = 0, k = 0;
FILE* fp = NULL;
errno_t err = fopen_s(&fp, ".\\rank.txt", "rb");
if (err)
{
printf("cannot open the file\n");
system("pause");
return -1;
}
if (fp == NULL)//这个貌似没有什么用处
{
printf_s("暂无数据!");
//return 0;
}
rewind(fp);
while (!feof(fp))
{
fread(&rec[i], sizeof(record), 1, fp);
i++;
}
for (int j = 0; j < i; j++)//排序用
{
for (int k = 0; k < i; k++)
{
if (rec[k].score < rec[j].score)
{
_temp = rec[k];
rec[k] = rec[j];
rec[j] = _temp;
}
}
}
if (i >= 10)
i = 10;
cleardevice();
putimage(0, 0, &img_bkimg_rank);
settextcolor(BLACK);
settextstyle(4 * UNIT, 0, L"华文新魏");
outtextxy(WIDTH / 2 - 7*UNIT, 0, _T("排行榜"));
settextstyle(3 * UNIT, 0, L"华文楷体");
outtextxy(7 * UNIT, 4 * UNIT, L"得 分");
outtextxy(20 * UNIT, 4*UNIT, L"年 月 日 时 分 秒");
settextstyle( UNIT, 0, L"方正姚体");
for (int j = 0; j < i ; j++)
{
TCHAR s1[6];
_stprintf_s(s1,_T("%d"), rec[j].score);//分数
outtextxy(10 * UNIT, j * (UNIT * 2) + 9 * UNIT, s1);
TCHAR s2[6];
_stprintf_s(s2, _T("%d"), rec[j].year);//年
outtextxy(20 * UNIT, j * (UNIT * 2) + 9 * UNIT, s2);
TCHAR s3[6];
_stprintf_s(s3, _T("%d"), rec[j].mon);//月
outtextxy(25 * UNIT, j * (UNIT * 2) + 9 * UNIT, s3);
TCHAR s4[6];
_stprintf_s(s4, _T("%d"), rec[j].day);//日
outtextxy(30 * UNIT, j * (UNIT * 2) + 9 * UNIT, s4);
TCHAR s5[6];
_stprintf_s(s5, _T("%d"), rec[j].hour);//时
outtextxy(40 * UNIT, j * (UNIT * 2) + 9 * UNIT, s5);
TCHAR s6[6];
_stprintf_s(s6, _T("%d"), rec[j].min);//分
outtextxy(45 * UNIT, j * (UNIT * 2) + 9 * UNIT, s6);
TCHAR s7[6];
_stprintf_s(s7, _T("%d"), rec[j].sec);//秒
outtextxy(50 * UNIT, j * (UNIT * 2) + 9 * UNIT, s7);
}
outtextxy(5 * UNIT, 30 * UNIT, L"按任意键返回……");
fclose(fp);
return 0;
}
void regame()
{
clearrectangle(0,0,WIDTH,HIGH);
gameover();
main();
}
void autofind()
{
snakeDir;
int chax = snake[0].x - food.x;//蛇头与食物的坐标差
int chay = snake[0].y - food.y;
if (chax > 0 && snakeDir != 0x44)
{
snakeDir = 0x41;
}
if (chax < 0 && snakeDir != 0x41)
{
snakeDir = 0x44;
}
if (chay > 0 && snakeDir != 0x53)
{
snakeDir = 0x57;
}
if (chay < 0 && snakeDir != 0x57)
{
snakeDir = 0x53;
}
if (chay == 0 && chax > 0 && snakeDir == 0x44)
{
snakeDir = 0x53;
}
if (chay == 0 && chax < 0 && snakeDir == 0x41)
{
snakeDir = 0x53;
}
if (chax == 0 && chay > 0 && snakeDir == 0x53)
{
snakeDir = 0x41;
}
if (chay == 0 && chax < 0 && snakeDir == 0x57)
{
snakeDir = 0x41;
}
}