Merge pull request 'add animations' (#28) from experiment/animations into dev

Reviewed-on: #28
This commit was merged in pull request #28.
This commit is contained in:
2026-10-10 13:33:35 +02:00
14 changed files with 605 additions and 460 deletions
+11 -2
View File
@@ -4,13 +4,22 @@
#include <stdbool.h>
#include <stdint.h>
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;
+55
View File
@@ -0,0 +1,55 @@
#ifndef DANGERZONE_RENDER_ANIM_H
#define DANGERZONE_RENDER_ANIM_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#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
+4
View File
@@ -1,9 +1,12 @@
#pragma once
#include <dangerzone/engine.h>
#include <stdint.h>
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
);
+4
View File
@@ -1,9 +1,12 @@
#pragma once
#include <dangerzone/engine.h>
#include <stdint.h>
void
put_rect(
framebuffer_e framebuffer,
uint32_t x,
uint32_t y,
uint32_t w,
@@ -11,6 +14,7 @@ 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);
+4
View File
@@ -1,9 +1,12 @@
#pragma once
#include <dangerzone/engine.h>
#include <stdint.h>
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,
+41 -9
View File
@@ -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);
}
+17 -4
View File
@@ -3,6 +3,8 @@
#include "internal/engine_internal.h"
#include "internal/util/error.h"
#include <stdlib.h>
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,
+89
View File
@@ -0,0 +1,89 @@
#include <dangerzone/render/anim.h>
#include <string.h>
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;
}
+25 -2
View File
@@ -2,16 +2,36 @@
#include <dangerzone/render/misc.h>
#include <dangerzone/render/rect.h>
#include "internal/engine_internal.h"
#include "internal/util/error.h"
#include <stdint.h>
// 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(
+4 -2
View File
@@ -10,6 +10,7 @@
*/
void
put_rect(
framebuffer_e framebuffer,
uint32_t x,
uint32_t y,
uint32_t w,
@@ -18,12 +19,13 @@ put_rect(
) {
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;
}
+4 -1
View File
@@ -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;
}
+280 -377
View File
@@ -1,14 +1,19 @@
#include "dangerzone/render/rect.h"
#include <dangerzone/engine.h>
#include <dangerzone/render/anim.h>
#include <dangerzone/render/misc.h>
#include <dangerzone/render/rect.h>
#include <dangerzone/render/text.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#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]);
switch (number) {
case 0: return 0xFFFFFF;
case 2: return 0x00FF00;
case 4: return 0xFF0000;
case 8: return 0x0000FF;
default: return 0xFFFFFF;
}
printf("%d]\n", game.area[i][SIZE - 1]);
}
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;
}
}
}
}
break;
default:
*row = SIZE - 1 - p;
*col = line;
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;
}
/*
* 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)
{
float side = (float)(tile_size - BORDER_WIDTH);
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;
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
};
}
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;
}
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;
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;
}
}
}
uint32_t tile_size = game_state->width / SIZE;
return retval;
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)
};
}
bool
combine(
bool has_moved
) {
if (game.direction == LEFT || game.direction == RIGHT) {
return combine_horizontal(has_moved);
/*
* 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
};
}
}
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 {
return combine_vertical(has_moved);
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;
}
// finds a random empty position and puts a two there
void
new_2()
static bool
slide(dir_e direction)
{
uint32_t row = rand() % SIZE;
uint32_t col = rand() % SIZE;
while (game.area[row][col] != 0) {
row = rand() % SIZE;
col = rand() % SIZE;
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;
}
game.area[row][col] = 2;
if (!changed) {
move_track_count = 0;
return false;
}
return anim_start(&movement, move_tracks, move_track_count,
MOVE_DURATION);
}
void
slide()
static void
new_2(void)
{
// 1. move all squares in one direction
bool has_moved = move();
int empty_rows[SIZE * SIZE];
int empty_cols[SIZE * SIZE];
int count = 0;
// 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");
// 3. generate a new 2
if (has_moved || has_combined) {
new_2();
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;
}
}
}
// printf("score: %d\n", game.score);
if (count == 0)
return;
// print_area();
int pick = rand() % count;
game.area[empty_rows[pick]][empty_cols[pick]] = 2;
}
// polling events every frame
/* Polling events every frame. */
void
client_poll_event()
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) {
if (ev.type != SDL_EVENT_KEY_DOWN)
return;
switch (ev.key.key) {
case SDLK_Q:
game_state->is_running = false;
break;
case SDLK_W:
game.direction = UP;
slide();
slide(UP);
break;
case SDLK_S:
game.direction = DOWN;
slide();
slide(DOWN);
break;
case SDLK_A:
game.direction = LEFT;
slide();
slide(LEFT);
break;
case SDLK_D:
game.direction = RIGHT;
slide();
slide(RIGHT);
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()
static double
monotonic_seconds(void)
{
// clear_framebuffer(0xFFFF00);
// put_string("Welcome to the most\n*DANGEROUS* 2048 game!",
// 20, 20, 2, 2, 3, 0x000000, 0xFFFF00
// );
struct timespec ts;
timespec_get(&ts, TIME_UTC);
return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9;
}
clear_framebuffer(0x000000);
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
client_update_framebuffer(void)
{
double now = monotonic_seconds();
float dt = last_frame_time == 0.0
? 0.0f
: (float)(now - last_frame_time);
last_frame_time = now;
if (dt > 0.05f)
dt = 0.05f;
if (anim_is_active(&movement) && anim_update(&movement, dt)) {
new_2();
}
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;
clear_framebuffer(OVERDRAW, 0x000000);
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;
/* 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);
}
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);
}
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));
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;
/* Start with two 2-tiles. */
new_2();
new_2();
}
int
main()
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,
if (engine_init(window_width, window_height,
"Dangerous 2048",
client_poll_event,
client_update_framebuffer
)) {
client_update_framebuffer)) {
engine_start();
engine_stop();
return 0;
} else {
return 1;
}
return 1;
}
+4 -4
View File
@@ -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)
+6 -2
View File
@@ -1,9 +1,9 @@
#pragma once
#include <dangerzone/engine.h>
#include <SDL3/SDL.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#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
);