4
\$\begingroup\$

I've written a smaller version of 15 puzzle in C, using OpenGL 3.3 and freeglut. I've found a place you can play it online to get familiar. I'm new to OpenGL, so I have no idea if I'm doing this in the right way. Nevertheless, this game compiles and works as intended on my 64-bit HP COMPAQ laptop with Intel HD graphics card and lubuntu 15.04 (64-bit) OS installed. I would be glad if you would point out the major (and minor) mistakes I made to avoid them before moving on to bigger projects.

~200 CLOC + comments

Makefile

all: 8

8: main.o render.o
    gcc main.o render.o -lglut -lGLEW -lGL -o 8

main.o:
    gcc -c -std=c99 main.c

render.o:
    gcc -c -std=c99 render.c

clean:
    rm -f main.o
    rm -f render.o

main.c

/* 8 shades of grey - a sliding puzzle */

/* OpenGL dependencies */
#include <GL/glew.h>
#include <GL/freeglut.h>

/* Standard dependencies */
#include <stdlib.h>
#include <stdio.h>

/* Home dependencies */
#include "render.h"

/* Constants */
#define WIN_TITLE "8 shades of grey"
#define WIN_WIDTH 600
#define WIN_HEIGHT 600

int empty_tile_loc = 8;
int board_loc_contents[9] = {0, 1, 2, 3, 4, 5, 6, 7, 8};

/* Swaps tiles */
void swap_tiles(int is_x, int diff) {
    empty_tile_loc += diff;
    board_loc_contents[empty_tile_loc - diff] = board_loc_contents[empty_tile_loc];
    board_loc_contents[empty_tile_loc] = 8;
    if(is_x) {
        if(diff < 0) offsetx[board_loc_contents[empty_tile_loc - diff]] += 0.666;
        else offsetx[board_loc_contents[empty_tile_loc - diff]] -= 0.666; }
    else {
        if(diff < 0) offsety[board_loc_contents[empty_tile_loc - diff]] -= 0.666;
        else offsety[board_loc_contents[empty_tile_loc - diff]] += 0.666; }
    glutPostRedisplay(); }

/* Keyboard arrow callback */
void kboard_arrow_callback(int key, int mouse_x, int mouse_y) {
    switch(key) {
        case GLUT_KEY_LEFT:
            if(empty_tile_loc % 3 != 0) swap_tiles(1, -1); break;
        case GLUT_KEY_RIGHT:
            if(empty_tile_loc % 3 != 2) swap_tiles(1,  1); break;
        case GLUT_KEY_UP:
            if(empty_tile_loc > 2) swap_tiles(0, -3); break;
        case GLUT_KEY_DOWN:
            if(empty_tile_loc < 6) swap_tiles(0,  3); break; } }

/* The main function */
int main(int argc, char **argv) {
    /* Initializing freeGLUT */
    glutInit(&argc, argv);
    glutInitContextFlags(GLUT_FORWARD_COMPATIBLE);
    glutInitContextProfile(GLUT_CORE_PROFILE);
    glutInitContextVersion(3, 3);
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
    glutInitWindowSize(WIN_WIDTH, WIN_HEIGHT);
    glutCreateWindow(WIN_TITLE);

    /* Initializing GLEW */
    glewExperimental = GL_TRUE; /* to render VAO */
    if(glewInit() != GLEW_OK) {
        fprintf(stderr, "GLEW failed to initialize!\n");
        return EXIT_FAILURE; }

    /* Setting freeGLUT function callbacks */
    glutDisplayFunc(draw_callback);
    glutSpecialFunc(kboard_arrow_callback);

    /* Initializing VBOs, VAO, shader program and variables... */
    if(!draw_initialize()) {
        fprintf(stderr, "Drawing failed to initialize!\n");
        draw_terminate();
        return EXIT_FAILURE; };

    /* Main loop! */
    glutMainLoop();

    /* Exiting... */
    draw_terminate();
    return EXIT_SUCCESS; }

render.h

#ifndef RENDER_H
#define RENDER_H

/* Amount of tiles */
#define NUM_BUFFERS 8

/* Shader variables */
float offsetx[NUM_BUFFERS];
float offsety[NUM_BUFFERS];

/* Self-explanatory public draw functions */
int draw_initialize(void);
void draw_terminate(void);
void draw_callback(void);

#endif

