<repo_name>RoolApps/YetAnotherFindPair<file_sep>/gridcontroller.cpp
#include "gridcontroller.h"

/*ctor*/
GridController::GridController(Item object) : IQmlComponentController(object){

}

/*dtor*/
GridController::~GridController(){
    clear();
}

/*private*/
bool GridController::checkIfSolved(){
    for(int row = 0; row < c_gridArray2d.size(); row++){
        for(int col = 0; col < c_gridArray2d.at(row).size(); col++){
            if (c_gridArray2d.at(row).at(col)->canRotate()) {
                return false;
            }
        }
    }
    return true;
}

void GridController::clear(){
    for(int row = 0; row < c_gridArray2d.size(); row++){
        for(int col = 0; col < c_gridArray2d.at(row).size(); col++){
            CellController * cell = c_gridArray2d.at(row).at(col);
            if(cell->type() != Game::CellType::Transparent) {
                disconnect(cell, SIGNAL(cellClicked(CellController*)), this, SLOT(cellClicked(CellController*)));
                disconnect(cell, SIGNAL(cellRotationStopped(CellController*)), this, SLOT(cellRotationStopped(CellController*)));
            }
        }
    }

    c_hints.clear();

    for(GridArray2d::iterator array = c_gridArray2d.begin(); array != c_gridArray2d.end(); ++array){
        for(GridArray::iterator it = array->begin(); it != array->end(); ++it){
            delete *it; *it = nullptr;
        }
        array->clear();
    }
    c_gridArray2d.clear();

    while(!c_qmlObject->childItems().empty()){
        Item item = c_qmlObject->childItems().takeFirst();
        delete item; item = nullptr;
    }
}

CellController * GridController::createCellController(int size) const{
    Item cell = Game::createQmlObject("CppCell");
    cell->setParent(c_qmlObject);
    cell->setParentItem(c_qmlObject);
    cell->setWidth(size);
    cell->setHeight(size);
    return new CellController(cell);
}

CellController * GridController::atIndex(Index index){
    return c_gridArray2d.at(index.r).at(index.c);
}

int GridController::calculateCellSize() const {
    int rows = read(qmlObject(), "rows").toInt();
    int columns = read(qmlObject(), "columns").toInt();
    int spacing = read(qmlObject(), "spacing").toInt();
    int width = read(qobject_cast<Item>(qmlObject()->parent()), "width").toInt() - (spacing * columns);
    int height = read(qobject_cast<Item>(qmlObject()->parent()), "height").toInt() - (spacing * rows);
    int size = qMin(width / columns, height / rows);
    return size - (size * .2);
}

/*public*/
void GridController::loadLevel(Level &level, bool shuffle){
    clear();

    c_hints = level.hints();
    write(c_qmlObject, "rows", level.rows());
    write(c_qmlObject, "columns", level.columns());
    this->rows = level.rows();
    this->columns = level.columns();
    this->difficulty = level.difficulty();

    if(shuffle) level.shuffle();

    int cellSize = calculateCellSize();

    for(int row = 0; row < level.rows(); row++){
        GridArray gridArray;
        for(int col = 0; col < level.columns(); col++){
            int type = level.hints().at(row * level.columns() + col).toInt();
            CellController *cellController = createCellController(cellSize);
            cellController->setType(static_cast<Game::CellType>(type));
            if (type == Game::CellType::Transparent) {
                cellController->setCanRotate(false);
            }
            else {
                connect(cellController, SIGNAL(cellClicked(CellController*)), this, SLOT(cellClicked(CellController*)));
                connect(cellController, SIGNAL(cellRotationStopped(CellController*)), this, SLOT(cellRotationStopped(CellController*)));
            }
            gridArray.append(cellController);
        }
        c_gridArray2d.append(gridArray);
    }

    levelReady();
}

Index GridController::getIndex(CellController * controller) {
    for(int row = 0; row < c_gridArray2d.size(); row++){
        for(int col = 0; col < c_gridArray2d.at(row).size(); col++){
            CellController * cell = c_gridArray2d.at(row).at(col);
            if(cell == controller) {
                return Index(row, col);
            }
        }
    }
    return Index();
}

/*private slot*/
void GridController::cellClicked(CellController * controller) {

    if (firstCell != NULL && secondCell != NULL) {
        return;
    }

    if (firstCell == controller || secondCell == controller) {
        return;
    }

    if (!controller->canRotate()) {
        return;
    }

    emit makeMoveSound();

    if (firstCell == NULL) {
        firstCell = controller;
    }
    else {
        secondCell = controller;
    }
    controller->rotate();
}

