为了练习链表和指针,用easyX写了个贪吃蛇.
源代码和可执行文件
提取码:GGYY
界面
功能
- 正常的行走
- 按Q加速
- 按空格暂停
原理
实现一个方块的移动
要想让一个小方块的移动,其实就是先把这个方块消失(涂成背景色),然后在要移往的方向生成一个方块(画一个矩形),涂成其他颜色(不同于背景色即可)
实现多个方块的移动
一开始,我的想法是:让蛇身跟着蛇头走,蛇头移动一次,蛇身的每个方块都移动一次.那么假设移动一个方块执行5行代码,蛇长20,一秒钟移动4格,那么完成一次移动,需要执行100行代码,一秒钟需要执行400行代码.这太蠢了.
后来睡不着,就想到了另一个方法:每次移动,实际上不就是尾部消失,和在移动的方向长出新的蛇头嘛.这样以来,性能就提高很多了.
如何判断吃到食物
判断蛇头中心点与食物中心点的距离,这个距离小于矩形一半宽度时,则表明吃到食物
源码
使用说明:
- 需要安装easyX;
- 需要VisualStudio;
- 项目设置为x86;
- 文件后缀为.cpp.
函数功能说明
- void init():绘制窗体,网格,生成蛇,生成一个食物,
- void move(int, int):移动,参数一为方向(1=上,2=下,3=左,4=右),参数二为速度(移动完成后的睡眠时间)
- **void draw_rec(RECP)**绘制小方块,参数一表示一个方块结构体指针
- **RECP get_tail()**获取蛇的蛇尾,返回蛇尾小方块的指针
- **RECP get_newHead(int)**获取新的头部,参数1表示新的头部位于旧头部的方向
- **void colide_check()**检测是否发生碰撞,以及绘制文字
- **void gen_food()**生成食物,每次迟到食物后会调用此函数
- **void game_over()**游戏结束,撞墙后会调用此函数
数据及变量说明
结构体REC:
是矩形(rectangle)的缩写,蛇身的最小单元,食物也用这个结构体来表示,x1,y1表示左上角顶点的坐标.x2,y2表示右下角顶点的位置,next是一个指向REC的指针,food_type表示食物的类型,color表示矩形的颜色.
指针RECP:
REC类型的指针
- REC r[NUMB]:指定初始化时,蛇的长度
- RECP snake_head = &r[0]; 蛇头指针
- RECP food;食物指针
- int offset = (CELL_WIDTH / 2) - 2;为了防止消除蛇块时,抹除网格,使得蛇块长宽小于网格长宽2像素
- int fcx, fcy, scx, scy;食物的中心点 蛇的中心点
- int score = 0;分数
- float distance = 0;蛇头与食物的距离
- int up_fruit_n = 10; 加速果实的数量
- int length = 2;蛇的长度
- 10.bool crushed = false; 是否撞到了墙
#include <stdio.h>
#include <stdlib.h>
#include <graphics.h>
#include <conio.h>
#include <Windows.h>
#include <time.h>
#include <math.h>
#include <string.h>
#pragma warning (disable:4996) //抑制警告: sprintf unsafe
#define NUMB 2 //初始长度
#define ROW 100 //行数
#define WIDTH 1000 //窗口长宽
#define SPEED 50 //速度
#define CELL_WIDTH WIDTH / ROW //时间间隔
#define INIT_X 800 //蛇头初始X
#define INIT_Y 100 //蛇头初始Y
#define SNAKE_COLOR WHITE //蛇身颜色
#define FU_EFFECT 20 //加速果实效果
#define GRID_COLOR BLUE //网格颜色
#define DRAW_GRID true //是否绘制网格
typedef struct node {
int x1;
int y1;
int x2;
int y2;
struct node* next;
int food_type;
COLORREF color;
} REC;
typedef REC* RECP;
//食物类型:
//加分类:1:距离中心点最远 +500分 2:距离中 +100分 3:距离近 +10分
//buff类:4:快速向当前方向移动10*CELL_WIDTH
void init(); //初始界面
void move(int, int); //向指定的方向移动一次 参数一 方向 参数二 速度
void draw_rec(RECP); //绘制指定(参数一)的矩形
RECP get_tail(); //获取蛇尾节点
RECP get_newHead(int); //获取新的蛇头 参数一表示新蛇头相对于旧蛇头的位置
void colide_check(); //检测是否撞墙或者撞到自己
void gen_food(); //生成食物
void game_over();
REC r[NUMB];
RECP snake_head = &r[0]; //指定数组第一位为蛇头
RECP food;
int offset = (CELL_WIDTH / 2) - 2; //矩形中心点与边的距离
int fcx, fcy, scx, scy; //食物中心点 蛇头中心点
int score = 0; //分数
float distance = 0; //蛇头与食物的距离
int up_fruit_n = 10; //加速果实的数量
int length = 2; //蛇长
bool crushed = false; //是否撞墙
int main()
{
init();
char to; //用来记录暂停前 蛇头的朝向
char c = 's'; //游戏开始默认 向下走
while (1) {
switch (c) {
w:case 'w':
to = 'w';
while (1) {
if (crushed) goto b;//如果撞墙状态为true 则结束游戏
if (!_kbhit()) move(1, SPEED); //当键盘没有输入时,持续向当前方向移动
else {
char t = _getch();
if (t == 'a')goto a;//输入a时跳转到向左移动
if (t == ' ')goto k;输入k时跳转暂停
if (t == 'd')goto d; //当键盘输入时,判断输入的按键
if (t == 'q' && up_fruit_n > 0) {
up_fruit_n--;//完成一次加速后 加速果实-1
for (int i = 0; i < FU_EFFECT; i++) move(1, 10);//FU_EFFECT表示使用加速果实后,移动的移动的格数
}
}
}
break;
s:case 's':
to = 's';
while (1) {
if (crushed) goto b;
if (!_kbhit()) move(2, SPEED);
else {
char t = _getch();
if (t == 'a')goto a;
if (t == 'd')goto d;
if (t == ' ')goto k;
if (t == 'q' && up_fruit_n > 0) {
up_fruit_n--;
for (int i = 0; i < FU_EFFECT; i++) move(2, 10);
}
}
}
break;
a:case 'a':
to = 'a';
while (1) {
if (crushed)goto b;
if (!_kbhit()) move(3, SPEED);
else {
char t = _getch();
if (t == 'w')goto w;
if (t == 's')goto s;
if (t == ' ')goto k;
if (t == 'q' && up_fruit_n > 0) {
up_fruit_n--;
for (int i = 0; i < FU_EFFECT; i++) move(3, 10);
}
}
}
break;
d:case 'd':
to = 'd';
while (1) {
if (crushed)goto b;
if (!_kbhit()) move(4, SPEED);
else {
char t = _getch();
if (t == 'w')goto w;
if (t == 's')goto s;
if (t == ' ')goto k;
if (t == 'q' && up_fruit_n > 0) {
up_fruit_n--;
for (int i = 0; i < FU_EFFECT; i++) move(4, 10);
}
}
}
break;
k:case ' ': //暂停
while (_getch() != ' ');//使用_getch函数来判断键盘输入的是不是空格,不是空格则一直执行空的循环体,是空格则不执行,to表示进入暂停前的移动方向,解除暂停后就会继续向这个方向移动
if (to == 'w')goto w;
if (to == 'a')goto a;
if (to == 's')goto s;
if (to == 'd')goto d;
break;
}
}
b:game_over();
return 0;
}
RECP get_tail() {//遍历整个蛇链,找到蛇尾的前继的节点
REC* current = snake_head;
while (1) {
if (current->next) {
if (current->next->next)
current = current->next;
else break;
}
}
return current;
}
void move(int direction, int _sleep_time) {
RECP preTail = get_tail();
RECP newHead = get_newHead(direction);
newHead->color = RED;
snake_head = newHead; //将新的头部设置为蛇头
preTail->next->color = BLACK;
draw_rec(preTail->next); //清除尾部
preTail->next = NULL; //断开尾部的链接
//free(preTail->next); //清理已断开的尾部所占用的内存
draw_rec(newHead); //画出新的头部
colide_check();
Sleep(_sleep_time);
}
void colide_check() { //检测蛇头碰撞到了 什么物体(食物,墙,自己)
fcx = food->x1 + offset;
fcy = food->y1 + offset;
scx = snake_head->x1 + offset;
scy = snake_head->y1 + offset;
distance = sqrt((scx - fcx) * (scx - fcx) + (scy - fcy) * (scy - fcy));
if (distance < offset) {//吃到食物
food->next = snake_head;//让食物成为新的蛇头
snake_head = food;
if (food->food_type == 1)score += 10;//判断食物的类型,进行奖励
if (food->food_type == 2)score += 100;
if (food->food_type == 3)up_fruit_n++;
if (food->food_type == 4)score += 500;
char s_t[20]; //分数文字
sprintf(s_t, "分数:%d ", score);
outtextxy(10, 110, s_t);//显示分数
gen_food(); //重新生成食物
length++;//长度+1
}; //蛇头中点与食物中点的距离 < 矩形中点到边的距离 则 蛇吃到食物
if (scx <0 || scy < 30 || scx > WIDTH || scy > WIDTH)crushed = true;
char f_loc[20]; //食物位置
char up_count[20]; //加速果实
char s_h[20]; //蛇头位置
char s_len[20]; //蛇长
sprintf(s_len, "长度:%d", length);
sprintf(up_count, "加速果实:%d ", up_fruit_n);
sprintf(f_loc, "食物位置:%d,%d ", fcx, fcy);
sprintf(s_h, "蛇头位置:%d,%d", scx, scy);
outtextxy(10, 30, f_loc);
outtextxy(10, 50, up_count);
outtextxy(10, 70, s_h);
outtextxy(10, 90, s_len);
}
void gen_food() {
srand((int)time(0));
food = (RECP)malloc(sizeof(REC));
food->x1 = (rand() % ROW - 10) * CELL_WIDTH + 2; //随机生成食物的位置
food->y1 = (rand() % ROW - 10) * CELL_WIDTH + 2;
food->x2 = food->x1 + CELL_WIDTH - 4;
food->y2 = food->y1 + CELL_WIDTH - 4;
int res;
srand(time(0));
int ran = rand() % 11; //生成 一个11以内的随机数
if (ran % 6 == 0) res = 6; //如果这个随机数能被6整除 让概率res=6 ,11以内的数里只有6能被6整除,概率为10%
else if (ran % 5 == 0) res = 5; //如果这个随机数能被5整除 让概率res=5 ,11以内的数里10和5能被5整除,概率为20%
else if (ran % 3 == 0) res = 3; //如果这个随机数能被3整除 让概率res=3 ,11以内的数里3,6,9能被3整除,概率为30%
else res = 2;//否则让概率res=2 ,11以内的数里2,4,6,8,10能被2整除,概率为50%
switch (res) {
case 6: //10% 几率最小 生成食物1 +500分
food->food_type = 4;
food->color = RED;
draw_rec(food);
break;
case 5: //20% 生成 加速果实
food->food_type = 3;
food->color = GREEN;
draw_rec(food);
break;
case 3: //30% 生成 食物二 + 100分
food->food_type = 2;
food->color = YELLOW;
draw_rec(food);
break;
case 2: //50% 生成 食物三 +10分
food->food_type = 1;
food->color = DARKGRAY;
draw_rec(food);
break;
}
}
RECP get_newHead(int direction) {
RECP newHead = (RECP)malloc(sizeof(REC));
REC head = *snake_head;
switch (direction) {
case 1: //向上移动时 x1 x2 相等 y1-cell_width y2-cell_width
*newHead = { head.x1,head.y1 - CELL_WIDTH,head.x2,head.y2 - CELL_WIDTH,snake_head };
break;
case 2: //向下移动时 x1 x2 相等 y1+cell_width y2+cell_width
*newHead = { head.x1,head.y1 + CELL_WIDTH,head.x2,head.y2 + CELL_WIDTH,snake_head };
break;
case 3: //向左移动时 y1 y2 相等 x1-cell_width x2-cell_width
*newHead = { head.x1 - CELL_WIDTH,head.y1,head.x2 - CELL_WIDTH,head.y2,snake_head };
break;
case 4: //向右移动时 y1 y2 相等 x1+cell_width x2+cell_width
*newHead = { head.x1 + CELL_WIDTH,head.y1,head.x2 + CELL_WIDTH,head.y2,snake_head };
break;
}
return newHead;
}
void init() {
initgraph(WIDTH + 1, WIDTH + 1, EW_DBLCLKS);
HWND hwnd = GetHWnd();
MoveWindow(hwnd, 0, 0, WIDTH + 6, WIDTH + 9, false); //默认窗体过于靠下 向上移动窗体
setlinecolor(GRID_COLOR);
if (DRAW_GRID) {
for (int i = 0; i <= ROW; i++) { //绘制网格
line(i * CELL_WIDTH, 0, i * CELL_WIDTH, WIDTH);
line(0, i * CELL_WIDTH, WIDTH, i * CELL_WIDTH);
}
}
setfillcolor(BLACK);
solidrectangle(0, 0, WIDTH, 29);
r[0] = { INIT_X + 2, INIT_Y + 2, INIT_X + CELL_WIDTH - 2, INIT_Y + CELL_WIDTH - 2, &r[1] };
for (int i = 0; i < NUMB; i++) { //对蛇进行初始化
r[i] = {
snake_head->x1 - (CELL_WIDTH * i),
snake_head->y1,
snake_head->x2 - (CELL_WIDTH * i),
snake_head->y2,
&r[i + 1] };
if (i == NUMB - 1) {
r[i].next = NULL;
}
draw_rec(&r[i]);
} //绘制蛇
outtextxy(10, 130, "Q=加速,空格=暂停"); //显示提示
gen_food();
}
void draw_rec(RECP _rec) {
if (_rec->color == NULL) setfillcolor(SNAKE_COLOR);
setfillcolor(_rec->color);
solidrectangle(_rec->x1, _rec->y1, _rec->x2, _rec->y2);
}
void game_over() {
char s[20];
sprintf(s,"GAME OVER! 的得分为%d",score);
outtextxy(500,500,s);
_getch();
closegraph();
};