render.c

#include "render.h"

/* OpenGL dependencies */
#include <GL/glew.h>
#include <GL/freeglut.h>

/* Standard dependencies */
#include <string.h>
#include <stdio.h>

/* Size of infolog char array - used to report errors */
#define INFOLOG_SIZE 1024

/* Drawing data */
GLuint VAO;
GLuint VBO_ids[NUM_BUFFERS];
GLfloat VBO0[] = {-1.00, 1.00,0,-.333, 1.00,0,-1.00, .333,0,
                  -1.00, .333,0,-.333, .333,0,-.333, 1.00,0};
GLfloat VBO1[] = {-.333, 1.00,0, .333, 1.00,0,-.333, .333,0,
                  -.333, .333,0, .333, .333,0, .333, 1.00,0};
GLfloat VBO2[] = { .333, 1.00,0, 1.00, 1.00,0, .333, .333,0,
                   .333, .333,0, 1.00, .333,0, 1.00, 1.00,0};
GLfloat VBO3[] = {-1.00, .333,0,-.333, .333,0,-1.00,-.333,0,
                  -1.00,-.333,0,-.333,-.333,0,-.333, .333,0};
GLfloat VBO4[] = {-.333, .333,0, .333, .333,0,-.333,-.333,0,
                  -.333,-.333,0, .333,-.333,0, .333, .333,0};
GLfloat VBO5[] = { .333, .333,0, 1.00, .333,0, .333,-.333,0,
                   .333,-.333,0, 1.00,-.333,0, 1.00, .333,0};
GLfloat VBO6[] = {-1.00,-.333,0,-.333,-.333,0,-1.00,-1.00,0,
                  -1.00,-1.00,0,-.333,-1.00,0,-.333,-.333,0};
GLfloat VBO7[] = {-.333,-.333,0, .333,-.333,0,-.333,-1.00,0,
                  -.333,-1.00,0, .333,-1.00,0, .333,-.333,0};
GLfloat *VBOs[NUM_BUFFERS] = { VBO0, VBO1, VBO2, VBO3, VBO4, VBO5, VBO6, VBO7 };
size_t VBO_sizes[] = { sizeof(VBO0), sizeof(VBO1), sizeof(VBO2), sizeof(VBO3),
                       sizeof(VBO4), sizeof(VBO5), sizeof(VBO6), sizeof(VBO7) };
float offsetx[NUM_BUFFERS] = {0.00};
float offsety[NUM_BUFFERS] = {0.00};
float color_r[NUM_BUFFERS] = {0.00, .125, .250, .375, .500, .675, .750, .875};
float color_g[NUM_BUFFERS] = {0.00, .125, .250, .375, .500, .675, .750, .875};
float color_b[NUM_BUFFERS] = {0.00, .125, .250, .375, .500, .675, .750, .875};

/* Shader program data */
GLuint shader_prog_id;
GLuint vshade_offsetx;
GLuint vshade_offsety;
GLuint fshade_color_r;
GLuint fshade_color_g;
GLuint fshade_color_b;

const GLchar *vert_src =
"#version 330\n"
"uniform float offsetx;\n"
"uniform float offsety;\n"
"layout (location = 0) in vec3 Pos;\n"
"void main() {\n"
"   gl_Position = vec4(Pos.x + offsetx, Pos.y + offsety, Pos.z, 1.0); }\n";

const GLchar *frag_src =
"#version 330\n"
"uniform float color_r;\n"
"uniform float color_g;\n"
"uniform float color_b;\n"
"out vec4 FragColor;\n"
"void main() {\n"
"   FragColor = vec4(color_r, color_g, color_b, 1.0); }\n";

/* Function declarations */
int draw_initialize(void);
void draw_callback(void);
void draw_terminate(void);
void vbo_init(void);
int shader_init(const GLchar **v_src, const GLchar **f_src, GLint *v_size, GLint *f_size);
int find_shader_vars(void);
int compile_shader(GLuint shader);
void shader_err(GLuint id, GLchar *msg);

/* Initializes drawing */
int draw_initialize(void) {
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    vbo_init();
    GLint vsrcsize = strlen(vert_src);
    GLint fsrcsize = strlen(frag_src);
    if(!shader_init(&vert_src, &frag_src, &vsrcsize, &fsrcsize)) {
        fprintf(stderr, "Shader program failed to initialize!\n");
        return 0; }
    if(!find_shader_vars()) {
        fprintf(stderr, "Could not find all shader variables!\n");
        return 0; }
    return 1; }

