用C语言写一个简单的贪吃蛇游戏(用到easyx图形库)

  • Post author:
  • Post category:其他



学习内容:

1.游戏的存储与读写

2.制作游戏菜单

3.制作定时器


目录


一、需要用到的头文件、全局变量和一些函数


二、定时器


三、数据的初始化和绘制


四、蛇的运行和吃食物


五、游戏菜单界面


六、游戏存储与读写


七、其他函数


八、主函数入口



主菜单


游戏内容

一、需要用到的头文件、全局变量和一些函数

#include<stdio.h>
#include<easyx.h>
#include<time.h>
#include<conio.h>
#pragma comment (lib,"winmm.lib")

#define WIDTH 600//屏幕宽度与高度
#define HEIGHT 500
#define MAX_SNAKE 30//最大蛇长度

//全局变量
int game_state = 1;//游戏状态:1.游戏菜单选项 2.运行游戏 3.退出游戏  
int option;//菜单栏状态

bool game_pause;
bool snake_die;
MOUSEMSG m1;
MOUSEMSG m2;

//定一个方向枚举
enum DIR
{
	up,
	down,
	left,
	right
};

//蛇结构体
struct Snake
{
	POINT coord[MAX_SNAKE];
	int num;
	int len;
	int exis;
	int score;
	int dir;
}snake;

//食物结构体
struct Food
{
	POINT fd;
	int exis;
}food;

//需要用到的函数
void InitData();//初始化数据
void DrawImag();//绘制图画
void ReadGame();//读取要保存的数据
void SaveGame();//保存数据
void EatFood();//吃食物
void GameOver();//游戏结束
void GameMenu();//游戏菜单
void UpdataWithInput();//有数据输入
void UpdataWithoutInput();//无数据输入
void SnakeMove();//蛇的移动
void KeyControl();//键盘输入

二、定时器

用static 定义 start避免循环时被覆盖

开始start与end处以同一时间,start保存的时间不变,end时间一直在变化,直至end和start时间差大于等于ms,当相等时,再将start与end时间同步,重复以上操作,用时间差来控制程序运行快慢。

作用:满足时间要求时可进行某个操作,也不影响其他操作的进行。(可用于蛇的运动快慢)

bool timer(int ms, int id)
{
	static int start[6] = { clock() };
	int end = clock();
	if (end - start[id] >= ms)
	{
		start[id] = end;
		return true;
	}
	return false;
}

三、数据的初始化和绘制

void InitData()
{

	initgraph(WIDTH, HEIGHT, 1);//1表示显示控制台
	
	//播放背景音乐
	mciSendString("open .\\resource\\bk1.mp3 alias bgm1", NULL, 0, NULL);
	mciSendString("play bgm1 repeat", NULL, 0, NULL);

	//初始化蛇
	snake.len = 10;
	snake.num = 3;
	snake.dir = right;
	snake.score = 0;
	snake.coord[2].x = 0;
	snake.coord[2].y = 0;
	snake.coord[1].x = snake.len;
	snake.coord[1].y = 0;
	snake.coord[0].x = 2 * snake.len;//作为蛇头
	snake.coord[0].y = 0;

	// 画食物
	food.fd.x = rand() % (WIDTH / 10) * 10;
	food.fd.y = rand() % (HEIGHT / 10) * 10;
	food.exis = 1;

	//按键选项初始化0
	option = 0;
	//蛇是否死亡
	snake_die = false;
	//游戏是否暂停初始化为否
	game_pause = false;
}

