Example of Snake game from NoobTuts tutorial rewritten in Qt C++ OpenGL

Published November 23, 2020
Advertisement

Example of Snake game from NoobTuts tutorial (Python Snake Game) rewritten in Qt C++ OpenGL

Demo for Windows: Snake2DNoobTuts_OpenGLES20_Qt5Cpp (11 MB)

There are two versions of sources:

main.cpp (OpenGL 3.3)

// Add this line to .pro:
// win32: LIBS += -lopengl32

#ifdef _WIN32
#include <windows.h>
extern "C" __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
extern "C" __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001;
#endif

#include <QtWidgets/QApplication>
#include <QtWidgets/QWidget>
#include <QtWidgets/QOpenGLWidget>
#include <QtWidgets/QVBoxLayout>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QLabel>
#include <QtGui/QOpenGLShaderProgram>
#include <QtGui/QOpenGLBuffer>
#include <QtGui/QMatrix4x4>
#include <QtGui/QKeyEvent>
#include <QtCore/QList>
#include <QtCore/QMutableListIterator>
#include <QtCore/QTimer>
#include <QtCore/QRandomGenerator>

class OpenGLWidget : public QOpenGLWidget {
    Q_OBJECT
public:
    OpenGLWidget(QWidget *parent = nullptr) : QOpenGLWidget(parent) {
        setFocusPolicy(Qt::StrongFocus);
    }
signals:
    void updateScore(QString score);
    void updateLives(QString lives);
private slots:
    void onUpdate() {
        // Move snake
        // Insert new position in the beginning of the snake list
        m_snake.insert(0, m_snake[0] + m_snakeDir);
        m_snake.removeLast();
        // Collision with itself
        int hx = m_snake[0].x();
        int hy = m_snake[0].y();
        for (int i = 0; i < m_snake.length(); i++) {
            if (i == 0)
                continue;
            if (hx == m_snake[i].x() && hy == m_snake[i].y()) {
                m_food.clear();
                m_snake.clear();
                m_snake.append(m_startPos);
                m_snakeDir = m_startDir;
                emit updateLives("Lives: " + QString::number(--m_lives));
                update();
                return;
            }
        }
        // Spawn food
        // Spawn food with 5% chance
        int r = QRandomGenerator::global()->bounded(0, 20);
        if (r == 0) {
            int x = QRandomGenerator::global()->bounded(0, m_fieldWidth);
            int y = QRandomGenerator::global()->bounded(0, m_fieldHeight);
            m_food.append(QVector2D(x, y));
        }
        // Let the snake eat the food
        // Get the snake's head x and y position
        QMutableListIterator<QVector2D> i(m_food);
        while (i.hasNext()) {
            QVector2D f = i.next();
            if (hx == f.x() && hy == f.y()) { // Is the head where the food is?
                m_snake.append(QVector2D(f.x(), f.y())); // Make the snake longer
                i.remove(); // Remove the food
                m_score += 10;
                emit updateScore("Score: " + QString::number(m_score));
            }
        }
        // Collisions with borders
        if (hx < 0 || m_fieldWidth <= hx ||
            hy < 0 || m_fieldHeight <= hy)
        {
            m_lives--;
            m_food.clear();
            m_snake.clear();
            m_snake.append(m_startPos);
            m_snakeDir = m_startDir;
            if (m_lives == 0) {
                m_lives = 3;
                m_score = 0;
                emit updateScore("Score: " + QString::number(m_score));
            }
            emit updateLives("Lives: " + QString::number(m_lives));
        }
        update();
    }
private:
    QOpenGLShaderProgram m_program;
    QOpenGLBuffer m_vertPosBuffer;
    float m_fieldWidth = 20.f; // Internal resolution
    float m_fieldHeight = 20.f; // Internal resolution
    QMatrix4x4 m_projMatrix;
    QMatrix4x4 m_modelMatrix;
    QList<QVector2D> m_snake; // Snake list of (x, y) positions
    QList<QVector2D> m_food;
    QVector2D m_startPos = QVector2D(5.f, 10.f);
    QVector2D m_startDir = QVector2D(1, 0);
    QVector2D m_snakeDir = m_startDir; // Snake movement direction
    QTimer m_timer;
    int m_score = 0;
    int m_lives = 3;

