简易贪吃蛇(c语言)
贪吃蛇游戏是指在规定的方框内,通过操控键盘上的 ↑ ↓ ← → 键 来进行贪吃蛇的方向控制。在方框内吃掉随机出现的食物来延长蛇的身体。
需要的头文件及函数原型
#ifndef __SNAKE_H__
#define __SNAKE_H__
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#define WALL "X" //墙壁符号
#define FOOD "O" //蛇的符号
#define INIT_X 20 //初始化蛇的位置 X坐标
#define INIT_Y 20 //初始化蛇的位置 Y坐标
typedef int SDataType;
typedef struct SnakeNode {
int x;//节点的x坐标
int y;//节点的y坐标
struct SnakeNode * next;
}SnakeNode ,*pSnakeNode;
enum Direction {
UP = 1,
DOWN,
LEFT,
RIGHT,
};//蛇当前运行方向状态
enum State
{
OK, //存活
NORMAL_END, //按esc正常退出
KILL_BY_WALL,//撞到墙壁死亡
KILL_BY_SELF,//撞到蛇身死亡
};//蛇的状态
typedef struct Snake
{
SnakeNode *psnake;// 蛇的头结点
int TotalScore; //总分数
int AddScore; //分数增加比
int SleepTime; //蛇的运行速度(电脑刷新间隔)
enum Direction Dir; //蛇当前所处的方向状态
enum State Status; //社当前的状态
SnakeNode *pFood; //食物所处的位置坐标
}Snake , *pSnake;
//蛇结构体
void GameStart(pSnake ps);//游戏开始函数
void InitSnake(pSnake ps);//初始化蛇函数
void SetPos(int x, int y);//运用控制台命令设置节点出现的位置
void PrintMap(); //打印地图
pSnakeNode BuyNode(); //生成一个节点
void CreateSnake(pSnake ps);//创建一个蛇
void CreateFood(pSnake ps);//创建一个食物节点
void GameRun(pSnake ps); //游戏运行函数
void Pause(); //游戏暂停函数
int HasNextNode(pSnakeNode pf, pSnakeNode pn);//判断蛇头所指向的下一个节点是不是食物节点
void EatFood(pSnake ps, pSnakeNode pn);//进食食物使得整体延长
void NoFood(pSnake ps, pSnakeNode pn); //没有食物节点向蛇头方向运行
void SnakeMove(pSnake ps);//蛇的方向操作函数
void helpinfo(pSnake ps); //当前状态
void KillByWall(pSnake ps);//被墙撞死
void KillBySelf(pSnake ps);//蛇吃自己
void GameEnd(pSnake ps); //游戏结束释放空间,打印结果
#endif//__SNAKE_H__
//test.c
#include "Snake.h"
void test()
{
Snake ps;
GameStart(&ps);
GameRun(&ps);
GameEnd(&ps);
}
int main()
{
test();
return 0;
}
函数实现分析
#include "Snake.h"
void InitSnake(pSnake ps)
//初始化蛇函数
{
ps->AddScore = 10;
ps->Dir = UP;
ps->SleepTime = 200;
ps->TotalScore = 0;
ps->psnake = NULL;
ps->Status = OK;
}
void SetPos(int x, int y)
//运用控制台命令设置节点出现的位置
{
COORD pos;
HANDLE handle;
pos.X = x;
pos.Y = y;
handle = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorPosition(handle,pos);
}
COORD 是 api函数。它的作用是确定横纵坐标。
HANDLE 是句柄,用来函数运行时的状态。
GetStdHandle()获取句柄的状态。STD_OUT_HANDLE获取标准输出的句柄。
SetConsoleCursorPosition(handle, pos)。将句柄当前的状态传入,该函数
可以确定将光标移动到 pos所指向的位置。
void PrintMap()
//打印地图
{
int i = 0;
for (i = 0; i <58; i+=2)
{
SetPos(i, 0);
printf(WALL);
}
for (i = 0; i <58; i+=2)
{
SetPos(i, 27);
printf(WALL);
}
for (i = 0; i < 27; i++)
{
SetPos(0, i);
printf(WALL);
}
for (i = 0; i < 27; i++)
{
SetPos(56, i);
printf(WALL);
}
}
在给定的范围内打印边界。假设整个地图的范围是 27 * 58.
pSnakeNode BuyNode()
//生成一个节点
{
pSnakeNode cur = (pSnakeNode)malloc(sizeof(SnakeNode));
if (cur == NULL)
{
printf("BuyNode :: malloc failue ! \n");
}
cur->next = NULL;
return cur;
}
void CreateSnake(pSnake ps)
//创建一个蛇
{
pSnakeNode cur = NULL;
pSnakeNode first = NULL;
first = ps->psnake;
int i = 0;
for (i = 0; i < 5; i++)
{
cur = BuyNode();
cur->x = 20 + 2*i;
cur->y = 20;
SetPos(cur->x, cur->y);
printf(FOOD);
cur->next = first;
first = cur;
}
ps->psnake = first;
}
采用头插的方式初始化蛇的样子。假定蛇身的长度为5。注意在X轴方向上才有用间距为偶数的移动方式。
void CreateFood(pSnake ps)
//创建一个食物节点
{
//对蛇的食物进行初始化
ps->pFood = BuyNode();
pSnakeNode cur = ps->psnake;
//食物不能出现在边界
ps->pFood->y = rand() % 25 + 1;
do
{
ps->pFood-> x= rand() %53+2;
} while (ps->pFood->x % 2 != 0);
//食物不能出现在蛇身
while (cur)
{
if ((cur->x == ps->pFood->x) && (cur->y == ps->pFood->y))
{
CreateFood(ps);
return;
}
cur = cur->next;
}
SetPos(ps->pFood->x, ps->pFood->y);
printf(FOOD);
//食物要随机出现;
}
创建一个食物节点。食物节点要随机出现所以用到 rand()函数获取随机数。另一方面。食物不能出现在边界,并且食物不能出现在蛇的身体里。最后食物的X坐标必须是偶数否则蛇吃不到食物。
void welcome()
{
system("mode con cols=100 lines=30");
//采用mode 命令来初始化黑框的大小 假定行为30 列为100
SetPos(50, 13);
printf("欢迎来到贪吃蛇游戏!\n");
system("pause");
system("cls");
SetPos(50, 13);
printf("使用↑↓←→来进行操控,F1加速,F2减速。\n");
SetPos(50, 14);
printf("使用空格键进行暂停\n");
SetPos(50, 15);
printf("使用esc键进行退出操作。\n");
system("pause");
system("cls");
}
mode命令可以确定黑框的大小。 它的使用格式是:
mode con cols=数字 lines=数字
void GameStart(pSnake ps)
//游戏开始函数
{
InitSnake(ps);
welcome();
PrintMap();
CreateSnake(ps);
CreateFood(ps);
}
void Pause()
//游戏暂停函数
{
while (1)
{
Sleep(100);
if(GetAsyncKeyState(VK_SPACE))
{
return;
}
}
}
游戏暂停函数,程序运行的时候当从键盘上接收到空格键,游戏界面暂停。实际上是将当前程序执行到一个死循环里边。只有当再次按下空格键时跳出循环。游戏继续。
void GameRun(pSnake ps)
//游戏运行函数
{
do
{
helpinfo(ps);
if ((GetAsyncKeyState(VK_UP)) && (ps->Dir != DOWN))
{
ps->Dir = UP;
}
else if ((GetAsyncKeyState(VK_DOWN)) && (ps->Dir != UP))
{
ps->Dir = DOWN;
}
else if ((GetAsyncKeyState(VK_LEFT)) && (ps->Dir != RIGHT))
{
ps->Dir = RIGHT;
}
else if ((GetAsyncKeyState(VK_RIGHT)) && (ps->Dir != LEFT))
{
ps->Dir = LEFT;
}
else if (GetAsyncKeyState(VK_F1))
{
//加速
if (ps->SleepTime > 100)
{
ps->AddScore += 2;
ps->SleepTime -= 20;
}
}
else if (GetAsyncKeyState(VK_F2))
{
//减速
if (ps->SleepTime < 300)
{
ps->AddScore -= 2;
ps->SleepTime += 20;
}
}
else if (GetAsyncKeyState(VK_SPACE))
{
Pause();
}
else if (GetAsyncKeyState(VK_ESCAPE))
{
//退出
ps->Status = NORMAL_END;
}
KillBySelf(ps);
KillByWall(ps);
SnakeMove(ps);
Sleep(ps->SleepTime);
} while (ps->Status == OK);
}
游戏的运行函数,使用了一个do while()语句来进行运作 当ps->status蛇的状态来判断游戏是否需要继续进行。当蛇的状态不为 OK时跳出循环。GetAsyncKeyState()函数是判断键盘上的虚拟键有没有被按下。VK_UP是上键,依次类推获得 ↑ ↓ ← →, F1加速,F2减速, SPACE空格键暂停 ,ESC退出。
值得注意的是,蛇不可能旋转180度吃自己,所以if条件里要判断该种条件。
Sleep()是睡眠函数,它用来限制程序运行速度,进而限制蛇的移动速度。
当if条件满足的时候就改变蛇的ps->Dir方向状态。
void SnakeMove(pSnake ps)
//蛇的方向操作函数
{
pSnakeNode pnext =BuyNode();
switch (ps->Dir)
{
case UP:
pnext->x = ps->psnake->x;
pnext->y = ps->psnake->y - 1;
if ( HasNextNode(ps->pFood ,pnext) )
{
EatFood(ps ,pnext);
CreateFood(ps);
ps->TotalScore += ps->AddScore;
}
else
{
NoFood(ps,pnext);
}
break;
case DOWN:
pnext->x = ps->psnake->x;
pnext->y = ps->psnake->y + 1;
if (HasNextNode(ps->pFood, pnext))
{
EatFood(ps, pnext);
CreateFood(ps);
ps->TotalScore += ps->AddScore;
}
else
{
NoFood(ps, pnext);
}
break;
case LEFT:
pnext->x = ps->psnake->x+2;
pnext->y = ps->psnake->y ;
if (HasNextNode(ps->pFood, pnext))
{
EatFood(ps, pnext);
CreateFood(ps);
ps->TotalScore += ps->AddScore;
}
else
{
NoFood(ps, pnext);
}
break;
case RIGHT:
pnext->x = ps->psnake->x - 2;
pnext->y = ps->psnake->y;
if (HasNextNode(ps->pFood, pnext))
{
EatFood(ps, pnext);
CreateFood(ps);
ps->TotalScore += ps->AddScore;
}
else
{
NoFood(ps, pnext);
}
break;
}
}
由于在GameRun()中可能改变蛇的运动状态。所以move函数里需要判断下一步所需要移动的位置。如果下一步是一个食物节点,蛇需要加长。如果下一个节点没有食物那就继续往前走。获取一个pnext指针,将pnext 的赋值根据情况区别开来。如果是向上走,那么将ps->psnake蛇头的y轴减1赋值给pnext。然后使用HasNextNode()判断下一个节点是否为食物节点。
int HasNextNode(pSnakeNode pf, pSnakeNode pn)
//判断蛇头所指向的下一个节点是不是食物节点
{
return (pf->x == pn->x) && (pf->y == pn->y);
}
判断pnext的x .y坐标是否与ps->pfood相等,如果相等代表着下一个节点就是食物。
void EatFood(pSnake ps,pSnakeNode pn)
//进食食物使得整体延长
{
pn->next = ps->psnake;
ps->psnake = pn;
pSnakeNode cur = ps->psnake;
SetPos(ps->psnake->x, ps->psnake->y);
printf(FOOD);
}
void NoFood(pSnake ps, pSnakeNode pn)
//没有食物节点向蛇头方向运行
{
pn->next = ps->psnake;
ps->psnake = pn;
pSnakeNode cur = ps->psnake ;
while (cur->next->next != NULL)
{
SetPos(cur->x, cur->y);
printf(FOOD);
cur = cur->next;
}
SetPos(cur->next->x, cur->next->y);
printf(" ");
free(cur->next);
cur->next = NULL;
}
EatFood()函数只需要将pnext头插入蛇身体里,然后再次打印蛇头就行了。
NoFood()函数。需要将pnext头插入蛇身体 (现在 5+1)。然后遍历打印蛇,且遍历的条件是
cur->next->next != NULL。(打印了 5)最后一个(剩下的1)置为空格 就不改变蛇的身体完成移动。
void helpinfo(pSnake ps)
//当前状态
{
SetPos(62, 12);
printf("Sleeptime : %d \n", ps->SleepTime);
SetPos(63, 13);
printf("AddScore : %d \n", ps->AddScore );
SetPos(63, 14);
printf("TotalScore : %d \n", ps->TotalScore);
}
void KillByWall(pSnake ps)
//被墙撞死
{
if (ps->psnake->y == 0 || ps->psnake->y == 27 || ps->psnake->x == 57 || ps->psnake->x == 0)
{
ps->Status = KILL_BY_WALL;
}
}
void KillBySelf(pSnake ps)
//蛇吃自己
{
pSnakeNode cur = ps->psnake;
cur = cur->next;
while (cur)
{
if ((ps->psnake->x == cur->x) && (ps->psnake->y == cur->y))
{
ps->Status = KILL_BY_SELF;
break;
}
cur = cur->next;
}
}
蛇被墙撞死要看当前状态是不是在边界上,如果在边界上那么就要改变Status为KILL_BY_WALL。
蛇自杀,判断蛇的头结点在不在蛇的身体里。通过while遍历整个蛇的身体只要发现条件(ps->snake->x == cur->x && ps->snake->y == cur->y)就自杀。
void GameEnd(pSnake ps)
//游戏结束释放空间,打印结果
{
if (ps->Status == KILL_BY_SELF)
{
system("cls");
printf("有时候饿的连自己都想吃······ \n");
printf("你获得的总分 : %d \n", ps->TotalScore);
system("pause");
}
if (ps->Status == KILL_BY_WALL)
{
system("cls");
printf("撞了南墙不回头 ! \n");
printf("你获得的总分 : %d \n", ps->TotalScore);
system("pause");
}
if (ps->Status == NORMAL_END)
{
system("cls");
printf("平稳着陆 ! 稳 ! \n");
printf("你获得的总分 : %d \n", ps->TotalScore);
system("pause");
}
pSnakeNode cur = ps->psnake;
pSnakeNode del = NULL;
while (cur)
{
del = cur;
cur = cur->next;
free(del);
del = NULL;
}
ps->psnake = NULL;
cur = ps->pFood;
free(cur);
ps->pFood = NULL;
}
检查状态打印结果 ,游戏结束时要释放节点。