//绘制图形画面
void DrawImag()
{
	setbkcolor(GREEN);
	cleardevice();
	BeginBatchDraw();
	while (game_state == 1)
	{
		GameMenu();
	}
	//画蛇
	for (int i = snake.num - 1; i >= 0; i--)
	{
		setfillcolor(YELLOW);
		setlinecolor(BLACK);
		fillrectangle(snake.coord[i].x, snake.coord[i].y, snake.coord[i].x + snake.len, snake.coord[i].y + snake.len);
		//printf("%d,%d\n", snake.coord[i].x, snake.coord[i].y);
	}

	//画食物
	setfillcolor(RED);
	solidellipse(food.fd.x, food.fd.y, food.fd.x + snake.len, food.fd.y + snake.len);

	settextstyle(25, 0, "黑体");
	outtextxy(20, 20,"得分:");
	TCHAR c[3];
	sprintf(c, "%02d", snake.score);
	outtextxy(90, 20, c);
	setbkmode(TRANSPARENT);

	//画出暂停与返回菜单界面按键
	char button_name1[10] = { "暂停游戏" };
	char button_name2[10] = { "继续游戏" };
	char button_name3[10] = { "返回菜单" };
	//按键框(仅供参考,可以自己在做一个小按键隐藏按钮,避免遮挡视线)
	setfillcolor(WHITE);
	fillrectangle(10, 400, 130, 440);
	fillrectangle(10, 450, 130, 490);
	setlinecolor(WHITE);//当鼠标不再范围内边框变白
	rectangle(10, 400, 130, 440);
	rectangle(10, 450, 130, 490);

	setbkmode(TRANSPARENT);
	settextcolor(BLACK);
	outtextxy(10 + 60 - textwidth(button_name3) / 2, 450 + 20 - textheight(button_name3) / 2, button_name3);
	if (game_pause)
	{
		outtextxy(10 + 60 - textwidth(button_name1) / 2, 400 + 20 - textheight(button_name1) / 2, button_name2);
	}
	else
	{
		outtextxy(10 + 60 - textwidth(button_name2) / 2, 400 + 20 - textheight(button_name2) / 2, button_name1);
	}
	if (m2.x >= 10 && m2.x <= 130 && m2.y >= 400 && m2.y <= 440)
	{
		setlinecolor(RED);//鼠标在方形区域时边框变红
		rectangle(10, 400, 130, 440);
		option = 1;
	}
	if (m2.x >= 10 && m2.x <= 130 && m2.y >= 450 && m2.y <= 490)
	{
		setlinecolor(RED);
		rectangle(10, 450, 130, 490);
		option = 2;
	}
    //当游戏结束时
	if (snake_die)
	{
		setbkmode(TRANSPARENT);
		settextcolor(RED);
		settextstyle(40, 20, "黑体");
		outtextxy(100, 200, "吃到自己了,游戏结束!!");
		outtextxy(100, 240, "是否继续游戏!!");
		outtextxy(100, 280, "继续请按Y   否请按N");
	}

	FlushBatchDraw();
}

四、蛇的运行和吃食物

//蛇的移动
void SnakeMove()
{
	
	for (int i = snake.num - 1; i > 0; i--)
	{
		snake.coord[i].x = snake.coord[i - 1].x;
		snake.coord[i].y = snake.coord[i - 1].y;
	}
	//snake.coord[0].x += 10;
	switch (snake.dir)
	{
	case up:
		snake.coord[0].y -= 10;
		if (snake.coord[0].y +10 == 0)
		{
			snake.coord[0].y = HEIGHT;
		}
		break;
	case down:
		snake.coord[0].y += 10;
		if (snake.coord[0].y - 10 == HEIGHT)
		{
			snake.coord[0].y = 0 ;
		}
		break;
	case right:
		snake.coord[0].x += 10;
		if (snake.coord[0].x -10 == WIDTH)
		{
			snake.coord[0].x = 0;
		}
		break;
	case left:
		snake.coord[0].x -= 10;
		if (snake.coord[0].x +10 == 0)
		{
			snake.coord[0].x = WIDTH;
		}
		break;
	default:
		break;
	}
	//判断蛇是不是吃到自己了
	for (int i = 3; i < snake.num; i++)//当蛇身只有三个时吃不到自己
	{
		if (snake.coord[0].x == snake.coord[i].x&&snake.coord[0].y == snake.coord[i].y)
		{
			snake_die = true;
		}
	}
}

//用键盘控制
void KeyControl()
{
	if (GetAsyncKeyState(VK_UP) && snake.dir != down)
	{
		snake.dir = up;
	}
	if (GetAsyncKeyState(VK_DOWN) && snake.dir != up)
	{
		snake.dir = down;
	}
	if (GetAsyncKeyState(VK_RIGHT) && snake.dir != left)
	{
		snake.dir = right;
	}
	if (GetAsyncKeyState(VK_LEFT) && snake.dir != right)
	{
		snake.dir = left;
	}
}

//吃食物
void EatFood()
{
	srand((unsigned int)time(NULL));
	if (snake.coord[0].x == food.fd.x&&snake.coord[0].y == food.fd.y)
	{
		food.fd.x = rand() % (WIDTH / 10)  * 10;
		food.fd.y = rand() % (HEIGHT / 10)  * 10;
		snake.num++;
		snake.score++;
	}
}

