C语言小游戏-贪吃蛇(配合EasyX,代码规范版本)

游戏效果:

 

 

 

头文件:

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include <stdio.h>
#include <stdbool.h>
#include <easyx.h>
#include <graphics.h>
#include <conio.h>
#include <stdlib.h>

//下面两个文件是用来播放音乐的,对媒体设备接口的头文件和另一个文件
#include <mmsystem.h> 
#include <Windows.h>
#pragma comment(lib,"winmm.lib") //因为我们在附加值里面加了winmm.lib,所以这行代码可加可不加

/*
	贪吃蛇
	用到的知识:结构体,循环,函数,easyx图形库,数组、部分指针
*/

void changetitle(void);//修改标题为贪吃蛇
void modeselect(void);//模式选择
void GameInit(void);//数据的初始化,也就是蛇的起始位置,同时蛇的节数最开始是一节
void GameDraw(int* num, int* HP);//给游戏的相应位置添加颜色
void SnakeMovebasis(int* sudu);//蛇的移动基础版
void SnakeMoveSpeed(int* sudu);//蛇的移动加速版
void SnakeMove_Survival_Mode(int* HP, int* sudu);//蛇的移动——生存模式
void SnakeMove_WithholdingBlood(int* HP);//蛇生存模式的吃到身体的扣血机制
void SnakeMove_Infinite_Mode(int* sudu);//蛇的移动——无限模式
void keyDirControl(void);//通过按键改变蛇的方向,同时兼顾游戏暂停和蛇的加速
void EatFood(int* p);//蛇吃食物
void snakeScoreAppear(int* score);//蛇吃食物的分数
void snakeSize(void);//显示蛇的节数


#define SNAKE_NUM 500//蛇的最大节数

//模式标记
bool mode = false;

//模式按钮范围标记
int fanwei = 0;

//实际模式按钮选择数据保存1
int shiji1 = 0;


//用户操作的方向上下左右,dir
enum DIR
{
	UP, DOWN, LEFT, RIGHT
};


//蛇的结构
struct Snake
{
	int size;//蛇的多少节

	enum DIR dir;//蛇的方向

	int speed;//实际移动速度

	int speed1 = 100;//调节速度

	int r;//蛇的一节(也可以是蛇头)的半径,注意在cpp里面的这里别赋值就是写成int r = speed / 2;,这样的结果只会是0
	//c语言的结构体成员是禁止赋值的

	//蛇的位置坐标,用数组
	POINT coor[SNAKE_NUM];

} snake;//因为只有一条蛇,所以用死定义变量

//食物结构
struct Food
{
	int x;//x坐标
	int y;//y坐标
	int r;//食物的半径,食物用圆去表示
	bool flag;	 //代表食物是否被吃了的标记
	DWORD color; //食物的颜色
}food;//只用一个食物,这里不是起别名,是直接定义了一个全局的struct Food类型的变量,名为food

核心代码:

#include "game.h"

//修改标题为贪吃蛇
void changetitle(void)
{
	HWND hnd = GetHWnd();//获得窗口句柄

	//修改窗口标题
	SetWindowText(hnd, "贪吃蛇");

}