/* Draw callback */
void draw_callback(void) {
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(shader_prog_id);
    glEnableVertexAttribArray(0);

    for(int i = 0; i < NUM_BUFFERS; ++i) {
        glBindBuffer(GL_ARRAY_BUFFER, VBO_ids[i]);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
        glUniform1f(vshade_offsetx, offsetx[i]);
        glUniform1f(vshade_offsety, offsety[i]);
        glUniform1f(fshade_color_r, color_r[i]);
        glUniform1f(fshade_color_g, color_g[i]);
        glUniform1f(fshade_color_b, color_b[i]);
        glDrawArrays(GL_TRIANGLES, 0, VBO_sizes[i]/sizeof(GLfloat)); }

    glDisableVertexAttribArray(0);
    glutSwapBuffers(); }

/* Draw termination */
void draw_terminate(void) {
    glDeleteProgram(shader_prog_id); }

/* Initializes VBOs and VAO */
void vbo_init(void) {
    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);
    glGenBuffers(NUM_BUFFERS, VBO_ids);
    for(int i = 0; i < NUM_BUFFERS; ++i) {
        glBindBuffer(GL_ARRAY_BUFFER, VBO_ids[i]);
        glBufferData(GL_ARRAY_BUFFER, VBO_sizes[i], VBOs[i], GL_STATIC_DRAW); } }

/* Prints shader program error */
void shader_prog_err(GLuint id, GLchar *msg) {
    GLchar infolog[INFOLOG_SIZE];
    glGetProgramInfoLog(id, INFOLOG_SIZE, NULL, infolog);
    fprintf(stderr, "%s '%s'\n", msg, infolog); }

/* Initializes shader program */
int shader_init(const GLchar **v_src, const GLchar **f_src, GLint *v_size, GLint *f_size) {
    GLint success;
    shader_prog_id = glCreateProgram();
    GLuint v_shader = glCreateShader(GL_VERTEX_SHADER);
    GLuint f_shader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(v_shader, 1, v_src, v_size);
    glShaderSource(f_shader, 1, f_src, f_size);
    if(!compile_shader(v_shader)) {
        fprintf(stderr, "Vertex shader compilation failed.\n");
        glDeleteShader(v_shader);
        glDeleteShader(f_shader);
        return 0; }
    if(!compile_shader(f_shader)) {
        fprintf(stderr, "Fragment shader compilation failed.\n");
        glDeleteShader(v_shader);
        glDeleteShader(f_shader);
        return 0; }
    glAttachShader(shader_prog_id, v_shader);
    glAttachShader(shader_prog_id, f_shader);
    glLinkProgram(shader_prog_id);
    glDetachShader(shader_prog_id, v_shader);
    glDetachShader(shader_prog_id, f_shader);
    glDeleteShader(v_shader);
    glDeleteShader(f_shader);
    glGetProgramiv(shader_prog_id, GL_LINK_STATUS, &success);
    if(!success) {
        shader_prog_err(shader_prog_id, "Error: failed to link shader program:");
        return 0; }
    glValidateProgram(shader_prog_id);
    glGetProgramiv(shader_prog_id, GL_VALIDATE_STATUS, &success);
    if(!success) {
        shader_prog_err(shader_prog_id, "Error: failed to validate shader program:");
        return 0; }
    return 1; }

/* Finds location of uniform variables in shader program */
int find_shader_vars(void) {
    vshade_offsetx = glGetUniformLocation(shader_prog_id, "offsetx");
    vshade_offsety = glGetUniformLocation(shader_prog_id, "offsety");
    fshade_color_r = glGetUniformLocation(shader_prog_id, "color_r");
    fshade_color_g = glGetUniformLocation(shader_prog_id, "color_g");
    fshade_color_b = glGetUniformLocation(shader_prog_id, "color_b");
    if(vshade_offsetx == -1 || vshade_offsety == -1) return 0;
    if(fshade_color_r == -1 || fshade_color_g == -1 || fshade_color_b == -1) return 0;
    return 1; }