void GridController::cellRotationStopped(CellController * controller) {

    if (firstCell == NULL || secondCell == NULL) {
        return;
    }
    if (secondCell != controller) {
        return;
    }
    if(firstCell->type() == secondCell->type()) {
        firstCell->setCanRotate(false);
        secondCell->setCanRotate(false);
    }
    else {
        firstCell->rotate();
        secondCell->rotate();
    }
    if(checkIfSolved()) {
        firstCell = NULL;
        secondCell = NULL;
        emit solved();
        return;
    }
    moveCells();
}

void GridController::shiftCells(GridArray * cells, int chunkSize) {

    Index * indices = new Index[cells->size()];
    GridArray * newCells = new GridArray();

    int itemsProcessed = 0;
    int chunksProcessed = 0;

    int offset = randomBool() ? 1 : -1;

    for(int cellIndex=0;cellIndex<cells->size();cellIndex++) {
        int sourceCellIndex = cellIndex + offset;
        if (sourceCellIndex == chunksProcessed * chunkSize - 1) {
            sourceCellIndex = (chunksProcessed + 1) * chunkSize - 1;
        }
        if (sourceCellIndex == (chunksProcessed + 1) * chunkSize) {
            sourceCellIndex = chunksProcessed * chunkSize;
        }
        indices[cellIndex] = getIndex(cells->at(sourceCellIndex));
        newCells->append(cells->at(sourceCellIndex));
        itemsProcessed++;
        if(itemsProcessed==chunkSize) {
            chunksProcessed++;
            itemsProcessed=0;
            offset = randomBool() ? 1 : -1;
        }
    }

    GridArray * movedCells = new GridArray();

    for(int cellIndex=0;cellIndex<cells->size();cellIndex++) {
        QMetaObject::Connection * connection = new QMetaObject::Connection();
        *connection = QObject::connect(cells->at(cellIndex), &CellController::cellMovementStopped, [movedCells, cells, newCells, indices, connection, this](CellController * controller) {
            QObject::disconnect(*connection);
            delete connection;
            movedCells->append(controller);
            if(std::equal(cells->begin(), cells->end(), movedCells->begin())) {
                delete indices;
                delete newCells;
                delete cells;
                delete movedCells;
                this->firstCell = NULL;
                this->secondCell = NULL;
            }
        });
        Index index = indices[cellIndex];
        c_gridArray2d[index.r][index.c] = cells->at(cellIndex);
        cells->at(cellIndex)->setPosition(newCells->at(cellIndex)->getPosition());
    }

}

void GridController::moveCells() {

    bool moveRow = randomBool();
    int length = moveRow ? this->columns : this->rows;
    QVector<int> vector = QVector<int>();
    int vectorLength = moveRow ? this->rows : this->columns;
    for(int i=0;i<vectorLength;i++) {
        vector.append(i);
    }
    std::random_shuffle(vector.begin(), vector.end());
    GridArray * cells = new GridArray();

    for(int move=0;move<this->difficulty;move++) {
        for(int cell=0;cell<length;cell++) {
            if(moveRow) {
                cells->append(c_gridArray2d.at(vector.at(move)).at(cell));
            }
            else {
                cells->append(c_gridArray2d.at(cell).at(vector.at(move)));
            }
        }
    }
    shiftCells(cells, length);
}

/*public virtual*/
void GridController::info() const{
    return IQmlComponentController::info();
}
<file_sep>/main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQmlEngine>
#include <QSettings>

#include "QtAdMob/QtAdMobBanner.h"
#include "QtAdMob/QtAdMobInterstitial.h"

#include "game.h"
#include "level.h"
#include "settings.h"
#include "gameplane.h"
#include "gameresult.h"

QString appPath;

int main(int argc, char *argv[]){
    QGuiApplication app(argc, argv);
    appPath = app.applicationDirPath();

    app.setOrganizationName(GAME_ORGANIZATION);
    app.setApplicationName(GAME_NAME);

    QmlAdMobBanner::DeclareQML();
    QmlAdMobInterstitial::DeclareQML();

    qmlRegisterType<Game>("CppItems", 0, 1, "Game");
    qmlRegisterType<GamePlane>("CppItems", 0, 1, "GamePlane");
    qmlRegisterType<Level>("CppItems", 0, 1, "Level");
    qmlRegisterType<GameResult>("CppItems", 0, 1, "GameResult");

    QQmlApplicationEngine engine;

    Game::setEngine(&engine);

    Settings settings;

    engine.rootContext()->setContextProperty("applicationPath", appPath);
    engine.rootContext()->setContextProperty("applicationSettings", &settings);

    engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));

    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}
