游戏简介(From WikiPedia)
《俄罗斯方块》(俄语:Тетрис,英语:Tetris),是1980年末期至1990年代初期风靡全世界的电脑游戏,是落下型益智游戏的始祖,电子游戏领域的代表作之一,为苏联首个在美国发布的娱乐软件。此游戏最初由阿列克谢·帕基特诺夫在苏联设计和编写,于1984年6月6日首次发布,当时他正在苏联科学院电算中心工作。此游戏的名称是由希腊语数字“四”的前缀“tetra-”(因所有落下方块皆由四块组成)和帕基特诺夫最喜欢的运动网球(“tennis”)拼接而成,华语地区则因游戏为俄罗斯人发明普遍称为“俄罗斯方块”。
此游戏和其续作可于几乎所有电子游戏机和电脑操作系统上游玩,亦可于图形计算器、手提电话、便携式媒体播放器、个人数码助理、互联网无线电设备上游玩,甚至能以彩蛋的形式在非媒体产品上游玩,如示波器。
于1980年代,此游戏除了成为一个热门的家用电脑和街机游戏外,还成为Game Boy史上最受欢迎的游戏。《电子游戏月刊》在2007年将此游戏列为“最伟大的100个游戏”中的第1位,并获IGN列为“最伟大的100个游戏”中的第2位。截至2009年,此游戏已售出逾7000万套游戏。于2010年1月,此游戏于手提电话上已售出逾1亿套游戏。
直到今日,俄罗斯方块在各平台上以2亿200万总销量(约7000万的实体销量和约1亿3200万的付费游戏下载销量)成为有史以来第二畅销的电子游戏,仅次于《我的世界》。
- 类GameModule负责调用SDL组件库初始化游戏环境,创建游戏循环,以及调用游戏机制类GameMechanic提供的三个游戏接口API来实现游戏的交互、更新和渲染
- 抽象类GameMechanic负责充当底层游戏环境与上层游戏状态机的层间接口
- 类Tetris继承自抽象类GameMechanic,负责实现俄罗斯方块的具体游戏机制
- 类Block负责实例化各种类型的方块,辅助类Tetris的功能实现
游戏组件类GameModule并没有直接访问游戏逻辑类Tetris,而是通过基类指针GameMechanic来间接访问,这使该游戏代码框架便于维护且易于复用。
类GameModule负责调用SDL组件库初始化游戏环境,创建游戏循环,以及通过调用抽象类GameMechanic公开的三个接口成员函数API,间接实现对游戏状态机的键鼠事件交互、游戏状态更新以及游戏图像渲染。
class GameModule {
private:
HINSTANCE hInst = 0;
SDL_Window* pWin = nullptr;
SDL_Renderer* pRenderer = nullptr;
const char* title;
unsigned int FPS = 0u;
SIZE winSize = {0l, 0l};
GameMechanic* gameMechanic;
public:
explicit GameModule(HINSTANCE _hInst, const char* _title,
LONG _winSizeWidth, LONG _winSizeHeight, unsigned int _FPS)
: hInst(_hInst),
title(_title),
winSize({_winSizeWidth, _winSizeHeight}),
FPS(_FPS) {}
~GameModule() {}
GameModule(const GameModule&) = delete;
GameModule(const GameModule&&) = delete;
GameModule& operator&=(const GameModule&) = delete;
GameModule& operator&=(const GameModule&&) = delete;
void initGameModules();
void uninitGameModules();
void runGame() const;
SDL_Window* getpWin() { return pWin; }
SDL_Renderer* getpRenderer() { return pRenderer; }
void embedGameMechanic(GameMechanic& _GameMechanic) {
gameMechanic = &_GameMechanic;
}
};- initGameModules() 负责初始化SDL的基本库、文字库、图像库、音频库,创建游戏窗口以及创建渲染器
void GameModule::initGameModules() {
int res;
res = SDL_Init(SDL_INIT_EVERYTHING);
res = TTF_Init();
res = IMG_Init(IMG_INIT_PNG); // IMG_INIT_JPG | IMG_INIT_WEBP;
res = Mix_Init(MIX_INIT_MID | MIX_INIT_MP3);
res = Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT,
MIX_DEFAULT_CHANNELS, 2048);
pWin = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED, winSize.cx, winSize.cy, 0);
pRenderer = SDL_CreateRenderer(pWin, -1, SDL_RENDERER_ACCELERATED);
}- uninitGameModules() 负责在窗口关闭后释放initGameModules()调用的资源
void GameModule::uninitGameModules() {
SDL_DestroyRenderer(pRenderer);
SDL_DestroyWindow(pWin);
pRenderer = nullptr;
pWin = nullptr;
Mix_CloseAudio();
Mix_Quit();
IMG_Quit();
TTF_Quit();
SDL_Quit();
}- runGame() 负责依据游戏帧率FPS来创建定时游戏循环,在循环中通过不断调用抽象类GameMechanic暴露的三个层间接口间接来实现键鼠事件交互、游戏状态更新以及游戏图像渲染
void GameModule::runGame() const{
int quit = 0;
SDL_Event evt;
Uint64 nFrequency, nPrevCounter, nCurrCounter, nElapsedCounter;
float elapsed = 0.0f, lag = 0.0f, frameMS = 1000.0f / FPS;
nFrequency = SDL_GetPerformanceFrequency();
nPrevCounter = SDL_GetPerformanceCounter();
while (!quit) {
if (SDL_PollEvent(&evt))
if (evt.type == SDL_QUIT)
quit = 1;
else
gameMechanic->processGameEvent(evt);
else {
nCurrCounter = SDL_GetPerformanceCounter();
nElapsedCounter = nCurrCounter - nPrevCounter;
nPrevCounter = nCurrCounter;
elapsed = (nElapsedCounter * 1000.0f) / nFrequency; // ms
lag += elapsed;
while (lag >= frameMS) {
gameMechanic->updateGame(frameMS);
lag -= frameMS;
}
SDL_SetRenderDrawColor(pRenderer, 25, 25, 100, 255);
SDL_RenderClear(pRenderer);
gameMechanic->renderGame();
SDL_RenderPresent(pRenderer);
}
}
}由上述可知,所有从GameMechanic派生出的游戏类都需要重新实现基类的三个层间接口成员函数,以供GameModule调用。以下是三个层间接口成员函数:
- processGameEvent() 负责处理键鼠交互事件
- updateGame() 负责向GameMechanic传入当前游戏时间,并实现游戏状态机的更新
- renderGame() 负责将之前更新过的游戏状态机渲染到到当前窗口
class GameMechanic {
public:
GameMechanic() {}
virtual ~GameMechanic() {}
virtual void processGameEvent(const SDL_Event _evt) = 0;
virtual void updateGame(const float _ms) = 0;
virtual void renderGame() const = 0;
};enum TetrisState 定义了共6种游戏状态:菜单界面、初始化俄罗斯方块、运行俄罗斯方块、开启AI自动模式、暂停游戏、游戏失败结束
- Tetris-Interface.cpp 负责根据从GameModule接受到的键鼠交互事件,切换至所需的游戏状态(例如:从菜单界面进入到游戏进行界面、退出游戏回到菜单、暂停游戏、开启AI模式、调整游戏难度)
- Tetris-Logic.cpp 负责游戏逻辑的实现(例如:方块的碰撞检测、旋转移动下落方块、将触底方块固定至背景容器、消去已填满行)
- Tetris-AI.cpp 负责游戏AI计算与评分机制的实现(采用EI-Tetris算法)
- Tetris-Render.cpp 负责游戏资源的渲染(例如:背景渲染、方块渲染、记分板文字与数字渲染)
- resource.h 负责维护从Game-Table.rc 加载至内存的游戏资源(例如:纹理、字体、背景音乐、游戏音效)
class Tetris : public GameMechanic {
enum TetrisState {
TS_MENU = 0,
TS_INIT,
TS_RUN,
TS_AI,
TS_PAUSE,
TS_DEAD,
TS_NUM
};
private:
HINSTANCE hInst = 0;
SDL_Window* pWin = nullptr;
SDL_Renderer* pRenderer = nullptr;
/* Game Resource Pointer*/
TTF_Font* gDefFont = nullptr;
TTF_Font* gOptionsFont = nullptr;
TTF_Font* gDisplayFont = nullptr;
TTF_Font* gTextFont = nullptr;
SDL_Texture* gTexture = nullptr;
Mix_Music* gBGM = nullptr;
Mix_Music* gGameOver = nullptr;
Mix_Chunk* gMove = nullptr;
Mix_Chunk* gRotate = nullptr;
Mix_Chunk* gErase = nullptr;
Mix_Chunk* gDrop = nullptr;
bool Container[CONTAINER_HEIGHT][CONTAINER_WIDTH];
bool AITestContainer[CONTAINER_HEIGHT][CONTAINER_WIDTH];
TetrisState GameState = TS_MENU;
Block LoadingBlock;
Block CurrentBlock;
Block UnderDropBlock;
Block AIBlock;
int menuFrameIndex = 0;
int deadFrameIndex = 0;
bool deadFrameDown;
bool deadFrameUp;
float tick = 0.0f;
int level = 1;
int speed = 0;
int score = 0;
int hiScore = 0;
private:
/* Tetris-Interface.cpp */
void playTetris();
void quitTetris();
void toggleRunPause();
void toggleAIMod();
void adjustMenuLevel(int _num);
/* Tetris-Logic.cpp */
bool hitBlock(const Block& _block) const;
void moveLeftBlock();
void moveRightBlock();
void moveDownBlock();
void dropBlock();
void initUnderDropBlock();
void rotateBlock();
void mergeBlock();
void eraseLines();
/* Tetris-AI.cpp */
void initAIRecommendBlock();
double evaluateScore(const Block& _block);
void initAIMergeContainer(const Block& _block);
double getLandingHeight(const Block& _block) const;
int getEliminateRows() const;
int getRowTransitions() const;
int getColumnTransitions() const;
int getNumberOfHoles() const;
int getWellSums() const;
/* Tetris-Render.cpp */
SDL_RWops* importResourceData(HINSTANCE, LPCWSTR, LPCWSTR);
void transHextoContainer(int _containRow, unsigned short _Hex);
void renderBlock(const Block& _block, const int _alpha) const;
int renderBlocktoXY(int _Winy, const Block& _block, int _alpha) const;
int renderTextNum(const int _y, const char* _text, const int _num) const;
public:
explicit Tetris(HINSTANCE _hInst, SDL_Window* _pWin,
SDL_Renderer* _pRenderer)
: hInst(_hInst),
pWin(_pWin),
pRenderer(_pRenderer),
GameState(TS_MENU) {}
~Tetris() {}
Tetris(const Tetris&) = delete;
Tetris(const Tetris&&) = delete;
Tetris& operator&=(const Tetris&) = delete;
Tetris& operator&=(const Tetris&&) = delete;
/* resource.h */
void loadResources();
void unloadResources();
/* GameMechanic.h */
void processGameEvent(const SDL_Event _evt);
void updateGame(const float ms);
void renderGame() const;
};
- GameModule GM() 实例化类GameModule并传入所需参数(窗口句柄、窗口命名、窗口宽度、游戏帧率)
- GM.initGameModules() 初始化游戏组件及窗口环境
- Tetris T() 实例化类Tetris并传入所需参数(窗口句柄、窗口指针、渲染器指针)
- T.loadResources() 加载游戏资源至内存,并将其地址赋值到类Tetris中对应的资源指针
- GM.embedGameMechanic(T) 通过将派生类Tetris地址赋给GameMoudle中的基类指针gameMechanic,使上层的Tetris游戏状态机嵌入到GameModule搭建好的游戏环境中
- GM.runGame() 启动运行游戏循环
- T.unloadResources() 释放加载至内存的游戏资源
- GM.uninitGameModules() 释放加载的窗口指针和渲染器指针以及各项SDL游戏组件
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine, _In_ int nShowCmd) {
GameModule GM(hInstance, "TetrisGame", TETRIS_WINDOW_WIDTH, TETRIS_WINDOW_HEIGHT, 60);
GM.initGameModules();
Tetris T(hInstance, GM.getpWin(), GM.getpRenderer());
T.loadResources();
GM.embedGameMechanic(T);
GM.runGame();
T.unloadResources();
GM.uninitGameModules();
return 0;
}