贪吃蛇对于大家来说肯定不陌生,那么今天我们将用C语言来实现贪吃蛇的功能,自己动手做一款属于自己的小游戏。
首先先看几个我做的贪吃蛇游戏截图:
在编写程序之前,我们先来简单分析一下,我们最终要实现成什么样子呢,要有哪些功能,要将这些功能分为哪些方面,分别要进行几步实现,对于自己设计就有了自己的玩法,界面和规则,所以一定要有独特看法,一定要考虑到游戏各个方面的问题。
首先我们要确定大体框架,因为我最近在学习数据结构,因此我会采用链表的形式来实现基本操作,大家可以有自己想法。
上面是我们做这个小项目的一些简单说明,接下来正式进入贪吃蛇的设计。
先来说明我的设计思路,我将整个项目分为以下几个方面 :
- 屏幕坐标的获取与设置
- 地图的设计
- 节点的设计
- Snake蛇的设计
- 食物的设计
- 蛇的移动方式
- 控制蛇的方式
- 游戏输赢的判断方式
- 游戏界面的设计
void SetPos(int x,int y)
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//获得句柄
COORD pos;
pos.X = x;
pos.Y = y;
SetConsoleCursorPosition(handle, pos);//设置光标位置
}
SetPos()
函数有两个参数 x,y
,作为屏幕的横坐标和纵坐标输入。函数里面,handle
为一个句柄,GetStdHandle(STD_OUTPUT_HANDLE)
获得标准输出句柄给handle
。
COORD pos
定义一个COORD类型的数据pos,用来保存坐标值。
关于COORD,有如下介绍
The COORD structure defines the coordinates of a character cell in a console screen buffer. The origin of the coordinate system (0,0) is at the top, left cell of the buffer.
COORD原型:
typedef struct _COORD {
SHORT X;
SHORT Y;
} COORD;
SetConsoleCursorPosition(handle, pos)
该函数的作用是移动屏幕光标到指定位置
函数原型:
BOOL SetConsoleCursorPosition(
HANDLE hConsoleOutput, // handle to screen buffer
COORD dwCursorPosition // new cursor coordinates);
以上是关于坐标的设置,这也是我们这个小项目中最重要的一步,因为不管是整个画面的打印,还是蛇的控制与移动都离不开坐标,是我们完成项目的基础。
void CreateMap()
{
int i;
for ( i = 0; i <= 56; i+=2)
{
SetPos(i, 0);
printf(WALL);
SetPos(i, 26);
printf(WALL);
}
for (i = 1; i <= 25; i++)
{
SetPos(0, i);
printf(WALL);
SetPos(56, i);
printf(WALL);
}
}
打印一个27*25大小的地图,WALL是一个宏定义表示“■”。
地图打印的样子是:
//节点
typedef struct Node
{
struct Node *next;//下一个节点
int x;//该节点x坐标
int y;//该节点y坐标
}SnakeNode,*pSnakeNode;
使用结构体作为节点,里面封装一个横坐标x,纵坐标y,和一个指向下一节点的指针。
//蛇结构体
typedef struct Snake
{
pSnakeNode _pSnake;//指向蛇
pSnakeNode _pFood;//食物
enum status _status;//蛇的状态
enum Dir _Dir;//蛇的行走方向
int SleepTime;//蛇的停留时长
}Snake ,*pSnake;
用一个结构体表示蛇的属性,在结构体中包含:指针_pSnake
用来指向蛇的身体,指针_pFood
用来指向食物,枚举类型的 _status
用来表示当前蛇的状态,_Dir
用来表示当前蛇的行走方向,SleepTime
用来表示蛇的停留时间,也就是蛇行走的快慢
//3.初始化蛇
void InitSnake(pSnake pS)
{
pSnakeNode Cur = malloc(sizeof(SnakeNode));
Cur->x = INIT_X;
Cur->y = INIT_Y;
Cur->next = NULL;
//采用头插
for (size_t i = 1; i <= 4; i++)
{
//头插进一个新的节点
pS->_pSnake = malloc(sizeof(SnakeNode));
pS->_pSnake->next = Cur;//使新节点连接上之前的节点
pS->_pSnake->x = INIT_X + i * 2;//新节点赋值
pS->_pSnake->y = INIT_Y;
Cur = pS->_pSnake; //使Cur指向第一个节点
}
pS->_Dir = RIGHT;
pS->_status = OK;
}
首先有一个节点指针Cur,他的初始位置(INIT_X,INIT_Y), 然后采用头插的方法再次插入3个节点,以这4个节点作为蛇的初始身体。这里选择头插的方式,时间复杂度为O(1),较与尾插的方式,更加方便快捷。
最后再将这只蛇的状态和方向设定好。
void CreadeFood(pSnake pS)
{
pSnakeNode pFood = malloc(sizeof(SnakeNode));
pSnakeNode Cur = NULL;
assert(pS);
int flag = 1;//标记
while (flag)
{
GetRandFood(pFood);//随机生成食物坐标
Cur = pS->_pSnake;
while (Cur != NULL)//判断
{
if (Cur->x != pFood->x && Cur->y != pFood->y)
Cur = Cur->next;
else break;
}
if(Cur == NULL)
flag = 0;
}
pS->_pFood = pFood;
}
void GetRandFood(pSnakeNode pFood)//生成随机食物
{
srand((unsigned)time(NULL));
do
{
pFood->x = rand() % 53 + 2;
} while (pFood->x % 2 != 0);
pFood->y = rand() % 25 + 1;
}
食物的生成很简单,只需要一个生成随机数的函数 rand(),但是每次生成完成后要进行判断,看看该点时候合法,比如如果该点生成的位置刚好在墙上或者蛇身体上,那么该点就不合法。要注意的地方是x坐标的应该为2的倍数,因为每一个点是一个符号,占两个字节。
到这里我们来写一个打印函数,使得蛇可以出现在我们的屏幕上:
//4.打印蛇
void SnakePrint(pSnake pS)
{
pSnakeNode Cur = NULL;
pSnakeNode pFood = NULL;
assert(pS);
//打印蛇
Cur = pS->_pSnake;
while (Cur)
{
SetPos(Cur->x, Cur->y);
printf(SNAKE);
Cur = Cur->next;
}
SetPos(60, 20);
}
//打印食物
void FoodPrint(pSnake pS)
{
SetPos(pS->_pFood->x, pS->_pFood->y);
printf(FOOD);
}
让我们来看一下效果:
void SnakeMove(pSnake pS)
{
pSnakeNode NextNode = malloc(sizeof(SnakeNode));
NextNode->x = pS->_pSnake->x;
NextNode->y = pS->_pSnake->y;
switch (pS->_Dir)
{
case UP:
NextNode->y -= 1;
break;
case DOWN:
NextNode->y += 1;
break;
case LEFT:
NextNode->x -= 2;
break;
case RIGHT:
NextNode->x += 2;
break;
}
if (HasFood(pS, NextNode))
EatFood(pS, NextNode);
else NEatFood(pS, NextNode);
}
判断当前蛇的走向,选择行走方向,之后要进行下一个行走点进行判断,看是否有食物,有食物与没有食物是两种不同的方式
查看下一个点是否有食物:
int HasFood( pSnake pS , pSnakeNode pNextNode)
{
if (pNextNode->x == pS->_pFood->x && pNextNode->y == pS->_pFood->y)
return 1;
else return 0;
}
有食物的话,就让将下一个点头插进蛇的身体节点
void EatFood(pSnake pS, pSnakeNode pNextNode)
{
assert(pS);
assert(pNextNode);
pNextNode->next = pS->_pSnake;
pS->_pSnake = pNextNode;
CreadeFood(pS);
FoodPrint(pS);
}
没有食物的话,就要在最后一个节点位置上打印一个空白,将之前显示的蛇身清除掉,然后将下一个节点头插进蛇身节点,并且将最后一个节点删掉。
void NEatFood(pSnake pS, pSnakeNode pNextNode)
{
pSnakeNode Cur = NULL;
assert(pS);
assert(pNextNode);
pNextNode->next = pS->_pSnake;
pS->_pSnake = pNextNode;
Cur = pS->_pSnake;
while (Cur && Cur->next != NULL )
{
if (Cur->next->next == NULL)
{
SetPos(Cur->next->x, Cur->next->y);
printf(" ");
free(Cur->next);
Cur->next = NULL;
break;
}
Cur = Cur->next;
}
}
void SnakeRun(pSnake pS)
{
do{
if (GetAsyncKeyState(VK_UP) && pS->_Dir != DOWN)
pS->_Dir = UP;
if (GetAsyncKeyState(VK_DOWN) && pS->_Dir != UP)
pS->_Dir = DOWN;
if (GetAsyncKeyState(VK_LEFT) && pS->_Dir != RIGHT)
pS->_Dir = LEFT;
if (GetAsyncKeyState(VK_RIGHT) && pS->_Dir != LEFT)
pS->_Dir = RIGHT;
} while (pS->_status == OK);
}
GetAsyncKeyState()
函数是得到键盘的输入,由此可以用来判断你想让蛇的下一步怎么走。
游戏结束的判断很简单就是看当前头节点坐标是否与墙的坐标或者自身的坐标重合,重合便游戏结束,蛇死亡,没有便继续游戏:
int KillByWall(pSnake pS)
{
if (pS->_pSnake->x == 0 || pS->_pSnake->x == 56 ||
pS->_pSnake->y == 0 || pS->_pSnake->y == 27)
return 1;
else return 0;
}
int KillBySelf(pSnake pS)
{
pSnakeNode pCur = NULL;
assert(pS);
pCur = pS->_pSnake->next;
while (pCur)
{
if(pS->_pSnake->x == pCur->x && pS->_pSnake->y == pCur->y)
{
return 1;
}
pCur = pCur->next;
}
return 0;
}
游戏界面的设计就看自己的想法了,只要有一个较为实用好看的界面就可以,你可以在界面上写上作者的名字,或者游戏的说明,操作方法等等,能写的很多,就看你的脑洞了。
好了,到这里就可以我们的贪吃蛇游戏也就全部完成了,你们可以去玩玩自己的小游戏,最后我会放上我自己的全部代码,以供大家学习参考,因为我也是一个变成新手,如果有那个地方写的不完善或者不好,希望大家指出,我也会进行修改与学习,以保证之后能做出更加完善的项目。
编译环境(Visual Studio 2017)
int main(void)
{
game();
SetPos(6, 28);
system("pause");
return 0;
}
#ifndef _SNAKE_H_
#define _SNAKE_H_
#define WALL "■"
#define FOOD "●"
#define SNAKE "□"
#define INIT_X 24 //初始坐标
#define INIT_Y 3
//蛇的状态
enum Status
{
OK, //正常
KILL_BY_SELF, //撞到自己
KILL_BY_WALL, //撞墙
KILL_NORMAL //自杀
};
//蛇得方向
enum Dir
{ UP,DOWN,LEFT,RIGHT };
//节点
typedef struct Node
{
struct Node *next;//下一个节点
int x;//该节点x坐标
int y;//该节点y坐标
}SnakeNode,*pSnakeNode;
//蛇结构体
typedef struct Snake
{
pSnakeNode _pSnake;//指向蛇
pSnakeNode _pFood;//食物
enum status _status;//蛇的状态
enum Dir _Dir;//蛇的行走方向
int SleepTime;//蛇的停留时长
}Snake ,*pSnake;
//1.设置坐标
void SetPos(int x, int y);
//2.建立地图
void CreateMap();
//3.初始化蛇
void InitSnake(pSnake pS);
//4.打印
void SnakePrint(pSnake pS);
void FoodPrint(pSnake pS);
//5. 设置食物
void CreadeFood(pSnake pS);
//6. 蛇移动
void SnakeMove(pSnake ps);
//7. 控制蛇运动
void SnakeRun(pSnake pS);
//8. 游戏结束
int KillByWall(pSnake pS);
int KillBySelf(pSnake pS);
int KillByNo(pSnake pS);
void GomeOver(pSnake pS);
//9. 欢迎界面
void Welcome();
#endif // !_SNAKE_H_
#include <windows.h>
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <time.h>
#include "Snake.h"
//1.设置坐标
void SetPos(int x,int y)
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//获得句柄
COORD pos;
pos.X = x;
pos.Y = y;
SetConsoleCursorPosition(handle, pos);//设置光标位置
}
//2.建立地图
void CreateMap()
{
int i;
for ( i = 0; i <= 56; i+=2)
{
SetPos(i, 0);
printf(WALL);
SetPos(i, 26);
printf(WALL);
}
for (i = 1; i <= 25; i++)
{
SetPos(0, i);
printf(WALL);
SetPos(56, i);
printf(WALL);
}
}
//3.初始化蛇
void InitSnake(pSnake pS)
{
pSnakeNode Cur = malloc(sizeof(SnakeNode));
Cur->x = INIT_X;
Cur->y = INIT_Y;
Cur->next = NULL;
//采用头插
for (size_t i = 1; i <= 4; i++)
{
//头插进一个新的节点
pS->_pSnake = malloc(sizeof(SnakeNode));
pS->_pSnake->next = Cur;//使新节点连接上之前的节点
pS->_pSnake->x = INIT_X + i * 2;//新节点赋值
pS->_pSnake->y = INIT_Y;
Cur = pS->_pSnake; //使Cur指向第一个节点
}
pS->_Dir = RIGHT;
pS->_status = OK;
}
//4.打印蛇
void SnakePrint(pSnake pS)
{
pSnakeNode Cur = NULL;
pSnakeNode pFood = NULL;
assert(pS);
//打印蛇
Cur = pS->_pSnake;
while (Cur)
{
SetPos(Cur->x, Cur->y);
printf(SNAKE);
Cur = Cur->next;
}
SetPos(60, 20);
}
//打印食物
void FoodPrint(pSnake pS)
{
SetPos(pS->_pFood->x, pS->_pFood->y);
printf(FOOD);
}
//5. 设置食物
void GetRandFood(pSnakeNode pFood)//生成随机食物
{
srand((unsigned)time(NULL));
do
{
pFood->x = rand() % 53 + 2;
} while (pFood->x % 2 != 0);
pFood->y = rand() % 25 + 1;
}
void CreadeFood(pSnake pS)
{
pSnakeNode pFood = malloc(sizeof(SnakeNode));
pSnakeNode Cur = NULL;
assert(pS);
int flag = 1;
while (flag)
{
GetRandFood(pFood);
Cur = pS->_pSnake;
while (Cur != NULL)
{
if (Cur->x != pFood->x && Cur->y != pFood->y)
Cur = Cur->next;
else break;
}
if(Cur == NULL)
flag = 0;
}
pS->_pFood = pFood;
}
//6. 蛇的移动
int HasFood( pSnake pS , pSnakeNode pNextNode)
{
if (pNextNode->x == pS->_pFood->x && pNextNode->y == pS->_pFood->y)
return 1;
else return 0;
}
void EatFood(pSnake pS, pSnakeNode pNextNode)
{
assert(pS);
assert(pNextNode);
pNextNode->next = pS->_pSnake;
pS->_pSnake = pNextNode;
CreadeFood(pS);
FoodPrint(pS);
}
void NEatFood(pSnake pS, pSnakeNode pNextNode)
{
pSnakeNode Cur = NULL;
assert(pS);
assert(pNextNode);
pNextNode->next = pS->_pSnake;
pS->_pSnake = pNextNode;
Cur = pS->_pSnake;
while (Cur && Cur->next != NULL )
{
if (Cur->next->next == NULL)
{
SetPos(Cur->next->x, Cur->next->y);
printf(" ");
free(Cur->next);
Cur->next = NULL;
break;
}
Cur = Cur->next;
}
}
void SnakeMove(pSnake pS)
{
pSnakeNode NextNode = malloc(sizeof(SnakeNode));
NextNode->x = pS->_pSnake->x;
NextNode->y = pS->_pSnake->y;
switch (pS->_Dir)
{
case UP:
NextNode->y -= 1;
break;
case DOWN:
NextNode->y += 1;
break;
case LEFT:
NextNode->x -= 2;
break;
case RIGHT:
NextNode->x += 2;
break;
}
if (HasFood(pS, NextNode))
EatFood(pS, NextNode);
else NEatFood(pS, NextNode);
}
//7. 控制蛇运动
void SnakeRun(pSnake pS)
{
do{
if (GetAsyncKeyState(VK_UP) && pS->_Dir != DOWN)
pS->_Dir = UP;
if (GetAsyncKeyState(VK_DOWN) && pS->_Dir != UP)
pS->_Dir = DOWN;
if (GetAsyncKeyState(VK_LEFT) && pS->_Dir != RIGHT)
pS->_Dir = LEFT;
if (GetAsyncKeyState(VK_RIGHT) && pS->_Dir != LEFT)
pS->_Dir = RIGHT;
SnakeMove(pS);
SnakePrint(pS);
GomeOver(pS);
Sleep(300);
} while (pS->_status == OK);
}
//8. 游戏结束
int KillByWall(pSnake pS)
{
if (pS->_pSnake->x == 0 || pS->_pSnake->x == 56 ||
pS->_pSnake->y == 0 || pS->_pSnake->y == 27)
return 1;
else return 0;
}
int KillBySelf(pSnake pS)
{
pSnakeNode pCur = NULL;
assert(pS);
pCur = pS->_pSnake->next;
while (pCur)
{
if(pS->_pSnake->x == pCur->x && pS->_pSnake->y == pCur->y)
{
return 1;
}
pCur = pCur->next;
}
return 0;
}
int KillByNo(pSnake pS)
{
if (GetAsyncKeyState(VK_F1))
{
return 1;
}
else return 0;
}
void GomeOver(pSnake pS)
{
if (KillByWall(pS))
{
pS->_status = KILL_BY_WALL;
SetPos(22,13);
printf("撞墙死亡\n");
}
else if (KillBySelf(pS))
{
pS->_status = KILL_BY_WALL;
SetPos(22, 13);
printf("撞到自己\n");
}
else if (KillByNo(pS))
{
pS->_status = KILL_NORMAL;
SetPos(22, 13);
printf("自杀\n");
}
}
void SnakeStart(pSnake pS)
{
CreateMap();
InitSnake(pS);
CreadeFood(pS);
FoodPrint(pS);
SnakePrint(pS);
SnakeRun(pS);
}
//9. 欢迎界面
void Welcome()
{
CreateMap();
SetPos(14, 10);
printf("*********************************\n");
SetPos(14, 14);
printf("*********************************\n");
SetPos(16, 11);
printf("欢 迎 来 到 贪 吃 蛇 游 戏...\n");
SetPos(20, 13);
system("pause");
system("cls");
Sleep(500);
}
//游戏
void game()
{
Snake snake;
pSnake pS = &snake;
Welcome();
SnakeStart(pS);
}