diff --git a/include/dangerzone/engine.h b/include/dangerzone/engine.h index 114481d..e532845 100644 --- a/include/dangerzone/engine.h +++ b/include/dangerzone/engine.h @@ -4,13 +4,22 @@ #include #include +typedef enum { + BASE, + OVERDRAW +} framebuffer_e; + typedef struct { SDL_Event event; bool is_running; - bool framebuffer_dirty; + bool framebuffer_base_dirty; + bool framebuffer_overdraw_dirty; SDL_Rect dirty_rect; uint16_t width, height; - uint32_t *framebuffer; + // base layer ~ layer 0 + uint32_t *framebuffer_base; + // layer on top of that ~ layer 1 + uint32_t *framebuffer_overdraw; } game_state_t; extern game_state_t *game_state; @@ -28,4 +37,4 @@ void engine_start(); void -engine_stop(); \ No newline at end of file +engine_stop(); diff --git a/include/dangerzone/render/anim.h b/include/dangerzone/render/anim.h new file mode 100644 index 0000000..01cdbdb --- /dev/null +++ b/include/dangerzone/render/anim.h @@ -0,0 +1,55 @@ +#ifndef DANGERZONE_RENDER_ANIM_H +#define DANGERZONE_RENDER_ANIM_H + +#include +#include +#include + +#define ANIM_MAX_TRACKS 64 + +typedef struct { + float x; + float y; + float w; + float h; +} anim_rect_t; + +/* payload is application data; the animation module does not interpret it. */ +typedef struct { + uint32_t payload; + anim_rect_t from; + anim_rect_t to; +} anim_track_t; + +typedef struct { + uint32_t payload; + anim_rect_t rect; +} anim_item_t; + +typedef struct { + anim_track_t tracks[ANIM_MAX_TRACKS]; + anim_item_t items[ANIM_MAX_TRACKS]; + size_t count; + float elapsed; + float duration; + bool active; +} anim_t; + +void anim_init(anim_t *anim); + +/* + * Start tracks from their initial rectangles to their destination rectangles. + * Returns false for invalid input or if count exceeds ANIM_MAX_TRACKS. + * A zero-duration animation completes immediately. + */ +bool anim_start(anim_t *anim, const anim_track_t *tracks, + size_t count, float duration); + +/* Advance by nonnegative seconds. Returns true once the animation is finished. */ +bool anim_update(anim_t *anim, float dt); + +bool anim_is_active(const anim_t *anim); +size_t anim_count(const anim_t *anim); +const anim_item_t *anim_items(const anim_t *anim); + +#endif diff --git a/include/dangerzone/render/misc.h b/include/dangerzone/render/misc.h index 10a9b46..5662a46 100644 --- a/include/dangerzone/render/misc.h +++ b/include/dangerzone/render/misc.h @@ -1,9 +1,12 @@ #pragma once +#include + #include void put_pixel( + framebuffer_e framebuffer, unsigned int x, unsigned int y, uint32_t color @@ -11,5 +14,6 @@ put_pixel( void clear_framebuffer( + framebuffer_e framebuffer, uint32_t color -); \ No newline at end of file +); diff --git a/include/dangerzone/render/rect.h b/include/dangerzone/render/rect.h index e5ea19b..bebcae8 100644 --- a/include/dangerzone/render/rect.h +++ b/include/dangerzone/render/rect.h @@ -1,9 +1,12 @@ #pragma once +#include + #include void put_rect( + framebuffer_e framebuffer, uint32_t x, uint32_t y, uint32_t w, @@ -11,8 +14,9 @@ put_rect( uint32_t color ); +// TODO: remove, deprecated, you can use put_rect for the same thing void clear_rect(int x, int y, int w, int h, uint32_t color); void -mark_dirty_rect(int x, int y, int w, int h); \ No newline at end of file +mark_dirty_rect(int x, int y, int w, int h); diff --git a/include/dangerzone/render/text.h b/include/dangerzone/render/text.h index a7e767d..e73cce7 100644 --- a/include/dangerzone/render/text.h +++ b/include/dangerzone/render/text.h @@ -1,9 +1,12 @@ #pragma once +#include + #include void put_char( + framebuffer_e framebuffer, char c, uint32_t x, uint32_t y, @@ -14,6 +17,7 @@ put_char( void put_string( + framebuffer_e framebuffer, const char *text, uint32_t x, uint32_t y, @@ -22,4 +26,4 @@ put_string( uint32_t line_spacing, uint32_t fg_color, uint32_t bg_color -); \ No newline at end of file +); diff --git a/src/engine/engine.c b/src/engine/engine.c index 2a46828..77aabf6 100644 --- a/src/engine/engine.c +++ b/src/engine/engine.c @@ -42,9 +42,15 @@ mark_dirty_rect(int x, int y, int w, int h) .h = h }; - if (!game_state->framebuffer_dirty) { + if (!game_state->framebuffer_base_dirty) { game_state->dirty_rect = rect; - game_state->framebuffer_dirty = true; + game_state->framebuffer_base_dirty = true; + return; + } + + if (!game_state->framebuffer_overdraw_dirty) { + game_state->dirty_rect = rect; + game_state->framebuffer_overdraw_dirty = true; return; } @@ -83,9 +89,24 @@ mark_dirty_rect(int x, int y, int w, int h) } bool -upload_dirty_texture(void) -{ - if (!game_state->framebuffer_dirty) { +upload_dirty_texture( + framebuffer_e framebuffer +) { + uint32_t *current_framebuffer; + switch (framebuffer) { + case BASE: + current_framebuffer = game_state->framebuffer_base; + break; + case OVERDRAW: + current_framebuffer = game_state->framebuffer_overdraw; + break; + default: + break; + } + + if (!game_state->framebuffer_base_dirty && + !game_state->framebuffer_overdraw_dirty + ) { return true; } @@ -117,7 +138,7 @@ upload_dirty_texture(void) (size_t)rect.w * sizeof(uint32_t); const uint8_t *source = - (const uint8_t *)game_state->framebuffer + + (const uint8_t *)current_framebuffer + (size_t)rect.y * source_pitch + (size_t)rect.x * sizeof(uint32_t); @@ -134,7 +155,16 @@ upload_dirty_texture(void) SDL_UnlockTexture(texture); - game_state->framebuffer_dirty = false; + switch (framebuffer) { + case BASE: + game_state->framebuffer_base_dirty = false; + break; + case OVERDRAW: + game_state->framebuffer_overdraw_dirty = false; + break; + default: + break; + } return true; } @@ -152,7 +182,8 @@ engine_start() { internal_state->engine_update_framebuffer(); // update the texture with the modified framebuffer - upload_dirty_texture(); + upload_dirty_texture(BASE); + upload_dirty_texture(OVERDRAW); SDL_RenderClear( internal_state->sdl_state->renderer @@ -174,6 +205,7 @@ void engine_stop() { engine_sdl_destroy(internal_state->sdl_state); free(internal_state); - free(game_state->framebuffer); + free(game_state->framebuffer_base); + free(game_state->framebuffer_overdraw); free(game_state); -} \ No newline at end of file +} diff --git a/src/engine/engine_init.c b/src/engine/engine_init.c index 1a360c8..adbb477 100644 --- a/src/engine/engine_init.c +++ b/src/engine/engine_init.c @@ -3,6 +3,8 @@ #include "internal/engine_internal.h" #include "internal/util/error.h" +#include + bool engine_sdl_init_state( engine_sdl_state_t **state, @@ -160,14 +162,23 @@ engine_init( return RCODE_FAILURE; } - game_state->framebuffer = (uint32_t *) + game_state->framebuffer_base = (uint32_t *) malloc(sizeof(uint32_t) * window_width * window_height); - if (game_state->framebuffer == NULL) { + if (game_state->framebuffer_base == NULL) { free(internal_state); free(game_state); return RCODE_FAILURE; } + game_state->framebuffer_overdraw = (uint32_t *) + malloc(sizeof(uint32_t) * window_width * window_height); + if (game_state->framebuffer_overdraw == NULL) { + free(internal_state); + free(game_state->framebuffer_base); + free(game_state); + return RCODE_FAILURE; + } + internal_state->engine_poll_events = engine_poll_events; @@ -181,13 +192,15 @@ engine_init( ); if (internal_state->sdl_state == NULL) { free(internal_state); - free(game_state->framebuffer); + free(game_state->framebuffer_base); + free(game_state->framebuffer_overdraw); free(game_state); return RCODE_FAILURE; } game_state->is_running = true; - game_state->framebuffer_dirty = true; + game_state->framebuffer_base_dirty = true; + game_state->framebuffer_overdraw_dirty = true; game_state->dirty_rect = (SDL_Rect) { .x = 0, .y = 0, @@ -198,4 +211,4 @@ engine_init( game_state->height = internal_state->sdl_state->height; return RCODE_SUCCESS; -} \ No newline at end of file +} diff --git a/src/engine/render/anim.c b/src/engine/render/anim.c new file mode 100644 index 0000000..e8a7288 --- /dev/null +++ b/src/engine/render/anim.c @@ -0,0 +1,89 @@ +#include + +#include + +void +anim_init(anim_t *anim) +{ + if (anim != NULL) + memset(anim, 0, sizeof *anim); +} + +bool +anim_start(anim_t *anim, const anim_track_t *tracks, + size_t count, float duration) +{ + if (anim == NULL || (count != 0 && tracks == NULL) || + count > ANIM_MAX_TRACKS || duration < 0.0f) { + return false; + } + + anim->count = count; + anim->elapsed = 0.0f; + anim->duration = duration; + anim->active = duration > 0.0f && count > 0; + + for (size_t i = 0; i < count; ++i) { + anim->tracks[i] = tracks[i]; + anim->items[i].payload = tracks[i].payload; + anim->items[i].rect = tracks[i].from; + } + + if (!anim->active) { + for (size_t i = 0; i < count; ++i) + anim->items[i].rect = tracks[i].to; + } + + return true; +} + +bool +anim_update(anim_t *anim, float dt) +{ + if (anim == NULL || !anim->active) + return true; + + if (dt < 0.0f) + dt = 0.0f; + + anim->elapsed += dt; + + float t = anim->elapsed / anim->duration; + if (t >= 1.0f) { + t = 1.0f; + anim->active = false; + } + + /* Ease-out. Replace with t for linear motion. */ + float eased = 1.0f - (1.0f - t) * (1.0f - t); + + for (size_t i = 0; i < anim->count; ++i) { + const anim_track_t *track = &anim->tracks[i]; + anim_rect_t *rect = &anim->items[i].rect; + + rect->x = track->from.x + (track->to.x - track->from.x) * eased; + rect->y = track->from.y + (track->to.y - track->from.y) * eased; + rect->w = track->from.w + (track->to.w - track->from.w) * eased; + rect->h = track->from.h + (track->to.h - track->from.h) * eased; + } + + return !anim->active; +} + +bool +anim_is_active(const anim_t *anim) +{ + return anim != NULL && anim->active; +} + +size_t +anim_count(const anim_t *anim) +{ + return anim != NULL ? anim->count : 0; +} + +const anim_item_t * +anim_items(const anim_t *anim) +{ + return anim != NULL ? anim->items : NULL; +} diff --git a/src/engine/render/misc.c b/src/engine/render/misc.c index 2e8159a..88dbe8e 100644 --- a/src/engine/render/misc.c +++ b/src/engine/render/misc.c @@ -2,16 +2,36 @@ #include #include +#include "internal/engine_internal.h" #include "internal/util/error.h" + #include +// helper function to return the correct pointer to a framebuffer for drawing selection +uint32_t* +get_framebuffer( + framebuffer_e framebuffer +) { + switch (framebuffer) { + case BASE: + return game_state->framebuffer_base; + case OVERDRAW: + return game_state->framebuffer_overdraw; + default: + return game_state->framebuffer_base; + } +} + // alter a pixel at an imaginary x & y coordinates void put_pixel( + framebuffer_e framebuffer, unsigned int x, unsigned int y, uint32_t color ) { + uint32_t *current_framebuffer = get_framebuffer(framebuffer); + uint16_t w = game_state->width; uint16_t h = game_state->height; @@ -21,7 +41,7 @@ put_pixel( "at x = %d, y = %d", x, y ); } else { - game_state->framebuffer[game_state->width * y + x] + current_framebuffer[game_state->width * y + x] = color; mark_dirty_rect(x, y, 1, 1); @@ -31,12 +51,15 @@ put_pixel( // fill the entire framebuffer with a given color void clear_framebuffer( + framebuffer_e framebuffer, uint32_t color ) { + uint32_t *current_framebuffer = get_framebuffer(framebuffer); + for (size_t i = 0; i < (size_t)game_state->width * game_state->height; ++i) { - game_state->framebuffer[i] = color; + current_framebuffer[i] = color; } mark_dirty_rect( @@ -45,4 +68,4 @@ clear_framebuffer( game_state->width, game_state->height ); -} \ No newline at end of file +} diff --git a/src/engine/render/rect.c b/src/engine/render/rect.c index ab6b74c..1946471 100644 --- a/src/engine/render/rect.c +++ b/src/engine/render/rect.c @@ -10,20 +10,22 @@ */ void put_rect( + framebuffer_e framebuffer, uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint32_t color -) { +) { for (int i = x; i < x + w; i++) { for (int j = y; j < y + h; j++) { - put_pixel(i, j, color); + put_pixel(framebuffer, i, j, color); } } mark_dirty_rect(x, y, w, h); } +// TODO: remove, see rect.h void clear_rect(int x, int y, int w, int h, uint32_t color) { @@ -33,7 +35,7 @@ clear_rect(int x, int y, int w, int h, uint32_t color) px < game_state->width && py >= 0 && py < game_state->height) { - game_state->framebuffer[ + game_state->framebuffer_base[ py * game_state->width + px ] = color; } diff --git a/src/engine/render/text.c b/src/engine/render/text.c index 3a660c8..7d0b225 100644 --- a/src/engine/render/text.c +++ b/src/engine/render/text.c @@ -81,6 +81,7 @@ static const uint8_t font[128][FONT_ROWS] = { */ void put_char( + framebuffer_e framebuffer, char c, uint32_t x, uint32_t y, @@ -107,6 +108,7 @@ put_char( ? fg_color : bg_color; put_rect( + framebuffer, x + col * size, y + row * size, size, @@ -121,6 +123,7 @@ put_char( void put_string( + framebuffer_e framebuffer, const char *text, uint32_t x, uint32_t y, @@ -137,7 +140,7 @@ put_string( x = start_x; y += (FONT_ROWS + line_spacing) * size; } else { - put_char(*text, x, y, size, fg_color, bg_color); + put_char(framebuffer, *text, x, y, size, fg_color, bg_color); x += (FONT_COLS + spacing) * size; } diff --git a/src/examples/2048.c b/src/examples/2048.c index 9a06621..a62758e 100644 --- a/src/examples/2048.c +++ b/src/examples/2048.c @@ -1,14 +1,19 @@ -#include "dangerzone/render/rect.h" #include +#include #include +#include #include +#include #include #include #include +#include #include #define SIZE 4 +#define MOVE_DURATION 0.12f +#define BORDER_WIDTH 10 typedef enum { LEFT, @@ -18,9 +23,10 @@ typedef enum { } dir_e; typedef struct { - uint32_t x; - uint32_t y; -} v2_t; + uint32_t value; + int row; + int col; +} line_tile_t; typedef struct { uint32_t area[SIZE][SIZE]; @@ -30,470 +36,367 @@ typedef struct { static game_t game; -void -print_area() +static anim_t movement; +static anim_track_t move_tracks[SIZE * SIZE]; +static size_t move_track_count; + +static double last_frame_time; + +/* Pixel size is calculated from the current window size while drawing. */ +static uint32_t +colors(uint32_t number) { - printf("AREA:\n"); - for (int i = 0; i < SIZE; i++) { - printf("["); - for (int j = 0; j < SIZE - 1; j++) { - printf("%d, ", game.area[i][j]); - } - printf("%d]\n", game.area[i][SIZE - 1]); + switch (number) { + case 0: return 0xFFFFFF; + case 2: return 0x00FF00; + case 4: return 0xFF0000; + case 8: return 0x0000FF; + default: return 0xFFFFFF; } - printf("\n"); } -bool -move() +/* + * Return the board coordinate for position p in a line, where p=0 is + * the side toward which tiles move. + */ +static void +line_coordinate(dir_e direction, int line, int p, int *row, int *col) { - bool retval = false; - switch (game.direction) { + switch (direction) { case LEFT: - for (int i = 0; i < SIZE; i++) { - for (int j = 0; j < SIZE; j++) { - for (int k = 0; k < SIZE - 1; k++) { - if (game.area[i][k] == 0 && game.area[i][k + 1] != 0) { - game.area[i][k] = game.area[i][k + 1]; - game.area[i][k + 1] = 0; - retval = true; - } - } - } - } + *row = line; + *col = p; break; case RIGHT: - for (int i = 0; i < SIZE; i++) { - uint32_t *row = game.area[i]; - for (int j = 0; j < SIZE; j++) { - for (int k = SIZE - 1; k > 0; k--) { - if (game.area[i][k] == 0 && game.area[i][k - 1] != 0) { - game.area[i][k] = game.area[i][k - 1]; - game.area[i][k - 1] = 0; - retval = true; - } - } - } - } + *row = line; + *col = SIZE - 1 - p; break; case UP: - for (int i = 0; i < SIZE; i++) { - for (int j = 0; j < SIZE; j++) { - for (int k = 0; k < SIZE - 1; k++) { - if (game.area[k][i] == 0 && game.area[k + 1][i] != 0) { - game.area[k][i] = game.area[k + 1][i]; - game.area[k + 1][i] = 0; - retval = true; - } - } - } - } + *row = p; + *col = line; break; case DOWN: - for (int i = 0; i < SIZE; i++) { - for (int j = 0; j < SIZE; j++) { - for (int k = SIZE - 1; k > 0; k--) { - if (game.area[k][i] == 0 && game.area[k - 1][i] != 0) { - game.area[k][i] = game.area[k - 1][i]; - game.area[k - 1][i] = 0; - retval = true; - } - } - } - } + *row = SIZE - 1 - p; + *col = line; break; - default: - break; - } - - return retval; -} - -bool -combine_horizontal( - bool has_moved -) { - bool retval = has_moved; - int8_t direction; - uint32_t start, end; - switch (game.direction) { - case LEFT: - // combine left - direction = -1; - start = 0; - end = SIZE - 1; - break; - case RIGHT: - // combine right - direction = 1; - start = SIZE - 1; - end = 0; - break; - default: - break; - } - - for (int row_index = 0; row_index < SIZE; row_index++) { - uint32_t *row = game.area[row_index]; - - for (int i = start; (game.direction == LEFT) ? i < end : i > end; i -= direction) { - if (row[i] == 0) { - continue; - } - if (row[i] == row[i - direction]) { - retval = true; - row[i] *= 2; - game.score += row[i]; - row[i - direction] = 0; - for (int j = i - direction; (game.direction == LEFT) ? j < end : j > end; j -= direction) { - row[j] = row[j - direction]; - } - row[end] = 0; - } - } - } - - return retval; -} - -bool -combine_vertical( - bool has_moved -) { - bool retval = has_moved; - int8_t direction; - uint32_t start, end; - switch (game.direction) { - case UP: - // combine up - direction = -1; - start = 0; - end = SIZE - 1; - break; - case DOWN: - // combine down - direction = 1; - start = SIZE - 1; - end = 0; - break; - default: - break; - } - - for (int col_index = 0; col_index < SIZE; col_index++) { - for (int i = start; (game.direction == UP) ? i < end : i > end; i -= direction) { - if (game.area[i][col_index] == 0) { - continue; - } - if (game.area[i][col_index] == game.area[i - direction][col_index]) { - retval = true; - game.area[i][col_index] *= 2; - game.score += game.area[i][col_index]; - game.area[i - direction][col_index] = 0; - for (int j = i - direction; (game.direction == UP) ? j < end : j > end; j -= direction) { - game.area[j][col_index] = game.area[j - direction][col_index]; - } - game.area[end][col_index] = 0; - } - } - } - - return retval; -} - -bool -combine( - bool has_moved -) { - if (game.direction == LEFT || game.direction == RIGHT) { - return combine_horizontal(has_moved); - } else { - return combine_vertical(has_moved); } } -// finds a random empty position and puts a two there -void -new_2() +/* + * Convert a board cell into the pixel rectangle used to draw its tile. + * Keep this consistent with draw_tile_rect() and the cell backgrounds. + */ +static anim_rect_t +tile_rect(int row, int col, uint32_t tile_size) { - uint32_t row = rand() % SIZE; - uint32_t col = rand() % SIZE; - while (game.area[row][col] != 0) { - row = rand() % SIZE; - col = rand() % SIZE; + float side = (float)(tile_size - BORDER_WIDTH); + + return (anim_rect_t) { + .x = BORDER_WIDTH / 2.0f + col * (float)tile_size, + .y = BORDER_WIDTH / 2.0f + row * (float)tile_size, + .w = side, + .h = side + }; +} + +static void +add_move_track(uint32_t value, + int from_row, int from_col, + int to_row, int to_col) +{ + if (move_track_count >= SIZE * SIZE) + return; + + uint32_t tile_size = game_state->width / SIZE; + + move_tracks[move_track_count++] = (anim_track_t) { + .payload = value, + .from = tile_rect(from_row, from_col, tile_size), + .to = tile_rect(to_row, to_col, tile_size) + }; +} + +/* + * Process one row or column in movement order. Update the board and record + * pixel-space animation tracks for every original tile. + */ +static bool +process_line(dir_e direction, int line, uint32_t output[SIZE]) +{ + line_tile_t input[SIZE]; + int input_count = 0; + int output_count = 0; + + for (int p = 0; p < SIZE; ++p) { + int row, col; + line_coordinate(direction, line, p, &row, &col); + + uint32_t value = game.area[row][col]; + if (value != 0) { + input[input_count++] = (line_tile_t) { + .value = value, + .row = row, + .col = col + }; + } } - game.area[row][col] = 2; + memset(output, 0, sizeof(uint32_t) * SIZE); + + for (int i = 0; i < input_count; ) { + int dest_row, dest_col; + line_coordinate(direction, line, output_count, + &dest_row, &dest_col); + + if (i + 1 < input_count && + input[i].value == input[i + 1].value) { + uint32_t merged_value = input[i].value * 2; + + output[output_count] = merged_value; + game.score += merged_value; + + /* Both source tiles travel to the same merge destination. */ + add_move_track(input[i].value, + input[i].row, input[i].col, + dest_row, dest_col); + add_move_track(input[i + 1].value, + input[i + 1].row, input[i + 1].col, + dest_row, dest_col); + + i += 2; + } else { + output[output_count] = input[i].value; + + add_move_track(input[i].value, + input[i].row, input[i].col, + dest_row, dest_col); + + ++i; + } + + ++output_count; + } + + bool changed = false; + for (int p = 0; p < SIZE; ++p) { + int row, col; + line_coordinate(direction, line, p, &row, &col); + + if (game.area[row][col] != output[p]) + changed = true; + + game.area[row][col] = output[p]; + } + + return changed; +} + +static bool +slide(dir_e direction) +{ + if (anim_is_active(&movement)) + return false; + + game.direction = direction; + move_track_count = 0; + + bool changed = false; + + for (int line = 0; line < SIZE; ++line) { + uint32_t output[SIZE]; + if (process_line(direction, line, output)) + changed = true; + } + + if (!changed) { + move_track_count = 0; + return false; + } + + return anim_start(&movement, move_tracks, move_track_count, + MOVE_DURATION); +} + +static void +new_2(void) +{ + int empty_rows[SIZE * SIZE]; + int empty_cols[SIZE * SIZE]; + int count = 0; + + for (int r = 0; r < SIZE; ++r) { + for (int c = 0; c < SIZE; ++c) { + if (game.area[r][c] == 0) { + empty_rows[count] = r; + empty_cols[count] = c; + ++count; + } + } + } + + if (count == 0) + return; + + int pick = rand() % count; + game.area[empty_rows[pick]][empty_cols[pick]] = 2; +} + +/* Polling events every frame. */ +void +client_poll_event(void) +{ + SDL_Event ev = game_state->event; + + if (ev.type == SDL_EVENT_QUIT) { + game_state->is_running = false; + return; + } + + if (ev.type != SDL_EVENT_KEY_DOWN) + return; + + switch (ev.key.key) { + case SDLK_Q: + game_state->is_running = false; + break; + case SDLK_W: + slide(UP); + break; + case SDLK_S: + slide(DOWN); + break; + case SDLK_A: + slide(LEFT); + break; + case SDLK_D: + slide(RIGHT); + break; + default: + break; + } +} + +static double +monotonic_seconds(void) +{ + struct timespec ts; + timespec_get(&ts, TIME_UTC); + return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9; +} + +static void +draw_tile_rect(uint32_t value, float x, float y, float w, float h) +{ + uint32_t ix = (uint32_t)x; + uint32_t iy = (uint32_t)y; + uint32_t iw = (uint32_t)w; + uint32_t ih = (uint32_t)h; + uint32_t color = colors(value); + + put_rect(OVERDRAW, ix, iy, iw, ih, color); + + if (value != 0) { + char text[16]; + snprintf(text, sizeof text, "%u", value); + put_string(OVERDRAW, text, + ix + BORDER_WIDTH, iy + BORDER_WIDTH, + 3, 1, 12, 0x000000, color); + } +} + +static void +draw_tile(uint32_t value, int row, int col, uint32_t tile_size) +{ + anim_rect_t rect = tile_rect(row, col, tile_size); + draw_tile_rect(value, rect.x, rect.y, rect.w, rect.h); } void -slide() +client_update_framebuffer(void) { - // 1. move all squares in one direction - bool has_moved = move(); + double now = monotonic_seconds(); + float dt = last_frame_time == 0.0 + ? 0.0f + : (float)(now - last_frame_time); + last_frame_time = now; - // 2. then combine them and shift the potential rest by one in the given direction - bool has_combined = combine(has_moved); - - // printf("moved? %s, combined? %s\n", (has_moved) ? "true" : "false", (has_combined) ? "true" : "false"); + if (dt > 0.05f) + dt = 0.05f; - // 3. generate a new 2 - if (has_moved || has_combined) { + if (anim_is_active(&movement) && anim_update(&movement, dt)) { new_2(); } - // printf("score: %d\n", game.score); - - // print_area(); -} - -// polling events every frame -void -client_poll_event() -{ - SDL_Event ev = game_state->event; - - if (ev.type == SDL_EVENT_QUIT) { - game_state->is_running = false; - } - - if (ev.type == SDL_EVENT_KEY_DOWN) { - switch (ev.key.key) { - case SDLK_Q: - game_state->is_running = false; - break; - case SDLK_W: - game.direction = UP; - slide(); - break; - case SDLK_S: - game.direction = DOWN; - slide(); - break; - case SDLK_A: - game.direction = LEFT; - slide(); - break; - case SDLK_D: - game.direction = RIGHT; - slide(); - break; - default: - break; - } - } -} - -#define CHR(ord) ((ord) + 48) -#define ORD(chr) ((chr) - 48) - -uint32_t -colors( - uint32_t number -) { - switch (number) { - case 0: - return 0xFFFFFF; - case 2: - return 0x00FF00; - case 4: - return 0xFF0000; - case 8: - return 0x0000FF; - default: - return 0xFFFFFF; - } -} - -uint32_t -numlen( - uint32_t n -) { - uint32_t len = 0; - - while (n > 0) { - len++; - n /= 10; - } - - return len; -} - -uint32_t -power( - uint32_t x, - uint32_t n -) { - // x ^ n - uint32_t acc = 1; - for (int i = 0; i < n; i++) { - acc *= x; - } - - return acc; -} - -uint32_t -numrev( - uint32_t n -) { - uint32_t len = numlen(n); - uint32_t new = 0; - int i = 0; - - while (n > 0) { - new += (n % 10) * power(10, len - (++i)); - n /= 10; - } - - return new; -} - -// performed every frame -void -client_update_framebuffer() -{ - // clear_framebuffer(0xFFFF00); - // put_string("Welcome to the most\n*DANGEROUS* 2048 game!", - // 20, 20, 2, 2, 3, 0x000000, 0xFFFF00 - // ); - - clear_framebuffer(0x000000); - uint32_t tile_size = game_state->width / SIZE; - uint8_t border_width = 10; - char *n_str; - bool alloced = false; - for (int i = 0; i < SIZE; i++) { - for (int j = 0; j < SIZE; j++) { - alloced = true; - // background - uint32_t x = (border_width / 2) + i * tile_size; - uint32_t y = (border_width / 2) + j * tile_size; - uint32_t w = tile_size - border_width; - uint32_t h = tile_size - border_width; - uint32_t background_color = colors(game.area[j][i]); - put_rect(x, y, w, h, background_color); + uint32_t border_width = BORDER_WIDTH; - // number in foreground - uint32_t foreground_color = 0x000000; - uint8_t font_size = 3; - - uint32_t n = game.area[j][i]; - uint32_t n_len = numlen(n); - n_str = calloc(n_len, sizeof(char)); - if (n_str == NULL) { - game_state->is_running = false; - } - sprintf(n_str, "%d", n); - uint32_t n_rev = numrev(n); - uint32_t digit_offset = 1; - if (n != 0) { - put_string(n_str, x + border_width, y + border_width, font_size, digit_offset, 12, foreground_color, background_color); + clear_framebuffer(OVERDRAW, 0x000000); + + /* Draw all cell backgrounds first. */ + for (int r = 0; r < SIZE; ++r) { + for (int c = 0; c < SIZE; ++c) { + uint32_t x = border_width / 2 + (uint32_t)c * tile_size; + uint32_t y = border_width / 2 + (uint32_t)r * tile_size; + uint32_t side = tile_size - border_width; + + put_rect(BASE, x, y, side, side, 0xFFFFFF); + put_rect(OVERDRAW, x, y, side, side, 0xFFFFFF); + } + } + + if (anim_is_active(&movement)) { + const anim_item_t *items = anim_items(&movement); + size_t count = anim_count(&movement); + + for (size_t i = 0; i < count; ++i) { + draw_tile_rect(items[i].payload, + items[i].rect.x, items[i].rect.y, + items[i].rect.w, items[i].rect.h); + } + } else { + for (int r = 0; r < SIZE; ++r) { + for (int c = 0; c < SIZE; ++c) { + draw_tile(game.area[r][c], r, c, tile_size); } } } - // score display line (single line) - char *score_text = calloc(numlen(game.score), sizeof(char)); - if (score_text == NULL) { - game_state->is_running = false; - } - alloced = true; - - sprintf(score_text, "score: %6d", game.score); - put_string(score_text, border_width / 2, tile_size * SIZE + border_width / 2, 6, 1, 12, 0xFFFFFF, 0x000000); - - if (alloced) { - free(n_str); - free(score_text); - } + char score_text[32]; + snprintf(score_text, sizeof score_text, "score: %u", game.score); + put_string(OVERDRAW, score_text, + border_width / 2, tile_size * SIZE + border_width / 2, + 6, 1, 12, 0xFFFFFF, 0x000000); } -bool -v2_eq(v2_t a, v2_t b) +static void +game_init(void) { - return a.x == b.x && a.y == b.y; + memset(&game, 0, sizeof game); + anim_init(&movement); + srand((unsigned)time(NULL)); + + /* Start with two 2-tiles. */ + new_2(); + new_2(); } -bool -v2_contains(v2_t v[], size_t size, v2_t elem) -{ - for (int i = 0; i < size; i++) { - if (v2_eq(v[i], elem)) { - return true; - } - } - - return false; -} - -void -game_init() -{ - srand(time(NULL)); - - // generate MAX_NUMBER_OF_4 + MAX_NUMBER_OF_2 random indices in range 0..15 (row in 0..3, col in 0..3) - // this means that there will be at least MAX_NUMBER_OF_2 of 2s and MAX_NUMBER_OF_4 <= MAX_NUMBER_OF_2 4s - // => usually less 4s than 2s because the indices can get replaced -#define MAX_NUMBER_OF_4 1 -#define MAX_NUMBER_OF_2 2 - - uint8_t row, col; - v2_t elem; - v2_t fours[MAX_NUMBER_OF_4], twos[MAX_NUMBER_OF_2]; - - int i = 0; - while (i < MAX_NUMBER_OF_4) { - row = rand() % SIZE; - col = rand() % SIZE; - elem = (v2_t){ .x = col, .y = row }; - if (!v2_contains(twos, MAX_NUMBER_OF_4, elem)) { - fours[i++] = elem; - } - } - - i = 0; - while (i < MAX_NUMBER_OF_2) { - row = rand() % SIZE; - col = rand() % SIZE; - elem = (v2_t){ .x = col, .y = row }; - if (!v2_contains(twos, MAX_NUMBER_OF_2, elem)) { - twos[i++] = elem; - } - } - - for (i = 0; i < MAX_NUMBER_OF_4; i++) { - game.area[fours[i].y][fours[i].x] = 4; - } - - for (i = 0; i < MAX_NUMBER_OF_2; i++) { - game.area[twos[i].y][twos[i].x] = 2; - } - - game.direction = RIGHT; - game.score = 0; -} - -int -main() +int +main(void) { uint32_t window_width = 500; - uint32_t window_height = window_width * 1.12f; + uint32_t window_height = (uint32_t)(window_width * 1.12f); game_init(); - // initialize the engine - if (engine_init( - window_width, window_height, - "Dangerous 2048", - client_poll_event, - client_update_framebuffer - )) { - engine_start(); - engine_stop(); - return 0; - } else { - return 1; - } + if (engine_init(window_width, window_height, + "Dangerous 2048", + client_poll_event, + client_update_framebuffer)) { + engine_start(); + engine_stop(); + return 0; + } + + return 1; } + diff --git a/src/examples/CMakeLists.txt b/src/examples/CMakeLists.txt index dce4e40..81d3f48 100644 --- a/src/examples/CMakeLists.txt +++ b/src/examples/CMakeLists.txt @@ -1,8 +1,8 @@ -add_executable(game_snake snake.c) -target_link_libraries(game_snake PRIVATE dangerzone_engine) +#add_executable(game_snake snake.c) +#target_link_libraries(game_snake PRIVATE dangerzone_engine) add_executable(game_2048 2048.c) target_link_libraries(game_2048 PRIVATE dangerzone_engine) -add_executable(game_window window.c) -target_link_libraries(game_window PRIVATE dangerzone_engine) +#add_executable(game_window window.c) +#target_link_libraries(game_window PRIVATE dangerzone_engine) diff --git a/src/internal/engine_internal.h b/src/internal/engine_internal.h index bc4cd0b..d4898aa 100644 --- a/src/internal/engine_internal.h +++ b/src/internal/engine_internal.h @@ -1,9 +1,9 @@ #pragma once +#include + #include #include -#include -#include #include #define WINDOW_NAME_MAX_LENGTH 128 @@ -47,3 +47,7 @@ engine_sdl_destroy( engine_sdl_state_t *state ); +uint32_t* +get_framebuffer( + framebuffer_e framebuffer +);