//模式选择
void modeselect(void)
{
	char snakename[10] = "贪吃蛇";
	char survivor[13] = "生存模式";
	char endless[13] = "无限模式";

	//画框框
	setfillcolor(BROWN);
	setlinecolor(DARKGRAY);
	setlinestyle(PS_SOLID, 2);
	fillroundrect(215, 210, 415, 260, 15, 15);//
	fillroundrect(215, 280, 415, 330, 15, 15);//

	//插入字体
	setbkmode(TRANSPARENT);
	settextstyle(40, 0, "楷体");
	settextcolor(BLACK);
	outtextxy(250, 130, snakename);//贪吃蛇

	settextstyle(30, 0, "楷体");
	int widths = (415 - 215) / 2 - textwidth(survivor) / 2;
	int heights = (260 - 210) / 2 - textheight(survivor) / 2;
	outtextxy(215 + widths, 210 + heights, survivor);//生存模式

	int widthe = (415 - 215) / 2 - textwidth(endless) / 2;
	int heighte = (330 - 280) / 2 - textheight(endless) / 2;
	outtextxy(215 + widthe, 280 + heighte, endless);//无限模式

	//刷新绘图
	FlushBatchDraw();

	//用鼠标点击信息判断是什么模式
	ExMessage msg1;
	while (1)
	{
		if (peekmessage(&msg1, EM_MOUSE))
		{
			switch (msg1.message)
			{
			case WM_LBUTTONDOWN:
				if (msg1.x >= 215 && msg1.x <= 415 && msg1.y >= 210 && msg1.y <= 260)
					fanwei = 1;
				if (msg1.x >= 215 && msg1.x <= 415 && msg1.y >= 280 && msg1.y <= 330)
					fanwei = 2;
				break;

			}
		}

		if (fanwei != 0)
		{
			shiji1 = fanwei;
			break;
		}
	}
}

//数据的初始化,也就是蛇的起始位置,同时蛇的节数最开始是一节
void GameInit(void)
{
	//init 初始化  ;graph 图形窗口     ,SHOWCONSOLE——显示控制台
	initgraph(640, 480/*, SHOWCONSOLE*/);

	//修改窗口标题
	changetitle();

	//播放背景音乐,需要一个函数mciSendString()
	mciSendString("open 2.mp3 alias a ", 0, 0, 0); //打开音乐文件 ,记住这里音乐文件基本名是识别不了中文
	mciSendString("play a", 0, 0, 0);        //播放音乐
	mciSendString("repeat a", 0, 0, 0);





	//设置随机数起始位置,GetTickCount()函数是获取系统开机,到现在所经过的毫秒数
	srand(GetTickCount());

	//蛇的初始化数据
	snake.size = 3;			//没有节数,就只有头
	snake.speed = 10;		//速度为10
	snake.dir = RIGHT;		//开始方向
	snake.r = snake.speed / 2;//蛇的半径

	//蛇的开始的位置,这里c语言的坐标的y轴是朝下的,x轴依旧朝右,然后按size腾出整个蛇身体
	for (int i = 0; i < snake.size; i++)
	{
		snake.coor[i].x = 40 - 10 * i;
		snake.coor[i].y = 10;
	}

	//初始化食物,用随机产生一个数的函数rand()去初始化坐标,如果没有设置随机数种子(这个随机数种子就是rand能随机到的值),每次产生的都是固定的整数
	//设置种子前需要头文件<stdlib.h>,因为它里面专门有个设置随机数种子的函数,一般我们把时间作为种子,因为时间是不断变化的,可以用GetTickCount()函数获取时间
	food.x = rand() % 640;//为什么要用百分号除以窗口尺寸呢,因为这样写能保证这样始终不会超出窗口的尺寸
	food.y = rand() % 480;
	food.color = RGB(rand() % 256, rand() % 256, rand() % 256);//因为颜色每一个参数只能到255,所以随机数(随机数依旧用前面设置的种子数)取余256
	food.r = rand() % 10 + 5;
	food.flag = true;

}