    void initializeGL() override {
//        qDebug() << QString("w = %1, h = %2").arg(width()).arg(height());
        glClearColor(0.f, 0.f, 0.f, 1.f);
        glEnable(GL_DEPTH_TEST);
        const char *vertShaderSrc =
                "#version 330 core\n"
                "in vec3 aPosition;"
                "uniform mat4 uMvpMatrix;"
                "void main()"
                "{"
                "    gl_Position = uMvpMatrix * vec4(aPosition, 1.0);"
                "}";
        const char *fragShaderSrc =
                "#version 330 core\n"
                "uniform vec4 uColor;"
                "out vec4 fragColor;"
                "void main()"
                "{"
                "    fragColor = uColor;"
                "}";
        m_program.addShaderFromSourceCode(QOpenGLShader::Vertex, vertShaderSrc);
        m_program.addShaderFromSourceCode(QOpenGLShader::Fragment, fragShaderSrc);
        m_program.link();
        m_program.bind();
        float vertPositions[] = {
            0.f, 0.f, 0.f,
            1.f, 0.f, 0.f,
            0.f, 1.f, 0.f,
            1.f, 1.f, 0.f
        };
        m_vertPosBuffer.create();
        m_vertPosBuffer.bind();
        m_vertPosBuffer.allocate(vertPositions, sizeof(vertPositions));
        m_program.bindAttributeLocation("aPosition", 0);
        m_program.setAttributeBuffer(0, GL_FLOAT, 0, 3);
        m_program.enableAttributeArray(0);
        m_projMatrix.ortho(0.f, m_fieldWidth, 0.f, m_fieldHeight, -100.f, 100.f);
        m_snake.append(m_startPos);
        emit updateScore("Score: " + QString::number(m_score));
        emit updateLives("Lives: " + QString::number(m_lives));
        connect(&m_timer, &QTimer::timeout, this, &OpenGLWidget::onUpdate);
        m_timer.start(200);
    }
    void paintGL() override {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        drawFood();
        drawSnake();
    }
    void resizeGL(int w, int h) override {
        glViewport(0, 0, w, h);
    }
    void keyPressEvent(QKeyEvent *e) override {
        if (e->key() == Qt::Key_W || e->key() == Qt::Key_Up)
            if (m_snakeDir != QVector2D(0, -1))
                m_snakeDir = QVector2D(0, 1);
        if (e->key() == Qt::Key_S || e->key() == Qt::Key_Down)
            if (m_snakeDir != QVector2D(0, 1))
                m_snakeDir = QVector2D(0, -1);
        if (e->key() == Qt::Key_A || e->key() == Qt::Key_Left)
            if (m_snakeDir != QVector2D(1, 0))
                m_snakeDir = QVector2D(-1, 0);
        if (e->key() == Qt::Key_D || e->key() == Qt::Key_Right)
            if (m_snakeDir != QVector2D(-1, 0))
                m_snakeDir = QVector2D(1, 0);
    }
    void drawRect(float x, float y, float width, float height, QColor color) {
        m_modelMatrix.setToIdentity();
        m_modelMatrix.translate(QVector3D(x, y, 0.f));
        m_modelMatrix.scale(width, height, 1.f);
        m_program.bind();
        m_program.setUniformValue("uMvpMatrix", m_projMatrix * m_modelMatrix);
        m_program.setUniformValue("uColor", color);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    }
    void drawSnake() {
        foreach (const QVector2D &cell, m_snake) {
            drawRect(cell.x(), cell.y(), 1, 1, QColor(255, 255, 255, 255));
        }
    }
    void drawFood() {
        foreach (const QVector2D &f, m_food) {
            drawRect(f.x(), f.y(), 1, 1, QColor(0, 0, 255, 255));
        }
    }
};

class Window : public QWidget {
public:
    Window(QWidget *parent = nullptr) : QWidget(parent) {
        setWindowTitle("C++ OpenGL");
        setFixedSize(239, 268);
        QFont font = QFont("Areal", 14);
        m_labelScore.setFont(font);
        m_labelScore.setText("");
        m_labelScore.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
        m_labelLives.setFont(font);
        m_labelLives.setText("");
        m_labelLives.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
        QHBoxLayout *hboxOutput = new QHBoxLayout();
        hboxOutput->addWidget(&m_labelScore);
        hboxOutput->addWidget(&m_labelLives);
        QHBoxLayout *hbox = new QHBoxLayout();
        hbox->addWidget(&m_openGLWidget);
        QVBoxLayout *vbox = new QVBoxLayout(this);
        vbox->addLayout(hboxOutput);
        vbox->addLayout(hbox);
        connect(&m_openGLWidget, &OpenGLWidget::updateScore,
                [this](const QString &s){ m_labelScore.setText(s); });
        connect(&m_openGLWidget, &OpenGLWidget::updateLives,
                [this](const QString &s){ m_labelLives.setText(s); });
    }
private:
    OpenGLWidget m_openGLWidget;
    QLabel m_labelScore;
    QLabel m_labelLives;
};