<file_sep>/gameresult.cpp
#include "gameresult.h"

/*ctor*/
GameResult::GameResult(QObject *parent) : QObject(parent), c_levelId(0), c_moves(0){

}

/*public*/
int GameResult::levelId() const{
    return c_levelId;
}

void GameResult::setLevelId(int id){
    if(c_levelId != id){
        c_levelId = id;
        emit levelIdChanged();
    }
}

int GameResult::moves() const{
    return c_moves;
}

void GameResult::setMoves(int moves){
    if(c_moves != moves){
        c_moves = moves;
        emit movesChanged();
    }
}

QString GameResult::time() const{
    return c_time;
}

void GameResult::setTime(QString time){
    if(c_time != time){
        c_time = time;
        emit timeChanged();
    }
}
<file_sep>/settings.cpp
#include "settings.h"

/*ctor*/
Settings::Settings(QObject *parent) : QSettings("app.ini", QSettings::IniFormat, parent){
    if(!contains("settingsInitialized")){
        setValue("settingsInitialized", true);

        // Levels
        beginWriteArray("levels");
        for(int id = 0; id < GAME_LEVELS_COUNT; id++){
            setArrayIndex(id);
            setValue("id", id);
            setValue("open", false);
        }
        endArray();
        setValue("levels/1/open", true);

        // Sound
        beginGroup("sound");
        setValue("backgroundMusic", true);
        setValue("slideEffectSound", true);
        endGroup();

        // Timer
        setValue("timer", false);
    }
}

/*public INVOKABLE*/
void Settings::print() const{
    dbg << "SETTINGS:";
    dbg << value("settingsInitialized").toBool();
    dbg << value("levels/size");
}

int Settings::levelId(int id) const{
    QString val = QString("levels/%1/id").arg(++id);
    return value(val).toInt();
}

bool Settings::levelOpened(int id) const{
    QString val = QString("levels/%1/open").arg(++id);
    return value(val).toBool();
}

void Settings::setLevelOpened(int id, bool state){
    QString target = QString("levels/%1/open").arg(++id);
    setValue(target, state);
    emit levelStateChanged(id, state);
}

int Settings::levelsCount() const{
    return value("levels/size").toInt();
}

bool Settings::backgroundMusic() const{
    return value("sound/backgroundMusic").toBool();
}

void Settings::setBackgroundMusic(bool state){
    setValue("sound/backgroundMusic", state);
    emit backgroundMusicStateChanged(state);
}

bool Settings::slideEffectSound() const{
    return value("sound/slideEffectSound").toBool();
}

void Settings::setSlideEffectSound(bool state){
    setValue("sound/slideEffectSound", state);
    emit slideEffectSoundStateChanged(state);
}

bool Settings::timer() const{
    return value("timer").toBool();
}

void Settings::setTimer(bool state){
    setValue("timer", state);
    emit timerStateChanged(state);
}
<file_sep>/cellcontroller.h
#ifndef CELLCONTROLLER_H
#define CELLCONTROLLER_H

#include "game.h"
#include "iqmlcomponentcontroller.h"
#include <QPoint>

class CellController : public QObject, public IQmlComponentController{
    Q_OBJECT

private:
    bool c_canRotate = true;
    Game::CellType c_type;
    bool flipped();

public:
    explicit CellController(Item = nullptr);

public:
    bool canRotate();
    Game::CellType type() const;
    void setType(Game::CellType);
    void moveLeft(int);
    void moveRight(int);
    void moveUp(int);
    void moveDown(int);
    QPoint getPosition();
    void setPosition(QPoint);
    void rotate();
    void setCanRotate(bool);

public:
    virtual void info() const;

private slots:
    void onAnimationStopped();
    void onClicked();
    void onRotationStopped();

signals:
    void cellClicked(CellController *);
    void cellRotationStopped(CellController *);
    void cellMovementStopped(CellController *);
};