//给游戏的相应位置添加颜色
void GameDraw(int* num, int* HP)
{


	//设置背景颜色
	setbkcolor(RGB(30, 109, 150));
	cleardevice();//清空原来的黑色覆盖的背景

	//给蛇的身体填充颜色
	setfillcolor(GREEN);

	//绘制蛇,用函数绘制一个实心圆,函数是图形库自带的。参数需要x、y轴坐标和一个半径
	for (int i = 0; i < snake.size; i++)
	{
		solidcircle(snake.coor[i].x, snake.coor[i].y, snake.r);
	}

	//绘制食物
	if (food.flag)
	{
		solidcircle(food.x, food.y, food.r);
	}


	if (shiji1 != 0)
	{
		//绘制分数
		snakeScoreAppear(num);
		//绘制蛇的节数
		snakeSize();
	}

	if (shiji1 == 1)
	{
		for (int i = 0; i < *HP; i++)
		{
			setfillcolor(RED);
			solidcircle(624 - i * 18, 20, 7);
		}

		if (fanwei != 0)
			fanwei = 0;
	}

	if (shiji1 == 2)
	{
		settextcolor(LIGHTRED);
		settextstyle(20, 0, "黑体");
		outtextxy(540, 10, "HP :∞");
		if (fanwei != 0)
			fanwei = 0;
	}



	//刷新绘图
	FlushBatchDraw();

	if (shiji1 == 0)
		//绘制模式面板
		modeselect();



}

//蛇的移动基础版
void SnakeMovebasis(int* sudu)
{
	//移动改变的因素是坐标,让蛇头移动
	for (int i = snake.size - 1; i > 0; i--)
	{
		snake.coor[i] = snake.coor[i - 1];//使得后一节的坐标与前一节相同
	}

	if (snake.speed1 == 100)
	{
		if (*sudu == 50)
			*sudu = snake.speed1;
	}
}

//蛇的移动加速版
void SnakeMoveSpeed(int* sudu)
{
	//移动改变的因素是坐标,让蛇头移动
	for (int i = snake.size - 1; i > 0; --i)
	{
		snake.coor[i] = snake.coor[i - 1];
	}

	if (snake.speed1 == 50)
	{
		if (*sudu == 100)
			*sudu = snake.speed1;
	}
}

//蛇的移动——生存模式
void SnakeMove_Survival_Mode(int* HP, int* sudu)
{
	if (snake.speed1 == 100)
		SnakeMovebasis(sudu);
	else if (snake.speed1 == 50)
		SnakeMoveSpeed(sudu);


	//dir的方向判断
	switch (snake.dir)
	{
		//朝上边,则每一节do往y轴方向减一
	case UP:
		snake.coor[0].y -= snake.speed;
		if (snake.coor[0].y + snake.speed == 0)//超出上面边界时
		{
			*HP -= 1;
			//扣血后回到初始位置
			for (int i = 0; i < snake.size; i++)
			{
				snake.coor[i].x = 40 - 10 * i;
				snake.coor[i].y = 10;
			}
			//初始化方向
			snake.dir = RIGHT;

		}
		break;

		//朝上边,则每一节do往y轴方向加一
	case DOWN:
		snake.coor[0].y += snake.speed;
		if (snake.coor[0].y - snake.speed == 480)//超出下面边界时
		{
			*HP -= 1;

			//扣血后回到初始位置
			for (int i = 0; i < snake.size; i++)
			{
				snake.coor[i].x = 40 - 10 * i;
				snake.coor[i].y = 10;
			}
			//初始化方向
			snake.dir = RIGHT;
		}
		break;

		//朝左边,则每一节do往x轴方向减一
	case LEFT:
		snake.coor[0].x -= snake.speed;
		if (snake.coor[0].x + snake.speed == 0)//超出左边边界时
		{
			*HP -= 1;

			//扣血后回到初始位置
			for (int i = 0; i < snake.size; i++)
			{
				snake.coor[i].x = 40 - 10 * i;
				snake.coor[i].y = 10;
			}
			//初始化方向
			snake.dir = RIGHT;
		}
		break;

		//朝右边,则每一节do往x轴方向加一
	case RIGHT:
		snake.coor[0].x += snake.speed;
		if (snake.coor[0].x - snake.speed == 640)//超出右边边界时
		{
			*HP -= 1;

			//扣血后回到初始位置
			for (int i = 0; i < snake.size; i++)
			{
				snake.coor[i].x = 40 - 10 * i;
				snake.coor[i].y = 10;
			}
			//初始化方向
			snake.dir = RIGHT;
		}
		break;

	default:
		break;

	}

}

