diff --git a/Makefile b/Makefile index f161aca..5670541 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ BIN=cudoku CC=gcc -CFLAGS=-Wall -Wextra -pedantic -O3 -g `pkg-config --cflags freetype2` -I3rdparty/glad/include -I3rdparty/stb -I3rdparty/fmod/include -OBJ=main.o x11.o cudoku.o shader.o font.o helper.o audio.o 3rdparty/glad/src/gl.o 3rdparty/glad/src/glx.o 3rdparty/stb/stb.o +CFLAGS=-Wall -Wextra -pedantic -g `pkg-config --cflags freetype2` -I3rdparty/glad/include -I3rdparty/stb -I3rdparty/fmod/include +OBJ=main.o x11.o cudoku.o shader.o font.o helper.o audio.o timer.o 3rdparty/glad/src/gl.o 3rdparty/glad/src/glx.o 3rdparty/stb/stb.o LDFLAGS=`pkg-config --libs x11 freetype2` -lm -L3rdparty/fmod/lib -Wl,-rpath=3rdparty/fmod/lib -lfmod DEPS=3rdparty/glad/include/glad/gl.h 3rdparty/glad/include/glad/glx.h 3rdparty/stb/stb_image.h 3rdparty/fmod/include/fmod.h diff --git a/cudoku.c b/cudoku.c index d41a5da..03423cb 100644 --- a/cudoku.c +++ b/cudoku.c @@ -10,10 +10,10 @@ #include "cudoku.h" #include "font.h" #include "shader.h" -#include "helper.h" #include "audio.h" -#define help_text_size 13 +#define HELP_TEXT_SIZE 13 +#define HELP_OVERLAY_WIDTH 700 static const float quad_vertices[] = { // positions // texture coords @@ -32,7 +32,7 @@ static const Color mistake_color = {1.f, 0.f, 0.f, 0.5f}; static const char *win_text = "You won!"; -static const char *help_texts[help_text_size] = { +static const char *help_texts[HELP_TEXT_SIZE] = { "F1 - Toggle help", "F11 - Toggle fullscreen", "1-9 - Set number", @@ -165,7 +165,7 @@ unsigned int prepare_win_overlay() { return vao; } -void draw_win_overlay(Shader win_shader, Shader font_shader, unsigned int vao, unsigned int font_vao, unsigned int font_vbo, float *transform) { +void draw_win_overlay(Shader win_shader, Shader font_shader, unsigned int vao, unsigned int font_vao, unsigned int font_vbo, float *transform, Cudoku *game) { use_shader(win_shader); set_mat4f(win_shader, "transform", transform); @@ -177,8 +177,14 @@ void draw_win_overlay(Shader win_shader, Shader font_shader, unsigned int vao, u glBindVertexArray(0); - Size text_size = calculate_text_size(win_text, 1.0f); - draw_text(font_shader, win_text, text_size, 1.5f, font_vao, font_vbo, transform); + char time_text[64]; + float time_elapsed = game->win_time - game->timer.time; + snprintf(time_text, 64, "Solved in: %02dm%02ds", (int)time_elapsed / 60, (int)time_elapsed % 60); + + Size win_text_size = calculate_text_size(win_text, 1.0f); + Size time_text_size = calculate_text_size(time_text, 1.0f); + draw_text_center(font_shader, win_text, win_text_size, 1.5f, font_vao, font_vbo, transform, 0.0, 75.0); + draw_text_center(font_shader, time_text, time_text_size, 0.8f, font_vao, font_vbo, transform, 0.0, -75.0); } void prepare_selection_box(unsigned int *vao, unsigned int *vbo) { @@ -250,6 +256,8 @@ void toggle_check(Cudoku *game) { } void do_selection(Cudoku *game, int x, int y, int width, int height, float x_scale, float y_scale) { + if (game->has_won) return; + float board_x_start = (width / 2.f) - ((width * x_scale) / 2); float board_y_start = (height / 2.f) - ((height * y_scale) / 2); @@ -295,6 +303,9 @@ bool check_win(Cudoku *game) { game->should_draw_selection = false; game->should_highlight_mistakes = false; game->should_draw_help = false; + game->win_time = get_time(); + timer_stop(&game->timer); + audio_play_win(); return true; @@ -553,9 +564,13 @@ void generate_random_board(Cudoku *game) { } remove_numbers(game); + + timer_reset(&game->timer); } void reset_board(Cudoku *game) { + if (game->has_won) return; + for (int i = 0; i < 9; i++) { for (int j = 0; j < 9; j++) { if (game->board[i][j].is_locked == false) { @@ -563,18 +578,23 @@ void reset_board(Cudoku *game) { } } } + + timer_reset(&game->timer); } -void toggle_help(Cudoku *game) { +bool toggle_help(Cudoku *game) { if (!game->has_won) { game->should_draw_help = !game->should_draw_help; + return game->should_draw_help; } + + return false; } -void draw_ui_element_at(Shader shader, unsigned int vao, unsigned int vbo, Matrix4x4 projection, Vec2 pos, Size size, Color color) { +void draw_ui_element_at(Shader shader, unsigned int vao, unsigned int vbo, Matrix4x4 *projection, Vec2 pos, Size size, Color color) { use_shader(shader); - set_mat4f(shader, "transform", (float *)projection.m); + set_mat4f(shader, "transform", (float *)projection->m); set_vec4f(shader, "aColor", color.r, color.g, color.b, color.a); glBindVertexArray(vao); @@ -598,27 +618,80 @@ void draw_ui_element_at(Shader shader, unsigned int vao, unsigned int vbo, Matri glBindVertexArray(0); } -void draw_help_overlay(Shader overlay_shader, Shader font_shader, unsigned int overlay_vao, unsigned int overlay_vbo, unsigned int font_vao, unsigned int font_vbo, Matrix4x4 projection, int window_height) { +void draw_help_overlay(Shader overlay_shader, Shader font_shader, unsigned int overlay_vao, unsigned int overlay_vbo, unsigned int font_vao, unsigned int font_vbo, Matrix4x4 *projection, int window_height, Timer *timer) { int total_help_texts_height = 20; int const text_padding = 10; float const text_scale = 0.3f; int overlay_height = 0; - for (int i = 0; i < help_text_size; i++) { + for (int i = 0; i < HELP_TEXT_SIZE; i++) { Size text_size = calculate_text_size(help_texts[i], text_scale); overlay_height += text_size.height + text_padding; } - - Size size = {.width = 700, .height = overlay_height + 20}; - Vec2 pos = {.x = 0, .y = window_height - size.height}; - Color color = { .r = 0.1f, .g = 0.1f, .b = 0.1f, .a = 0.95f }; +Size size = {.width = HELP_OVERLAY_WIDTH, .height = overlay_height + 20}; + Vec2 pos = { + .x = 0, + .y = window_height - size.height + }; + Color color = { + .r = 0.1f, + .g = 0.1f, + .b = 0.1f, + .a = 0.95f + }; draw_ui_element_at(overlay_shader, overlay_vao, overlay_vbo, projection, pos, size, color); - for (int i = 0; i < help_text_size; i++) { + for (int i = 0; i < HELP_TEXT_SIZE; i++) { Size text_size = calculate_text_size(help_texts[i], text_scale); - Vec2 text_pos = {.x = text_padding, .y = window_height - total_help_texts_height - (text_padding * 2)}; + Vec2 text_pos = { + .x = text_padding, + .y = window_height - total_help_texts_height - (text_padding * 2) + }; total_help_texts_height += text_size.height + text_padding; - draw_text_at(font_shader, help_texts[i], text_pos, text_scale, font_vao, font_vbo, (float *)projection.m); + draw_text_at(font_shader, help_texts[i], text_pos, text_scale, font_vao, font_vbo, (float *)projection->m, NULL); + } + + if (timer->is_running) { + char closing_in_text[128]; + snprintf(closing_in_text, sizeof(closing_in_text), "Hiding in %ds", (int)timer_remaining(timer) + 1); + Size closing_in_text_size = calculate_text_size(closing_in_text, text_scale / 2.0); + Vec2 closing_in_text_pos = { + .x = HELP_OVERLAY_WIDTH - closing_in_text_size.width, + .y = window_height - closing_in_text_size.height - text_padding, + }; + Color closing_in_text_color = { + .r = 255.0f, + .g = 150.0f, + .b = 0.0f, + .a = 1.0f, + }; + draw_text_at(font_shader, closing_in_text, closing_in_text_pos, text_scale / 2.0, font_vao, font_vbo, (float *)projection->m, &closing_in_text_color); } } + +void draw_timer(Shader shader, unsigned int vao, unsigned int vbo, float *projection, Timer *timer, int win_width) { + if (!timer->is_running) return; + + char timer_text[64]; + + float elapsed = timer_elapsed(timer); + int minutes = elapsed / 60; + int seconds = (int)elapsed % 60; + + snprintf(timer_text, sizeof(timer_text), "Time: %02d:%02d", minutes, seconds); + float text_scale = 0.35f; + Size text_size = calculate_text_size(timer_text, text_scale); + Vec2 text_pos = { + .x = win_width - text_size.width - 10, + .y = 10, + }; + Color text_color = { + .r = 66.0f, + .g = 92.0f, + .b = 124.0f, + .a = 1.0f, + }; + + draw_text_at(shader, timer_text, text_pos, text_scale, vao, vbo, projection, &text_color); +} diff --git a/cudoku.h b/cudoku.h index b5a7b7b..33128a8 100644 --- a/cudoku.h +++ b/cudoku.h @@ -2,7 +2,9 @@ #include +#include "helper.h" #include "shader.h" +#include "timer.h" typedef struct Vec2 { int x; @@ -22,6 +24,9 @@ typedef struct Cudoku { bool should_draw_selection; bool should_highlight_mistakes; bool should_draw_help; + Timer help_timer; + Timer timer; + float win_time; } Cudoku; typedef struct Color { @@ -37,7 +42,7 @@ void draw_bg_grid_shader(Shader shader, unsigned int vao, float* transform, floa void draw_bg_grid_texture(Shader shader, unsigned int vao, unsigned int texture, float* transform); void draw_numbers(Shader shader, unsigned int vao, unsigned int vbo, float *transform, Cell board[9][9]); unsigned int prepare_win_overlay(); -void draw_win_overlay(Shader win_shader, Shader font_shader, unsigned int vao, unsigned int font_vao, unsigned int font_vbo, float *transform); +void draw_win_overlay(Shader win_shader, Shader font_shader, unsigned int vao, unsigned int font_vao, unsigned int font_vbo, float *transform, Cudoku *game); void prepare_selection_box(unsigned int *vao, unsigned int *vbo); void draw_selection_box(Shader shader, unsigned int vao, unsigned int vbo, int x, int y, float *transform, Color color); void highlight_mistakes(Shader shader, unsigned int vao, unsigned vbo, float *transform, Cudoku *game); @@ -48,5 +53,16 @@ void move_selection(Cudoku *game, int x, int y); void toggle_selection(Cudoku *game); void generate_random_board(Cudoku *game); void reset_board(Cudoku *game); -void toggle_help(Cudoku *game); -void draw_help_overlay(); +bool toggle_help(Cudoku *game); +void draw_help_overlay( + Shader overlay_shader, + Shader font_shader, + unsigned int overlay_vao, + unsigned int overlay_vbo, + unsigned int font_vao, + unsigned int font_vbo, + Matrix4x4 *projection, + int window_height, + Timer *timer +); +void draw_timer(Shader shader, unsigned int vao, unsigned int vbo, float *projection, Timer *timer, int win_width); diff --git a/font.c b/font.c index 3e59344..81e3a52 100644 --- a/font.c +++ b/font.c @@ -177,7 +177,7 @@ void draw_number(Shader shader, Cell cell, int row, int column, float scale, uns glBindTexture(GL_TEXTURE_2D, 0); } -void draw_text(Shader shader, const char *text, Size text_size, float scale, unsigned int vao, unsigned int vbo, float *transform) { +void draw_text_center(Shader shader, const char *text, Size text_size, float scale, unsigned int vao, unsigned int vbo, float *transform, float offset_x, float offset_y) { use_shader(shader); set_vec3f(shader, "textColor", 200.f / 255.f, 200.f / 255.f, 200.f / 255.f); set_mat4f(shader, "transform", transform); @@ -189,8 +189,8 @@ void draw_text(Shader shader, const char *text, Size text_size, float scale, uns while (text[c] != '\0') { Character ch = characters[(int)text[c]]; - float xpos = ((x + ch.bearing.width * scale) / 900.f) - ((text_size.width / 2.f) * scale / 900.f); - float ypos = (((ch.bearing.height - ch.size.height) * scale) / 900.f) - ((text_size.height / 2.f) * scale / 900.f); + float xpos = ((x + ch.bearing.width * scale + offset_x) / 900.f) - ((text_size.width / 2.f) * scale / 900.f); + float ypos = (((ch.bearing.height - ch.size.height + offset_y) * scale) / 900.f) - ((text_size.height / 2.f) * scale / 900.f); float w = (ch.size.width * scale / 900.f); float h = (ch.size.height * scale / 900.f); @@ -223,9 +223,15 @@ void draw_text(Shader shader, const char *text, Size text_size, float scale, uns glBindTexture(GL_TEXTURE_2D, 0); } -void draw_text_at(Shader shader, const char *text, Vec2 pos, float scale, unsigned int vao, unsigned int vbo, float *projection) { +Color default_text_color = { 200.f / 255.f, 200.f / 255.f, 200.f / 255.f, 1.f }; + +void draw_text_at(Shader shader, const char *text, Vec2 pos, float scale, unsigned int vao, unsigned int vbo, float *projection, Color *color) { use_shader(shader); - set_vec3f(shader, "textColor", 200.f / 255.f, 200.f / 255.f, 200.f / 255.f); + if (color != NULL) { + set_vec3f(shader, "textColor", color->r / 255.f, color->g / 255.f, color->b / 255.f); + } else { + set_vec3f(shader, "textColor", 200.f / 255.f, 200.f / 255.f, 200.f / 255.f); + } set_mat4f(shader, "transform", (float *)projection); glActiveTexture(GL_TEXTURE0); glBindVertexArray(vao); diff --git a/font.h b/font.h index a6a9ba8..a3074e5 100644 --- a/font.h +++ b/font.h @@ -19,5 +19,5 @@ int init_fonts(const char *font_path); void prepare_font(unsigned int *vao, unsigned int *vbo); Size calculate_text_size(const char *text, float scale); void draw_number(Shader shader, Cell cell, int row, int column, float scale,unsigned int vao, unsigned int vbo, float* transform); -void draw_text(Shader shader, const char *text, Size text_size, float scale, unsigned int vao, unsigned int vbo, float *transform); -void draw_text_at(Shader shader, const char *text, Vec2 pos, float scale, unsigned int vao, unsigned int vbo, float *projection); +void draw_text_center(Shader shader, const char *text, Size text_size, float scale, unsigned int vao, unsigned int vbo, float *transform, float offset_x, float offset_y); +void draw_text_at(Shader shader, const char *text, Vec2 pos, float scale, unsigned int vao, unsigned int vbo, float *projection, Color *color); diff --git a/helper.h b/helper.h index 24d8817..bebcdee 100644 --- a/helper.h +++ b/helper.h @@ -1,3 +1,4 @@ +#pragma once typedef struct { float m[4][4]; diff --git a/main.c b/main.c index 867cdac..2324b69 100644 --- a/main.c +++ b/main.c @@ -8,7 +8,6 @@ #include "x11.h" #include "cudoku.h" #include "font.h" -#include "helper.h" #include "audio.h" Display *display; @@ -24,6 +23,20 @@ void usage() { void handle_keypress(XEvent xev, Cudoku *game) { if (XLookupKeysym(&xev.xkey, 0) == XK_r) { reset_board(game); + } else if ( + xev.xkey.state & ControlMask && + xev.xkey.state & Mod1Mask && + XLookupKeysym(&xev.xkey, 0) == XK_w) { + printf("DEBUG: Ctrl + Shift + W pressed. Winning game\n"); + + game->has_won = true; + game->should_draw_selection = false; + game->should_highlight_mistakes = false; + game->should_draw_help = false; + game->win_time = get_time(); + timer_stop(&game->timer); + + audio_play_win(); } else if (XLookupKeysym(&xev.xkey, 0) >= XK_0 && XLookupKeysym(&xev.xkey, 0) <= XK_9) { set_selected_number(game, XLookupKeysym(&xev.xkey, 0) - XK_0); } else if (XLookupKeysym(&xev.xkey, 0) == XK_BackSpace || @@ -62,7 +75,9 @@ void handle_keypress(XEvent xev, Cudoku *game) { /* } else if (XLookupKeysym(&xev.xkey, 0) == XK_m) { */ /* toggle_mute(&game); */ } else if (XLookupKeysym(&xev.xkey, 0) == XK_F1) { - toggle_help(game); + if (!toggle_help(game)) { + timer_stop(&game->help_timer); + } } } @@ -129,18 +144,6 @@ int main(int argc, char *argv[]) { game.should_draw_help = true; generate_random_board(&game); - for (int i = 0; i < 9; i++) { - for (int j = 0; j < 9; j++) { - if (game.board[i][j].value) { - printf("%d | ", game.board[i][j].value); - } else { - printf(" | "); - } - } - printf("\n"); - } - printf("\n"); - unsigned int font_vao, font_vbo; prepare_font(&font_vao, &font_vbo); Shader font_shader = create_shader("shaders/font_v.vert", "shaders/font_f.frag"); @@ -162,8 +165,17 @@ int main(int argc, char *argv[]) { Shader win_shader = create_shader("shaders/board_v.vert", "shaders/quad.frag"); unsigned int win_vao = prepare_win_overlay(); + float delta_t, last_t = 0.0; + start_internal_timer(); + timer_start(&game.help_timer, 5.0f); + timer_start(&game.timer, 0.0f); + bool quit = false; while (!quit) { + float now = get_time(); + delta_t = now - last_t; + last_t = now; + while (XPending(display)) { XEvent xev; XNextEvent(display, &xev); @@ -195,6 +207,10 @@ int main(int argc, char *argv[]) { } } + /* printf("clock: %f\n", get_time()); */ + /* printf("delta: %f\n", delta_t); */ + /* printf("FPS: %f\n", 1.0 / delta_t); */ + glClearColor(0.2, 0.2, 0.2, 1.0); glClear(GL_COLOR_BUFFER_BIT); @@ -224,14 +240,29 @@ int main(int argc, char *argv[]) { draw_numbers(font_shader, font_vao, font_vbo, (float *)transform, game.board); - if (game.should_highlight_mistakes) + if (game.should_highlight_mistakes) { highlight_mistakes(selection_shader, selection_vao, selection_vbo, (float *)transform, &game); + char* text = "Mistake highlighter is ON"; + float text_scale = 0.2; + Size text_size = calculate_text_size(text, text_scale); + Vec2 text_pos = { 0.0f, text_size.height - 10.f }; + Color text_color = { 200.0f, 50.0f, 50.0f, 1.0f }; + draw_text_at(font_shader, text, text_pos, text_scale, font_vao, font_vbo, (float *)projection.m, &text_color); + } + + draw_timer(font_shader, font_vao, font_vbo, (float *)projection.m, &game.timer, width); + + if (timer_ended(&game.help_timer)) { + timer_stop(&game.help_timer); + game.should_draw_help = false; + } + if (game.should_draw_help) - draw_help_overlay(selection_shader, font_shader, selection_vao, selection_vbo, font_vao, font_vbo, projection, height); + draw_help_overlay(selection_shader, font_shader, selection_vao, selection_vbo, font_vao, font_vbo, &projection, height, &game.help_timer); if (game.has_won) { - draw_win_overlay(win_shader, font_shader, win_vao, font_vao, font_vbo, (float *)transform); + draw_win_overlay(win_shader, font_shader, win_vao, font_vao, font_vbo, (float *)transform, &game); } glXSwapBuffers(display, window); diff --git a/timer.c b/timer.c new file mode 100644 index 0000000..fd45de0 --- /dev/null +++ b/timer.c @@ -0,0 +1,44 @@ +#include "timer.h" + +struct timeval start_time; + +double get_time() { + struct timeval current_time; + gettimeofday(¤t_time, NULL); + + return (double)(current_time.tv_sec - start_time.tv_sec) + (double)(current_time.tv_usec - start_time.tv_usec) / 1000000.0; +} + +void start_internal_timer() { + gettimeofday(&start_time, NULL); +} + +bool timer_ended(Timer *timer) { + if (!timer->is_running) return false; + + return get_time() - timer->time >= timer->duration; +} + +void timer_start(Timer *timer, float duration) { + timer->time = get_time(); + timer->duration = duration; + timer->is_running = true; +} + +void timer_reset(Timer *timer) { + timer->time = get_time(); + timer->is_running = true; +} + +float timer_remaining(Timer *timer) { + return timer->duration - (get_time() - timer->time); +} + +void timer_stop(Timer *timer) { + timer->time = 0.0f; + timer->is_running = false; +} + +float timer_elapsed(Timer *timer) { + return get_time() - timer->time; +} diff --git a/timer.h b/timer.h new file mode 100644 index 0000000..639daa0 --- /dev/null +++ b/timer.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include +extern struct timeval start_time; + +typedef struct Timer { + float time; + float duration; + bool is_running; +} Timer; + + +double get_time(); +void start_internal_timer(); +bool timer_ended(Timer *timer); +void timer_start(Timer *timer, float duration); +void timer_reset(Timer *timer); +float timer_remaining(Timer *timer); +void timer_stop(Timer *timer); +float timer_elapsed(Timer *timer); diff --git a/x11.c b/x11.c index 27c0477..04360b8 100644 --- a/x11.c +++ b/x11.c @@ -96,6 +96,7 @@ int init_x11(Display **display, Window *window) { XWindowAttributes win_attrs; XGetWindowAttributes(*display, *window, &win_attrs); + glad_glXSwapIntervalEXT(*display, *window, 1); // we enable blending for text glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);