五、游戏菜单界面

//游戏 界面菜单
void GameMenu()
{
	
	cleardevice();

	BeginBatchDraw();
	//游戏名字
	char game_name[20] = { "贪吃蛇" };
	setbkcolor(TRANSPARENT);
	settextcolor(GREEN);
	settextstyle(40, 20, "华文琥珀");
	outtextxy(WIDTH / 2 - textwidth(game_name) / 2, 40, game_name);
	
	if (MouseHit())
	{
		m1 = GetMouseMsg();
		if (m1.uMsg == WM_LBUTTONDOWN)
		{
			//开始新游戏
			if (option == 1) 
			{
				game_state = 2;
				mciSendString("close bgm1", NULL, 0, NULL);
				Sleep(400);
				mciSendString("open .\\resource\\bk2.mp3 alias bgm2", NULL, 0, NULL);
				mciSendString("play bgm2 repeat", NULL, 0, NULL);
			}
			else if (option == 2) 
			{ 
				ReadGame(); game_state = 2; mciSendString("close bgm1", NULL, 0, NULL);
				Sleep(400);
				mciSendString("open .\\resource\\bk2.mp3 alias bgm2", NULL, 0, NULL);
				mciSendString("play bgm2 repeat", NULL, 0, NULL);
			}//读取进度开始游戏
			else if (option == 3) { SaveGame(); closegraph(); _exit(0); }//保存并退出游戏
		}
	}

	option = 0;
	char select1[9] = { "游戏开始" };
	char select2[9] = { "继续游戏" };
	char select3[9] = { "保存退出" };

	int textw = 140;
	int texth = 40;

	
	settextcolor(WHITE);
	//开始游戏按钮
	if (m1.x >= WIDTH*0.39 &&m1.x <= WIDTH*0.39 + textw &&m1.y >= HEIGHT*0.3&&m1.y <= HEIGHT*0.3 + texth)
	{
		//settextcolor(RED);
		settextstyle(35, 0, "华文彩云");//悬浮在按钮上
		outtextxy(WIDTH*0.39, HEIGHT*0.3, select1);
		option = 1;
		//getchar();
	}
	else 
	{ 
		settextstyle(35, 0, "黑体"); //不在按钮上
		outtextxy(WIDTH*0.39, HEIGHT*0.3, select1);
	}

	//继续游戏
	if (m1.x >= WIDTH*0.39 &&m1.x <= WIDTH*0.39 + textw &&m1.y >= HEIGHT*0.5&&m1.y <= HEIGHT*0.5 + texth)
	{
		settextstyle(35, 0, "华文彩云");
		outtextxy(WIDTH*0.39, HEIGHT*0.5, select2);
		option = 2;
	}
	else
	{
		settextstyle(35, 0, "黑体");
		outtextxy(WIDTH*0.39, HEIGHT*0.5, select2);
	}

	//保存并退出游戏
	if (m1.x >= WIDTH*0.39 &&m1.x <= WIDTH*0.39 + textw &&m1.y >= HEIGHT*0.7&&m1.y <= HEIGHT*0.7 + texth)
	{
		settextstyle(35, 0, "华文彩云");
		outtextxy(WIDTH*0.39, HEIGHT*0.7, select3);
		option = 3;
	}
	else
	{
		settextstyle(35, 0, "黑体");
		outtextxy(WIDTH*0.39, HEIGHT*0.7, select3);
	}
	FlushBatchDraw();
	//clearrectangle(0, 0, WIDTH - 1, HEIGHT - 1);//用背景色清除当前区域
}

六、游戏存储与读写

文件打开失败没有连接其他功能,可自行添加

打开文件之后需要关闭文件