//蛇生存模式的吃到身体的扣血机制
void SnakeMove_WithholdingBlood(int* HP)
{

	if (snake.coor[0].x == snake.coor[snake.size - 1].x && snake.coor[0].y == snake.coor[snake.size - 1].y)
	{
		*HP -= 1;
	}

	for (int i = 1; i < snake.size; ++i)
		if ((snake.coor[0].x == snake.coor[i].x) && (snake.coor[0].y == snake.coor[i].y))
		{
			*HP -= 1;
			snake.size = i;
		}
}

//蛇的移动——无限模式
void SnakeMove_Infinite_Mode(int* sudu)
{

	if (snake.speed1 == 100)
		SnakeMovebasis(sudu);
	else if (snake.speed1 == 50)
		SnakeMoveSpeed(sudu);

	//dir的方向判断
	switch (snake.dir)
	{
		//朝上边,则每一节do往y轴方向减一
	case UP:
		snake.coor[0].y -= snake.speed;
		if (snake.coor[0].y + snake.speed == 0)//超出上面边界时
		{
			snake.coor[0].y = 480;
		}
		break;

		//朝上边,则每一节do往y轴方向加一
	case DOWN:
		snake.coor[0].y += snake.speed;
		if (snake.coor[0].y - snake.speed == 480)//超出下面边界时
		{
			snake.coor[0].y = 0;
		}
		break;

		//朝左边,则每一节do往x轴方向减一
	case LEFT:
		snake.coor[0].x -= snake.speed;
		if (snake.coor[0].x + snake.speed == 0)//超出左边边界时
		{
			snake.coor[0].x = 640;
		}
		break;

		//朝右边,则每一节do往x轴方向加一
	case RIGHT:
		snake.coor[0].x += snake.speed;
		if (snake.coor[0].x - snake.speed == 640)//超出右边边界时
		{
			snake.coor[0].x = 0;
		}
		break;

	default:
		break;

	}
}

//通过按键改变蛇的方向,同时兼顾游戏暂停和蛇的加速
void keyDirControl(void)
{
	//判断有没有按键,_kbhit()函数是判断是否有按键,如果有就返回真(1),否则返回假(0),这个也是<conio.h>里面的函数
	if (_kbhit())
	{

		//tips: 72 80 75 77是靠近数字键盘的上下左右对应通过ASCII码转换的整数值
		switch (_getch())//要带头文件<conio.h>,getchar()不行,因为getchar()按了蛇就停止了
		{
		case 'w':
		case 'W':
		case 72:
			if (snake.dir != DOWN)  //当为DOWN时不能反向向上
				snake.dir = UP;
			break;

		case 's':
		case 'S':
		case 80:
			if (snake.dir != UP)   //当为UP时不能反向向下
				snake.dir = DOWN;
			break;

		case 'a':
		case 'A':
		case 75:
			if (snake.dir != RIGHT)	//当为RIGHT时不能反向向左
				snake.dir = LEFT;
			break;

		case 'd':
		case 'D':
		case 77:
			if (snake.dir != LEFT)	//当为LEFT时不能反向向右
				snake.dir = RIGHT;
			break;

		case ' ':
			settextcolor(YELLOW);
			settextstyle(40, 0, "黑体");
			outtextxy(230, 180, "游戏暂停");
			FlushBatchDraw();
			if (_getch() == ' ')//这里是第一次暂停后,等待按下一次空格
				return;

		case '0':
			if (snake.speed1 == 100)
			{
				snake.speed1 = 50;
			}
			else if (snake.speed1 == 50)
			{
				snake.speed1 = 100;
			}
			break;
		}
	}
}