/* Compiles shader, returns if was succesfull, prints error msg if not */
int compile_shader(GLuint shader) {
    GLint success;
    glCompileShader(shader);
    glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
    if(!success) {
        GLchar infolog[INFOLOG_SIZE];
        glGetShaderInfoLog(shader, INFOLOG_SIZE, NULL, infolog);
        fprintf(stderr, "Failed to compile shader: '%s'\n", infolog); }
    return success; }
\$\endgroup\$
2
\$\begingroup\$

Makefile

Your Makefile isn't as useful as it could be, since it doesn't rebuild anything when you change the source: there's no dependency information in it. Also if you want to change your compiler or compiler flags, you'll need to do that all over the place. Adding a new source file requires adding three lines and modifying (at least) two others.

So, I'd start off with:

CC=gcc
CFLAGS=-std=c99 -Wall -Wextra -pedantic

These are standard variable names, people will be familiar with them. Then you could:

SOURCES=main.c render.c
LIBS=-lglut -lGLEW -lGL

Now you've got all your source files and the necessary libraries in one place, easy to change down the line.

OBJECTS=$(SOURCES:.c=.o)

(See Substitution References.) This generates the name foo.o from foo.c: got all your object files now too. So you can:

all: 8

8: $(OBJECTS)
    $(CC) $(CFLAGS) -o $@ $(OBJECTS) $(LIBS)

clean:
    rm -f $(OBJECTS)

(See Automatic Variables.) And... that's it. Make knows how to build .o files from .c files, and will use $(CC) and $(CFLAGS), so no need to create those rules yourself. If the built-in rule doesn't suit your need, create a generic one, e.g.:

%.o: %.c
  @echo Building $@ from $^
  @$(CC) $(CFLAGS) -c -o $@ $^

This isn't quite complete, in particular you have no build dependency on your header file. If you want to go further, though that would be a bit overkill here, look for generating dependencies automatically, e.g. generate dependencies for a makefile.

Header

float offsetx[NUM_BUFFERS];
float offsety[NUM_BUFFERS];

These should be declared extern.

Code

Unfortunately I don't know OpenGL, so can't really comment on that.

But your bracing style is very strange for me, very hard to read and makes your code way too compact. I'd urge you to put the closing braces on their own line. And put a space between if (and while) and the conditional.

Examples:

int compile_shader(GLuint shader)
{
  GLint success;
  glCompileShader(shader);
  glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
  if (!success) {
    GLchar infolog[INFOLOG_SIZE];
    glGetShaderInfoLog(shader, INFOLOG_SIZE, NULL, infolog);
    fprintf(stderr, "Failed to compile shader: '%s'\n", infolog);
  }
  return success;
}
void swap_tiles(int is_x, int diff)
{
  empty_tile_loc += diff;
  board_loc_contents[empty_tile_loc - diff] =
      board_loc_contents[empty_tile_loc];
  board_loc_contents[empty_tile_loc] = 8;
  if (is_x) {
    if (diff < 0)
      offsetx[board_loc_contents[empty_tile_loc - diff]] += 0.666;
    else
      offsetx[board_loc_contents[empty_tile_loc - diff]] -= 0.666;
  } else {
    if (diff < 0)
      offsety[board_loc_contents[empty_tile_loc - diff]] -= 0.666;
    else
      offsety[board_loc_contents[empty_tile_loc - diff]] += 0.666;
  }
  glutPostRedisplay();
}

If you want to practice "ultra-dense" C, you could hop over to Programming Puzzles and Code Golf for a real challenge, but otherwise don't try and minimize your LOC count.

int draw_initialize(void);
void draw_callback(void);
void draw_terminate(void);

You've already got those in the header, not needed in render.c.

\$\endgroup\$
  • \$\begingroup\$ Thank You. I've taken your advice on makefile, extern variables and redundant function declarations. \$\endgroup\$ – Marius Macijauskas Sep 14 '15 at 14:42
  • \$\begingroup\$ Ok, thanks for the feedback. You might want to wait a bit more before accepting an answer though - I didn't touch the OpenGL part which is probably the most important. If you accept an answer, it is less likely that other people will look at your question. \$\endgroup\$ – Mat Sep 14 '15 at 14:52
  • \$\begingroup\$ Okay. Yes, the OpenGL part is the most important. I'm just learning it and I'm afraid of getting bad habits early on. \$\endgroup\$ – Marius Macijauskas Sep 14 '15 at 15:07

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.