#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include
#include
#include
#include
#include
#include
//下面两个文件是用来播放音乐的,对媒体设备接口的头文件和另一个文件
#include
#include
#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能随机到的值),每次产生的都是固定的整数
//设置种子前需要头文件,因为它里面专门有个设置随机数种子的函数,一般我们把时间作为种子,因为时间是不断变化的,可以用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),这个也是里面的函数
if (_kbhit())
{
//tips: 72 80 75 77是靠近数字键盘的上下左右对应通过ASCII码转换的整数值
switch (_getch())//要带头文件,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;
}