前言
每年学校都要求娃参加各种科技比赛,这哪里是娃参加啊,明明就是难为老爹嘛,娃才3年级,能做什么科技作品?我要是不搞点东西应付一下的话,娃他妈那关过不了,算了,花点时间写个C++的贪吃蛇吧。整体写的比较简陋,娃他妈催的很急,很多想法没来得及写,代码也没好好的整,凑合看吧。
网上也有不少C语言写的贪吃蛇的源码,代码都很精炼,我也看过,觉得写的很好,奈何做C++好多年,写代码不封装几个类出来浑身难受。代码比较简单,egg就是贪吃蛇里面那个蛋,map就是地图,player就是指蛇,director就是字面意思,控制整体流程。从我自己的角度来看,分类之后分工明确了,什么代码该写在什么类里面一目了然。
一、目录结构
第一层目录:
include目录:
二、直接上代码
egg.h
#ifndef __CPPS_EGG_H__
#define __CPPS_EGG_H__
class CPPSEgg
{
public:
CPPSEgg();
~CPPSEgg()=default;
void display(int offset);
char getStyle() const { return mStyle; };
private:
int mX;
int mY;
char mStyle;
};
#endif
map.h
#ifndef __CPPS_MAP_H__
#define __CPPS_MAP_H__
#define DEFAULT_MAP_WALL_X_THICKNESS 1
#define DEFAULT_MAP_WALL_Y_THICKNESS 1
class CPPSMap
{
public:
CPPSMap();
CPPSMap(int width, int height);
~CPPSMap()=default;
public:
int getWidth() const { return mMapWidth; };
int getHeight() const { return mMapHeight; };
int setWidth(int width) { mMapWidth = width; };
int setheight(int height) { mMapHeight = height; };
char getStyleLandscape() const { return mStyleLandscape; };
char getStylePortrait() const { return mStylePortrait; };
char getStyleFill() const { return mStyleFill; };
private:
int mMapWidth;
int mMapHeight;
char mStyleLandscape; //width
char mStylePortrait;
char mStyleFill;
};
#endif
player.h
#ifndef __CPPS_PLAYER_H__
#define __CPPS_PLAYER_H__
#include <vector>
using namespace std;
struct CPPSPlayerCoordinate
{
public:
CPPSPlayerCoordinate(int _x, int _y) {
x = _x;
y = _y;
};
int x;
int y;
};
class CPPSPlayer
{
public:
CPPSPlayer();
~CPPSPlayer() {};
enum PLAYER_MOVE_DIRECTION {
PLAYER_MOVE_DIRECTION_UP = 5,
PLAYER_MOVE_DIRECTION_DOWN = -5,
PLAYER_MOVE_DIRECTION_LEFT = 10,
PLAYER_MOVE_DIRECTION_RIGHT = -10,
PLAYER_MOVE_DIRECTION_INVALID = -1,
};
enum PLAYER_EAT_STATE {
PLAYER_EAT_STATE_PROGRESS = 1,
PLAYER_EAT_STATE_FINISHED,
};
void eat();
int getPlayerLength() const { return mvecCoordinate.size(); };
vector<CPPSPlayerCoordinate> getCoordinate() const { return mvecCoordinate; };
bool setMoveDirection(PLAYER_MOVE_DIRECTION direction);
PLAYER_MOVE_DIRECTION getMoveDirection() const { return mMoveDirection; };
char getStyleHead() const { return mStyleHead; };
char getStyleBody() const { return mStyleBody; };
private:
char mStyleHead;
char mStyleBody;
PLAYER_MOVE_DIRECTION mMoveDirection;
// index 0 means head
vector<CPPSPlayerCoordinate> mvecCoordinate;
PLAYER_EAT_STATE mEatState;
void initCoordinate();
void updateCoordinate();
void eatInternal();
};
#endif
director.h
#ifndef __CPPS_DIRECTOR_H__
#define __CPPS_DIRECTOR_H__
#include "egg.h"
#include "map.h"
#include "player.h"
#include <vector>
#include <map>
#include <string>
#include <pthread.h>
class CPPSDirector
{
public:
CPPSDirector();
~CPPSDirector();
void start();
void refresh();
enum CPPSSTATE {
CPPSSTATE_WAIT_START = 1,
CPPSSTATE_RUNNING,
CPPSSTATE_STOP,
CPPSSTATE_COLLISION_WALL
};
private:
//map
map<int, map<int, string>> mmapRealMapData;
CPPSMap* mMap;
//player
CPPSPlayer* mPlayer;
//egg
CPPSEgg* mEgg;
int mEggX;
int mEggY;
// game
// game-display
string mstrFormat;
bool mNeedToCleanScreen;
pthread_t mthUI;
CPPSPlayer::PLAYER_MOVE_DIRECTION mPlyaerMoveDirection;
bool mExitThreadUI;
CPPSSTATE mGameState;
private:
// map
void initMapInternal();
// player
void initPlayerInternal();
// egg
void initEggInternal();
void resetEggCoordinateInternal();
// game
// return true is Collision
bool mathCollisionPlayerAndWallInternal();
// return true is Collision
bool mathCollisionPlayerAndEggInternal();
void formatInternal();
void updateAllCoordinateDataInternal();
void resetCoordinateInternal(short x, short y);
void cleanScreenInternal();
void initUIThreadInternal();
void refreshInternal();
void setPlyaerMoveDirection(CPPSPlayer::PLAYER_MOVE_DIRECTION direction);
CPPSPlayer::PLAYER_MOVE_DIRECTION getPlyaerMoveDirection() const;
};
#endif
egg.cpp
#include "include/egg.h"
CPPSEgg::CPPSEgg()
{
mX = 0;
mY = 0;
mStyle = 'X';
}
map.cpp
#include "include/map.h"
CPPSMap::CPPSMap()
{
mMapWidth = 50;
mMapHeight = 10;
mStyleLandscape = '-';
mStylePortrait = '|';
mStyleFill = ' ';
}
CPPSMap::CPPSMap(int width, int height)
{
mMapWidth = width;
mMapHeight = height;
mStyleLandscape = '-';
mStylePortrait = '|';
mStyleFill = ' ';
}
player.cpp
#include "include/player.h"
CPPSPlayer::CPPSPlayer()
{
mMoveDirection = PLAYER_MOVE_DIRECTION_RIGHT;
mEatState = PLAYER_EAT_STATE_FINISHED;
mStyleHead = 'O';
mStyleBody = 'o';
initCoordinate();
}
void CPPSPlayer::initCoordinate()
{
mvecCoordinate.clear();
mvecCoordinate.push_back(CPPSPlayerCoordinate(2, 0));
mvecCoordinate.push_back(CPPSPlayerCoordinate(1, 0));
mvecCoordinate.push_back(CPPSPlayerCoordinate(0, 0));
}
bool CPPSPlayer::setMoveDirection(PLAYER_MOVE_DIRECTION direction)
{
if (direction != PLAYER_MOVE_DIRECTION_INVALID &&
(mMoveDirection + direction != 0)) {
mMoveDirection = direction;
}
updateCoordinate();
return true;
}
void CPPSPlayer::updateCoordinate()
{
auto maxSize = mvecCoordinate.size();
if (mEatState == PLAYER_EAT_STATE_PROGRESS) {
mvecCoordinate.push_back(mvecCoordinate[mvecCoordinate.size() - 1]);
mEatState = PLAYER_EAT_STATE_FINISHED;
}
for (auto i = maxSize - 1; i > 0; --i) {
mvecCoordinate[i].x = mvecCoordinate[i - 1].x;
mvecCoordinate[i].y = mvecCoordinate[i - 1].y;
}
switch (mMoveDirection) {
case PLAYER_MOVE_DIRECTION_UP:
mvecCoordinate[0].y -= 1;
break;
case PLAYER_MOVE_DIRECTION_DOWN:
mvecCoordinate[0].y += 1;
break;
case PLAYER_MOVE_DIRECTION_LEFT:
mvecCoordinate[0].x -= 1;
break;
case PLAYER_MOVE_DIRECTION_RIGHT:
mvecCoordinate[0].x += 1;
break;
}
}
void CPPSPlayer::eat()
{
eatInternal();
}
void CPPSPlayer::eatInternal()
{
mEatState = PLAYER_EAT_STATE_PROGRESS;
}
director.cpp
#include "include/director.h"
#include "include/player.h"
#include <Windows.h>
#include <conio.h>
#include <ctime>
#include <cstdlib>
#include <iostream>
void* refreshThreadFun(void* arg)
{
CPPSDirector* driector = (CPPSDirector*)arg;
driector->refresh();
return NULL;
}
CPPSDirector::CPPSDirector()
{
#define DEFAULT_MAP_WIDTH 50
#define DEFAULT_MAP_HEIGHT 10
// 0 means free
#define DEFAULT_MAP_DATA_VALUE 0
initMapInternal();
initPlayerInternal();
initEggInternal();
mGameState = CPPSSTATE_WAIT_START;
mPlyaerMoveDirection = mPlayer->getMoveDirection();
mNeedToCleanScreen = true;
updateAllCoordinateDataInternal();
srand(unsigned(time(NULL)));
// last init ui thread
mExitThreadUI = false;
initUIThreadInternal();
}
void CPPSDirector::initMapInternal()
{
mMap = new CPPSMap(DEFAULT_MAP_WIDTH, DEFAULT_MAP_HEIGHT);
}
void CPPSDirector::initPlayerInternal()
{
mPlayer = new CPPSPlayer();
}
void CPPSDirector::initEggInternal()
{
#define DEFAULT_EGG_COORDINATE_X 5
#define DEFAULT_EGG_COORDINATE_Y 5
mEgg = new CPPSEgg();
mEggX = DEFAULT_EGG_COORDINATE_X + DEFAULT_MAP_WALL_X_THICKNESS;
mEggY = DEFAULT_EGG_COORDINATE_Y + DEFAULT_MAP_WALL_Y_THICKNESS;
}
void CPPSDirector::resetEggCoordinateInternal()
{
while (1) {
mEggX = 1 + (mMap->getWidth() - 1) * rand() / RAND_MAX;
mEggY = 1 + (mMap->getHeight() - 1) * rand() / RAND_MAX;
if ((mmapRealMapData[mEggY])[mEggX] == " ") {
//cout << mEggX << "+" << mEggY << endl;
break;
}
}
}
void CPPSDirector::initUIThreadInternal()
{
int ret = pthread_create(&mthUI, NULL, refreshThreadFun, this);
if (ret != 0) {
cout << "[error]pthread_create failed, errno="<< ret << endl;
}
}
void CPPSDirector::start()
{
// wait for start
while (1) {
if (mGameState == CPPSSTATE_WAIT_START) {
Sleep(1000);
continue;
} else {
break;
}
}
// wait for input
char input;
while (1) {
if (mGameState == CPPSSTATE_STOP ||
mGameState == CPPSSTATE_COLLISION_WALL) {
break;
}
input = getch();
if ((int)input == -32) {
input = getch();
}
switch (int(input)) {
case 72://up
setPlyaerMoveDirection(CPPSPlayer::PLAYER_MOVE_DIRECTION_UP);
break;
case 80://down
setPlyaerMoveDirection(CPPSPlayer::PLAYER_MOVE_DIRECTION_DOWN);
break;
case 75://left
setPlyaerMoveDirection(CPPSPlayer::PLAYER_MOVE_DIRECTION_LEFT);
break;
case 77://right
setPlyaerMoveDirection(CPPSPlayer::PLAYER_MOVE_DIRECTION_RIGHT);
break;
case int('q'):
mGameState = CPPSSTATE_STOP;
mExitThreadUI = true;
goto END;
break;
default :
setPlyaerMoveDirection(CPPSPlayer::PLAYER_MOVE_DIRECTION_INVALID);
break;
};
}
END:;
}
void CPPSDirector::updateAllCoordinateDataInternal()
{
// update map Coordinate
for (auto i = 0; i < DEFAULT_MAP_HEIGHT; ++i) {
map<int, string> mapData;
for (auto j = 0; j < DEFAULT_MAP_WIDTH; ++j) {
if ((i == 0) || (i == DEFAULT_MAP_HEIGHT - 1)) {
if (j == 0) {
mapData[j] = mMap->getStylePortrait();
continue;
} else if (j > 0 && j < DEFAULT_MAP_WIDTH - 1) {
mapData[j] = mMap->getStyleLandscape();
continue;
} else if (j == DEFAULT_MAP_WIDTH - 1) {
mapData[j] = mMap->getStylePortrait();
continue;
} else {
mapData[j] = mMap->getStyleFill();
continue;
}
} else if (i > 0 && i < DEFAULT_MAP_HEIGHT - 1) {
if (j == 0) {
mapData[j] = mMap->getStylePortrait();
continue;
} else if (j == DEFAULT_MAP_WIDTH - 1) {
mapData[j] = mMap->getStylePortrait();
continue;
} else {
mapData[j] = mMap->getStyleFill();
continue;
}
} else {
mapData[j] = mMap->getStyleFill();
}
}
mmapRealMapData[i] = mapData;
}
// update egg Coordinate
(mmapRealMapData[mEggY])[mEggX] = mEgg->getStyle();
// update player Coordinate
for (auto i = 0; i < mPlayer->getPlayerLength(); ++i) {
if (i == 0) {
(mmapRealMapData[mPlayer->getCoordinate()[i].y +
1])[mPlayer->getCoordinate()[i].x + 1] = mPlayer->getStyleHead();//"^";//"T";//"鈽?;
} else {
(mmapRealMapData[mPlayer->getCoordinate()[i].y +
1])[mPlayer->getCoordinate()[i].x + 1] = mPlayer->getStyleBody();
}
}
}
CPPSDirector::~CPPSDirector()
{
pthread_join(mthUI, NULL);
if (!mMap) {
delete mMap;
mMap = NULL;
}
if (!mPlayer) {
delete mPlayer;
mPlayer = NULL;
}
if (!mEgg) {
delete mEgg;
mEgg = NULL;
}
}
void CPPSDirector::formatInternal()
{
mstrFormat = "";
auto height = mMap->getHeight();
auto width = mMap->getWidth();
for (auto i = 0; i < mmapRealMapData.size(); ++i) {
for (auto j = 0; j < (mmapRealMapData[i]).size(); ++j) {
mstrFormat.append((mmapRealMapData[i])[j]);
}
mstrFormat.append("\n");
}
}
void CPPSDirector::resetCoordinateInternal(short x, short y) {
COORD pos;
pos.X = x;
pos.Y = y;
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorPosition(hOut, pos);
cout << std::flush;
}
void CPPSDirector::cleanScreenInternal()
{
if (mNeedToCleanScreen) {
system("cls");
mNeedToCleanScreen = false;
}
}
void CPPSDirector::refreshInternal()
{
// control move speed
#define DEFAULT_REFRESH_RATE 200
unsigned long time_start, time_end, time_sleep;
mGameState = CPPSSTATE_RUNNING;
cleanScreenInternal();
while (1) {
if (mExitThreadUI) {
break;
}
time_start = GetTickCount();
updateAllCoordinateDataInternal();
formatInternal();
resetCoordinateInternal(0, 2);
cout << mstrFormat << std::flush;
if (mathCollisionPlayerAndWallInternal()) {
mGameState = CPPSSTATE_COLLISION_WALL;
cout << "game over" << endl;
cout << "game over" << endl;
cout << "game over" << endl;
cout << std::flush;
break;
}
if (mathCollisionPlayerAndEggInternal()) {
mPlayer->eat();
resetEggCoordinateInternal();
}
time_end = GetTickCount();
time_sleep = (time_end - time_start) > DEFAULT_REFRESH_RATE ?
0 : DEFAULT_REFRESH_RATE - (time_end - time_start);
Sleep(time_sleep);
mPlayer->setMoveDirection(getPlyaerMoveDirection());
if (mExitThreadUI) {
break;
}
}
}
void CPPSDirector::refresh()
{
refreshInternal();
}
void CPPSDirector::setPlyaerMoveDirection(CPPSPlayer::PLAYER_MOVE_DIRECTION direction)
{
mPlyaerMoveDirection = direction;
}
CPPSPlayer::PLAYER_MOVE_DIRECTION CPPSDirector::getPlyaerMoveDirection() const
{
return mPlyaerMoveDirection;
}
bool CPPSDirector::mathCollisionPlayerAndWallInternal()
{
if ((mPlayer->getCoordinate()[0].x + DEFAULT_MAP_WALL_X_THICKNESS == 0 ||
mPlayer->getCoordinate()[0].x + DEFAULT_MAP_WALL_X_THICKNESS ==
DEFAULT_MAP_WIDTH - DEFAULT_MAP_WALL_X_THICKNESS) ||
(mPlayer->getCoordinate()[0].y + DEFAULT_MAP_WALL_Y_THICKNESS == 0 ||
mPlayer->getCoordinate()[0].y + DEFAULT_MAP_WALL_Y_THICKNESS ==
DEFAULT_MAP_HEIGHT - DEFAULT_MAP_WALL_Y_THICKNESS)) {
return true;
}
return false;
}
bool CPPSDirector::mathCollisionPlayerAndEggInternal()
{
if (mPlayer->getCoordinate()[0].x + 1 == mEggX &&
mPlayer->getCoordinate()[0].y + 1 == mEggY) {
return true;
}
return false;
}
main.cpp
#include "include/director.h"
#include <windows.h>
#include <stdio.h>
int main(int /*argc*/, char** /*argv*/)
{
CPPSDirector director;
director.start();
system("pause");
return 0;
}
三、编译
我本地是windows+mingw32的环境。编译命令是:
g++ src/main.cpp src/map.cpp src/egg.cpp src/director.cpp src/player.cpp -lpthread
总结
今年学校的科技活动总算应付过去了,本来想写的好一点的,再增加线程用来控制速度,再封装一个消息在几个线程之间传递控制。cout输出的时候也有优化的空间,不需要全部重新输出,代码也需要整理整理。奈何学校要的急,凑合着赶快交上去吧。等有空了再优化优化吧。