//存档
void SaveGame()
{
	char ch[20];
	sprintf(ch, "del storage.txt");
	system(ch);//删除已有存档

	FILE* stor;

	if ((stor = fopen("storage.txt", "w+")) == NULL)//打开失败
	{
		cleardevice();
		setbkmode(TRANSPARENT);
		settextcolor(RED);
		settextstyle(40, 20, "黑体");
		outtextxy(200, 300, "存档失败!!");
	}
	else
	{
		fwrite(&(snake.num), sizeof(snake.num), 1, stor);
		fwrite(&(snake.score), sizeof(snake.score), 1, stor);
		fwrite(&(snake.score), sizeof(snake.score), 1, stor);
		fwrite(&(snake.dir), sizeof(snake.dir), 1, stor);
		fwrite(&(snake.len), sizeof(snake.len), 1, stor);
		fwrite(&(food.fd), sizeof(food.fd), 1, stor);
		fwrite(&(food.exis), sizeof(food.exis), 1, stor);
		for (int i = 0; i <= snake.num; i++)
		{
			fwrite(&snake.coord[i], sizeof(snake.coord[i]), 1, stor);
		}
	}
	fclose(stor);
}

//读取游戏
void ReadGame()
{
	FILE* stor;
	if ((stor = fopen("storage.txt", "r")) == NULL)
	{
		cleardevice();
		setbkmode(TRANSPARENT);
		settextcolor(RED);
		settextstyle(40, 20, "黑体");
		outtextxy(200, 300, "读写失败,可能没有存档!!");
		getchar();
	}
	else
	{
		fread(&(snake.num), sizeof(snake.num), 1, stor);
		fread(&(snake.score), sizeof(snake.score), 1, stor);
		fread(&(snake.exis), sizeof(snake.exis), 1, stor);
		fread(&(snake.dir), sizeof(snake.dir), 1, stor);
		fread(&(snake.len), sizeof(snake.len), 1, stor);
		fread(&(food.fd), sizeof(food.fd), 1, stor);
		fread(&(food.exis), sizeof(food.exis), 1, stor);
		for (int i = 0; i <= snake.num ; i++)
		{
			fread(&snake.coord[i] , sizeof(snake.coord[i]) , 1, stor);
		}
	}
	fclose(stor);
}

七、其他函数

//当蛇吃到自己时游戏结束
void GameOver()
{
	if (snake_die)
	{
		char ch;
		ch = _getch();
		switch (ch)
		{
		case 'Y':
		case 'y':
			closegraph();
			InitData();
			mciSendString("close bgm1", NULL, 0, NULL);
			break;
		case'N':
		case'n':
			EndBatchDraw();
			closegraph();
			_exit(0);
			break;
		default:
			break;
		}
	}
}

//需输入更新
void UpdataWithInput()
{
	KeyControl();

	//判断是否有鼠标介入
	while (MouseHit())
	{
		m2 = GetMouseMsg();
		if (m2.uMsg == WM_LBUTTONDOWN)
		{
			if (option == 1)
			{
				game_pause = true;
			}
			else if (option == 2)
			{
				game_state = 1;
				mciSendString("close bgm2", NULL, 0, NULL);
				mciSendString("open .\\resource\\bk1.mp3 alias bgm1", NULL, 0, NULL);
				mciSendString("play bgm1 repeat", NULL, 0, NULL);
			}
		}

		//游戏暂停将会一直在此循环直到点击继续游戏或者返回主页菜单
		while (game_pause)
		{
			DrawImag();
			while (MouseHit())
			{
				m2 = GetMouseMsg();
				if (m2.uMsg == WM_LBUTTONDOWN)
				{
					if (option == 1)
					{
						game_pause = false;
					}
					else if (option == 2)
					{
						game_state = 1;
						mciSendString("close bgm2", NULL, 0, NULL);
						mciSendString("open .\\resource\\bk1.mp3 alias bgm1", NULL, 0, NULL);
						mciSendString("play bgm1 repeat", NULL, 0, NULL);
					}
				}
			}
		}

	}
}

//无需输入更新
void UpdataWithoutInput()
{
	if (timer(100, 0))
	{
		SnakeMove();
	}
	GameOver();
	EatFood();
	
}

八、主函数入口

//主入口
int main()
{
	InitData();
	
	while (true)
	{
		DrawImag();
		UpdataWithInput();
		UpdataWithoutInput();
	}
	closegraph();
	getchar();
	return 0;
}


总结:难点在于设计菜单需要用到多重循环,以及按键效果,数据存储时避免遗漏。

此游戏功能简单,仅供学习参考。(有不足的地方欢迎大家指出,希望能和大家一起学习进步)

有兴趣可以尝试完成其他功能,比如封装、添加ai蛇,速度随着长度增加,障碍物或用链表写一个贪吃蛇等。

easyx图形库资源,音乐资源网上可下载。



版权声明:本文为lwx1051046458原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。