#endif // CELLCONTROLLER_H
<file_sep>/gridcontroller.h
#ifndef GRIDCONTROLLER_H
#define GRIDCONTROLLER_H

#include <QObject>
#include <QScreen>
#include <QGuiApplication>

#include "game.h"
#include "utils.h"
#include "level.h"
#include "cellcontroller.h"

class GridController : public QObject, public IQmlComponentController{
    Q_OBJECT

private:
    typedef QVector<CellController *> GridArray;
    typedef QVector<GridArray> GridArray2d;
    int difficulty;
    int columns;
    int rows;

private:
    GridArray2d c_gridArray2d;
    QVariantList c_hints;
    CellController * firstCell = NULL;
    CellController * secondCell = NULL;

public:
    GridController(Item = nullptr);
    ~GridController();

private:
    void clear();
    bool checkIfSolved();
    Index cellIndex(Game::CellType) const;
    CellController * createCellController(int = 100) const;
    CellController * atIndex(Index);
    int calculateCellSize() const;
    Index getIndex(CellController *);
    void moveCells();
    void shiftCells(GridArray * cells, int chunkSize);

private slots:
    void cellClicked(CellController *);
    void cellRotationStopped(CellController * controller);

public:
    void loadLevel(Level &, bool = false);

public:
    virtual void info() const;

signals:
    void levelReady();
    void solved();
    void makeMoveSound();
};

#endif // GRIDCONTROLLER_H
<file_sep>/gameplane.cpp
#include "gameplane.h"

#include <QTime>

#include "settings.h"

extern QString appPath;

/*ctor*/
GamePlane::GamePlane(QQuickItem *parent) : QQuickItem(parent), c_movesCounter(0),
    c_foregroundGridController(nullptr), c_foregroundGrid(nullptr), c_backgroundGrid(nullptr), c_currentLevel(nullptr){

    c_tickTimer.setInterval(1000);
    connect(&c_tickTimer, SIGNAL(timeout()), this, SLOT(timerTick()));
    c_tickTimer.start();
}

GamePlane::~GamePlane(){
    delete c_foregroundGridController;
}

/*private*/
void GamePlane::makeSoundEffect(){
    c_movesCounter++;
    emit soundEffect();
}

QQuickItem * GamePlane::foregroundGrid() const{
    return c_foregroundGrid;
}

QQuickItem * GamePlane::backgroundGrid() const{
    return c_backgroundGrid;
}

void GamePlane::setForegroundGrid(QQuickItem *grid){
    delete c_foregroundGridController;
    c_foregroundGrid = grid;
    c_foregroundGrid->setParent(this);
    c_foregroundGrid->setParentItem(this);
    c_foregroundGridController = new GridController(c_foregroundGrid);
    connect(c_foregroundGridController, &GridController::solved, this, &GamePlane::openNewLevel);
    connect(c_foregroundGridController, &GridController::makeMoveSound, this, &GamePlane::makeSoundEffect);
    if(c_backgroundGrid != nullptr) {
        connect(c_foregroundGrid, SIGNAL(heightChanged()), this, SLOT(foregroundGridHeightChanged()));
    }
}

void GamePlane::foregroundGridHeightChanged() {
    if(c_foregroundGrid!=nullptr) {
        c_backgroundGrid->setHeight(c_foregroundGrid->height() + c_magicNumber);
        c_backgroundGrid->setWidth(c_foregroundGrid->width() + c_magicNumber);
    }
}

void GamePlane::setBackgroundGrid(QQuickItem * grid){
    c_backgroundGrid = grid;
    c_backgroundGrid->setParent(this);
    c_backgroundGrid->setParentItem(this);
    if(c_foregroundGrid != nullptr) {
        foregroundGridHeightChanged();
        connect(c_foregroundGrid, SIGNAL(heightChanged()), this, SLOT(foregroundGridHeightChanged()));
    }
}

/*public Q_INVOKABLE*/
void GamePlane::restartLevel(){
    loadLevel(c_currentLevel);
}

