Съдържание
- Декларация на класа.
- Използване на класа.
- Модел на играта.
- Описание на някои от методите.
Декларация на класа.
Основно ще работим с един клас Numbers. В него ще бъде логиката на играта и няма да наследява никакъв клас.
Да си създадем файловете gsrc/Numbers.cpp и gsrc/Numbers.h – да припомним, че gsrc е директорията съдържаща класовете на играта.
- #ifndef NUMBERS_H
- #define NUMBERS_H
- #include <algorithm>
- #include „../include/AnimatedSprite.h“
- class Numbers
- {
- public:
- Numbers();
- ~Numbers();
- // определяме повърхността върху която ще показваме плочките
- void setSurface(SDL_Surface* screen);
- // показваме плочките
- void render();
- // започваме играта отначало
- void restartGame();
- // започваме нова игра
- void newGame();
- private:
- // посоки на движение на плочка
- enum DIRECTIONS {
- LEFT = 1, RIGHT, TOP, BOTTOM
- } mDirections;
- // x позиция на първата плочка
- int tilePosX;
- // y позиция на първата плочка
- int tilePosY;
- // широчина на плочката
- int tileWidth;
- // височина на плочката
- int tileHeight;
- // съхранява текущата позиция – само числата
- int* currentPositionOfNumbers;
- // Първоначална подредба
- int initialPosition[16];
- // Позицията към която се стремим
- int targetPosition[16];
- // повърхността върху която ще рисуваме
- SDL_Surface* screen;
- // използва се нулевата плочка
- AnimatedSprite* emptyTile;
- // обект, който използваме за попълване на vectorOfTiles
- AnimatedSprite* tile;
- // вектор съдържащ плочките
- vector<AnimatedSprite*> vectorOfTiles;
- // връща начална позиция
- int* getNumberOrder();
- // преценява може ли да се премести дадена плочка и накъде
- int moveDecision(int);
- // инициализиране
- void clear();
- // попълва vectorOfTiles с данните от getNumberOrder
- void initVectorOfTiles(bool);
- // връща true, ако играта е достигнала крайна позиция
- bool isFinish(int*);
- };
- #endif // NUMBERS_H
В Numbers.cpp да напишем методите с празни тела, за да ги сложим първоначално на правилните места в играта.
- #include „Numbers.h“
- Numbers::Numbers(){}
- // тук ще извеждаме плочките използвайки тяхният метод render
- void Numbers::render() {}
- Numbers::~Numbers(){}
- void Numbers::setSurface(SDL_Surface* screen) {}
- // вземане на решение, на къде може да се премести плочката
- int Numbers::moveDecision(int pos) {}
- // връща валидна начална позиция на пъзела
- int* Numbers::getValidNumbersPosition() {}
- // започваме нова игра
- void Numbers::newGame() {}
- // започваме играта отначало
- void Numbers::restartGame(){}
- // изчистване на данните
- void Numbers::clear() {}
- // инициализиране на вектора съдържащ плочките
- void Numbers::initVectorOfTiles(bool restart) {}
- // достигнали ли сме целта
- bool Numbers::isFinish(int* target) {}
Използване на класа.
Да декларираме обект от тип Numbers в Game13.h – файлът, отговарящ за обработката на графиката, регистрирането на UI компонентите (бутони, background и т.н.) и т.н.
- Numbers* mNumbers;
В метода init на Game15 в края добавяме:
- mNumbers = new Numbers();
- mNumbers->setSurface(screen);
В render след:
- btnRestartGame->render(screen);
добавяме:
- if (mNumbers) {
- mNumbers->render();
- }
Добавяме и обработка на бутоните за нова игра и рестартиране на игра.
- if (btnToMainMenu->isMouseButtonDown()) {
- …………………………………………….
- } else if (btnNewGame->isMouseButtonDown()) {
- mNumbers->newGame();
- } else if (btnRestartGame->isMouseButtonDown()) {
- mNumbers->restartGame();
- }
В деструктора Game15::~Game15() добавяме:
- delete mNumbers;
- mNumbers = 0;
Сега можем да компилираме и стартираме. Поведението на играта няма да се промени, но ще можем да пристъпим към кодирането на Numbers.cpp.
Модел на играта.
Като погледнем на играта една от идеите, които ни идва за описанието й, е представянето й като масив. Но в случая аз ще използвам линейна структура – вектор, който ще пази обектите – плочките с числа. Причината е, че много по-лесно е да се получи вектор със случайни стойности, отколкото матрица и въобще някои алгоритми за линейни структури от данни са вече готови и няма нужда да ги приспособяваме за масиви. Оттук и всички останали помощни масиви ще са едномерни.
В конструктора инициализираме началната позиция на плочката – по нея ще се подредят и останалите. Също така инициализираме масива с началната позиция (initialPosition) и масива с позицията към която ще се стремим targetPosition.
Описание на някои от методите.
- Numbers::Numbers(){
- tilePosX = 30;
- tilePosY = 30;
- tileWidth = 80;
- tileHeight = 80;
- for (int i = 0; i < 16; i++) {
- initialPosition[i] = 0;
От горния код се вижда, че позицията, към която ще се стремим, е най-разпространената:
Накрая в конструктора извикваме метода initVectorOfTiles(false), с който вземаме валидна начална позиция на пъзела и попълваме currentPositionOfNumbers, vectorOfTiles и SpriteManager. Параметърът, който се подава, е false, защото нямаме рестарт на играта. Да припомня, че обектът за този клас ще се създава и унищожава в Game15.cpp.
Тук инициализираме и emptyTile обекта, който няма число, освен за представяне на празната плочка, emptyTile се използва и да зареди спрайта за числата:
и останалите обекти да не зареждат всеки свой екземпляр, а да го вземат от emptyTile.
- void Numbers::initVectorOfTiles(bool restart) {
- emptyTile = new AnimatedSprite();
- emptyTile->setImage(Constants::RESOURCE_DIR + Constants::pathSeparator + „numbers.png“);
- emptyTile->setName(„EMPTY“);
- int x = 0;
- int y = 0;
- if (restart) {
- for (int i = 0; i < 16;i++) {
- currentPositionOfNumbers[i] = initialPosition[i];
- }
- } else {
- currentPositionOfNumbers = getValidNumbersPosition();
- for (int i = 0; i < 16;i++) {
- initialPosition[i] = currentPositionOfNumbers[i];
- }
- }
- for (int m = 0; m < 16; m++) {
- if (currentPositionOfNumbers[m] != 0) {
- tile = new AnimatedSprite();
- tile->setTilesXY(15, 1);
- tile->setImage(emptyTile->getImage());
- tile->setGroup(„numbers“);
- tile->setName(„number_“+ConvertFunctions::intToString(currentPositionOfNumbers[m]-1));
- tile->setFrameBeginEnd(currentPositionOfNumbers[m]-1, currentPositionOfNumbers[m]-1);
- tile->calculate(10);
- tile->setX(tilePosX + tileWidth * x);
- tile->setY(tilePosY + tileHeight * y);
- SpriteManager::addSprite(tile);
- vectorOfTiles.push_back(tile);
- } else {
- vectorOfTiles.push_back(emptyTile);
- }
- x++;
- if (x % 4 == 0) {
- x = 0;
- y++;
- }
- }
- }
Ако сега компилираме и стартираме външното поведение на играта няма да се промени, защото още нищо не сме правили по метода render.
В setSurface определяме this->screen:
- void Numbers::setSurface(SDL_Surface* screen) {
- this->screen = screen;
- }
В render ще показваме плочките, които са в vectorOfTiles и ще обработваме mouse click. Ето фрагмент от кода:
- // ако сме попълни вектора
- if (vectorOfTiles.size() > 0) {
- // да покажем всички плочки
- for (int m = 0; m < 16; m++) {
- // ако сме кликнали върху плочката vectorOfTiles[m]
- if (vectorOfTiles[m]->isMouseButtonDown()) {
- // ако плочката може да се премести
- switch (moveDecision(m)) {
- // на ляво
- case LEFT:
- vectorOfTiles[m]->setX(vectorOfTiles[m]->getX() – tileWidth);
- vectorOfTiles[m]->setMouseButtonDown(false);
- swap(vectorOfTiles[m], vectorOfTiles[m–1]);
- break;
- case TOP:
- ……………………………………………………..
- default: // ако не може стои на място
- break;
- }
- // ако след преместването сме достигнали желаната позиция
- // извеждаме съобщение
- if (isFinish(targetPosition)) {
- cout << „Game over!“ << endl;
- }
- }
- // показваме плочките
- vectorOfTiles[m]->render(this->screen);
- }
Кодъг може да свалите оттук: Github Repo
Приятно кодиране 🙂
Автор: Янко Попов