Compare commits
12
Commits
e4a2fef052
..
v0.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9734c3c0a | ||
|
|
f367ef48a8 | ||
|
|
0dafe93b51 | ||
|
|
a14bd280ff | ||
|
|
dcd3ed9245 | ||
|
|
ffc738604d | ||
|
|
601eb47cad | ||
|
|
ea1e99f022 | ||
|
|
9deb40d080 | ||
|
|
87559e409f | ||
|
|
a6da01d4ac | ||
|
|
4fffad5540 |
@@ -3,3 +3,4 @@
|
||||
build/
|
||||
src/engine/CMakeFiles
|
||||
examples/CMakeFiles
|
||||
*.xopp
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
);
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
+43
-9
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "internal/engine_internal.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
engine_internal_state_t *internal_state = NULL;
|
||||
game_state_t *game_state = NULL;
|
||||
|
||||
@@ -42,9 +44,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 +91,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 +140,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 +157,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 +184,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 +207,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);
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+475
-40
@@ -1,55 +1,490 @@
|
||||
#include <dangerzone/engine.h>
|
||||
#include <dangerzone/render/anim.h>
|
||||
#include <dangerzone/render/misc.h>
|
||||
#include <dangerzone/render/rect.h>
|
||||
#include <dangerzone/render/text.h>
|
||||
|
||||
// polling events every frame
|
||||
void
|
||||
client_poll_event()
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <math.h>
|
||||
|
||||
#define SIZE 4
|
||||
#define MOVE_DURATION 0.12f
|
||||
#define BORDER_WIDTH 10
|
||||
|
||||
typedef enum {
|
||||
LEFT,
|
||||
RIGHT,
|
||||
UP,
|
||||
DOWN
|
||||
} dir_e;
|
||||
|
||||
typedef struct {
|
||||
uint32_t value;
|
||||
int row;
|
||||
int col;
|
||||
} line_tile_t;
|
||||
|
||||
typedef enum {
|
||||
GAME_PLAYING,
|
||||
GAME_WON,
|
||||
GAME_LOST
|
||||
} game_status_t;
|
||||
|
||||
static game_status_t game_status;
|
||||
|
||||
typedef struct {
|
||||
uint32_t area[SIZE][SIZE];
|
||||
dir_e direction;
|
||||
uint32_t score;
|
||||
} game_t;
|
||||
|
||||
static game_t game;
|
||||
|
||||
static anim_t movement;
|
||||
static anim_track_t move_tracks[SIZE * SIZE];
|
||||
static size_t move_track_count;
|
||||
|
||||
static double last_frame_time;
|
||||
|
||||
static bool
|
||||
board_is_full(void)
|
||||
{
|
||||
SDL_Event ev = game_state->event;
|
||||
for (int r = 0; r < SIZE; ++r)
|
||||
for (int c = 0; c < SIZE; ++c)
|
||||
if (game.area[r][c] == 0)
|
||||
return false;
|
||||
|
||||
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;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// performed every frame
|
||||
void
|
||||
client_update_framebuffer()
|
||||
/* Pixel size is calculated from the current window size while drawing. */
|
||||
static uint32_t
|
||||
colors(uint32_t number)
|
||||
{
|
||||
// single green demo pixel
|
||||
game_state->framebuffer[0] = 0x00FF00;
|
||||
switch (number) {
|
||||
case 0: return 0xFFFFFF;
|
||||
case 2: return 0x00FF00;
|
||||
case 4: return 0xFF0000;
|
||||
case 8: return 0x0000FF;
|
||||
default: return 0xFFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
clear_framebuffer(0xFFFF00);
|
||||
put_string("Welcome to the most\n*DANGEROUS* 2048 game!",
|
||||
20, 20, 2, 2, 3, 0x000000, 0xFFFF00
|
||||
);
|
||||
/* Maps positive values logarithmically: powers of two get evenly spaced hues.
|
||||
Returns 0xRRGGBB. Values outside [min_value, max_value] are clamped. */
|
||||
uint32_t
|
||||
color_for_value(double value, double min_value, double max_value)
|
||||
{
|
||||
if (min_value <= 0.0 || max_value <= min_value || value <= 0.0)
|
||||
return 0xFFFFFF;
|
||||
|
||||
if (value < min_value) value = min_value;
|
||||
if (value > max_value) value = max_value;
|
||||
|
||||
double t = (log2(value) - log2(min_value)) /
|
||||
(log2(max_value) - log2(min_value));
|
||||
|
||||
t = sqrt(t); /* accelerates color changes near the low end */
|
||||
|
||||
/* Sweep hue from blue to red in HSV; full saturation and brightness. */
|
||||
// double h = (1.0 - t) * 240.0;
|
||||
|
||||
double exponent = log2(value / min_value); /* 0 at min_value, 1 per doubling */
|
||||
double h = fmod(20.0 + exponent * 45.0, 360.0);
|
||||
|
||||
double c = 1.0;
|
||||
double x = 1.0 - fabs(fmod(h / 60.0, 2.0) - 1.0);
|
||||
double r, g, b;
|
||||
|
||||
if (h < 60.0) { r = c; g = x; b = 0.0; }
|
||||
else if (h < 120.0) { r = x; g = c; b = 0.0; }
|
||||
else if (h < 180.0) { r = 0.0; g = c; b = x; }
|
||||
else { r = 0.0; g = x; b = c; }
|
||||
|
||||
return ((uint32_t)(r * 255.0) << 16) |
|
||||
((uint32_t)(g * 255.0) << 8) |
|
||||
(uint32_t)(b * 255.0);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
switch (direction) {
|
||||
case LEFT:
|
||||
*row = line;
|
||||
*col = p;
|
||||
break;
|
||||
case RIGHT:
|
||||
*row = line;
|
||||
*col = SIZE - 1 - p;
|
||||
break;
|
||||
case UP:
|
||||
*row = p;
|
||||
*col = line;
|
||||
break;
|
||||
case DOWN:
|
||||
*row = SIZE - 1 - p;
|
||||
*col = line;
|
||||
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);
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
if (merged_value >= 2048)
|
||||
game_status = GAME_WON;
|
||||
|
||||
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 (game_status != GAME_PLAYING || anim_is_active(&movement))
|
||||
return false;
|
||||
|
||||
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:
|
||||
case SDLK_UP:
|
||||
if (game_status == GAME_PLAYING)
|
||||
slide(UP);
|
||||
break;
|
||||
case SDLK_S:
|
||||
case SDLK_DOWN:
|
||||
if (game_status == GAME_PLAYING)
|
||||
slide(DOWN);
|
||||
break;
|
||||
case SDLK_A:
|
||||
case SDLK_LEFT:
|
||||
if (game_status == GAME_PLAYING)
|
||||
slide(LEFT);
|
||||
break;
|
||||
case SDLK_D:
|
||||
case SDLK_RIGHT:
|
||||
if (game_status == GAME_PLAYING)
|
||||
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 = color_for_value((double)value, 2, 131072);
|
||||
|
||||
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)) {
|
||||
if (game_status == GAME_PLAYING) {
|
||||
new_2();
|
||||
|
||||
if (board_is_full())
|
||||
game_status = GAME_LOST;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t tile_size = game_state->width / SIZE;
|
||||
uint32_t border_width = BORDER_WIDTH;
|
||||
|
||||
clear_framebuffer(OVERDRAW, 0x000000);
|
||||
|
||||
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);
|
||||
|
||||
if (game_status != GAME_PLAYING) {
|
||||
clear_framebuffer(BASE, 0x000000);
|
||||
const char *message =
|
||||
game_status == GAME_WON ? "YOU WIN!" : "YOU LOSE!";
|
||||
|
||||
put_string(OVERDRAW, message,
|
||||
0 + border_width * 2,
|
||||
game_state->height / 2,
|
||||
6, 1, 12, 0xFFFFFF, 0x000000);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* Draw all cell backgrounds. */
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
game_init(void)
|
||||
{
|
||||
game_status = GAME_PLAYING;
|
||||
memset(&game, 0, sizeof game);
|
||||
anim_init(&movement);
|
||||
srand((unsigned)time(NULL));
|
||||
|
||||
/* Start with two 2-tiles. */
|
||||
new_2();
|
||||
new_2();
|
||||
}
|
||||
|
||||
int
|
||||
main()
|
||||
main(void)
|
||||
{
|
||||
// initialize the engine
|
||||
if (engine_init(
|
||||
500, 500,
|
||||
"Dangerous 2048",
|
||||
client_poll_event,
|
||||
client_update_framebuffer
|
||||
)) {
|
||||
engine_start();
|
||||
engine_stop();
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
uint32_t window_width = 500;
|
||||
uint32_t window_height = (uint32_t)(window_width * 1.12f);
|
||||
|
||||
game_init();
|
||||
|
||||
if (engine_init(window_width, window_height,
|
||||
"Dangerous 2048",
|
||||
client_poll_event,
|
||||
client_update_framebuffer)) {
|
||||
engine_start();
|
||||
engine_stop();
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
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)
|
||||
target_link_libraries(game_2048 PRIVATE m)
|
||||
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
@@ -46,3 +46,8 @@ void
|
||||
engine_sdl_destroy(
|
||||
engine_sdl_state_t *state
|
||||
);
|
||||
|
||||
uint32_t*
|
||||
get_framebuffer(
|
||||
framebuffer_e framebuffer
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user