13 Commits
25 changed files with 467 additions and 231 deletions
+3
View File
@@ -1,2 +1,5 @@
.clangd .clangd
*.sublime-* *.sublime-*
build/
src/engine/CMakeFiles
examples/CMakeFiles
+3
View File
@@ -0,0 +1,3 @@
[submodule "third_party/munit"]
path = third_party/munit
url = https://github.com/nemequ/munit.git
+13
View File
@@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.20)
project(dangerzone LANGUAGES C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
find_package(PkgConfig REQUIRED)
pkg_check_modules(SDL3 REQUIRED IMPORTED_TARGET sdl3)
add_subdirectory(src/engine)
add_subdirectory(src/examples)
add_subdirectory(tests)
-41
View File
@@ -1,41 +0,0 @@
CC := gcc
LIBS := `pkg-config --libs sdl3`
INC := `pkg-config --cflags sdl3` -I./include
SRC := src/engine/engine.c \
src/engine/engine_init.c \
src/engine/render.c \
src/engine/text.c
OUT := build/main
SNAKE_SRC := src/examples/snake.c $(SRC)
SNAKE := build/snake
2048_SRC := src/examples/2048.c $(SRC)
2048 := build/2048
build:
$(CC) -o $(OUT) -O3 -DNDEBUG src/main.c $(SRC) $(INC) $(LIBS)
run:
$(OUT)
build-snake:
$(CC) -o $(SNAKE) -O3 -DNDEBUG $(SNAKE_SRC) $(INC) $(LIBS)
run-snake:
$(SNAKE)
clean-snake:
rm $(SNAKE)
build-2048:
$(CC) -o $(2048) -O3 -DNDEBUG $(2048_SRC) $(INC) $(LIBS)
run-2048:
$(2048)
clean-2048:
rm $(2048)
clean:
rm $(OUT)
.PHONY: build run
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include <SDL3/SDL.h>
#include <stdbool.h>
#include <stdint.h>
typedef struct {
SDL_Event event;
bool is_running;
bool framebuffer_dirty;
SDL_Rect dirty_rect;
uint16_t width, height;
uint32_t *framebuffer;
} game_state_t;
extern game_state_t *game_state;
bool
engine_init(
uint32_t window_width,
uint32_t window_height,
const char window_name[],
void (*engine_poll_events)(void),
void (*engine_update_framebuffer)(void)
);
void
engine_start();
void
engine_stop();
+15
View File
@@ -0,0 +1,15 @@
#pragma once
#include <stdint.h>
void
put_pixel(
unsigned int x,
unsigned int y,
uint32_t color
);
void
clear_framebuffer(
uint32_t color
);
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include <stdint.h>
void
put_rect(
uint32_t x,
uint32_t y,
uint32_t w,
uint32_t h,
uint32_t color
);
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);
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include <stdint.h>
void
put_char(
char c,
uint32_t x,
uint32_t y,
uint32_t size,
uint32_t fg_color,
uint32_t bg_color
);
void
put_string(
const char *text,
uint32_t x,
uint32_t y,
uint32_t size,
uint32_t spacing,
uint32_t line_spacing,
uint32_t fg_color,
uint32_t bg_color
);
-119
View File
@@ -1,119 +0,0 @@
#pragma once
#include <SDL3/SDL.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#define WINDOW_WIDTH 1280
#define WINDOW_HEIGHT 720
#define DEFAULT_WINDOW_NAME "Dangerzone"
#define WINDOW_NAME_LENGTH 128
#define TARGET_FRAME 1.0 / 60.0
#define FONT_ROWS 7
#define FONT_COLS 5
typedef struct {
char window_name[WINDOW_NAME_LENGTH];
uint16_t width, height;
SDL_Window *window;
SDL_Renderer *renderer;
SDL_Texture *texture;
} engine_sdl_state_t;
typedef struct {
/**
* this function runs inside of the SDL_PollEvent loop
* useful for user input, quitting the program and so on
*/
void (*engine_poll_events)(void);
/**
* meant as the "area" for manupulating the framebuffer;
* after this is executed, the contents of
* game_state->framebuffer get drawn
*/
void (*engine_update_framebuffer)(void);
engine_sdl_state_t *sdl_state;
} engine_internal_state_t;
typedef struct {
uint32_t framebuffer[WINDOW_WIDTH * WINDOW_HEIGHT];
SDL_Event event;
bool is_running;
bool framebuffer_dirty;
SDL_Rect dirty_rect;
uint16_t width, height;
} game_state_t;
extern engine_internal_state_t *internal_state;
extern game_state_t *game_state;
engine_sdl_state_t*
engine_sdl_init();
void
engine_sdl_destroy(
engine_sdl_state_t *state
);
bool
engine_init(
void (*engine_poll_events)(void),
void (*engine_update_framebuffer)(void)
);
void
engine_start();
void
engine_stop();
void
put_pixel(
unsigned int x,
unsigned int y,
uint32_t color
);
void
put_rect(
uint32_t x,
uint32_t y,
uint32_t w,
uint32_t h,
uint32_t color
);
void
clear_framebuffer(
uint32_t color
);
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);
void
put_char(
char c,
uint32_t x,
uint32_t y,
uint32_t size,
uint32_t fg_color,
uint32_t bg_color
);
void
put_string(
const char *text,
uint32_t x,
uint32_t y,
uint32_t size,
uint32_t spacing,
uint32_t line_spacing,
uint32_t fg_color,
uint32_t bg_color
);
+19
View File
@@ -0,0 +1,19 @@
file(GLOB_RECURSE ENGINE_SOURCES CONFIGURE_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/*.c"
)
add_library(dangerzone_engine STATIC
${ENGINE_SOURCES}
)
target_include_directories(dangerzone_engine
PUBLIC
${PROJECT_SOURCE_DIR}/include
PRIVATE
${PROJECT_SOURCE_DIR}/src
)
target_link_libraries(dangerzone_engine
PUBLIC
PkgConfig::SDL3
)
+5 -2
View File
@@ -1,4 +1,6 @@
#include "engine.h" #include <dangerzone/engine.h>
#include "internal/engine_internal.h"
engine_internal_state_t *internal_state = NULL; engine_internal_state_t *internal_state = NULL;
game_state_t *game_state = NULL; game_state_t *game_state = NULL;
@@ -172,5 +174,6 @@ void
engine_stop() { engine_stop() {
engine_sdl_destroy(internal_state->sdl_state); engine_sdl_destroy(internal_state->sdl_state);
free(internal_state); free(internal_state);
free(game_state); free(game_state->framebuffer);
free(game_state);
} }
+43 -13
View File
@@ -1,10 +1,15 @@
#include "engine.h" #include <dangerzone/engine.h>
#include "error.h"
#include <SDL3/SDL_render.h> #include "internal/engine_internal.h"
#include <SDL3/SDL_video.h> #include "internal/util/error.h"
bool bool
engine_sdl_init_state(engine_sdl_state_t **state) { engine_sdl_init_state(
engine_sdl_state_t **state,
uint32_t window_width,
uint32_t window_height,
const char window_name[]
) {
*state = (engine_sdl_state_t *) *state = (engine_sdl_state_t *)
malloc(sizeof(engine_sdl_state_t)); malloc(sizeof(engine_sdl_state_t));
@@ -13,12 +18,13 @@ engine_sdl_init_state(engine_sdl_state_t **state) {
return RCODE_FAILURE; return RCODE_FAILURE;
} }
(*state)->width = WINDOW_WIDTH; (*state)->width = window_width;
(*state)->height = WINDOW_HEIGHT; (*state)->height = window_height;
strncpy( strncpy(
(*state)->window_name, DEFAULT_WINDOW_NAME, (*state)->window_name, window_name,
WINDOW_NAME_LENGTH WINDOW_NAME_MAX_LENGTH - 1
); );
(*state)->window_name[WINDOW_NAME_MAX_LENGTH - 1] = '\0';
return RCODE_SUCCESS; return RCODE_SUCCESS;
} }
@@ -94,11 +100,19 @@ engine_sdl_init_texture(
} }
engine_sdl_state_t* engine_sdl_state_t*
engine_sdl_init() engine_sdl_init(
{ uint32_t window_width,
uint32_t window_height,
const char window_name[]
) {
engine_sdl_state_t *state; engine_sdl_state_t *state;
if (!engine_sdl_init_state(&state) || if (!engine_sdl_init_state(
&state,
window_width,
window_height,
window_name
) ||
!engine_sdl_init_root() || !engine_sdl_init_root() ||
!engine_sdl_init_window(state) || !engine_sdl_init_window(state) ||
!engine_sdl_init_renderer(state) || !engine_sdl_init_renderer(state) ||
@@ -127,6 +141,9 @@ engine_sdl_destroy(
bool bool
engine_init( engine_init(
uint32_t window_width,
uint32_t window_height,
const char window_name[],
void (*engine_poll_events)(void), void (*engine_poll_events)(void),
void (*engine_update_framebuffer)(void) void (*engine_update_framebuffer)(void)
) { ) {
@@ -143,15 +160,28 @@ engine_init(
return RCODE_FAILURE; return RCODE_FAILURE;
} }
game_state->framebuffer = (uint32_t *)
malloc(sizeof(uint32_t) * window_width * window_height);
if (game_state->framebuffer == NULL) {
free(internal_state);
free(game_state);
return RCODE_FAILURE;
}
internal_state->engine_poll_events internal_state->engine_poll_events
= engine_poll_events; = engine_poll_events;
internal_state->engine_update_framebuffer internal_state->engine_update_framebuffer
= engine_update_framebuffer; = engine_update_framebuffer;
internal_state->sdl_state = engine_sdl_init(); internal_state->sdl_state = engine_sdl_init(
window_width,
window_height,
window_name
);
if (internal_state->sdl_state == NULL) { if (internal_state->sdl_state == NULL) {
free(internal_state); free(internal_state);
free(game_state->framebuffer);
free(game_state); free(game_state);
return RCODE_FAILURE; return RCODE_FAILURE;
} }
+48
View File
@@ -0,0 +1,48 @@
#include <dangerzone/engine.h>
#include <dangerzone/render/misc.h>
#include <dangerzone/render/rect.h>
#include "internal/util/error.h"
#include <stdint.h>
// alter a pixel at an imaginary x & y coordinates
void
put_pixel(
unsigned int x,
unsigned int y,
uint32_t color
) {
uint16_t w = game_state->width;
uint16_t h = game_state->height;
if (x < 0 || x >= w || y < 0 || y >= h) {
eprintf(
"CRITICAL: attempted buffer overflow in put_pixel\n"
"at x = %d, y = %d", x, y
);
} else {
game_state->framebuffer[game_state->width * y + x]
= color;
mark_dirty_rect(x, y, 1, 1);
}
}
// fill the entire framebuffer with a given color
void
clear_framebuffer(
uint32_t color
) {
for (size_t i = 0;
i < (size_t)game_state->width * game_state->height;
++i) {
game_state->framebuffer[i] = color;
}
mark_dirty_rect(
0,
0,
game_state->width,
game_state->height
);
}
@@ -1,30 +1,9 @@
#include "engine.h" #include <dangerzone/engine.h>
#include "error.h" #include <dangerzone/render/misc.h>
#include <dangerzone/render/rect.h>
#include <stdint.h> #include <stdint.h>
// alter a pixel at an imaginary x & y coordinates
void
put_pixel(
unsigned int x,
unsigned int y,
uint32_t color
) {
uint16_t w = game_state->width;
uint16_t h = game_state->height;
if (x < 0 || x >= w || y < 0 || y >= h) {
eprintf(
"CRITICAL: attempted buffer overflow in put_pixel\n"
"at x = %d, y = %d", x, y
);
} else {
game_state->framebuffer[game_state->width * y + x]
= color;
mark_dirty_rect(x, y, 1, 1);
}
}
/** /**
* draws a rectangle with its left-top corner at (x,y) with * draws a rectangle with its left-top corner at (x,y) with
* a width of 'w' and height of 'h' filled with color 'color' * a width of 'w' and height of 'h' filled with color 'color'
@@ -45,25 +24,6 @@ put_rect(
mark_dirty_rect(x, y, w, h); mark_dirty_rect(x, y, w, h);
} }
// fill the entire framebuffer with a given color
void
clear_framebuffer(
uint32_t color
) {
for (size_t i = 0;
i < (size_t)game_state->width * game_state->height;
++i) {
game_state->framebuffer[i] = color;
}
mark_dirty_rect(
0,
0,
game_state->width,
game_state->height
);
}
void void
clear_rect(int x, int y, int w, int h, uint32_t color) clear_rect(int x, int y, int w, int h, uint32_t color)
{ {
@@ -1,7 +1,10 @@
// rendering characters in the framebuffer // rendering characters in the framebuffer
#include "engine.h" #include <dangerzone/engine.h>
#include <stdint.h> #include <dangerzone/render/rect.h>
#include <dangerzone/render/text.h>
#include "internal/engine_internal.h"
/* /*
* each character has 8 rows * each character has 8 rows
+7 -3
View File
@@ -1,4 +1,6 @@
#include "engine.h" #include <dangerzone/engine.h>
#include <dangerzone/render/misc.h>
#include <dangerzone/render/text.h>
// polling events every frame // polling events every frame
void void
@@ -28,8 +30,8 @@ client_update_framebuffer()
// single green demo pixel // single green demo pixel
game_state->framebuffer[0] = 0x00FF00; game_state->framebuffer[0] = 0x00FF00;
put_rect(0, 0, 1100, 200, 0xFFFF00); clear_framebuffer(0xFFFF00);
put_string("Hello, traveller. You have come\nto the right place!", put_string("Welcome to the most\n*DANGEROUS* 2048 game!",
20, 20, 2, 2, 3, 0x000000, 0xFFFF00 20, 20, 2, 2, 3, 0x000000, 0xFFFF00
); );
} }
@@ -39,6 +41,8 @@ main()
{ {
// initialize the engine // initialize the engine
if (engine_init( if (engine_init(
500, 500,
"Dangerous 2048",
client_poll_event, client_poll_event,
client_update_framebuffer client_update_framebuffer
)) { )) {
+8
View File
@@ -0,0 +1,8 @@
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)
+7 -1
View File
@@ -1,4 +1,8 @@
#include "engine.h" #include <dangerzone/engine.h>
#include <dangerzone/render/rect.h>
#include <dangerzone/render/text.h>
#include <dangerzone/render/misc.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdbool.h> #include <stdbool.h>
@@ -331,6 +335,8 @@ main()
// initialize the engine // initialize the engine
if (engine_init( if (engine_init(
500, 500,
"Dangerous Snake",
client_poll_event, client_poll_event,
client_update_framebuffer client_update_framebuffer
)) { )) {
+7 -5
View File
@@ -1,4 +1,6 @@
#include "engine.h" #include <dangerzone/engine.h>
#include <dangerzone/render/misc.h>
#include <dangerzone/render/text.h>
// polling events every frame // polling events every frame
void void
@@ -25,10 +27,8 @@ client_poll_event()
void void
client_update_framebuffer() client_update_framebuffer()
{ {
// single green demo pixel // example window contents
game_state->framebuffer[0] = 0x00FF00; clear_framebuffer(0xFFFF00);
put_rect(0, 0, 1100, 200, 0xFFFF00);
put_string("Hello, traveller. You have come\nto the right place!", put_string("Hello, traveller. You have come\nto the right place!",
20, 20, 2, 2, 3, 0x000000, 0xFFFF00 20, 20, 2, 2, 3, 0x000000, 0xFFFF00
); );
@@ -39,6 +39,8 @@ main()
{ {
// initialize the engine // initialize the engine
if (engine_init( if (engine_init(
500, 100,
"Dangerzone",
client_poll_event, client_poll_event,
client_update_framebuffer client_update_framebuffer
)) { )) {
+48
View File
@@ -0,0 +1,48 @@
#pragma once
#include <SDL3/SDL.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#define WINDOW_NAME_MAX_LENGTH 128
#define FONT_ROWS 7
#define FONT_COLS 5
typedef struct {
char window_name[WINDOW_NAME_MAX_LENGTH];
uint16_t width, height;
SDL_Window *window;
SDL_Renderer *renderer;
SDL_Texture *texture;
} engine_sdl_state_t;
typedef struct {
/**
* this function runs inside of the SDL_PollEvent loop
* useful for user input, quitting the program and so on
*/
void (*engine_poll_events)(void);
/**
* meant as the "area" for manupulating the framebuffer;
* after this is executed, the contents of
* game_state->framebuffer get drawn
*/
void (*engine_update_framebuffer)(void);
engine_sdl_state_t *sdl_state;
} engine_internal_state_t;
extern engine_internal_state_t *internal_state;
engine_sdl_state_t*
engine_sdl_init(
uint32_t window_width,
uint32_t window_height,
const char window_name[]
);
void
engine_sdl_destroy(
engine_sdl_state_t *state
);
+40
View File
@@ -0,0 +1,40 @@
add_library(munit STATIC
"${PROJECT_SOURCE_DIR}/third_party/munit/munit.c"
)
target_include_directories(munit
PUBLIC
${PROJECT_SOURCE_DIR}/third_party
)
file(GLOB TEST_SOURCES CONFIGURE_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/suites/*.c"
)
add_library(dangerzone_tests STATIC
${TEST_SOURCES}
)
target_include_directories(dangerzone_tests
PUBLIC
${PROJECT_SOURCE_DIR}/include
${PROJECT_SOURCE_DIR}/src
${PROJECT_SOURCE_DIR}/third_party
)
add_executable(test_runner
main.c
)
target_link_libraries(test_runner
PRIVATE
dangerzone_tests
munit
dangerzone_engine
)
add_custom_target(test
COMMAND tests/test_runner
DEPENDS test_runner
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
)
+26
View File
@@ -0,0 +1,26 @@
#include "munit/munit.h"
extern const MunitSuite sdl_suite;
int main(int argc, char *argv[]) {
MunitSuite sub_suites[] = {
sdl_suite,
{ // terminator
NULL,
NULL,
NULL,
0,
MUNIT_SUITE_OPTION_NONE
}
};
MunitSuite master_suite = {
"",
NULL,
sub_suites,
1,
MUNIT_SUITE_OPTION_NONE
};
return munit_suite_main(&master_suite, NULL, argc, argv);
}
+90
View File
@@ -0,0 +1,90 @@
#include <dangerzone/engine.h>
#include "internal/engine_internal.h"
#include "munit/munit.h"
static void*
test_setup(
const MunitParameter params[],
void* data
) {
(void)params;
(void)data;
internal_state = (engine_internal_state_t *)
malloc(sizeof(engine_internal_state_t));
munit_assert_not_null(internal_state);
internal_state->sdl_state = (engine_sdl_state_t *)
malloc(sizeof(engine_sdl_state_t));
munit_assert_not_null(internal_state->sdl_state);
internal_state->sdl_state->width = 500;
internal_state->sdl_state->height = 500;
strncpy(
internal_state->sdl_state->window_name, "Dangerzone",
WINDOW_NAME_MAX_LENGTH - 1
);
internal_state->sdl_state->window_name[WINDOW_NAME_MAX_LENGTH - 1] = '\0';
return internal_state;
}
static void
test_teardown(
void *fixture
) {
engine_internal_state_t *state = fixture;
if (state->sdl_state != NULL) {
free(state->sdl_state);
free(state);
}
internal_state = NULL;
}
static MunitResult
test_window_parameters(
const MunitParameter params[],
void* data
) {
(void)params;
engine_internal_state_t *state = data;
munit_assert_not_null(state);
munit_assert_not_null(state->sdl_state);
munit_assert_int(state->sdl_state->width, ==, 500);
munit_assert_int(state->sdl_state->height, ==, 500);
munit_assert_int(strcmp(state->sdl_state->window_name, "Dangerzone"), ==, 0);
return MUNIT_OK;
}
MunitTest tests[] = {
{
"/window-parameters",
test_window_parameters,
test_setup,
test_teardown,
MUNIT_TEST_OPTION_NONE,
NULL
},
{ // terminator
NULL,
NULL,
NULL,
NULL,
MUNIT_TEST_OPTION_NONE,
NULL
}
};
const MunitSuite sdl_suite = {
"/sdl",
tests,
NULL,
1,
MUNIT_SUITE_OPTION_NONE
};
Vendored Submodule
+1
Submodule third_party/munit added at ad67a3a2ca