文章目录
1.1.贪吃蛇的属性
本程序使用单链表连接组成贪吃蛇身,所以,在创建贪吃蛇属性时也要创建单个贪吃蛇节点,以及蛇的当前状态、前进方向等等,如下:
enum DIRECTION
{
UP = 1,
DOWN,
LEFT,
RIGHT
};//蛇当前方向
enum Game_Status{
OK,//正常运行
END_NORMAL,//按ESC退出
KILL_BY_WALL,
KILL_BY_SELF
};//蛇的状态,为OK时正常运行,其他情况结束游戏
typedef strct SnakeNode{
int x;
int y;
//x、y为节点当前所处的坐标位置
struct SnakeNode*next;
}SnakeNode,*pSnakeNode;//贪吃蛇节点
struct Snake{
struct SnakeNode*_pSnake;//蛇头节点
struct SnakeNode*_pFood;//食物节点
int _Score;//贪吃蛇累计得分
int _FoodWeight;//一个食物的分数
int _SleepTime;//每走一步的休息时间
enum DIRECTION _Dir;//描述蛇的方向
enum Status _Status;//游戏的状态:正常、退出、撞墙、吃到自己
}Snake,*pSnake;//贪吃蛇整体属性
1.2.窗口界面以及贪吃蛇身、食物的初始化和打印(了解win32 API有关服务的使用,调用这些服务需要包含“<windows.h>”头文件)
做以上事情需要调用win32 API的一些服务(相当于调用函数),先了解一些服务的使用:
1.2.1.获取终端(控制台窗口)的句柄
首先要想在窗口中随意构图,便要先获取终端设备的句柄(⽤来标识不同设备的数值,使⽤它可以操作设备)。调用GetStdHandle.
1.2.2.获取光标信息
光标结构体类型定义为CONSOLE_CURSOR_INFO,如下:
typedef struct _CONSOLE_CURSOR_INFO
{
DWORD dwSize;//光标填充的字符单元格的百分⽐,从1到100
BOOL bVisible;//光标可视度,非0为可视,0为不可视
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO
然后调用服务GetConsloeCursorInfo获取光标信息,在调用服务SetConsoleCursorInfo设置修改光标信息,如:
HANDLE handle=GetStdConsloe(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cursor_info;
GetConsoleCursorInfo(handle,&cursor_info);
cursor_info.dwSize=50;
cursor_info.bVisible=0;
SetConsoleCursorInfo(handle,&cursor_info);
这就把光标的填充百分比改成50%,且不可视了。
1.2.3.修改光标位置
typedef struct _COORD {
SHORT X;
SHORT Y;
} COORD, *PCOORD;
//COORD表示⼀个字符在控制台屏幕上的坐标
- 注意:这里的坐标系不同于普通直角坐标系,如下:
接着调用服务SetConsoleCursorPosition。例如:
COORD pos = { 10, 5};
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为pos
SetConsoleCursorPosition(hOutput, pos);
这就是把光标改到坐标为(10,5)的位置上。
- 下面实现一个能改变光标位置的函数:
void Setpos(int x,int y)
{
HANDLE handle=GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos={x,y};
SetConsoleCursorPosition(handle,pos);
}
1.2.4.创建游戏界面窗口
- 使用宽字符‘□’代表墙,‘★’代表食物,‘●’代表蛇,需要调用setlocal服务才能打印特殊符,打印一个‘▲’如下:
setlocale(LC_ALL, "");
wchar_t ch = L'▲';
wprintf(L“%lc”,ch);
- 制作一个高28,宽28的游戏界面,如下:
int i;
for (i = 0; i <= 54; i += 2)
{
wprintf(L"%lc", WALL);
}
SetPos(0, 27);
for (i = 0; i <= 54; i += 2)
{
wprintf(L"%lc", WALL);
}
for (i = 1; i <= 26; i++)
{
SetPos(0, i);
wprintf(L"%lc", WALL);
SetPos(54, i);
wprintf(L"%lc", WALL);
}
1.2.5.初始化贪吃蛇
接着初始化贪吃蛇,令其行进方向变量一开始为“RIGHT”’,状态为“OK”,休眠时间(即运动速度,休眠时间越长,速度越慢,反之)为200ms,一个食物的得分为10,初始累计得分为0,再创建5个蛇身节点,代码如下:
int i;
pSnakeNode pcur=NULL;
for (i = 0; i < 5; i++)
{
pcur = (pSnakeNode)malloc(sizeof(SnakeNode));
pcur->x = 20 + 2 * i;
pcur->y = 14;
pcur->next = NULL;
//采用头插法,从左往右构建蛇身
if (ps->_pSnake == NULL)
{
ps->_pSnake= pcur;
}
else
{
pcur->next = ps->_pSnake;
ps->_pSnake = pcur;
}
}
pSnakeNode cur = ps->_pSnake;
//打印蛇身
while (cur)
{
SetPos(cur->x, cur->y);//改变光标位置,实现从右往左打印蛇身
wprintf(L"%lc", BODY);
cur = cur->next;
}
//初始化贪吃蛇属性
ps->_Status = OK;
ps->_Score = 0;
ps->_pFood = NULL;
ps->_SleepTime = 200;
ps->_FoodWeight = 10;
ps->_Dir = RIGHT;
- 打印图如下:
1.2.6.食物的随机生成
食物不能生成在蛇身的位置,并且只能在方框内生成,代码如下:
#define FOOD L'★'
void CreateFood(pSnakeNode pSnake->_pSnake)
{
srand((unsigned int)time(NULL));
again:
while(1)
{
int x=rand()%51+2;//因为一个宽字符占两个普通字符大小,所以横坐标只能是2的倍数
int y=rand()%26+1;
if(x%2==0)
{
break;
}
}
pSnakeNode pcur=pSnake->_pSnake;
while(pcur)
{
if(x==pcur->x&&y==pcur->y)
{
goto again;//如果食物坐标等于蛇身坐标,返回“again”位置重新生成食物坐标
}
pcur=pcur->next;
}
pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pFood == NULL)
{
perror("CreatFood()::malloc()");
return;
}
pFood->x = x;
pFood->y = y;
ps->_pFood = pFood;//把当前地图上仅有的一个食物节点信息保存在贪吃蛇食物属性中
SetPos(x, y);
wprintf(L"%lc",FOOD);
}
生成图如下:
1.3.贪吃蛇的行动
贪吃蛇每行动一格就要考虑当前方向、下一步的方向、下一步是否吃到食物(如果吃到食物则下一步变成蛇的头其他不变;如果没吃到食物则下一步变成头,原蛇身最后一个节点销毁)。
- 在讲以上东西之前先了解win32API的GetAsyncKeyState服务。GetAsyncKeyState 的返回值是short类型,在上⼀次调⽤GetAsyncKeyState 函数后,如果返回的16位的short数据中,最位是1,说明按键的状态是按下,如果最⾼是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。如果我们要判断⼀个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1,所以可以定义:
#define KEY_PRESS(vs) (GetAsyncKeyState(vs)&1)?1:0
1.3.1.根据指定按键改变贪吃蛇运动方向
当按下的按键方向与原运动方向相反时,贪吃蛇方向不改变;只有当按下的按键方向与原运动方向不相反时,贪吃蛇方向变成按键方向。代码如下:
//pSnake->_Dir是贪吃蛇原运动方向
if(KEEY_PRESS(VK_UP)&&pSnake->_Dir!=DOWN)
{
pSnake->_Dir=UP;
}
if(KEEY_PRESS(VK_DOWN)&&pSnake->_Dir!=UP)
{
pSnake->_Dir=DOWN;
}
if(KEEY_PRESS(VK_LEFT)&&pSnake->_Dir!=RIGHT)
{
pSnake->_Dir=LEFT;
}
if(KEEY_PRESS(VK_RIGHT)&&pSnake->_Dir!=LEFT)
{
pSnake->_Dir=RIGHT;
}
1.3.2.根据贪吃蛇的当前方向来确定蛇头移动一格后的位置,从而推导至整条蛇的位置。
- 如果蛇方向向上,蛇头移动后的位置是当前蛇头上方一格。同理左、右、下都是如此。代码如下:
//pSnake->_Dir是贪吃蛇方向
PSnakeNode pNext=(pSnakeNode)malloc(sizeof(SnakeNode));//开辟下一格位置的空间
if(pSnake->_Dir==UP)
{
pNext->x=pSnake->_pSnake->x;
pNext->y=pSnake->_pSnake->y-1;
}
if(pSnake->_Dir==DOWN)
{
pNext->x=pSnake->_pSnake->x;
pNext->y=pSnake->_pSnake->y+1;
}
if(pSnake->_Dir==LEFT)
{
pNext->x=pSnake->_pSnake->x-1;
pNext->y=pSnake->_pSnake->y;
}
if(pSnake->_Dir==RIGHT)
{
pNext->x=pSnake->_pSnake->x+1;
pNext->y=pSnake->_pSnake->y;
}
1.3.3.考虑下一格位置是否是食物
- 如果下一格是食物,那么食物位置就变成了蛇头位置,其余不变,再重新打印一遍贪吃蛇;如果下一格不是食物,把蛇头移到下一格,销毁原蛇身尾节点,重新打印一遍贪吃蛇。
- 如果下一格是食物。
if(pNext->x==pSnake->_pFood->x&&pNext->y==pSnake->_pFood->y)//判断下一格是否是食物
{
pNext->next=pSnake->_pSnake;
pSnake->_pSnake=pNext;
pSnakeNode pcur=pSnake->_pSnake;
while(pcur)//打印移动后的蛇身
{
Setpos(pcur->x,pcur->y);//把光标定位到相应位置依次打印节点
wprintf(L"%lc",BODY);//打印宽字符
pcur=pcur->next;
}
free(pSnake->_pSnake);
pSnake->_pFood=NULL;
CreateFood(pSnake->_pSnake);//调用食物生成函数重新生成食物
pSnake->_Score+=pSnake->_FoodWeight;//累计分数等于当前分数加上单个食物的分数
}
- 如果下一格不是食物。
if(pNext->x!=pSnake->_pFood->x||pNext->y!=pSnake->_pFood->y)//判断下一格是否是食物
{
pNext->next=pSnake->_pSnake;
pSnake->_pSnake=pNext;
pSnakeNode pcur=pSnake->_pSnake;
while(pcur->next->next)//当遍历到倒数第二个节点时停下来,从而可以释放掉最后一个节点
{
Setpos(pcur->x,pcur->y);
wprintf(L"%lc",BODY);
pcur=pcur->next;
}
Setpos(pcur->x,pcur->y);
printf(" ");//把原来占两个字符大小的尾节点图形'●'换成同为两个字符大小的' '。
free(pcur->next);//释放掉最后一个节点
pcur->next=NULL;
}
1.4.游戏结束的几种情况
当蛇头碰到了墙、蛇头碰到了蛇身、按下“ESCAPE”正常退出、按下空格
1.4.1.蛇撞墙死完导致游戏结束
- 如果蛇头位置等于周围墙的位置,则游戏结束
pSnakeNode pcur=pSnake->_pSnake;
if(pcur->x==0||
pcur->x==54||
pcur->y==0||
pcur->y==27)
{
pSnake->_Status=KILL_BY_WALL;
}
1.4.2.蛇头撞到蛇身导致游戏结束
- 如果蛇头位置与其蛇身任意位置相同,则游戏结束。
pSnakeNode pcur=pSnake->_pSnake;
while(pcur)
{
if(pcur->x==(pSnake->_pSnake->x)&&pcur->y==(pSnake->_pSnake->y))
{
pSnake->_Status=KILL_BY_SELF;
}
pcur=pcur->next;
}
1.4.3.按“ESCAPE”键直接结束游戏
if(KEEY_PRESS(VK_ESCAPE))
{
pSnake->_Status=END_NORMAL;
}
1.5整体代码的实现
说了这么多,还是那句话——“Talk is cheap,show me the code”。直接上代码吧
- Snake.h
#define KEEY_PRESS(vk) ((GetAsyncKeyState(vk)&0x1)?1:0)
#include <stdlib.h>
#include <stdio.h>
#include <windows.h>
#include <stdbool.h>
#include <locale.h>
#include <time.h>
#define WALL L'□'
#define FOOD L'★'
#define BODY L'●'
enum DIRECTION
{
UP = 1,
DOWN,
LEFT,
RIGHT
};
enum Game_Status
{
OK,//正常运行
END_NORMAL,//按ESC退出
KILL_BY_WALL,
KILL_BY_SELF
};
typedef struct SnakeNode
{
int x;
int y;
struct SnakeNode* next;
}SnakeNode, * pSnakeNode;
typedef struct Snake
{
pSnakeNode _pSnake;//指向贪吃蛇头的指针
pSnakeNode _pFood;//指向食物的指针
int _Score;//贪吃蛇累计得分
int _FoodWeight;//一个食物的分数
int _SleepTime;//每走一步的休息时间
enum DIRECTION _Dir;//描述蛇的方向
enum Status _Status;//游戏的状态:正常、退出、撞墙、吃到自己
}Snake, * pSnake;
void GameStart(pSnake ps);
void GameRun(pSnake ps);
void GameEnd(pSnake ps);
- Snake.c
#include "Snake.h"
void SetPos(int x, int y)
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos = { x,y };
SetConsoleCursorPosition(handle, pos);
}
void PrintHelpInfo()
{
SetPos(64, 15);
printf("1.不能撞墙,不能咬到自己");
SetPos(64, 16);
printf("2.使用 'W''S''A''D'分别控制蛇移动");
SetPos(64, 17);
printf("3.F3加速,F4减速");
SetPos(64, 18);
printf("4.ESC-退出, 空格-暂停游戏");
SetPos(64, 20);
printf("胡桃的苟@版权");
}
void WelComeToGame()
{
//定位光标
SetPos(40, 14);
printf("欢迎来到胡桃的苟制作的贪吃蛇小游戏");
SetPos(40, 25);
system("pause");//pause是暂停
system("cls");
SetPos(20, 14);
printf("使用 'W''S''A''D'分别控制蛇的移动, F3是加速,F4是减速");
SetPos(40, 25);
system("pause");
system("cls");
}
void CreateMap()
{
int i;
for (i = 0; i <= 54; i += 2)
{
wprintf(L"%lc", WALL);
}
SetPos(0, 27);
for (i = 0; i <= 54; i += 2)
{
wprintf(L"%lc", WALL);
}
for (i = 1; i <= 26; i++)
{
SetPos(0, i);
wprintf(L"%lc", WALL);
SetPos(54, i);
wprintf(L"%lc", WALL);
}
}
void InitSnake(pSnake ps)
{
int i;
pSnakeNode pcur=NULL;
for (i = 0; i < 5; i++)
{
pcur = (pSnakeNode)malloc(sizeof(SnakeNode));
pcur->x = 20 + 2 * i;
pcur->y = 14;
pcur->next = NULL;
if (ps->_pSnake == NULL)
{
ps->_pSnake= pcur;
}
else
{
pcur->next = ps->_pSnake;
ps->_pSnake = pcur;
}
}
pSnakeNode cur = ps->_pSnake;
while (cur)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
ps->_Status = OK;
ps->_Score = 0;
ps->_pFood = NULL;
ps->_SleepTime = 200;
ps->_FoodWeight = 10;
ps->_Dir = RIGHT;
}
void CreateFood(pSnake ps)
{
int x;
int y;
again:
do
{
x = rand() % 51 + 2;
y = rand() % 26 + 1;
} while (x % 2 != 0);
pSnakeNode pcur = ps->_pSnake;
while (pcur)
{
if (x == pcur->x && y == pcur->y)
{
goto again;
}
pcur = pcur->next;
}
pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pFood == NULL)
{
perror("CreatFood()::malloc()");
return;
}
pFood->x = x;
pFood->y = y;
ps->_pFood = pFood;
SetPos(x, y);
wprintf(L"%lc", FOOD);
}
void GameStart(pSnake ps)
{
//打印欢迎界面
WelComeToGame();
//创建地图
CreateMap();
//初始化贪食蛇
InitSnake(ps);
//创建食物
CreateFood(ps);
}
void pause()
{
while (1)
{
Sleep(1000);
if(KEEY_PRESS(VK_SPACE))
{
break;
}
}
}
void EatFood(pSnake ps,pSnakeNode pNext)
{
pNext->next = ps->_pSnake;
ps->_pSnake = pNext;
pSnakeNode pcur = ps->_pSnake;
while (pcur)
{
SetPos(pcur->x, pcur->y);
wprintf(L"%lc", BODY);
pcur = pcur->next;
}
free(ps->_pFood);
CreateFood(ps);
ps->_Score += ps->_FoodWeight;
}
void EatNoFood(pSnake ps,pSnakeNode pNext)
{
pNext->next = ps->_pSnake;
ps->_pSnake = pNext;
pSnakeNode pcur = ps->_pSnake;
while (pcur->next->next)
{
SetPos(pcur->x, pcur->y);
wprintf(L"%lc", BODY);
pcur = pcur->next;
}
SetPos(pcur->next->x, pcur->next->y);
printf(" ");
SetPos(pcur->x, pcur->y);
wprintf(L"%lc", BODY);
free(pcur->next);
pcur->next = NULL;
}
void KILLBYSELF(pSnake ps)
{
pSnakeNode pcur = ps->_pSnake->next;
while (pcur)
{
if (ps->_pSnake->x == pcur->x && ps->_pSnake->y == pcur->y)
{
ps->_Status = KILL_BY_SELF;
}
pcur = pcur->next;
}
}
void KILLBYWALL(pSnake ps)
{
if (ps->_pSnake->y == 0 ||
ps->_pSnake->x == 54 ||
ps->_pSnake->x == 0 ||
ps->_pSnake->y == 27)
ps->_Status = KILL_BY_WALL;
}
void snakemove(pSnake ps)
{
pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pNext == NULL)
{
perror("snakemove()::malloc()");
return;
}
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 LEFT:
pNext->x = ps->_pSnake->x-2;
pNext->y = ps->_pSnake->y;
break;
case RIGHT:
pNext->x = ps->_pSnake->x+2;
pNext->y = ps->_pSnake->y;
break;
}
pSnake pcur = ps;
if (pNext->x==(pcur->_pFood)->x&&pNext->y==(pcur->_pFood)->y)
{
//下一步吃到食物
EatFood(ps,pNext);
}
else if (pNext->x != (pcur->_pFood)->x || pNext->y != (pcur->_pFood)->y)
{
//下一步没吃到食物
EatNoFood(ps,pNext);
}
KILLBYWALL(ps);
KILLBYSELF(ps);
}
void Destroy(pSnake ps)
{
pSnakeNode pcur = ps->_pSnake;
while (pcur)
{
pSnakeNode next = pcur->next;
free(pcur);
pcur = NULL;
pcur = next;
}
ps->_pSnake = NULL;
}
void GameRun(pSnake ps)
{
PrintHelpInfo();
do
{
SetPos(64, 9);
printf("得分:%5d", ps->_Score);
SetPos(64, 10);
printf("当前每个食物的分数:%2d", ps->_FoodWeight);
if (KEEY_PRESS(0x57) && ps->_Dir != DOWN)
{
ps->_Dir = UP;
}
if (KEEY_PRESS(0x53) && ps->_Dir != UP)
{
ps->_Dir = DOWN;
}
if (KEEY_PRESS(0x41) && ps->_Dir != RIGHT)
{
ps->_Dir = LEFT;
}
if (KEEY_PRESS(0x44) && ps->_Dir != LEFT)
{
ps->_Dir = RIGHT;
}
if (KEEY_PRESS(VK_SPACE))
{
//暂停
pause();
}
if (KEEY_PRESS(VK_ESCAPE))
{
ps->_Status = END_NORMAL;
break;
}
if (KEEY_PRESS(VK_F3))
{
if (ps->_SleepTime > 80)
{
ps->_SleepTime -=30;
ps->_FoodWeight += 2;
}
}
if (KEEY_PRESS(VK_F4))
{
if (ps->_SleepTime<320)
{
ps->_SleepTime += 30;
ps->_FoodWeight -= 2;
}
}
Sleep(ps->_SleepTime);
snakemove(ps);
} while (ps->_Status == OK);
}
void GameEnd(pSnake ps)
{
SetPos(25, 14);
switch(ps->_Status)
{
case END_NORMAL:
printf("你已退出游戏...\n");
break;
case KILL_BY_SELF:
printf("你已自杀...\n");
break;
case KILL_BY_WALL:
printf("你撞墙死了...\n");
break;
}
Destroy(ps);
}
- test.c
#include "Snake.h"
void test()
{
int ch = 0;
do
{
Snake snake = { 0 };//创建了贪吃蛇
//1. 游戏开始 - 初始化游戏
GameStart(&snake);
//2. 游戏运行 - 游戏的正常运行过程
GameRun(&snake);
//3. 游戏结束 - 游戏善后(释放资源)
GameEnd(&snake);
SetPos(25,13);
printf("再来一局吗?(Y/N):");
ch = getchar();
system("cls");
getchar();//消除\n
} while (ch == 'Y' || ch == 'y');
SetPos(0, 27);
}
int main()
{
//控制台窗口的设置
//其中贪吃蛇游戏界面宽28,高28
system("title 贪吃蛇");
system("mode con cols=100 lines=40");
//本地初始化
setlocale(LC_ALL, "");
//设置随机数种子
srand((unsigned int)time(NULL));
//获取终端设备句柄
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
//获取光标信号,并隐藏光标
CONSOLE_CURSOR_INFO cursor_info;
GetConsoleCursorInfo(handle, &cursor_info);
cursor_info.bVisible = false;
SetConsoleCursorInfo(handle, &cursor_info);
//开始测试
test();
}
好了以上就是我对于”c语言实现贪吃蛇小游戏”的一些理解了,喜欢的话可以点点赞哦。