2
\$\begingroup\$

I've been learning OpenGL and C++ recently and thought I'd attempt to make a snake game. It would be amazing if you could take your time to read my code and give advice please. Please prepare yourself though, there will be things that you see and wonder why...? I will continue to improve this version and will start over again and see if I can make an improved version from scratch. Then I will add features such as a menu and a scoring system.

#include <iostream>
#include <glad/glad.h>
#include <GLFW\glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <chrono>
#include <string>
#include <array>
#include <cmath>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
void gameUpdate();
void foodGeneration();

const unsigned int SCR_WIDTH = 800; //Currently not concerned about different screen sizes and aspect ratios
const unsigned int SCR_HEIGHT = 800; 

float drawValues[420] = {};
int bodyPosition[400] = {};
int whereIsTheHeadAt = 190; //spawnPoint
const int respawnPoint = 190; //respawnPoint
int whereIsTheFoodAt;
int snakeLength = 5;
int mostRecentMove = 4; //This variable is used to calculate the last moved direction after snake update, this ensures that the player does not go back into the snake. For example by quickly pressing d then s, whilst going up.
int snakeDirection = 0;


const char* vertexShaderSource = 
"#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"out vec4 vertexColor;\n"
"uniform mat4 trans;\n"
"uniform vec3 offsets[400];\n"
"void main()\n"
"{\n"
"   vec2 offset = offsets[gl_InstanceID].xy;\n"
"   vec2 offsetColor = offsets[gl_InstanceID].zz;\n"
"   gl_Position = trans * vec4(aPos.xy + offset, aPos.z, 1.0);\n"
    "vertexColor = vec4(0, offsetColor.x,0,1);\n"
"}\0";
const char* fragmentShaderSource = 
"#version 330 core\n"
"in vec4 vertexColor;\n"
"uniform vec4 ourColor;\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"   FragColor = vertexColor;\n"
"}\n\0";


int main()
{
    drawValues[whereIsTheHeadAt] = 1;
    foodGeneration();
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "Snake", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window." << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);

    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD." << std::endl;
        return -1;
    }


    glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);  

    int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);
    int success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    int shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
    }
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);


    GLfloat playerBox[] = {
         0.06f,  0.06f, 0.0f,
         0.06f, -0.06f, 0.0f,
        -0.06f, -0.06f, 0.0f,
        -0.06f,  0.06f, 0.0f,
         0.06f,  0.06f, 0.0f,
        -0.06f, -0.06f, 0.0f
    };



    GLuint VBO, VAO; 

    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(playerBox), playerBox, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    unsigned __int64 lastUsedTime = 0;
    while (!glfwWindowShouldClose(window))
    {
        unsigned __int64 now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
        unsigned __int64 trunc = std::trunc(now *  0.01);

        //Is this good? Is there any alternative?
            if (trunc != lastUsedTime) {
                gameUpdate();
                lastUsedTime = trunc;
            }


        processInput(window);

        glClearColor(0.1, 0.1, 0.1, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        glm::vec2 translations[400];
        int index = 0;
        float offset = 0.1f;
        for (int y = -20; y < 20; y += 2)
        {
            for (int x = -20; x < 20; x += 2)
            {
                glm::vec2 translation;
                translation.x = (float)x / 15.0f + offset;
                translation.y = (float)y / 15.0f + offset;
                translations[index++] = translation;

            }
        }
        glUseProgram(shaderProgram);

        for (unsigned int i = 0; i < 400; i++)
        {

            GLint offsetsLocation = glGetUniformLocation(shaderProgram, ("offsets[" + std::to_string(i) + "]").c_str());
            glUniform3f(offsetsLocation, translations[i].x, translations[i].y, drawValues[i]);  

        }
        glm::mat4 transform = glm::mat4(1.0f);
        transform = glm::scale(transform, glm::vec3(0.5f, 0.5f, 0));


        GLint uniTrans = glGetUniformLocation(shaderProgram, "trans");
        glUniformMatrix4fv(uniTrans, 1, GL_FALSE, glm::value_ptr(transform));
        glBindVertexArray(VAO);
        glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 400);
        glBindVertexArray(0);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);


    glfwTerminate();
    return 0;
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    glViewport(0, 0, width, height);
}

