C语言 贪吃蛇

本文详细介绍了如何使用C语言通过window.h库实现贪吃蛇游戏的动画效果,包括蛇的初始化、随机食物生成、移动与增长,以及游戏结束条件。通过实例代码演示了如何利用光标移动函数模拟蛇的动画和控制台界面的操作。
摘要由CSDN通过智能技术生成

C语言 贪吃蛇

感谢博友的文章,本文章借鉴了博友“隔壁的二大爷”的思路,小白入门——“贪吃蛇”的C语言实现(详细)

平常编写C语言代码都是在Console控制台进行调试,想要实现复杂的可视化操作需要引进其他的头文件。网上大部分利用C语言实现贪吃蛇都引入了graphic.h头文件,受限于本人技术有限,在dev-c++环境中没有测试成功。后来看到了博友隔壁二大爷的文章,利用window.h提供的光标移动函数可以模拟出蛇的动画效果

效果

下图是程序运行的效果图:
在这里插入图片描述

调试环境
  • 编译环境: Dev-C++ TDM-GCC 9.2.0 64位
  • 环境配置: 游戏添加了背景音乐,引入<mmsystem.h>头文件,在编译选项中需要加入编译条件: 工具 -> 编译选项 第二行中加入 -lwinmm 如下图

在这里插入图片描述

程序需要完成工作

  1. 初始化工作
  2. 随机食物
  3. 蛇的移动
  4. 蛇的增长
  5. 游戏结束

下面就以程序来讲解各项工作的实现方法

头文件
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>          //光标移动
#include <time.h>             //食物随机使用到时间函数
#include <conio.h>            //键盘事件获取
#include <mmsystem.h>         //音乐播放函数
数据定义
#define false 0
#define true  1
//坐标结构体
typedef struct Point {
	int x;
	int y;
} Point;
//蛇的结构体
typedef struct Snaker {
	Point pos;
	struct Snaker *next;
} Snake;
// 定义蛇头
Snake *snakeHead = NULL;

//食物结构体
typedef struct Food {
	Point pos;
} Food;
Food food;

//键盘方向,space是初始状态
enum Dirt {
	right = 77,
	left =  75,
	up   = 72,
	down = 80,
	space = 0
} dirt;

void startGame(); 					//游戏开始界面
void gameOver();					//游戏结束界面
void drawGraph();					//画背景
void gotoXY(int x, int y);			//到指定坐标
void gotoPrint(int x, int y);		//图标输出
void gotoDelete(int x, int y);		//图标删除
void drawFood();					//食物产生
int  keyPress();					//键盘操作
int  Judge();						//游戏状态
void snakerMove();					//蛇移动
void snakerEating();				//蛇吃食物
void snakerPostion();				//蛇的坐标
void freeLink();                    //删除蛇链表
void playBgMusic();					//播放背景音乐

char name[256]; //玩游戏输入的名字
int  score = 0; //记录分数
int  count  = 0; //蛇的长度
int  speed = 150; //蛇行驶速度
函数定义
  • 光标移动函数(gotoXY)
//该代码借鉴了上面提到的博友的代码,有兴趣的可以到原博主页面研究研究
void gotoXY(int x, int y) {
	//更新光标位置
	COORD pos;
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	pos.X = x;
	pos.Y = y;
	SetConsoleCursorPosition(handle, pos);

	// 隐藏光标
	CONSOLE_CURSOR_INFO cursor;
	cursor.bVisible = FALSE;
	cursor.dwSize = sizeof(cursor);
	SetConsoleCursorInfo(handle, &cursor);
}
  • 图标打印函数(gotoPrint)
//光标移动到指定位置,打印图标
void gotoPrint(int x, int y) {
	gotoXY(x, y);
	printf("■");

}
  • 图标删除函数(gotoDelete)
void gotoDelete(int x, int y) {
	gotoXY(x, y);
	printf("  "); //输出空格
}
  • 主函数:
int main(int argc, char *argv[]) {
	dirt = space;
	playBgMusic();
	system("color 0B");
	startGame();
	drawGraph();
	drawFood();

	if (keyPress() == 0)
		return 0;

	return 0;
}

  • 初始化背景音乐(playBgMusic)