//蛇吃食物,蛇头的中心和食物的中心这两者距离加起来小于双方半径相加,即为吃到
void EatFood(int* p)
{
	if (food.flag && (snake.coor[0].x >= (food.x - food.r)) && (snake.coor[0].x <= (food.x + food.r)) &&
		snake.coor[0].y >= (food.y - food.r) && snake.coor[0].y <= (food.y + food.r))
	{
		food.flag = false;//食物消失


		if (food.r >= 5 && food.r < 10)
			*p += 1;		  //分数+1,尽量别用++,会警告除非用span方法或者就用这个运算符
		else if (food.r >= 10 && food.r <= 15)
			*p += 2;

		snake.size++;//每吃两个食物加一节

		snakeScoreAppear(p);

		FlushBatchDraw();
	}

	//食物被吃后,再初始化食物,就可以刷新食物的信息并生成食物了
	if (!food.flag)
	{
		food.x = rand() % 640;
		food.y = rand() % 480;
		food.color = RGB(rand() % 256, rand() % 256, rand() % 256);
		food.r = rand() % 10 + 5;
		food.flag = true;
	}

}

//蛇吃食物的分数
void snakeScoreAppear(int* score)
{
	//现将整数分数转化为字符串
	char scorec[5];
	sprintf(scorec, "%d", *score);

	//拼接字符串
	char scorecp[15] = "总得分 :";
	strcat(scorecp, scorec);

	//打印整数分数字符串
	settextcolor(YELLOW);
	settextstyle(15, 0, "黑体");
	setbkmode(TRANSPARENT);
	outtextxy(10, 10, scorecp);
}

//显示蛇的节数
void snakeSize(void)
{
	char size2[17] = "蛇的节数 :";
	char size3[3];
	sprintf(size3, "%d", snake.size);
	strcat(size2, size3);
	settextcolor(WHITE);
	settextstyle(15, 0, "黑体");
	setbkmode(TRANSPARENT);
	outtextxy(240, 10, size2);

}

测试代码:

#include "game.h"


//做界面1:调用方法区创建一个背景窗口,相关因素的图形窗口
int main(void)
{

	//窗口创建,所有的数据初始化,蛇开始位置
	GameInit();

	//获得窗口句柄
	HWND hnd = GetHWnd();

	//吃食物的数据
	int eatNum = 0;

	//生存模式的血HP的值
	int HP = 10;

	//蛇的速度
	int sudu = 100;


	//双缓冲绘图,用来防止闪屏
	BeginBatchDraw(); //开启的意思

	//蛇的颜色填充,和背景的颜色填充
	GameDraw(&eatNum, &HP);




	while (1)
	{
		if (shiji1 == 1)
		{
			SnakeMove_Survival_Mode(&HP, &sudu);	//生存模式的移动后
			SnakeMove_WithholdingBlood(&HP);	//扣血机制
		}
		if (shiji1 == 2)
			SnakeMove_Infinite_Mode(&sudu);	//无线模式的移动后

		GameDraw(&eatNum, &HP);		//打印移动后的蛇


		if (HP == 0)
		{
			settextcolor(RED);
			settextstyle(24, 0, "黑体");
			outtextxy(170, 150, "你的生命值为零,游戏结束");
			FlushBatchDraw();
			int continueorfalse = MessageBox(hnd, "游戏结束,重新开始还是退出", "贪吃蛇——生存模式", MB_OKCANCEL);
			printf("%d", continueorfalse);

			if (continueorfalse == 2)
				return 0;

			eatNum = 0;
			HP = 10;

			for (int i = 0; i < snake.size; i++)
			{
				snake.coor[i].x = 40 - 10 * i;
				snake.coor[i].y = 10;
			}

			snake.dir = RIGHT;

			GameDraw(&eatNum, &HP);		//打印移动后的蛇
			FlushBatchDraw();

		}

		keyDirControl();//判断是否转方向,同时兼顾游戏暂停和蛇的加速


		EatFood(&eatNum);//吃食物的判定


		Sleep(sudu);
	}



	EndBatchDraw();//结束缓冲绘图的意思


	return 0;
}





  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小艺术生◎

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值