void processInput(GLFWwindow* window)
{
    if (glfwGetKey(window, 256) == GLFW_PRESS) {
        glfwSetWindowShouldClose(window, true);
    }

    if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS && snakeDirection != 1 && mostRecentMove != 1) {
        snakeDirection = 3;
    }

    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS && snakeDirection != 3 && mostRecentMove != 3) {
        snakeDirection = 1;
    }

    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS && snakeDirection != 2 && mostRecentMove != 2) {
        snakeDirection = 0;
    }
    if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS && snakeDirection != 0 && mostRecentMove != 0) {
        snakeDirection = 2;
    }
}


void foodGeneration() {
    int tempRandom = rand() % 400 + 1;
    int success = 1;
    if (0 != drawValues[tempRandom]) {
        foodGeneration();
        success = 0;
    }
    if (success == 1) {     
        whereIsTheFoodAt = tempRandom;
        drawValues[whereIsTheFoodAt] = 0.3;
    }



}

void manageBody() {
    for (int i = snakeLength; i > 0; i--) {

        bodyPosition[0] = whereIsTheHeadAt;
        bodyPosition[i] = { bodyPosition[i - 1] };
        drawValues[bodyPosition[i]] = { 0.6 };
        drawValues[bodyPosition[snakeLength]] = { 0 };

    }
}

void killSnake() {
    for (int i = 1; i < snakeLength; i++) {
        drawValues[bodyPosition[i]] = 0;
        bodyPosition[i] = 0;
    }
    snakeLength = 5;
    whereIsTheHeadAt = respawnPoint;
}

void gameUpdate() {

    if (whereIsTheHeadAt == whereIsTheFoodAt) {
        whereIsTheFoodAt = 0;
        foodGeneration();
        drawValues[whereIsTheHeadAt] = 1;
        snakeLength++;

    }
    for (int i = 0; i < snakeLength; i++) {
        if (whereIsTheHeadAt == bodyPosition[i]) {
            killSnake();
        }
    }
    for (int i = -20; i < 0; i++) {
        if (whereIsTheHeadAt == i) { //Hit the bottom border    
            drawValues[whereIsTheHeadAt] = 0;
            killSnake();
        }
    }
    for (int i = 400; i < 420; i++) {
        if (whereIsTheHeadAt == i) { //Hit the top border
            drawValues[whereIsTheHeadAt] = 0;
            killSnake();
        }
    }
    if (whereIsTheHeadAt % 20 == 0 && snakeDirection == 3) { //Hit the left border  
        drawValues[whereIsTheHeadAt] = 0;
        killSnake();
    }   
    if (((whereIsTheHeadAt == 19) || (whereIsTheHeadAt == 39) || (whereIsTheHeadAt == 59) || (whereIsTheHeadAt == 79) || (whereIsTheHeadAt == 99) || 
        (whereIsTheHeadAt == 119) || (whereIsTheHeadAt == 139) || (whereIsTheHeadAt == 159) || (whereIsTheHeadAt == 179) || (whereIsTheHeadAt == 199) || 
        (whereIsTheHeadAt == 219) || (whereIsTheHeadAt == 239) || (whereIsTheHeadAt == 259) || (whereIsTheHeadAt == 279) || (whereIsTheHeadAt == 299) || 
        (whereIsTheHeadAt == 319) || (whereIsTheHeadAt == 339) || (whereIsTheHeadAt == 359) || (whereIsTheHeadAt == 379) || (whereIsTheHeadAt == 399)) 
        && snakeDirection == 1) { //Hit the right border. I know there is probably a better way to do this ahaha. Right?    
        drawValues[whereIsTheHeadAt] = 0;
        killSnake();
    }


    switch (snakeDirection) {
        case 0:drawValues[whereIsTheHeadAt] = 0; manageBody(); drawValues[whereIsTheHeadAt + 20] = 1; whereIsTheHeadAt = whereIsTheHeadAt + 20; mostRecentMove = 0;
            break;
        case 1:drawValues[whereIsTheHeadAt] = 0; manageBody(); drawValues[whereIsTheHeadAt + 1] = 1; whereIsTheHeadAt = whereIsTheHeadAt + 1; mostRecentMove = 1;
            break;
        case 2:drawValues[whereIsTheHeadAt] = 0; manageBody(); drawValues[whereIsTheHeadAt - 20] = 1; whereIsTheHeadAt = whereIsTheHeadAt - 20; mostRecentMove = 2;
            break;
        case 3:drawValues[whereIsTheHeadAt] = 0; manageBody(); drawValues[whereIsTheHeadAt - 1] = 1; whereIsTheHeadAt = whereIsTheHeadAt - 1; mostRecentMove = 3;
            break;
    }

}

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.