void playBgMusic() {
    //调用mciSendString实现背景音乐播放, tsc.mp3 为软件同目录下的音乐文件
	mciSendString(TEXT("open tcs.mp3 alias bkmusic"), NULL, 0, NULL);
	mciSendString(TEXT("play bkmusic repeat"), NULL, 0, NULL);
}
  • 游戏开始界面初始化(startGame)
void startGame() {
	gotoXY(15, 10);
	printf("/*************************************/");
	gotoXY(15, 20);
	printf("/*************************************/");
	gotoXY(20, 13);
	printf("Welcome to the game of snaker");
	gotoXY(20, 15);
	printf("↑ → ↓ ← control position");
	gotoXY(20, 18);
	printf("Please enter your name:"); //输入用户名
	scanf("%s", name);

	system("cls");	//清除屏幕
}

初始化的界面如下,需要输入用户名才能进行进入下一个页面
在这里插入图片描述

  • 描述背景(drawGraph)
void drawGraph() {
	// 背景上下的围挡
	for (int i = 0; i < 58; i += 2) {
		gotoPrint(i, 0);
		gotoPrint(i, 26);
	}

	//背景左右的围挡
	for (int i = 0; i < 27; i++) {
		gotoPrint(0, i);
		gotoPrint(58, i);
	}

	//右侧提示得分信息
	gotoXY(63, 10);
	printf("hello %s, have fun!", name);
	gotoXY(63, 15);
	printf("Your Score Is: %d", score);


	//初始化蛇,默认三个方块,设定默认的初始位置,蛇头在最上方
	snakeHead = (Snake *)malloc(sizeof(Snake));
	Snake *body1 = (Snake *)malloc(sizeof(Snake));
	Snake *body2 = (Snake *)malloc(sizeof(Snake));

	snakeHead->pos.x = 16;
	snakeHead->pos.y = 15;

	body1->pos.x = 16;
	body1->pos.y = 16;

	body2->pos.x = 16;
	body2->pos.y = 17;

	snakeHead->next = body1;
	body1->next = body2;
	body2->next = NULL;

	//打印蛇
	gotoPrint(snakeHead->pos.x, snakeHead->pos.y);
	gotoPrint(body1->pos.x, body1->pos.y);
	gotoPrint(body2->pos.x, body2->pos.y);

	count = 3;

}

