C++实战贪食蛇
学了那么久的C++语法,没有点小项目来玩玩的话是会感觉挺枯燥的。这篇文章记录C++的贪食蛇,简单易懂。
首先使用的是多文件的写法,代码开起来不会太多。直接开始吧。
一、总体思路
把需要的蛇、食物、坐标都封装成类,然后在主函数中去调用。蛇的形状就用多个矩形连起来来代替。食物是随机在界面中产生,食物也是用矩形来代替。食物中有一个属性是用来标识蛇有没有吃掉食物的。蛇吃了食物就把标记变换,然后if判断判断标记是否改变得出食物是否被吃掉。食物被吃掉之后,在可见的界面中随机一个位置产生一个食物,然后蛇自己的身体增一。所以蛇中就必须有一个属性是用来表示蛇身长度的或者说大小。蛇和食物都是需要定位的,所以也封装了一个点类,当蛇头碰到食物的时候,也就是蛇头的坐标等于食物的坐标的时候,就是吃的过程。然后就是蛇的移动和改方向(改方向需要按键交互,只需改变蛇的头部方向即可,蛇身的节点直接使用上一个节点的坐标即可)。最后是结束游戏的判断,有主要的两个条件会使游戏结束:一是蛇头碰到墙,二是蛇头碰到自己的身体。第一个只需判断是否出界即可,第二个就比较有意思,先把蛇头的坐标记录下来,然后就用循环去判断蛇头的坐标是否等于蛇身中每个节点的坐标 。条件成立就结束游戏,不成立就继续。这里为什么要记录下来呢?因为不记录下来的话,蛇头和第一个要判断的蛇身节点判断结束后蛇头就变了,变的速度很快。所以要记录下来才行,不记录直接判断的话是有Bug的。
1.1 开发环境
Visual Studio 2019
1.2 插件
easyX 如果没有的话直接在网上下载就好,用不到五分钟。使用的时候直接包含头文件easyx.h
#include<easyx.h>
1.3 自定义的头文件和源文件
封装类,采用面向对象和面向过程的相互结合。
每个类写在一个头文件中。
已经分别创建三个头文件
类 | 头文件 | 源文件 |
---|
Point | point.h | point.cpp | Snake | snake.h | snake.cpp | Food | food.h | food.cpp |
1.3.1 头文件
- Point类(点,用来定位),在自定义头文件"point.h"中。
#pragma once
class Point
{
public:
Point(int x=0,int y=0):x(x),y(y){}
int& getX();
int& getY();
protected:
int x;
int y;
};
- Snake类(蛇,封装蛇的属性和行为),在自定义头文件"snake.h"中。
#pragma once
#include<easyx.h>
#include"point.h"
#include<ctime>
#include"food.h"
class Snake
{
public:
enum posNum
{
LEFT,RIGHT,UP,DOWN
};
Snake(int size = 100);
void drawSnake();
void moveSnake();
void keyDown();
void eatFood(Food& food);
bool GameOver();
int& getCurSize();
protected:
Point* pos;
int curSize;
int position;
};
- Food类(食物,封装行为和属性),在自定义头文件"food.h"中
#pragma once
#include<easyx.h>
#include"point.h"
#include<ctime>
class Food
{
friend class Snake;
public:
Food();
void drawFood();
int& getFlag();
protected:
Point* pos;
int flag;
};
1.3.2源文件
#include "point.h"
int& Point::getX()
{
return x;
}
int& Point::getY()
{
return y;
}
#include "snake.h"
Snake::Snake(int size)
{
curSize = 3;
pos = new Point[size];
position = RIGHT;
pos[0].getX() = 0;
pos[0].getY() = 0;
}
void Snake::drawSnake()
{
for (int i = 0; i < curSize; i++)
{
setlinecolor(BLACK);
setfillcolor(RGB(rand() % 256, rand() % 256, rand() % 256));
fillrectangle(pos[i].getX(), pos[i].getY(), pos[i].getX() + 10, pos[i].getY() + 10);
}
}
void Snake::moveSnake()
{
for (int i = curSize - 1; i > 0; i--)
{
pos[i].getX() = pos[i - 1].getX();
pos[i].getY() = pos[i - 1].getY();
}
switch (position)
{
case LEFT:
pos[0].getX() -= 10;
break;
case RIGHT:
pos[0].getX() += 10;
break;
case UP:
pos[0].getY() -= 10;
break;
case DOWN:
pos[0].getY() += 10;
break;
}
}
void Snake::keyDown()
{
if (position != DOWN && (GetAsyncKeyState('W') || GetAsyncKeyState(VK_UP)))
{
position = UP;
}
if (position != UP && (GetAsyncKeyState('S') || GetAsyncKeyState(VK_DOWN)))
{
position = DOWN;
}
if (position != LEFT&& (GetAsyncKeyState('D') || GetAsyncKeyState(VK_RIGHT)))
{
position = RIGHT;
}
if (position != RIGHT && (GetAsyncKeyState('A') || GetAsyncKeyState(VK_LEFT)))
{
position = LEFT;
}
}
void Snake::eatFood(Food& food)
{
if (pos[0].getX() == food.pos->getX() && pos[0].getY() == food.pos->getY())
{
food.flag = 0;
curSize++;
}
}
bool Snake::GameOver()
{
bool bNum = false;
if (pos[0].getX() < 0 || pos[0].getX() > 800 || pos[0].getY() < 0 || pos[0].getY() > 600)
return true;
int x = pos->getX();
int y = pos->getY();
for (int i = 1; i < curSize; i++)
{
if (pos[i].getX() == x && pos[i].getY() == y)
{
bNum = true;
}
}
return bNum;
}
int& Snake::getCurSize()
{
return curSize;
}
#include "food.h"
Food::Food()
{
pos = new Point;
pos->getX() = rand() % 80 * 10;
pos->getY() = rand() % 60 * 10;
flag = 1;
}
void Food::drawFood()
{
setfillcolor(RGB(rand() % 256, rand() % 256, rand() % 256));
fillrectangle(pos->getX(), pos->getY(), pos->getX() + 10, pos->getY() + 10);
}
int& Food::getFlag()
{
return flag;
}
二、细节内容
使用到友元类的时候,分清楚哪个是哪个的友元,分清楚哪个头文件应该包含哪个头文件。
在snake.h中因为,需要蛇身是一闪一闪的,所以需要随机函数rand(),所以snake.h中要包含时间头文件,利用时间来给播种,播种的函数srand()写在主函数所在的测试源文件即可。
#pragma once
#include<easyx.h>
#include<ctime>
#include"point.h"
#include"food.h"
不能再在food.h头文件中包含snake.h,因为这样写程序会编译snake.h的时候先编译food.h,而在food.h中,编译food.h的时候又先编译snake.h,两者矛盾了。所以不要这么写。
三、测试源代码
testApp.cpp
#include<iostream>
#include"snake.h"
#include"food.h"
#include<conio.h>
using namespace std;
void StartGameText();
int main()
{
srand((unsigned int)time(NULL));
initgraph(800, 600);
Snake* pSnake = new Snake;
Food* pFood = new Food;
StartGameText();
setbkcolor(WHITE);
cleardevice();
BeginBatchDraw();
while (1)
{
cleardevice();
pSnake->drawSnake();
pSnake->moveSnake();
pSnake->keyDown();
pFood->drawFood();
pSnake->eatFood(*pFood);
if (pFood->getFlag() == 0)
{
pFood = new Food;
}
if (pSnake->GameOver())
{
break;
}
Sleep(50);
FlushBatchDraw();
}
EndBatchDraw();
closegraph();
cout << "游戏结束" << endl;
cout << "最终分数:" << pSnake->getCurSize() << endl;
return 0;
}
void StartGameText()
{
setfillcolor(RGB(0, 255, 255));
settextstyle(50, 0, L"华文行楷");
outtextxy(200, 150, L"按任意键开始游戏");
char key = _getch();
}
只需把上面遇到的自定义头文件和源文件还有测试源文件都写在一个项目里,然后运行就可以。
四、效果展示
|