#include "main.moc"

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    Window w;
    w.show();
    return a.exec();
}

main.cpp (OpenGL ES 2.0)

// Add this line to .pro:
// win32: LIBS += -lopengl32

#ifdef _WIN32
#include <windows.h>
extern "C" __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
extern "C" __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001;
#endif

#include <QtWidgets/QApplication>
#include <QtWidgets/QWidget>
#include <QtWidgets/QOpenGLWidget>
#include <QtWidgets/QVBoxLayout>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QLabel>
#include <QtGui/QOpenGLShaderProgram>
#include <QtGui/QOpenGLBuffer>
#include <QtGui/QMatrix4x4>
#include <QtGui/QKeyEvent>
#include <QtCore/QList>
#include <QtCore/QMutableListIterator>
#include <QtCore/QTimer>
#include <QtCore/QRandomGenerator>

class OpenGLWidget : public QOpenGLWidget {
    Q_OBJECT
public:
    OpenGLWidget(QWidget *parent = nullptr) : QOpenGLWidget(parent) {
        setFocusPolicy(Qt::StrongFocus);
    }
signals:
    void updateScore(QString score);
    void updateLives(QString lives);
private slots:
    void onUpdate() {
        // Move snake
        // Insert new position in the beginning of the snake list
        m_snake.insert(0, m_snake[0] + m_snakeDir);
        m_snake.removeLast();
        // Collision with itself
        int hx = m_snake[0].x();
        int hy = m_snake[0].y();
        for (int i = 0; i < m_snake.length(); i++) {
            if (i == 0)
                continue;
            if (hx == m_snake[i].x() && hy == m_snake[i].y()) {
                m_food.clear();
                m_snake.clear();
                m_snake.append(m_startPos);
                m_snakeDir = m_startDir;
                emit updateLives("Lives: " + QString::number(--m_lives));
                update();
                return;
            }
        }
        // Spawn food
        // Spawn food with 5% chance
        int r = QRandomGenerator::global()->bounded(0, 20);
        if (r == 0) {
            int x = QRandomGenerator::global()->bounded(0, m_fieldWidth);
            int y = QRandomGenerator::global()->bounded(0, m_fieldHeight);
            m_food.append(QVector2D(x, y));
        }
        // Let the snake eat the food
        // Get the snake's head x and y position
        QMutableListIterator<QVector2D> i(m_food);
        while (i.hasNext()) {
            QVector2D f = i.next();
            if (hx == f.x() && hy == f.y()) { // Is the head where the food is?
                m_snake.append(QVector2D(f.x(), f.y())); // Make the snake longer
                i.remove(); // Remove the food
                m_score += 10;
                emit updateScore("Score: " + QString::number(m_score));
            }
        }
        // Collisions with borders
        if (hx < 0 || m_fieldWidth <= hx ||
            hy < 0 || m_fieldHeight <= hy)
        {
            m_lives--;
            m_food.clear();
            m_snake.clear();
            m_snake.append(m_startPos);
            m_snakeDir = m_startDir;
            if (m_lives == 0) {
                m_lives = 3;
                m_score = 0;
                emit updateScore("Score: " + QString::number(m_score));
            }
            emit updateLives("Lives: " + QString::number(m_lives));
        }
        update();
    }
private:
    QOpenGLShaderProgram m_program;
    QOpenGLBuffer m_vertPosBuffer;
    float m_fieldWidth = 20.f; // Internal resolution
    float m_fieldHeight = 20.f; // Internal resolution
    QMatrix4x4 m_projMatrix;
    QMatrix4x4 m_modelMatrix;
    QList<QVector2D> m_snake; // Snake list of (x, y) positions
    QList<QVector2D> m_food;
    QVector2D m_startPos = QVector2D(5.f, 10.f);
    QVector2D m_startDir = QVector2D(1, 0);
    QVector2D m_snakeDir = m_startDir; // Snake movement direction
    QTimer m_timer;
    int m_score = 0;
    int m_lives = 3;

