这个贪吃蛇是在比特特训营里学到的,同时我还写了用EasyX图形库实现的图形化贪吃蛇,含有每个函数的实现以及游戏中各种细节的讲解,感兴趣的可以去看一看。
贪吃蛇小游戏
实现效果
以下就是源码,感兴趣的小伙伴可以cv自己玩一玩改造改造,每个函数都有相应功能细节的注释,有用的话欢迎大家点赞
snake.h
#pragma once
#include <locale.h>
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>
#define wall L'¤'
#define body L'◎'
#define food L'★'
#define POS_X 24
#define POS_Y 5
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) &0x1)?1:0)//今天继续写,写完删除该行注释
enum DIRECTION//四个方向
{
UP = 1,
DOWN,
LEFT,
RIGHT
};
enum GAME_STATUS//游戏状态:正常,退出,撞墙,吃到自己
{
OK=1,
END_NOMAL,//正常退出
KILL_BY_WALL,
KILL_BY_SELF
};
typedef struct SnakeNode
{
int x;
int y;
struct SnakeNode* next;
}SnakeNode,* pSnakeNode;
//相当于
//typedef struct SnakeNode* pSnakeNode;//结构体指针的重命名
//描述蛇的结构体
typedef struct snake
{
pSnakeNode _psnake;//指向贪吃蛇头结点的指针。
pSnakeNode _fFood;//假设食物也是蛇节点的指针,吃掉时改变其状态即可。
int _Score;//分数,到时候要打印
int _Foodweight;
int SleepTime;//每走一步休息的时间,时间越短,速度越快
enum DIRECTION _Dir;//方向,用枚举常量给出
enum GAME_STATUS _status;
}Snake,*psnake;
//游戏开始
void GameStart(psnake);
//欢迎界面
WecomeGame();//打印游戏界面
//创建地图
void CreatMap();
void InitSnake(psnake ps);
void CreateFood(psnake ps);
//游戏的正常运行
void GameRun(psnake ps);
//打印帮助信息
void SetPos(short x, short y);
int KillBySelf(psnake ps);
void GameOver(psnake ps);
===============================
snake.c
#define _CRT_SECURE_NO_WARNINGS
#include "snake.h"
//设置光标位置
void SetPos(short x, short y)
{
COORD pos = { x,y };
HANDLE hOutput = NULL;
//获取标准输出的句柄
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为pos
SetConsoleCursorPosition(hOutput, pos);
}
WecomeGame()//打印游戏界面
{
//定位光标,打印欢迎语句
SetPos(40,15);
printf("欢迎来到贪吃蛇游戏");
SetPos(37, 27);
//printf("按任意键继续");
system("pause");//暂停程序,库函数的暂停命令
//清空屏幕
system("cls");
SetPos(20, 15);
printf("上下左右为↑↓←→,F3为加速,F4为减速 ");
system("pause");//暂停程序,库函数的暂停命令
system("cls");
CreatMap();
}
void CreatMap()
{
int i = 0;
//通过创建的终端大小打印地图
SetPos(0, 0);
for (i = 0; i <= 56; i+=2)
{
wprintf(L"%c",wall);
}
SetPos(0, 26);
for (i = 0; i <= 56; i += 2)
{
wprintf(L"%c", wall);
}
for (i = 1; i <= 25; i++)
{
SetPos(0, i);
wprintf(L"%c", wall);
}
for (i = 1; i <= 25; i++)
{
SetPos(56, i);
wprintf(L"%c", wall);
}
}
void GameStart(psnake ps)
{
//初始化控制台窗体及初始化信息
system("mode con cols=100 lines=30");//长宽
system("title 贪吃蛇");
//光标隐藏掉
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//得到控制台光标信息
CursorInfo.bVisible = false;//将状态设置为fasle,隐藏
SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态
//打印欢迎界面,提示
WecomeGame();//打印游戏界面
//初始化贪吃蛇
InitSnake(ps);
//创建食物
CreateFood(ps);
}
void CreateFood(psnake ps)//创建食物
{
//坐标范围内随机生成,且不可以生成在蛇身上。
int x = 0;
int y = 0;
again:
do
{
x = rand() % 53 + 2;
y = rand() % 25 + 1;
} while (x % 2 != 0);//横坐标为2的倍数,因为宽字符的原因
//坐标不可以和蛇的节点坐标冲突
pSnakeNode cur = ps->_psnake;
while (cur)
{
//比较坐标
if (cur->x == x && cur->y == y)
{
goto again;//也可以利用循环解决
}
cur = cur->next;
}
pSnakeNode pfood = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pfood == NULL)
{
perror("malloc fail");
return;
}
pfood->x = x;
pfood->y = y;
ps->_fFood = pfood;
//打印食物
SetPos(x, y);
wprintf(L"%lc", food);
//getchar();随时阻塞,判断效果
}
void InitSnake(psnake ps)//初始化蛇
{
int i = 0;
for (i = 0; i < 5; i++)
{
pSnakeNode snk = (pSnakeNode)malloc(sizeof(SnakeNode));
if (snk == NULL)//问题检查
{
perror("malloc fail");
return;
}
snk->x = POS_X + 2*i;//节点位置不同
snk->y = POS_Y;
snk->next = NULL;
if (ps->_psnake == NULL)
{
ps->_psnake = snk;
}
else//此时_psnake修饰的就是蛇节点的头结点
{
snk->next = ps->_psnake;
ps->_psnake = snk;
}
}
//打印蛇的身体
pSnakeNode cur = ps->_psnake;
while (cur)
{
SetPos(cur->x, cur->y);
wprintf(L"% c", body);
cur = cur->next;
}
//蛇的相关数据
ps->_status = OK;
ps->_Score = 0;
ps->_Foodweight = 10;
ps->_fFood = NULL;
ps->SleepTime = 200;//休眠时间关乎蛇移动的速度
ps->_Dir = RIGHT;
}
void PrintInform()
{
SetPos(60, 15);
printf("1:不能穿墙,不能咬到自己");
SetPos(60, 17);
printf("2:上下左右为↑↓←→,F3为加速,F4为减速");
SetPos(60, 19);
printf("3:F3加速,F4减速,Esc退出,空格暂停");
//getchar();
}
void Pause()//暂停游戏或者继续游戏
{
while (1)
{
Sleep(100);
if (KEY_PRESS(VK_SPACE))
{
break;
}
}
}
//判断是否吃掉食物
int NextIsFood(psnake ps,pSnakeNode pnext)
{
if (ps->_fFood->x == pnext->x && ps->_fFood->y == pnext->y)
{
return 1;
}
else
{
return 0;
}
}
void EatFood(psnake ps, pSnakeNode pnext)//吃掉
{
pnext->next = ps->_psnake;
ps->_psnake = pnext;//把蛇头换一换
//打印蛇
pSnakeNode cur = ps->_psnake;
while (cur)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", body);
cur = cur->next;
}
//清理食物节点(食物节点是malloc出来的,所以要清理,不然会造成内存浪费),加分
free(ps->_fFood);
ps->_Score += ps->_Foodweight;
//继续创建食物
CreateFood(ps);
}
void NotEatFood(psnake ps, pSnakeNode pnext)//没有吃掉
{
//如果不是食物,生成新的节点,删掉最后一个节点
//头插
pnext->next = ps->_psnake;
ps->_psnake = pnext;//把蛇头换一换
//打印社蛇的身体
pSnakeNode cur = ps->_psnake;
while (cur->next->next)//生成了新的头节点,只打印五个
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", body);
cur = cur->next;
}
//清理最后一节点
SetPos(cur->next->x, cur->next->y);
printf(" ");
free(cur->next);
cur ->next = NULL;
}
//蛇是否撞墙
int KillByWall(psnake ps)
{
if (ps->_psnake->x == 0 || ps->_psnake->x == 56 || ps->_psnake->y == 0 || ps->_psnake->y == 26)
{
ps->_status = KILL_BY_WALL;
return 1;
}
return 0;
}
//是否吃到自己
int KillBySelf(psnake ps)
{
pSnakeNode cur = ps->_psnake->next;
while (cur)
{
if (ps->_psnake->x == cur->x && ps->_psnake->y == cur->y)
{
ps->_status = KILL_BY_SELF;//改变状态
return 1;
}
cur = cur->next;
}
return 0;
}
//值得学习的地方,修改整个数组
void SnakeMove(psnake ps)
{
pSnakeNode pnext = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pnext == NULL)
{
perror("malloc fail");
return;
}
//pnext->next = NULL;
switch (ps->_Dir)
{
case UP:
//根据蛇头计算更新之后的坐标
pnext->x = ps->_psnake->x;
pnext->y = ps->_psnake->y-1;
break;
case DOWN:
pnext->x = ps->_psnake->x;
pnext->y = ps->_psnake->y + 1;
break;
case RIGHT:
pnext->x = ps->_psnake->x+2;//减2,因为宽度为2.
pnext->y = ps->_psnake->y;
break;
case LEFT:
pnext->x = ps->_psnake->x-2;
pnext->y = ps->_psnake->y;
break;
}
//判断是否吃掉食物,如果吃掉食物,最后一个节点不清理,如果没有吃掉食物,就将其清理、
//蛇头坐标和食物坐标
if (NextIsFood(ps, pnext))
{
//吃掉食物
EatFood(ps,pnext);
}
else
{
//没吃食物
NotEatFood(ps,pnext);
}
//蛇是否撞墙
KillByWall(ps);
//是否吃到自己
KillBySelf(ps);
}
void GameRun(psnake ps)
{
PrintInform();
do
{
SetPos(64, 10);
printf("得分:%0.5d", ps->_Score);
SetPos(64, 12);
printf("每个食物10分");
if(KEY_PRESS(VK_UP)&&ps->_Dir!=DOWN)
{
ps->_Dir = UP;
}
else if (KEY_PRESS(VK_DOWN) && ps->_Dir != UP)
{
ps->_Dir = DOWN;
}
else if (KEY_PRESS(VK_LEFT) && ps->_Dir != RIGHT)
{
ps->_Dir = LEFT;
}
else if (KEY_PRESS(VK_RIGHT) && ps->_Dir != LEFT)
{
ps->_Dir = RIGHT;
}
else if(KEY_PRESS(VK_ESCAPE))
{
ps->_status = END_NOMAL;//正常退出
break;
}
else if (KEY_PRESS(VK_SPACE))
{
//暂停函数
Pause();//封装一个函数,按下一次暂停,再按就继续
}
else if (KEY_PRESS(VK_F3))//加速
{
//ps->SleepTime = 100;//如果一直按着,那就一直二倍加速
//ps->_Foodweight = 15;
if (ps->SleepTime >= 80)//也可以逐渐加速
{
ps->SleepTime -= 30;
ps->_Foodweight += 2;
}
}
else if (KEY_PRESS(VK_F4))//减速
{
if (ps->SleepTime <= 300)//逐渐减速
{
ps->SleepTime += 30;
ps->_Foodweight -= 2;
}
}
Sleep(ps->SleepTime);
//蛇的移动
SnakeMove(ps);//继续封装成函数
} while (ps->_status==OK);
}
//善后处理,打印分数,清理贪吃蛇
void GameOver(psnake ps)
{
SetPos(20, 12);
switch (ps->_status)
{
case END_NOMAL:
printf("您主动退出游戏");
break;
case KILL_BY_SELF:
printf("自杀成功");
break;
case KILL_BY_WALL:
printf("撞墙啦");
break;
}
SetPos(0, 27);
//释放蛇的节点
pSnakeNode cur = ps->_psnake;
while (cur)//循环全部释放
{
pSnakeNode Next = cur->next;
free(cur);
cur = Next;
}
ps->_psnake = NULL;
}
=====================================
test.c
#define _CRT_SECURE_NO_WARNINGS
#include "snake.h"
//Win32,API,Windows 32位接口。
//宽字符类型,一个汉字占用两个字符。
// 一个字母宽一个字符,一个汉字占两个字符
//wchar_t宽字符类型□☆★¤◎㊣
//setlocale(LC_ALL,"");//适应中文环境
//宽字符的打印,前缀加上L
//int main()
//{
// SetPos(10, 10);
// setlocale(LC_ALL, "");
// wchar_t ch1 = L'●';
// wprintf(L"%lc\n", ch1);//打印时printf前边加w,打印时前边大写L,类型为lc=
// return 0;
//}
void test()
{
char ch = 0;
do
{
Snake snake = { 0 };//创建贪吃蛇
//1,游戏开始——初始化游戏
GameStart(&snake);
//getchar();//设置光标状态是否成功可以检查一下,用getchar阻塞程序运行
//2,游戏运行——正常运行
GameRun(&snake);
//3,游戏结束——如何结束,释放资源
GameOver(&snake);
SetPos(20, 15);
printf("是否想再来一把?(Y/N):");
ch = getchar();
getchar();//清理‘/n’。
} while (ch == 'Y' || ch == 'y');
}
int main()
{
srand((unsigned int)time(NULL));
//设置程序适应本地化
setlocale(LC_ALL, "");
test();
return 0;
}
//地图,长为宽的2倍