QVariantList GamePlane::generateHints(int rows, int columns) {
    int types = rows * columns / 2;

    const QMetaObject *metaObject = qt_getEnumMetaObject(Game::CellType());
    const char *name = qt_getEnumName(Game::CellType());
    QMetaEnum cellType = metaObject->enumerator(metaObject->indexOfEnumerator(name));
    int length = cellType.keyCount();
    QVector<int> vector = QVector<int>();
    for(int i=0;i<length;i++) {
        if((int)Game::CellType::Transparent != i) {
            vector.append(i);
        }
    }
    std::random_shuffle(vector.begin(), vector.end());
    QVariantList hints = QVariantList();
    for(int i=0;i<types;i++) {
       hints.append(QVariant(vector.at(i)));
       hints.append(QVariant(vector.at(i)));
    }
    if (types * 2 != rows * columns) {
        hints.append(QVariant((int)Game::CellType::Transparent));
    }
    std::random_shuffle(hints.begin(), hints.end());
    return hints;
}

void GamePlane::loadLevel(Level *level){
    if(c_currentLevel != nullptr) {

    }
    c_currentLevel = level;
    c_movesCounter = 0;

    if(c_foregroundGrid) {
        Level foregroundLevel(*level);

        QVariantList foregroundHints = generateHints(level->rows(), level->columns());
        foregroundLevel.setHints(foregroundHints);
        c_foregroundGridController->loadLevel(foregroundLevel, true);
        foregroundGridHeightChanged();

        c_timer.start();
        secondsElapsed = -1;
        timerTick();
    }
}

void GamePlane::timerTick() {
    secondsElapsed++;
    QTime time = QTime(0, secondsElapsed / 60, secondsElapsed % 60);
    emit secondTick(time.toString("mm:ss"));
}

/*public slots*/
void GamePlane::openNewLevel(){
    Settings settings;
    int newLevelId = c_currentLevel->levelId() + 1;
    if(newLevelId < settings.levelsCount()) settings.setLevelOpened(newLevelId, true);
    GameResult result;
    result.setLevelId(newLevelId);
    result.setMoves(c_movesCounter);
    result.setTime(QTime::fromMSecsSinceStartOfDay(c_timer.elapsed()).toString("mm:ss"));
    emit newLevelOpened(&result);
}
<file_sep>/gameplane.h
#ifndef GAMEPLANE_H
#define GAMEPLANE_H

#include <QQuickItem>
#include <QQmlProperty>
#include <QSoundEffect>
#include <QElapsedTimer>
#include <QTimer>

#include "game.h"
#include "level.h"
#include "gridcontroller.h"
#include "gameresult.h"

class GamePlane : public QQuickItem {
    Q_OBJECT

    Q_PROPERTY(QQuickItem * foregroundGrid READ foregroundGrid WRITE setForegroundGrid)
    Q_PROPERTY(QQuickItem * backgroundGrid READ backgroundGrid WRITE setBackgroundGrid)

private:
    int c_magicNumber = 50;
    int c_movesCounter;
    int secondsElapsed = 0;
    QElapsedTimer c_timer;
    QTimer c_tickTimer;
    GridController *c_foregroundGridController;
    QQuickItem *c_foregroundGrid;
    QQuickItem *c_backgroundGrid;
    Level *c_currentLevel;

public:
    explicit GamePlane(QQuickItem * = 0);
    ~GamePlane();

private:
    void makeSoundEffect();
    QVariantList generateHints(int, int);

private slots:
    void foregroundGridHeightChanged();
    void timerTick();

public:
    Q_INVOKABLE void restartLevel();
    Q_INVOKABLE void loadLevel(Level *);

public:
    QQuickItem * backgroundGrid() const;
    QQuickItem * foregroundGrid() const;
    void setForegroundGrid(QQuickItem *);
    void setBackgroundGrid(QQuickItem *);

signals:
    void newLevelOpened(GameResult *result);
    void soundEffect();
    void secondTick(QString);

public slots:
    void openNewLevel();
};

#endif // GAMEPLANE_H
<file_sep>/level.cpp
#include "level.h"

#include <ctime>

/*ctor*/
Level::Level(QObject *parent) : QObject(parent), c_levelId(0), c_rows(0), c_columns(0), c_difficulty(0){
    srand(time(NULL));
}

Level::Level(Level const &level) : QObject(nullptr){
    *this = level;
}

Level & Level::operator=(Level const &level){
    if(this == &level) return *this;
    c_levelId = level.c_levelId;
    c_rows = level.c_rows;
    c_columns = level.c_columns;
    c_difficulty = level.c_difficulty;
    c_hints = level.c_hints;
    return *this;
}

/*public*/
int Level::levelId() const{
    return c_levelId;
}

void Level::setLevelId(int id){
    if(c_levelId != id){
        c_levelId = id;
        emit levelIdChanged();
    }
}

