目录
游戏效果展示
QQ录屏20240507163633
文件
代码的展示
test.c
c
#define _CRT_SECURE_NO_WARNINGS
#include<locale.h>
//设置本地化
#include"Snake.h"
//游戏的测试逻辑
void test()
{
int ch = 0;
do
{
system("cls");
//每次上来清理一下屏幕的信息
//创建贪吃蛇
snake snake = {0};
//初始化游戏
//1.打印环境界面
//2.功能介绍
//3.绘制地图
//4. 创建蛇
//5.创建食物
//6.设置游戏的相关信息
//开始游戏
GameStart(&snake);
//运行游戏
GameRun(&snake);
//结束游戏
GameEnd(&snake);
SetPos(20,15);
printf("再来一局吗(Y/N):");
ch = getchar();
while (getchar() != '\n');
//防止输入太多的字符而出现的bug
} while (ch == 'Y' || ch == 'y');
SetPos(0, 27);
}
int main()
{
//设置本地化环境
setlocale(LC_ALL,"");
//生成随机数
srand((unsigned int)time(NULL));
//测试的逻辑
test();
return 0;
}
Snake.c
c
#define _CRT_SECURE_NO_WARNINGS
#include"Snake.h"
//坐标的定位
//定位光标的位置
void SetPos(short x, short y)
{
//获取标准输出设备的句柄
HANDLE houtput = NULL;
houtput = GetStdHandle(STD_OUTPUT_HANDLE);
//定位光标的位置
COORD pos = { x, y };
SetConsoleCursorPosition(houtput, pos);
}
//欢迎界面的打印
void WelComeToGame()
{
SetPos(40, 14);
wprintf(L"欢迎来到贪吃蛇小游戏\n");
SetPos(42, 20);
system("pause");//暂停
system("cls");//清理屏幕信息
SetPos(25, 14);
wprintf(L"↑ . ↓ . ← . → .来控制蛇的移动,按F3加速,F4减速\n");
SetPos(25, 15);
wprintf(L"加速能够获得更高的分数\n");
SetPos(42, 20);
system("pause");
system("cls");
}
//创建地图
void CreatMap()
{
//上
int i = 0;
for (i = 0; i < 29; i++)
{
wprintf(L"%lc",WALL);
}
//下
SetPos(0, 26);
for (int i = 0; i < 29; i++)
{
wprintf(L"%lc", WALL);
}
//左
for (int i = 1; i <= 25; i++)
{
SetPos(0, i);
wprintf(L"%lc", WALL);
}
//右
for (int i = 1; i <= 25; i++)
{
SetPos(56, i);
wprintf(L"%lc", WALL);
}
}
//初始化蛇身
void InitSnake(psnake ps)
{
int i = 0;
psnakenode cur = NULL;
for (i = 0; i < 5; i++)
{
cur = (psnakenode)malloc(sizeof(snakenode));
if (cur == NULL)
{
perror("InitSnake:malloc");
return;
}
//开辟新节点成功
cur->next = NULL;
cur->x = POS_X + 2*i;
cur->y = POS_Y;
//头插法插入节点
if (ps->_phead == NULL)//空链表
{
ps->_phead = cur;
}
else//非空链表
{
cur->next = ps->_phead;
ps->_phead = cur;
}
}
//打印蛇身
cur = ps->_phead;
while (cur)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
//设置蛇的属性
ps->_dir = RIGHT;//默认蛇的方向是向右的
ps->_food_weight = 10;//每个食物的分数
ps->_score = 0;//开始总分为0
ps->_status = OK;//蛇的状态是正常的
ps->_time_sleep = 200;//睡眠时间为200ms
}
//创建食物
void CreatFood(psnake ps)
{
int x = 0;
int y = 0;
//生成的x是2的倍数
// x - 2~54
// y - 1~25
again:
do
{
//创建的食物的节点不能和蛇身重叠
x = rand() % 53 + 2;
y = rand() % 25 + 1;
} while (x % 2 != 0);
psnakenode cur = ps->_phead;
while (cur)
{
if (cur->x == x && cur->y == y)
{
goto again;
}
cur = cur->next;
}
//创建食物的节点
psnakenode pfood = (psnakenode)malloc(sizeof(snakenode));
if (pfood == NULL)
{
//开辟失败
perror("CreatFood::malloc");
return;
}
//开辟成功
pfood->x = x;
pfood->y = y;
pfood->next = NULL;
SetPos(x, y);
//定位坐标,打印节点
wprintf(L"%lc", FOOD);
ps->_food = pfood;
//食物的信息记录到food(贪吃蛇)里面去
}
//游戏的初始化
void GameStart(psnake ps)
{
//1.先设置窗口的大小,再隐藏光标
system("mode con cols=100 lines=30");
system("title 贪吃蛇");
//设置窗口的名称
HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
//得到屏幕的句柄
//隐藏光标操作
CONSOLE_CURSOR_INFO cursoninfo;
//控制光标信息的结构体
GetConsoleCursorInfo(houtput, &cursoninfo);
//得到控制台的光标信息
cursoninfo.bVisible = false;
//将光标设置为不可见(隐藏控制台光标)
SetConsoleCursorInfo(houtput, &cursoninfo);
//设置控制台光标的状态(光标为隐藏)
//2.打印环境界面和功能介绍
WelComeToGame();
//3.创建地图
CreatMap();
//4.创建蛇
InitSnake(ps);
//5.创建食物
CreatFood(ps);
}
//打印帮助信息
void PrintHelpInfo()
{
SetPos(64, 15);
wprintf(L"%ls",L"不能穿墙,不能咬到自己");
SetPos(64, 16);
wprintf(L"%ls", L"↑ . ↓ . ← . → .来控制蛇的移动");
SetPos(64, 17);
wprintf(L"%ls", L"按F3加速,F4减速");
SetPos(64, 18);
wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏");
SetPos(64, 19);
wprintf(L"%ls", L"英雄不问出处制作");
}
#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1) ? 1 : 0)
//获取按键的情况,按了是真,没按是假
//?在:之前
//按了空格键的情况
void Pause()
{
while (1)
{
Sleep(200);
//睡眠200ms
if (KEY_PRESS(VK_SPACE))
{
break;
}
}
}
//判断下一个坐标是否是食物
//返回值判断真假,是否有食物
int NextIsFood(psnakenode pn, psnake ps)
{
return (pn->x == ps->_food->x && pn->y == ps->_food->y);
//如果下一个节点是食物就吃掉食物
}
//下一个坐标是食物就吃掉食物
void EatFood(psnakenode pn, psnake ps)
{
//把食物的节点头插
ps->_food->next = ps->_phead;
ps->_phead = ps->_food;
//释放下一个节点的地址,因为下一个节点和食物的节点冲突了
free(pn);
pn = NULL;
//把蛇打印出来
psnakenode cur = ps->_phead;
while (cur)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
//吃掉一个食物要加分
ps->_score += ps->_food_weight;
//一个食物吃掉后重新创建一个食物
CreatFood(ps);
}
//下一个坐标不是食物
void NoFood(psnakenode pn, psnake ps)
{
//头插法
pn->next = ps->_phead;
ps->_phead = pn;
psnakenode cur = ps->_phead;
while (cur->next->next!=NULL)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
//把最后一个节点打印成空
SetPos(cur->next->x, cur->next->y);
printf(" ");
//释放最后一个节点
free(cur->next);
//把倒数第二个节点的下一个节点的地址置为NULL
cur->next = NULL;
}
//检测蛇是否撞墙
void KillByWall(psnake ps)
{
if (ps->_phead->x == 0 || ps->_phead->y == 0 ||
ps->_phead->x == 56 || ps->_phead->y == 26)
{
ps->_status = KILL_BY_WALL;
}
}
//检测蛇是否撞到自己
void KillByMyself(psnake ps)
{
psnakenode cur = ps->_phead->next;
//蛇头的下一个节点和蛇头的节点的坐标是否相同
while (cur)
{
if (cur->x == ps->_phead->x && cur->y == ps->_phead->y)
{
ps->_status = KILL_BY_MYSELF;
break;
//撞到了就跳出循环
}
cur = cur->next;
}
}
//蛇的移动 -- 走一步
//蛇的移动控制着蛇的主逻辑
void SnakeMove(psnake ps)
{
//创建一个节点,表示蛇即将到的下一个节点
psnakenode pNextnode = (psnakenode)malloc(sizeof(snakenode));
if (pNextnode == NULL)
{
perror("SnakeMove::malloc");
return;
}
//创建节点成功
//蛇的方向
switch (ps->_dir)
{
case UP:
pNextnode->x = ps->_phead->x;
pNextnode->y = ps->_phead->y - 1;
break;
case DOWN:
pNextnode->x = ps->_phead->x;
pNextnode->y = ps->_phead->y + 1;
break;
case LEFT:
pNextnode->x = ps->_phead->x - 2;
pNextnode->y = ps->_phead->y;
break;
case RIGHT:
pNextnode->x = ps->_phead->x + 2;
pNextnode->y = ps->_phead->y;
break;
}
//检查下一个节点是否是食物
if (NextIsFood(pNextnode, ps))
{
EatFood(pNextnode, ps);
}
else
{
NoFood(pNextnode, ps);
}
//判断蛇是否撞墙
KillByWall(ps);
//判断蛇是否撞到自己
KillByMyself(ps);
}
//游戏运行的逻辑
void GameRun(psnake ps)
{
//打印帮助信息
PrintHelpInfo();
do
{
//打印总分数和食物的分数
SetPos(64, 10);
printf("总分数是:%d\n", ps->_score);
SetPos(64, 11);
printf("当前食物的分数是:%2d\n", ps->_food_weight);
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_SPACE))
{
Pause();
}
else if (KEY_PRESS(VK_ESCAPE))
{
//正常退出游戏
ps->_status = EXIT;
}
else if (KEY_PRESS(VK_F3))
{
//加速
if (ps->_time_sleep > 80)
{
ps->_time_sleep -= 30;
ps->_food_weight += 2;
}
}
else if (KEY_PRESS(VK_F4))
{
//减速
if (ps->_food_weight > 2)
{
ps->_time_sleep += 30;
ps->_food_weight -= 2;
}
}
//蛇走一步的过程
SnakeMove(ps);
Sleep(ps->_time_sleep);
//蛇走完一步再让蛇睡眠一下,可以达到蛇动态的移动过程
} while (ps->_status == OK);
}
//游戏善后的工作
//游戏的状态是因为什么而结束的
void GameEnd(psnake ps)
{
SetPos(24, 12);
//定位到屏幕的正中央然后打印
switch (ps->_status)
{
case KILL_BY_MYSELF:
wprintf(L"您撞到了自己,游戏结束\n");
break;
case KILL_BY_WALL:
wprintf(L"您撞到墙上,游戏结束\n");
break;
case EXIT:
wprintf(L"您主动结束游戏\n");
break;
}
//释放掉蛇身的节点
psnakenode cur = ps->_phead;
while (cur)
{
psnakenode Del = cur;
cur = cur->next;
free(Del);
}
}
Snake.h
c
#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#include<time.h>
#include<stdbool.h>
//#define 后面不用打括号
//定位,Y是X的两倍
#define POS_X 24
#define POS_Y 5
#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
//类型的声明
//蛇的方向
enum DIRECTION
{
UP = 1,
DOWN,
LEFT,
RIGHT
};
//蛇的状态
//撞到自己,撞到墙,正常运行,正常退出
enum STATUS
{
OK,//正常运行
EXIT,//正常退出
KILL_BY_WALL,//撞到墙
KILL_BY_MYSELF//撞到自己
};
//蛇身的节点类型
typedef struct SnakeNode
{
int x;
int y;
//坐标
struct SnakeNode* next;
//指向下一个节点的指针
}snakenode,* psnakenode;
//typedef struct SnakeNode* pSnake; == 上面的写法
//贪吃蛇
typedef struct SnakeList
{
psnakenode _phead;//指向蛇头节点的指针
psnakenode _food;//指向食物节点的指针
enum DIRECTION _dir;//蛇的方向
enum STATUS _status;//蛇的状态
int _food_weight;//一个食物的分数
int _score;//总分数
int _time_sleep;//休息的时间,时间越短,速度越快,时间越长,速度越慢
}snake,* psnake;
//函数的声明
//坐标的定位
//定位光标的位置
void SetPos(short x, short y);
//游戏的初始化
void GameStart(psnake ps);
//欢迎界面的打印
void WelComeToGame();
//创建地图
void CreatMap();
//初始化蛇身
void InitSnake(psnake ps);
//创建食物
void CreatFood(psnake ps);
//游戏运行的逻辑
void GameRun(psnake ps);
//蛇的移动 -- 走一步
void SnakeMove(psnake ps);
//判断下一个坐标是否是食物
//pn是下一个节点的指针
int NextIsFood(psnakenode pn, psnake ps);
//下一个坐标是食物就吃掉食物
void EatFood(psnakenode pn, psnake ps);
//下一个坐标不是食物
void NoFood(psnakenode pn, psnake ps);
//检测蛇是否撞墙
void KillByWall(psnake ps);
//检测蛇是否撞到自己
void KillByMyself(psnake ps);
//游戏善后的工作
void GameEnd(psnake ps);
下一个坐标不是食物
打印前四个节点,打印完第四个节点退出循环,把cur下一个节点打印成空,也就是最后一个节点打印成空释放最后一个节点,把倒数第二个节点的下一个节点的地址置为NULL,倒数第二个节点不用打印,之前打印了前5个节点,节点不会消失,所以倒数第二个节点存在