C++实现贪吃蛇
该项目用到一个叫作eaxyX的图形库,具体使用参考以下链接
EasyX使用入门
效果截图
菜单界面
贪吃蛇
(红色为蛇头,绿色为蛇身,蓝色为水果,每吃到一个水果,蛇身会变长,蛇的移动速度会变快)
分数界面
源代码
#include<graphics.h>
#include<conio.h>
#include<vector>
#include<map>
#include<string>
#include<list>
#include<cmath>
using namespace std;
//蛇方块长度
const int snakelength = 10;
//界面宽度
const int windowLength = 720;
//界面高度
const int windowHeight = 640;
//水果大小
const int fruitsize = 15;
//水果数量
const int fruitnum = 10;
//水果数量最小值,小于该数时添加水果
const int minfruitnum = 4;
//坐标更新使用的数组
int arr[4][4] = {
{-snakelength,0,-snakelength,0},//左移
{0,-snakelength,0,-snakelength},//上移
{snakelength,0,snakelength,0},//右移
{0,snakelength,0,snakelength}//下移
};
//记录蛇转弯时的坐标和下一个状态
struct Point
{
int x;
int y;
int flag;
Point(int xx, int yy, int ff)
:x{ xx }, y{ yy }, flag{ ff }
{}
};
//蛇类
class Snake
{
private:
//蛇方块结构体
struct snake
{
//左,上,右,下坐标
int left;
int top;
int right;
int bottom;
//方向默认向右
int flag;
//记录蛇转弯时的坐标和方向
list<Point>l;
snake(int left, int top)
:left{ left }, top{ top }, flag{ 2 }
{
right = left + snakelength;
bottom = top + snakelength;
}
};
//水果结构体
struct Fruit
{
//坐标
int x;
int y;
//是否显示
bool ispicture;
};
//蛇方块数组
vector<snake>s;
//将键盘输入转换为数字
map<string, int>mp;
//水果数组
vector<Fruit>fruit;
//水果数量
int picturefruitnum;
//记录分数
int score;
public:
Snake()
{
//初始化蛇方块
for (int i = 5; i > 0; --i)
{
s.push_back(snake(i * snakelength, snakelength));
}
//初始化水果
fruit.resize(fruitnum);
for (int i = 0; i < fruit.size(); ++i)
{
fruit[i].x = 30 + rand() % windowLength - 30;
fruit[i].y = 30 + rand() % windowHeight - 30;
fruit[i].ispicture = true;
}
//初始化水果数量
this->picturefruitnum = fruit.size();
//初始化分数
this->score = 0;
this->initmp();
}
Snake(int left, int top)
{
//初始化蛇方块
s.push_back(snake(left, top));
for (int i = 3; i >= 0; --i)
s.push_back(snake(left - (4 - i) * snakelength, snakelength));
//初始化水果
fruit.resize(fruitnum);
for (int i = 0; i < fruit.size(); ++i)
{
fruit[i].x = 30 + rand() % windowLength - 30;
fruit[i].y = 30 + rand() % windowHeight - 30;
fruit[i].ispicture = true;
}
//初始化水果数量
this->picturefruitnum = fruit.size();
//初始化分数
this->score = 0;
this->initmp();
}
//初始化map,便于后续将用户输入转换为对应的方向
void initmp()
{
this->mp.insert(make_pair("a", 0));
this->mp.insert(make_pair("A", 0));
this->mp.insert(make_pair("w", 1));
this->mp.insert(make_pair("W", 1));
this->mp.insert(make_pair("d", 2));
this->mp.insert(make_pair("D", 2));
this->mp.insert(make_pair("s", 3));
this->mp.insert(make_pair("S", 3));
}
//菜单
void Menu()
{
//设置背景
setbkmode(TRANSPARENT);
setbkcolor(RGB(114, 107, 112));
cleardevice();
//鼠标
MOUSEMSG msg;
//设置填充颜色
setfillcolor(RGB(131, 83, 47));
//填充矩形
fillrectangle(windowLength / 2 - 100, windowHeight / 2 - 100, windowLength / 2 + 100, windowHeight / 2 - 50);
fillrectangle(windowLength / 2 - 100, windowHeight / 2 + 50, windowLength / 2 + 100, windowHeight / 2 + 100);
//设置文字颜色和字体
settextcolor(WHITE);
settextstyle(40, 0, "楷体");
outtextxy(windowLength / 2 - 75, windowHeight / 2 - 95, "开始游戏");
outtextxy(windowLength / 2 - 75, windowHeight / 2 + 55, "退出游戏");
while (1)
{
//获得鼠标信息
msg = GetMouseMsg();
BeginBatchDraw();
//画选项按钮
if (msg.x<windowLength / 2 + 100 && msg.x>windowLength / 2 - 100
&& msg.y > windowHeight / 2 - 100 && msg.y < windowHeight / 2 - 50)
{
setfillcolor(RGB(248, 168, 95));
fillrectangle(windowLength / 2 - 100, windowHeight / 2 - 100, windowLength / 2 + 100, windowHeight / 2 - 50);
outtextxy(windowLength / 2 - 75, windowHeight / 2 - 95, "开始游戏");
EndBatchDraw();
//左击鼠标时,进入游戏
if (msg.uMsg == WM_LBUTTONDOWN)
{
this->Game();
break;
}
}
else if (msg.x<windowLength / 2 + 100 && msg.x>windowLength / 2 - 100
&& msg.y > windowHeight / 2 + 50 && msg.y < windowHeight / 2 + 100)
{
setfillcolor(RGB(248, 168, 95));
fillrectangle(windowLength / 2 - 100, windowHeight / 2 + 50, windowLength / 2 + 100, windowHeight / 2 + 100);
outtextxy(windowLength / 2 - 75, windowHeight / 2 + 55, "退出游戏");
EndBatchDraw();
//左击鼠标时,退出游戏
if (msg.uMsg == WM_LBUTTONDOWN)
{
break;
}
}
else
{
setfillcolor(RGB(131, 83, 47));
fillrectangle(windowLength / 2 - 100, windowHeight / 2 - 100, windowLength / 2 + 100, windowHeight / 2 - 50);
fillrectangle(windowLength / 2 - 100, windowHeight / 2 + 50, windowLength / 2 + 100, windowHeight / 2 + 100);
settextcolor(WHITE);
settextstyle(40, 0, "楷体");
outtextxy(windowLength / 2 - 75, windowHeight / 2 - 95, "开始游戏");
outtextxy(windowLength / 2 - 75, windowHeight / 2 + 55, "退出游戏");
EndBatchDraw();
}
}
}
//更新水果位置和数量
void UpdateFruit()
{
for (int i = 0; i < fruit.size(); ++i)
{
if (fruit[i].ispicture == false)
{
//通过伪随机数设置水果位置
fruit[i].x = 30 + rand() % windowLength - 30;
fruit[i].y = 30 + rand() % windowHeight - 30;
fruit[i].ispicture = true;
}
}
this->picturefruitnum = fruit.size();
}
//更新蛇方块坐标
void UpdateSnake()
{
//用前面的arr数组更新蛇方块坐标
s[0].left += arr[s[0].flag][0];
s[0].top += arr[s[0].flag][1];
s[0].right += arr[s[0].flag][2];
s[0].bottom += arr[s[0].flag][3];
for (int i = 1; i < s.size(); ++i)
{
s[i].left += arr[s[i].flag][0];
s[i].top += arr[s[i].flag][1];
s[i].right += arr[s[i].flag][2];
s[i].bottom += arr[s[i].flag][3];
if (!s[i].l.empty())
{
//到达转弯处,更新方向
if (s[i].left == s[i].l.front().x && s[i].top == s[i].l.front().y)
{
s[i].flag = s[i].l.front().flag;
//删除转弯位置
s[i].l.pop_front();
}
}
}
}
//开始游戏
void Game()
{
//用于接受用户键盘输入
char ch;
//模拟蛇移动速度
int speed = 120;
while (1)
{
cleardevice();
BeginBatchDraw();
//画墙,蛇,水果
this->DrawWall();
this->DrawSnake();
this->DrawFruit();
//如果有键盘输入时
if (_kbhit())
{
//获取键盘输入
ch = _getch();
//判断键盘输入
if (ch == 'a' || ch == 'A' || ch == 'd' || ch == 'D'
|| ch == 's' || ch == 'S' || ch == 'w' || ch == 'W')
{
//不允许突然从左变为右,从上变为下,反之亦然
//转向成功后,添加list<Point>l,即添加蛇的转弯坐标
string tmp(1, ch);
if (mp[tmp] == 0 && s[0].flag != 2)
{
//更新蛇头方向
s[0].flag = 0;
//添加转弯处坐标
for (int i = 1; i < s.size(); ++i)
s[i].l.push_back(Point(s[0].left, s[0].top, s[0].flag));
}
else if (mp[tmp] == 1 && s[0].flag != 3)
{
//更新蛇头方向
s[0].flag = 1;
//添加转弯处坐标
for (int i = 1; i < s.size(); ++i)
s[i].l.push_back(Point(s[0].left, s[0].top, s[0].flag));
}
else if (mp[tmp] == 2 && s[0].flag != 0)
{
//更新蛇头方向
s[0].flag = 2;
//添加转弯处坐标
for (int i = 1; i < s.size(); ++i)
s[i].l.push_back(Point(s[0].left, s[0].top, s[0].flag));
}
else if (mp[tmp] == 3 && s[0].flag != 1)
{
//更新蛇头方向
s[0].flag = 3;
//添加转弯处坐标
for (int i = 1; i < s.size(); ++i)
s[i].l.push_back(Point(s[0].left, s[0].top, s[0].flag));
}
}
}
//蛇撞到墙时
if (s[0].left<0 || s[0].right >windowLength
|| s[0].top<0 || s[0].bottom>windowHeight)
{
EndBatchDraw();
int cur = 1;
while (cur < this->s.size())
{
BeginBatchDraw();
setfillcolor(RED);
for (int i = 0; i < cur; ++i)
fillrectangle(s[i].left, s[i].top, s[i].right, s[i].bottom);
setfillcolor(GREEN);
for (int i = cur; i < s.size(); ++i)
fillrectangle(s[i].left, s[i].top, s[i].right, s[i].bottom);
++cur;
Sleep(100);
EndBatchDraw();
}
Sleep(100);
//exit(0);
break;
}
//判断蛇是否有吃到水果
for (int i = 0; i < this->fruit.size(); ++i)
{
if (!fruit[i].ispicture)
continue;
if (pow(s[0].left + snakelength / 2 - fruit[i].x, 2) +
pow(s[0].top + snakelength / 2 - fruit[i].y, 2) <= fruitsize * fruitsize)
{
//添加蛇尾
if (s[s.size() - 1].flag == 0)
{
snake ss(s[s.size() - 1].left + snakelength, s[s.size() - 1].top);
ss.flag = s[s.size() - 1].flag;
s.push_back(ss);
}
else if (s[s.size() - 1].flag == 1)
{
snake ss(s[s.size() - 1].left, s[s.size() - 1].top + snakelength);
ss.flag = s[s.size() - 1].flag;
s.push_back(ss);
}
else if (s[s.size() - 1].flag == 2)
{
snake ss(s[s.size() - 1].left - snakelength, s[s.size() - 1].top);
ss.flag = s[s.size() - 1].flag;
s.push_back(ss);
}
else if (s[s.size() - 1].flag == 3)
{
snake ss(s[s.size() - 1].left, s[s.size() - 1].top - snakelength);
ss.flag = s[s.size() - 1].flag;
s.push_back(ss);
}
//为新蛇尾设置转弯链表
for (list<Point>::iterator it = s[s.size() - 2].l.begin();
it != s[s.size() - 2].l.end(); ++it)
{
s[s.size() - 1].l.push_back(*it);
}
//吃到的水果不再显示
fruit[i].ispicture = false;
//更新分数
this->score += 10;
//更新速度
speed -= 2;
//更新水果数量
this->picturefruitnum--;
if (this->picturefruitnum < minfruitnum)
this->UpdateFruit();
break;
}
}
//更新蛇方块
this->UpdateSnake();
//睡眠,防止蛇走太快
Sleep(speed);
EndBatchDraw();
}
}
//画墙
void DrawWall()
{
setfillcolor(RGB(60, 83, 89));
fillrectangle(0, 0, windowLength, snakelength);
fillrectangle(0, 0, snakelength, windowHeight);
fillrectangle(windowLength - snakelength, 0, windowLength, windowHeight);
fillrectangle(0, windowHeight - snakelength, windowLength, windowHeight);
}
//画蛇
void DrawSnake()
{
setfillcolor(RED);
fillrectangle(s[0].left, s[0].top, s[0].right, s[0].bottom);
setfillcolor(GREEN);
for (int i = 1; i < s.size(); ++i)
fillrectangle(s[i].left, s[i].top, s[i].right, s[i].bottom);
}
//画水果
void DrawFruit()
{
setfillcolor(BLUE);
for (int i = 0; i < this->fruit.size(); ++i)
{
if (fruit[i].ispicture)
fillcircle(fruit[i].x, fruit[i].y, fruitsize);
}
}
//显示分数
void ShowScore()
{
//将int转化为string
string Score = to_string(this->score);
setbkmode(TRANSPARENT);
setbkcolor(RGB(114, 107, 112));
cleardevice();
outtextxy(windowLength / 2 - 100, windowHeight / 2 - 50, "你的分数为:");
outtextxy(windowLength / 2 - 100, windowHeight / 2, Score.c_str());
}
};
int main()
{
initgraph(windowLength, windowHeight);
Snake s1;
s1.Menu();
s1.ShowScore();
Sleep(1000);
closegraph();
return 0;
}
总结
技术不行,设计很简陋,而且功能比较少,后续有时间会继续完善。