int Level::rows() const{
    return c_rows;
}

void Level::setRows(int rows){
    if(c_rows != rows){
        c_rows = rows;
        emit rowsChanged();
    }
}

int Level::columns() const{
    return c_columns;
}

void Level::setColumns(int columns){
    if(c_columns != columns){
        c_columns = columns;
        emit columnsChanged();
    }
}

int Level::difficulty() const{
    return c_difficulty;
}

void Level::setDifficulty(int difficulty){
    if(c_difficulty != difficulty){
        c_difficulty = difficulty;
        emit difficultyChanged();
    }
}

QList<QVariant> Level::hints() const{
    return c_hints;
}

void Level::setHints(QList<QVariant> &list){
    if(c_hints != list){
        c_hints = list;
        emit hintsChanged();
    }
}

void Level::shuffle(){
//    Index previousStep;
//    for(int steps = 0; steps < static_cast<int>(c_difficulty * 10);){
//        int row = empty / c_columns;
//        int col = empty % c_columns;
//        Index indexes[] = {Index(0, -1), Index(0, 1), Index(-1, 0), Index(1, 0)};
//        Index step = indexes[rand() % (sizeof(indexes) / sizeof(*indexes))];
//        if( (step == indexes[Game::Left] && previousStep == indexes[Game::Right]) ||
//            (step == indexes[Game::Right] && previousStep == indexes[Game::Left]) ||
//            (step == indexes[Game::Up] && previousStep == indexes[Game::Down]) ||
//            (step == indexes[Game::Down] && previousStep == indexes[Game::Up])) continue;
//        Index move(row + step.r, col + step.c);
//        if(move.isValid(c_rows, c_columns)){
//            previousStep = step;
//            int index = move.r * c_columns + move.c;
//            std::swap(c_hints[empty], c_hints[index]);
//            empty = index;
//            steps++;
//        }
//    }
}
<file_sep>/iqmlcomponentcontroller.cpp
#include "iqmlcomponentcontroller.h"

/*ctor*/
IQmlComponentController::IQmlComponentController(Item object) : c_qmlObject(object){
}

/*public*/
Item IQmlComponentController::qmlObject() const{
    return c_qmlObject;
}

void IQmlComponentController::setQmlObject(Item object){
    if(c_qmlObject != object){
        c_qmlObject = object;
    }
}

QVariant IQmlComponentController::read(Item item, QString property) const{
    return QQmlProperty::read(item, property);
}

void IQmlComponentController::write(Item item, QString property, QVariant value){
    QQmlProperty::write(item, property, value);
}

/*public virtual*/
void IQmlComponentController::info() const{
    dbg << "INFO:" << c_qmlObject;
    dbg << "Parent INFO" << c_qmlObject->parent();
    dbg << "ParentItem INFO" << c_qmlObject->parentItem();
    dbg << "Childrens count: " << c_qmlObject->children().length() << "childItems count:" << c_qmlObject->childItems().size();
    dbg << "Childrens: " << c_qmlObject->children();
    dbg << "ChildItems: " << c_qmlObject->childItems();
    dbg << " --------------------- ";
}
<file_sep>/iqmlcomponentcontroller.h
#ifndef IQMLCOMPONENTCONTROLLER_H
#define IQMLCOMPONENTCONTROLLER_H

#include <QQuickItem>
#include <QQmlProperty>

#include "game.h"

typedef QQuickItem * Item;

class IQmlComponentController{
protected:
    Item c_qmlObject;

public:
    explicit IQmlComponentController(Item = nullptr);

public:
    Item qmlObject() const;
    void setQmlObject(Item);
    QVariant read(Item, QString) const;
    void write(Item, QString, QVariant);

public:
    virtual void info() const;
};

#endif // IQMLCOMPONENTCONTROLLER_H
<file_sep>/level.h
#ifndef LEVEL_H
#define LEVEL_H

#include <QQuickItem>

#include "game.h"

/*
 * 0 - Empty
 * 1 - Red triangle
 * 2 - Red rectangle
 * 3 - Red circle
*/

class Level : public QObject{
    Q_OBJECT