    void initializeGL() override {
//        qDebug() << QString("w = %1, h = %2").arg(width()).arg(height());
        glClearColor(0.f, 0.f, 0.f, 1.f);
        glEnable(GL_DEPTH_TEST);
        const char *vertShaderSrc =
                "attribute vec3 aPosition;"
                "uniform mat4 uMvpMatrix;"
                "void main()"
                "{"
                "    gl_Position = uMvpMatrix * vec4(aPosition, 1.0);"
                "}";
        const char *fragShaderSrc =
                "uniform vec4 uColor;"
                "void main()"
                "{"
                "    gl_FragColor = uColor;"
                "}";
        m_program.addShaderFromSourceCode(QOpenGLShader::Vertex, vertShaderSrc);
        m_program.addShaderFromSourceCode(QOpenGLShader::Fragment, fragShaderSrc);
        m_program.link();
        m_program.bind();
        float vertPositions[] = {
            0.f, 0.f, 0.f,
            1.f, 0.f, 0.f,
            0.f, 1.f, 0.f,
            1.f, 1.f, 0.f
        };
        m_vertPosBuffer.create();
        m_vertPosBuffer.bind();
        m_vertPosBuffer.allocate(vertPositions, sizeof(vertPositions));
        m_program.bindAttributeLocation("aPosition", 0);
        m_program.setAttributeBuffer(0, GL_FLOAT, 0, 3);
        m_program.enableAttributeArray(0);
        m_projMatrix.ortho(0.f, m_fieldWidth, 0.f, m_fieldHeight, -100.f, 100.f);
        m_snake.append(m_startPos);
        emit updateScore("Score: " + QString::number(m_score));
        emit updateLives("Lives: " + QString::number(m_lives));
        connect(&m_timer, &QTimer::timeout, this, &OpenGLWidget::onUpdate);
        m_timer.start(200);
    }
    void paintGL() override {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        drawFood();
        drawSnake();
    }
    void resizeGL(int w, int h) override {
        glViewport(0, 0, w, h);
    }
    void keyPressEvent(QKeyEvent *e) override {
        if (e->key() == Qt::Key_W || e->key() == Qt::Key_Up)
            if (m_snakeDir != QVector2D(0, -1))
                m_snakeDir = QVector2D(0, 1);
        if (e->key() == Qt::Key_S || e->key() == Qt::Key_Down)
            if (m_snakeDir != QVector2D(0, 1))
                m_snakeDir = QVector2D(0, -1);
        if (e->key() == Qt::Key_A || e->key() == Qt::Key_Left)
            if (m_snakeDir != QVector2D(1, 0))
                m_snakeDir = QVector2D(-1, 0);
        if (e->key() == Qt::Key_D || e->key() == Qt::Key_Right)
            if (m_snakeDir != QVector2D(-1, 0))
                m_snakeDir = QVector2D(1, 0);
    }
    void drawRect(float x, float y, float width, float height, QColor color) {
        m_modelMatrix.setToIdentity();
        m_modelMatrix.translate(QVector3D(x, y, 0.f));
        m_modelMatrix.scale(width, height, 1.f);
        m_program.bind();
        m_program.setUniformValue("uMvpMatrix", m_projMatrix * m_modelMatrix);
        m_program.setUniformValue("uColor", color);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    }
    void drawSnake() {
        foreach (const QVector2D &cell, m_snake) {
            drawRect(cell.x(), cell.y(), 1, 1, QColor(255, 255, 255, 255));
        }
    }
    void drawFood() {
        foreach (const QVector2D &f, m_food) {
            drawRect(f.x(), f.y(), 1, 1, QColor(0, 0, 255, 255));
        }
    }
};

class Window : public QWidget {
public:
    Window(QWidget *parent = nullptr) : QWidget(parent) {
        setWindowTitle("C++ OpenGL");
        setFixedSize(239, 268);
        QFont font = QFont("Areal", 14);
        m_labelScore.setFont(font);
        m_labelScore.setText("");
        m_labelScore.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
        m_labelLives.setFont(font);
        m_labelLives.setText("");
        m_labelLives.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
        QHBoxLayout *hboxOutput = new QHBoxLayout();
        hboxOutput->addWidget(&m_labelScore);
        hboxOutput->addWidget(&m_labelLives);
        QHBoxLayout *hbox = new QHBoxLayout();
        hbox->addWidget(&m_openGLWidget);
        QVBoxLayout *vbox = new QVBoxLayout(this);
        vbox->addLayout(hboxOutput);
        vbox->addLayout(hbox);
        connect(&m_openGLWidget, &OpenGLWidget::updateScore,
                [this](const QString &s){ m_labelScore.setText(s); });
        connect(&m_openGLWidget, &OpenGLWidget::updateLives,
                [this](const QString &s){ m_labelLives.setText(s); });
    }
private:
    OpenGLWidget m_openGLWidget;
    QLabel m_labelScore;
    QLabel m_labelLives;
};

#include "main.moc"

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    Window w;
    w.show();
    return a.exec();
}
0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement