C语言[贪吃蛇]

为了练习链表和指针,用easyX写了个贪吃蛇.
源代码和可执行文件
提取码:GGYY

界面

在这里插入图片描述

功能

  1. 正常的行走
  2. 按Q加速
  3. 按空格暂停

原理

实现一个方块的移动
要想让一个小方块的移动,其实就是先把这个方块消失(涂成背景色),然后在要移往的方向生成一个方块(画一个矩形),涂成其他颜色(不同于背景色即可)

实现多个方块的移动
一开始,我的想法是:让蛇身跟着蛇头走,蛇头移动一次,蛇身的每个方块都移动一次.那么假设移动一个方块执行5行代码,蛇长20,一秒钟移动4格,那么完成一次移动,需要执行100行代码,一秒钟需要执行400行代码.这太蠢了.
后来睡不着,就想到了另一个方法:每次移动,实际上不就是尾部消失,和在移动的方向长出新的蛇头嘛.这样以来,性能就提高很多了.

如何判断吃到食物
判断蛇头中心点与食物中心点的距离,这个距离小于矩形一半宽度时,则表明吃到食物

源码

使用说明:

  1. 需要安装easyX;
  2. 需要VisualStudio;
  3. 项目设置为x86;
  4. 文件后缀为.cpp.

函数功能说明

  1. void init():绘制窗体,网格,生成蛇,生成一个食物,
  2. void move(int, int):移动,参数一为方向(1=上,2=下,3=左,4=右),参数二为速度(移动完成后的睡眠时间)
  3. **void draw_rec(RECP)**绘制小方块,参数一表示一个方块结构体指针
  4. **RECP get_tail()**获取蛇的蛇尾,返回蛇尾小方块的指针
  5. **RECP get_newHead(int)**获取新的头部,参数1表示新的头部位于旧头部的方向
  6. **void colide_check()**检测是否发生碰撞,以及绘制文字
  7. **void gen_food()**生成食物,每次迟到食物后会调用此函数
  8. **void game_over()**游戏结束,撞墙后会调用此函数

数据及变量说明

结构体REC:
是矩形(rectangle)的缩写,蛇身的最小单元,食物也用这个结构体来表示,x1,y1表示左上角顶点的坐标.x2,y2表示右下角顶点的位置,next是一个指向REC的指针,food_type表示食物的类型,color表示矩形的颜色.
指针RECP:
REC类型的指针

  1. REC r[NUMB]:指定初始化时,蛇的长度
  2. RECP snake_head = &r[0]; 蛇头指针
  3. RECP food;食物指针
  4. int offset = (CELL_WIDTH / 2) - 2;为了防止消除蛇块时,抹除网格,使得蛇块长宽小于网格长宽2像素
  5. int fcx, fcy, scx, scy;食物的中心点 蛇的中心点
  6. int score = 0;分数
  7. float distance = 0;蛇头与食物的距离
  8. int up_fruit_n = 10; 加速果实的数量
  9. int length = 2;蛇的长度
  10. 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();
};

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值