    Q_PROPERTY(int levelId READ levelId WRITE setLevelId NOTIFY levelIdChanged)
    Q_PROPERTY(int rows READ rows WRITE setRows NOTIFY rowsChanged)
    Q_PROPERTY(int columns READ columns WRITE setColumns NOTIFY columnsChanged)
    Q_PROPERTY(int difficulty READ difficulty WRITE setDifficulty NOTIFY difficultyChanged)
    Q_PROPERTY(QVariantList hints READ hints WRITE setHints NOTIFY hintsChanged)

private:
    int c_levelId;
    int c_rows;
    int c_columns;
    int c_difficulty;
    QVariantList c_hints;

public:
    Level(QObject * = nullptr);
    Level(Level const &);
    Level & operator=(Level const &);

public:
    int levelId() const;
    void setLevelId(int);
    int rows() const;
    void setRows(int);
    int columns() const;
    void setColumns(int);
    int difficulty() const;
    void setDifficulty(int);
    QVariantList hints() const;
    void setHints(QVariantList &);

    void shuffle();

signals:
    void levelIdChanged();
    void rowsChanged();
    void columnsChanged();
    void difficultyChanged();
    void hintsChanged();

public slots:

};

#endif // LEVEL_H
<file_sep>/cellcontroller.cpp
#include "cellcontroller.h"

extern QString appPath;

/*ctor*/
CellController::CellController(Item object) : IQmlComponentController(object) {
    connect(c_qmlObject, SIGNAL(animationStopped()), this, SLOT(onAnimationStopped()));
    connect(c_qmlObject, SIGNAL(clicked()), this, SLOT(onClicked()));
    connect(c_qmlObject, SIGNAL(rotationStopped()), this, SLOT(onRotationStopped()));
}

/*public*/
bool CellController::canRotate() {
    return c_canRotate;
}

void CellController::setCanRotate(bool canRotate) {
    c_canRotate = canRotate;
}

bool CellController::flipped() {
    return read(c_qmlObject, "flipped").toBool();
}

Game::CellType CellController::type() const {
    return c_type;
}

void CellController::setType(Game::CellType type) {
    write(c_qmlObject, "source", Game::fullImagePath(type));
    write(c_qmlObject, "frontSource", Game::fullFrontImagePath(type));
    c_type = type;
}

QPoint CellController::getPosition() {
    return QPoint(read(c_qmlObject, "x").toInt(), read(c_qmlObject, "y").toInt());
}

void CellController::setPosition(QPoint point) {
    write(c_qmlObject, "x", point.x());
    write(c_qmlObject, "y", point.y());
}

/*public virtual*/
void CellController::info() const {
    return IQmlComponentController::info();
}

/*private slots*/
void CellController::onAnimationStopped() {
    cellMovementStopped(this);
}

void CellController::onRotationStopped() {
    if(flipped()) {
        cellRotationStopped(this);
    }
}

void CellController::rotate() {
    write(c_qmlObject, "flipped", !flipped());
}

void CellController::onClicked() {
    cellClicked(this);
}
<file_sep>/game.cpp
#include "game.h"

QQmlEngine * Game::c_engine = nullptr;

/*ctor*/
Game::Game(QObject *parent) : QObject(parent){

}

/*public*/
QQmlEngine * Game::engine(){
    return Game::c_engine;
}

void Game::setEngine(QQmlEngine *engine){
    if(Game::c_engine != engine){
        Game::c_engine = engine;
    }
}

QQuickItem * Game::createQmlObject(QString const name){
    QQmlComponent component(Game::engine(), QUrl("qrc:/qml/Cpp/" + name + ".qml"));
    QQuickItem * qmlComponent = qobject_cast<QQuickItem *>(component.create());
    return qmlComponent;
}

QString Game::cellTypeAsString(CellType type){
    QString stype = staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("CellType")).valueToKey(type);
    return stype;
}

QString Game::fullImagePath(CellType type){
    if (type == Game::CellType::Transparent) {
        return QString("qrc:/assets/svg/Icons/_Transparent.svg");
    }
    else {
        return QString("qrc:/assets/svg/Icons/" + cellTypeAsString(type) + ".svg");
    }
}

QString Game::fullFrontImagePath(CellType type){
    if (type == Game::CellType::Transparent) {
        return QString("qrc:/assets/svg/Icons/_Transparent.svg");
    }
    else {
        return QString("qrc:/assets/svg/Icons/_Hidden.svg");
    }
}
<file_sep>/game.h
#ifndef GAME_H
#define GAME_H

#include <QObject>
#include <QQuickItem>
#include <QQmlEngine>

#include "utils.h"