游戏的界面如下图,左侧是游戏界面,右侧是信息提示界面,实时更新用户的得分。
在这里插入图片描述

  • 随机食物函数(drawFood
    食物的随机使用C语言的rand函数,使用rand函数前需要调用srand函数,否则随机出来的数据都是相同的。 另外,随机食物需要考虑食物的坐标是否在蛇的身上,如果出现在蛇的身上需要重新随机,直到食物位置和蛇不重叠。 可以看到上图@为随机出现的食物
/*
创建食物,要判断是否与蛇重叠 
*/
void drawFood() {
	char flag = false;

	while (!flag) {
		flag = true;
		srand((int)time(NULL));

		food.pos.x = rand() % 50 + 2;
		food.pos.y = rand() % 24 + 1;

		if (food.pos.x % 2 != 0) {
			food.pos.x = food.pos.x + 1;
		}

		//检查是否与蛇重叠
		Snake *judge = snakeHead;

		while (judge) {
			if ((food.pos.x == judge->pos.x)
			        && (food.pos.y == judge->pos.y )) {
				flag = false;
			}
			judge = judge->next;
		}
	}
	gotoXY(food.pos.x, food.pos.y);
	printf("@");
}

  • 蛇移动函数(snakeMove)
    蛇移动有两种种实现的方式:(1)清除蛇尾部,根据新的坐标新建蛇头,重新绘画蛇; (2)根据蛇头采用新的坐标,蛇身其他节点依次采用前一个节点的坐标; 该文章采用第二种实现方式
void snakerMove() {
	int x = snakeHead->pos.x;
	int y = snakeHead->pos.y;
    
	//根据键盘事件确定蛇头新的坐标
	switch (dirt) {
		case right:
			x += 2;
			break;
		case left:
			x -= 2;
			break;
		case up:
			y -= 1;
			break;
		case down:
			y += 1;
			break;
		default:
			break;
	}

	Snake *ptr = snakeHead;
	int t_x, t_y;

    //dirt 为space时,代表游戏刚开始,还没有键盘事件,蛇不动
	while (dirt && ptr) {
		t_x = ptr->pos.x;
		t_y = ptr->pos.y;

		ptr->pos.x = x;
		ptr->pos.y = y;

		x = t_x;
		y = t_y;

		ptr = ptr->next;
	}

	if (dirt) {
		gotoPrint(snakeHead->pos.x, snakeHead->pos.y);	//绘画新的蛇头
		gotoDelete(x, y); //清除蛇尾
	}


	//该部分是增加游戏难度,达到一定分数以后提高蛇的行走速度
	int count = score / 10;

	if (count <= 10)
		speed = 150;
	else if (count > 10 && count <= 20)
		speed = 100;
	else if (count > 20 && count <= 40)
		speed = 50;
	else
		speed = 20;
	Sleep(speed);

}
  • 键盘事件函数(keyPress)
int keyPress() {
	int c;

	while (1) {
		//判断是否达到结束条件
		if (Judge() == 0)
			return 0;
        //获取键盘事件
		if (_kbhit()) {
			_getch();        //方向键会输出两个值,去除第一个值,获取第二个值
			c = dirt;
			dirt = _getch();
			//蛇不能倒退行驶; 蛇头初始位置在上面,开始不能向下运动
			if (abs(c - dirt) == 8 || abs(c - dirt) == 2 || (c == space && dirt == down))
				dirt = c;
		}

		snakerMove();       //蛇移动
		snakerEating();     //吃食物

	}
	return 1;
}
  • 吃食物(snakerEating)
    蛇吃食物的判断条件是食物的坐标和蛇头的坐标相同。 蛇吃到食物后需要进行三项操作,一是随机新的食物,二是蛇身增加,三是更新得分
void snakerEating() {
	//蛇头的位置同食物的位置相同
	if (snakeHead->pos.x == food.pos.x
	        && snakeHead->pos.y == food.pos.y) {
		//创建新的食物
		drawFood();

		//蛇身加长,尾部增加新节点
		Snake *ptr = snakeHead;
		while (ptr->next) {
			ptr = ptr->next;
		}

		Snake *nBody = (Snake *)malloc(sizeof(Snake));
		nBody->next = NULL;
		nBody->pos.x = ptr->pos.x;
		nBody->pos.y = ptr->pos.y;
		ptr->next = nBody;
	

		//得分增加
		score += 10;
		gotoXY(77, 15);
		printf("%d", score);
	}
}

  • 结束条件(Judge)
    游戏结束条件有两种: 一是蛇撞击墙面,二是蛇撞击自己;两种情况都需要考虑
int Judge() {

	//蛇撞墙
	if (snakeHead->pos.x == 0 || snakeHead->pos.x == 58 ||
	        snakeHead->pos.y == 0 || snakeHead->pos.y == 26) {
		gameOver();
	}
	//蛇撞自己

	Snake *ptr = snakeHead->next;
	while (ptr) {
		if ((ptr->pos.x == snakeHead->pos.x) &&
		        (ptr->pos.y == snakeHead->pos.y)) {
			gameOver();
			return 0;
		}

		ptr = ptr->next;
	}

	return 1;
}
  • 游戏结束页面(gameOver)
void gameOver() {
	system("cls");
	gotoXY(15, 10);
	printf("/*************************************/");
	gotoXY(20, 13);
	printf("Game Over");
	gotoXY(20, 15);
	printf("Your Score is %d ", score);
	gotoXY(15, 20);
	printf("/*************************************/");

	//释放链表
	freeLink();
}

在这里插入图片描述

  • 游戏结束,释放链表(freeLink)
void freeLink() {
	Snake *ptr;
	while (snakeHead) {
		ptr = snakeHead;
		snakeHead = snakeHead->next;
		free(ptr);
	}

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值