#define GAME_ORGANIZATION "ROOL APPS"
#define GAME_NAME "Yet Another Find Pair"
#define GAME_LEVELS_COUNT 35

class Game : public QObject {
    Q_OBJECT

    Q_PROPERTY(QQmlEngine * engine READ engine WRITE setEngine)

public:
    enum MoveDirection {Left, Right, Up, Down};
    enum CellType {
                   Transparent,
                   Bug,
                   Flower,
                   Pepper,
                   Apple,
                   Ball,
                   Bird,
                   Bagel,
                   Cactus,
                   Cat,
                   Cloud,
                   Cup,
                   Cupcake,
                   Diamond,
                   Eye,
                   Feather,
                   Icecream,
                   Leaf,
                   Mushroom,
                   Peach,
                   Pizza,
                   Plant,
                   Soda,
                   Sun,
                   Tree,
                   Turtle,
                   Umbrella,
                   Candle,
                   Candy,
                   Glasses,
                   Bulb,
                   Medusa,
                   Pawprint,
                   Shell,
                   Strawberry,
                   Bear
                  };
    Q_ENUM(MoveDirection)
    Q_ENUM(CellType)

private:
    static QQmlEngine *c_engine;

public:
    Game(QObject * = 0);

public:
    static QQmlEngine * engine();
    static void setEngine(QQmlEngine *);
    static QQuickItem * createQmlObject(QString const);
    static QString cellTypeAsString(CellType);
    static QString fullImagePath(CellType);
    static QString fullFrontImagePath(CellType);

signals:

public slots:
};

#endif // GAMELOGIC_H
<file_sep>/Utils.js
function percent(p, value){
    return (value * p) / 100;
}
<file_sep>/settings.h
#ifndef SETTINGS_H
#define SETTINGS_H

#include <QObject>
#include <QSettings>

#include "game.h"

class Settings : public QSettings{
    Q_OBJECT

public:
    explicit Settings(QObject * = nullptr);

public:
    Q_INVOKABLE void print() const;
    Q_INVOKABLE int levelId(int) const;
    Q_INVOKABLE bool levelOpened(int) const;
    Q_INVOKABLE void setLevelOpened(int, bool);
    Q_INVOKABLE int levelsCount() const;
    Q_INVOKABLE bool backgroundMusic() const;
    Q_INVOKABLE void setBackgroundMusic(bool);
    Q_INVOKABLE bool slideEffectSound() const;
    Q_INVOKABLE void setSlideEffectSound(bool);
    Q_INVOKABLE bool timer() const;
    Q_INVOKABLE void setTimer(bool);

signals:
    void levelStateChanged(int, bool);
    void backgroundMusicStateChanged(bool);
    void slideEffectSoundStateChanged(bool);
    void timerStateChanged(bool);
};

#endif // SETTINGS_H
<file_sep>/utils.h
#ifndef UTILS_H
#define UTILS_H

#include <QDebug>
#include <QRandomGenerator>
#define dbg qDebug()

struct Index {
    int r;
    int c;

    Index(int row = -1, int col = -1) : r(row), c(col) {}

    bool operator==(Index other){
        return r == other.r && c == other.c;
    }

    bool operator!=(Index other){
        return !(*this == other);
    }

    bool isValid(int maxRow, int maxCol){
        return r >= 0 && r < maxRow && c >= 0 && c < maxCol;
    }

    void print(){
        qDebug() << "row:" << r << ", col:" << c;
    }
};

inline bool randomBool() {
    return QRandomGenerator::global()->generate() % 2 == 0;
}

#endif // UTILS_H
<file_sep>/gameresult.h
#ifndef GAMERESULT_H
#define GAMERESULT_H

#include "game.h"

class GameResult : public QObject {
    Q_OBJECT

    Q_PROPERTY(int levelId READ levelId WRITE setLevelId NOTIFY levelIdChanged)
    Q_PROPERTY(int moves READ moves WRITE setMoves NOTIFY movesChanged)
    Q_PROPERTY(QString time READ time WRITE setTime NOTIFY timeChanged)

private:
    int c_levelId;
    int c_moves;
    QString c_time;

public:
    explicit GameResult(QObject *parent = nullptr);

public:
    int levelId() const;
    void setLevelId(int);
    int moves() const;
    void setMoves(int);
    QString time() const;
    void setTime(QString);

signals:
    void levelIdChanged();
    void movesChanged();
    void timeChanged();

public slots:
};

#endif // GAMERESULT_H
