13 Commits
Author SHA1 Message Date
odweta f5be3992a6 add scaffold for 2048 2026-10-05 03:54:40 +02:00
odweta 904b8c3a29 update false claim 2026-10-05 03:47:51 +02:00
odweta 78ae90391a add much-needed optimizations and enhance the snake example game 2026-10-05 03:45:46 +02:00
odweta e72588f792 update README with text 2026-10-05 02:27:32 +02:00
odweta 760d2abc6c add basic text rendering 2026-10-05 02:24:13 +02:00
odweta 0faf401f67 add put_rect for direct rectangle rendering 2026-10-05 00:56:53 +02:00
odweta 931f625311 add snake example game 2026-10-05 00:38:59 +02:00
odweta 5c42066f5e update README with correct include path 2026-10-04 20:47:44 +02:00
odweta 52e4043f87 update README with . 2026-10-04 20:46:54 +02:00
odweta 433f625f41 update README features section 2026-10-04 20:46:08 +02:00
odweta 4045ae89d7 update README with syntax highlighting 2026-10-04 20:45:10 +02:00
odweta 65382a1663 add README.md 2026-10-04 20:43:45 +02:00
odweta 985f6e2fee update default window name 2026-10-04 20:41:40 +02:00
10 changed files with 921 additions and 63 deletions
+26 -4
View File
@@ -1,18 +1,40 @@
CC := gcc CC := gcc
LIBS := `pkg-config --libs sdl3` LIBS := `pkg-config --libs sdl3`
INC := `pkg-config --cflags sdl3` -I./include INC := `pkg-config --cflags sdl3` -I./include
SRC := src/main.c \ SRC := src/engine/engine.c \
src/engine/engine.c \
src/engine/engine_init.c \ src/engine/engine_init.c \
src/engine/render.c src/engine/render.c \
src/engine/text.c
OUT := build/main OUT := build/main
SNAKE_SRC := src/examples/snake.c $(SRC)
SNAKE := build/snake
2048_SRC := src/examples/2048.c $(SRC)
2048 := build/2048
build: build:
$(CC) -o $(OUT) $(SRC) $(INC) $(LIBS) $(CC) -o $(OUT) -O3 -DNDEBUG src/main.c $(SRC) $(INC) $(LIBS)
run: run:
$(OUT) $(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: clean:
rm $(OUT) rm $(OUT)
+86
View File
@@ -0,0 +1,86 @@
# Dangerzone
## A tiny game engine / software renderer using SDL for framebuffer visualization.
#### Features
1. Put Pixel - Lets you color one given pixel in the framebuffer.
```c
void put_pixel(uint32_t x, uint32_t y, uint32_t color);
```
2. Clear Framebuffer - Lets you fill the entire framebuffer with one color.
```c
void clear_framebuffer(uint32_t color);
```
3. Put String - Lets you display multiline text with a size, foreground and background color on a given (x,y). You can specify the spacing between letters and lines. Supported characters are in `text.c` - feel free to add some if you want to.
```c
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
);
```
#### Usage
The main function of a program that uses this engine should look something like this:
```c
int main() {
// initialize the engine
if (engine_init(
client_poll_event,
client_update_framebuffer
)) {
// start the main game loop
engine_start();
// once the engine finishes, end gracefully
engine_stop();
return 0;
} else {
return 1;
}
}
```
The `client_poll_event()` and `client_update_framebuffer()` are two special functions that let you poll events and update the framebuffer respectively. (Shocking, I know.) You are supposed to define them yourself and pass them as function pointers to `engine_init()`.
Polling events allows you to handle mouse clicks, keyboard key presses, quitting the program and so on.
Updating the framebuffer means changing color values in the respective struct and thus getting an image on the screen.
An excerpt from the main game loop is below:
```c
...
// main game loop
while(game_state->is_running) {
uint64_t start = SDL_GetPerformanceCounter();
// poll events
while (SDL_PollEvent(&game_state->event)) {
internal_state->engine_poll_events();
}
// call the user-defined framebuffer manipulation function
internal_state->engine_update_framebuffer();
...
}
...
```
If you want to modify the values for things like the window size or window name, (for now) go into `include/engine.h` and change them there on the `#define` lines.
+42 -1
View File
@@ -8,9 +8,11 @@
#define WINDOW_WIDTH 1280 #define WINDOW_WIDTH 1280
#define WINDOW_HEIGHT 720 #define WINDOW_HEIGHT 720
#define DEFAULT_WINDOW_NAME "SDL Playground" #define DEFAULT_WINDOW_NAME "Dangerzone"
#define WINDOW_NAME_LENGTH 128 #define WINDOW_NAME_LENGTH 128
#define TARGET_FRAME 1.0 / 60.0 #define TARGET_FRAME 1.0 / 60.0
#define FONT_ROWS 7
#define FONT_COLS 5
typedef struct { typedef struct {
char window_name[WINDOW_NAME_LENGTH]; char window_name[WINDOW_NAME_LENGTH];
@@ -39,6 +41,8 @@ typedef struct {
uint32_t framebuffer[WINDOW_WIDTH * WINDOW_HEIGHT]; uint32_t framebuffer[WINDOW_WIDTH * WINDOW_HEIGHT];
SDL_Event event; SDL_Event event;
bool is_running; bool is_running;
bool framebuffer_dirty;
SDL_Rect dirty_rect;
uint16_t width, height; uint16_t width, height;
} game_state_t; } game_state_t;
@@ -72,7 +76,44 @@ put_pixel(
uint32_t color uint32_t color
); );
void
put_rect(
uint32_t x,
uint32_t y,
uint32_t w,
uint32_t h,
uint32_t color
);
void void
clear_framebuffer( clear_framebuffer(
uint32_t color 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
);
+135 -18
View File
@@ -3,12 +3,144 @@
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;
void
mark_dirty_rect(int x, int y, int w, int h)
{
if (w <= 0 || h <= 0) {
return;
}
// clip rectangle to the framebuffer
if (x < 0) {
w += x;
x = 0;
}
if (y < 0) {
h += y;
y = 0;
}
if (x + w > game_state->width) {
w = game_state->width - x;
}
if (y + h > game_state->height) {
h = game_state->height - y;
}
if (w <= 0 || h <= 0) {
return;
}
SDL_Rect rect = {
.x = x,
.y = y,
.w = w,
.h = h
};
if (!game_state->framebuffer_dirty) {
game_state->dirty_rect = rect;
game_state->framebuffer_dirty = true;
return;
}
int left = game_state->dirty_rect.x < rect.x
? game_state->dirty_rect.x
: rect.x;
int top = game_state->dirty_rect.y < rect.y
? game_state->dirty_rect.y
: rect.y;
int old_right =
game_state->dirty_rect.x + game_state->dirty_rect.w;
int new_right = rect.x + rect.w;
int old_bottom =
game_state->dirty_rect.y + game_state->dirty_rect.h;
int new_bottom = rect.y + rect.h;
int right = old_right > new_right
? old_right
: new_right;
int bottom = old_bottom > new_bottom
? old_bottom
: new_bottom;
game_state->dirty_rect = (SDL_Rect) {
.x = left,
.y = top,
.w = right - left,
.h = bottom - top
};
}
bool
upload_dirty_texture(void)
{
if (!game_state->framebuffer_dirty) {
return true;
}
SDL_Texture *texture =
internal_state->sdl_state->texture;
SDL_Rect rect = game_state->dirty_rect;
void *locked_pixels = NULL;
int locked_pitch = 0;
if (!SDL_LockTexture(
texture,
&rect,
&locked_pixels,
&locked_pitch
)) {
SDL_Log(
"SDL_LockTexture failed: %s",
SDL_GetError()
);
return false;
}
const size_t source_pitch =
(size_t)game_state->width * sizeof(uint32_t);
const size_t row_bytes =
(size_t)rect.w * sizeof(uint32_t);
const uint8_t *source =
(const uint8_t *)game_state->framebuffer +
(size_t)rect.y * source_pitch +
(size_t)rect.x * sizeof(uint32_t);
uint8_t *destination =
(uint8_t *)locked_pixels;
for (int y = 0; y < rect.h; ++y) {
memcpy(
destination + (size_t)y * locked_pitch,
source + (size_t)y * source_pitch,
row_bytes
);
}
SDL_UnlockTexture(texture);
game_state->framebuffer_dirty = false;
return true;
}
void void
engine_start() { engine_start() {
// main game loop // main game loop
while(game_state->is_running) { while(game_state->is_running) {
uint64_t start = SDL_GetPerformanceCounter();
// poll events // poll events
while (SDL_PollEvent(&game_state->event)) { while (SDL_PollEvent(&game_state->event)) {
internal_state->engine_poll_events(); internal_state->engine_poll_events();
@@ -18,11 +150,7 @@ engine_start() {
internal_state->engine_update_framebuffer(); internal_state->engine_update_framebuffer();
// update the texture with the modified framebuffer // update the texture with the modified framebuffer
SDL_UpdateTexture( upload_dirty_texture();
internal_state->sdl_state->texture,
NULL, game_state->framebuffer,
internal_state->sdl_state->width * sizeof(uint32_t)
);
SDL_RenderClear( SDL_RenderClear(
internal_state->sdl_state->renderer internal_state->sdl_state->renderer
@@ -37,17 +165,6 @@ engine_start() {
SDL_RenderPresent( SDL_RenderPresent(
internal_state->sdl_state->renderer internal_state->sdl_state->renderer
); );
uint64_t end = SDL_GetPerformanceCounter();
double elapsed =
(double)(end - start) /
(double)SDL_GetPerformanceFrequency();
// cap the framerate at '1.0/TARGET_FRAME' fps
if (elapsed < TARGET_FRAME) {
SDL_Delay((TARGET_FRAME - elapsed) * 1000);
}
} }
} }
+25 -2
View File
@@ -65,6 +65,11 @@ engine_sdl_init_renderer(
return RCODE_FAILURE; return RCODE_FAILURE;
} }
// enable VSync
if (!SDL_SetRenderVSync(state->renderer, 1)) {
eprintf("SDL_SetRenderVSync failed: %s\n", SDL_GetError());
}
return RCODE_SUCCESS; return RCODE_SUCCESS;
} }
@@ -127,9 +132,16 @@ engine_init(
) { ) {
internal_state = (engine_internal_state_t *) internal_state = (engine_internal_state_t *)
malloc(sizeof(engine_internal_state_t)); malloc(sizeof(engine_internal_state_t));
if (internal_state == NULL) {
return RCODE_FAILURE;
}
game_state = (game_state_t *) game_state = (game_state_t *)
malloc(sizeof(game_state_t)); malloc(sizeof(game_state_t));
if (game_state == NULL) {
free(internal_state);
return RCODE_FAILURE;
}
internal_state->engine_poll_events internal_state->engine_poll_events
= engine_poll_events; = engine_poll_events;
@@ -138,11 +150,22 @@ engine_init(
= engine_update_framebuffer; = engine_update_framebuffer;
internal_state->sdl_state = engine_sdl_init(); internal_state->sdl_state = engine_sdl_init();
if (internal_state->sdl_state == NULL) {
free(internal_state);
free(game_state);
return RCODE_FAILURE;
}
game_state->is_running = true; game_state->is_running = true;
game_state->framebuffer_dirty = true;
game_state->dirty_rect = (SDL_Rect) {
.x = 0,
.y = 0,
.w = game_state->width,
.h = game_state->height
};
game_state->width = internal_state->sdl_state->width; game_state->width = internal_state->sdl_state->width;
game_state->height = internal_state->sdl_state->height; game_state->height = internal_state->sdl_state->height;
return (game_state == NULL || internal_state == NULL) return RCODE_SUCCESS;
? RCODE_FAILURE : RCODE_SUCCESS;
} }
+51 -4
View File
@@ -1,5 +1,6 @@
#include "engine.h" #include "engine.h"
#include "error.h" #include "error.h"
#include <stdint.h>
// alter a pixel at an imaginary x & y coordinates // alter a pixel at an imaginary x & y coordinates
void void
@@ -19,19 +20,65 @@ put_pixel(
} else { } else {
game_state->framebuffer[game_state->width * y + x] game_state->framebuffer[game_state->width * y + x]
= color; = color;
mark_dirty_rect(x, y, 1, 1);
} }
} }
/**
* draws a rectangle with its left-top corner at (x,y) with
* a width of 'w' and height of 'h' filled with color 'color'
*/
void
put_rect(
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);
}
}
mark_dirty_rect(x, y, w, h);
}
// fill the entire framebuffer with a given color // fill the entire framebuffer with a given color
void void
clear_framebuffer( clear_framebuffer(
uint32_t color uint32_t color
) { ) {
uint16_t w = game_state->width; for (size_t i = 0;
uint16_t h = game_state->height; i < (size_t)game_state->width * game_state->height;
++i) {
for (int i = 0; i < w * h; i++) {
game_state->framebuffer[i] = color; game_state->framebuffer[i] = color;
} }
mark_dirty_rect(
0,
0,
game_state->width,
game_state->height
);
} }
void
clear_rect(int x, int y, int w, int h, uint32_t color)
{
for (int py = y; py < y + h; ++py) {
for (int px = x; px < x + w; ++px) {
if (px >= 0 &&
px < game_state->width &&
py >= 0 &&
py < game_state->height) {
game_state->framebuffer[
py * game_state->width + px
] = color;
}
}
}
mark_dirty_rect(x, y, w, h);
}
+143
View File
@@ -0,0 +1,143 @@
// rendering characters in the framebuffer
#include "engine.h"
#include <stdint.h>
/*
* each character has 8 rows
* only the lower 4 bits of each byte are used
*/
static const uint8_t font[128][FONT_ROWS] = {
[' '] = { 0, 0, 0, 0, 0, 0, 0 },
['A'] = { 0x0E, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11 },
['B'] = { 0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E },
['C'] = { 0x0F, 0x10, 0x10, 0x10, 0x10, 0x10, 0x0F },
['D'] = { 0x1E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1E },
['E'] = { 0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x1F },
['F'] = { 0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x10 },
['G'] = { 0x0F, 0x10, 0x10, 0x17, 0x11, 0x11, 0x0F },
['H'] = { 0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11 },
['I'] = { 0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1F },
['J'] = { 0x01, 0x01, 0x01, 0x01, 0x11, 0x11, 0x0E },
['K'] = { 0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11 },
['L'] = { 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1F },
['M'] = { 0x11, 0x1B, 0x15, 0x15, 0x11, 0x11, 0x11 },
['N'] = { 0x11, 0x19, 0x15, 0x13, 0x11, 0x11, 0x11 },
['O'] = { 0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E },
['P'] = { 0x1E, 0x11, 0x11, 0x1E, 0x10, 0x10, 0x10 },
['Q'] = { 0x0E, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0D },
['R'] = { 0x1E, 0x11, 0x11, 0x1E, 0x14, 0x12, 0x11 },
['S'] = { 0x0F, 0x10, 0x10, 0x0E, 0x01, 0x01, 0x1E },
['T'] = { 0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04 },
['U'] = { 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E },
['V'] = { 0x11, 0x11, 0x11, 0x11, 0x11, 0x0A, 0x04 },
['W'] = { 0x11, 0x11, 0x11, 0x15, 0x15, 0x15, 0x0A },
['X'] = { 0x11, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x11 },
['Y'] = { 0x11, 0x11, 0x0A, 0x04, 0x04, 0x04, 0x04 },
['Z'] = { 0x1F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1F },
['0'] = { 0x0E, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0E },
['1'] = { 0x04, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x0E },
['2'] = { 0x0E, 0x11, 0x01, 0x02, 0x04, 0x08, 0x1F },
['3'] = { 0x1E, 0x01, 0x01, 0x0E, 0x01, 0x01, 0x1E },
['4'] = { 0x02, 0x06, 0x0A, 0x12, 0x1F, 0x02, 0x02 },
['5'] = { 0x1F, 0x10, 0x10, 0x1E, 0x01, 0x01, 0x1E },
['6'] = { 0x0E, 0x10, 0x10, 0x1E, 0x11, 0x11, 0x0E },
['7'] = { 0x1F, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08 },
['8'] = { 0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E },
['9'] = { 0x0E, 0x11, 0x11, 0x0F, 0x01, 0x01, 0x0E },
['.'] = { 0, 0, 0, 0, 0, 0x0C, 0x0C },
[','] = { 0, 0, 0, 0, 0, 0x0C, 0x08 },
[':'] = { 0, 0x0C, 0x0C, 0, 0, 0x0C, 0x0C },
[';'] = { 0, 0x0C, 0x0C, 0, 0, 0x0C, 0x08 },
['!'] = { 0x04, 0x04, 0x04, 0x04, 0x04, 0, 0x04 },
['?'] = { 0x0E, 0x11, 0x01, 0x02, 0x04, 0, 0x04 },
['-'] = { 0, 0, 0, 0x1F, 0, 0, 0 },
['_'] = { 0, 0, 0, 0, 0, 0, 0x1F },
['+'] = { 0, 0x04, 0x04, 0x1F, 0x04, 0x04, 0 },
['='] = { 0, 0x1F, 0, 0, 0x1F, 0, 0 },
['/'] = { 0x01, 0x01, 0x02, 0x04, 0x08, 0x10, 0x10 },
['\\'] = { 0x10, 0x10, 0x08, 0x04, 0x02, 0x01, 0x01 },
['('] = { 0x02, 0x04, 0x08, 0x08, 0x08, 0x04, 0x02 },
[')'] = { 0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08 },
['['] = { 0x0E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0E },
[']'] = { 0x0E, 0x02, 0x02, 0x02, 0x02, 0x02, 0x0E },
['<'] = { 0x02, 0x04, 0x08, 0x10, 0x08, 0x04, 0x02 },
['>'] = { 0x08, 0x04, 0x02, 0x01, 0x02, 0x04, 0x08 },
['*'] = { 0, 0x15, 0x0E, 0x1F, 0x0E, 0x15, 0 },
['#'] = { 0x0A, 0x0A, 0x1F, 0x0A, 0x1F, 0x0A, 0x0A },
['%'] = { 0x19, 0x19, 0x02, 0x04, 0x08, 0x13, 0x13 },
['@'] = { 0x0E, 0x11, 0x17, 0x15, 0x17, 0x10, 0x0F }
};
/**
* put a char 'c' on 'x' and 'y' in the framebuffer
* with a foreground color of 'color'
*/
void
put_char(
char c,
uint32_t x,
uint32_t y,
uint32_t size,
uint32_t fg_color,
uint32_t bg_color
) {
// convert lowercase letters to uppercase
if (c >= 'a' && c <= 'z') {
c -= 'a' - 'A';
}
// avoid indexing outside font[128]
if ((unsigned char)c >= 128) {
c = '?';
}
for (uint32_t row = 0; row < FONT_ROWS; row++) {
uint8_t bits = font[(unsigned char)c][row];
for (uint32_t col = 0; col < FONT_COLS; col++) {
uint32_t color =
bits & (1u << (FONT_COLS - 1 - col))
? fg_color : bg_color;
put_rect(
x + col * size,
y + row * size,
size,
size,
color
);
}
}
mark_dirty_rect(x, y, size, size);
}
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
) {
uint32_t start_x = x;
while (*text) {
if (*text == '\n') {
x = start_x;
y += (FONT_ROWS + line_spacing) * size;
} else {
put_char(*text, x, y, size, fg_color, bg_color);
x += (FONT_COLS + spacing) * size;
}
text++;
}
}
+51
View File
@@ -0,0 +1,51 @@
#include "engine.h"
// 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;
default:
break;
}
}
}
// performed every frame
void
client_update_framebuffer()
{
// single green demo pixel
game_state->framebuffer[0] = 0x00FF00;
put_rect(0, 0, 1100, 200, 0xFFFF00);
put_string("Hello, traveller. You have come\nto the right place!",
20, 20, 2, 2, 3, 0x000000, 0xFFFF00
);
}
int
main()
{
// initialize the engine
if (engine_init(
client_poll_event,
client_update_framebuffer
)) {
engine_start();
engine_stop();
return 0;
} else {
return 1;
}
}
+343
View File
@@ -0,0 +1,343 @@
#include "engine.h"
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>
bool left = true;
typedef struct {
int32_t x;
int32_t y;
} v2;
#define SNAKE_MAX_LENGTH 10
typedef struct {
// position of the head of the snake in the framebuffer
v2 head_pos;
// -1 or +1 for both x and y
v2 dir;
// tail length + 1 for the head
uint16_t length;
// width and/or height
uint8_t size;
v2 body[SNAKE_MAX_LENGTH];
uint64_t move_interval;
uint64_t next_move_time;
bool is_apple_alive;
bool direction_pending;
v2 pending_dir;
v2 apple_pos;
} snake_state_t;
snake_state_t snake;
bool put_string_game_over;
bool put_string_win;
bool redraw = false;
bool
is_opposite_direction(v2 a, v2 b)
{
return a.x == -b.x && a.y == -b.y;
}
void
queue_direction(v2 new_dir)
{
if (is_opposite_direction(new_dir, snake.dir)) {
return;
}
// ignore additional direction inputs until the next movement
if (snake.direction_pending) {
return;
}
snake.pending_dir = new_dir;
snake.direction_pending = true;
}
// 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:
queue_direction((v2){0, -1});
break;
case SDLK_S:
queue_direction((v2){0, 1});
break;
case SDLK_A:
queue_direction((v2){-1, 0});
break;
case SDLK_D:
queue_direction((v2){1, 0});
break;
default:
break;
}
}
}
// a wrapper function for put_rect using a v2 struct
void
put_rect_v2(
v2 pos,
unsigned int w,
unsigned int h,
uint32_t color
) {
put_rect(pos.x, pos.y, w, h, color);
}
bool
v2_eq(
v2 x,
v2 y
) {
return x.x == y.x && x.y == y.y;
}
void
advance_snake_pos()
{
if (snake.direction_pending) {
snake.dir = snake.pending_dir;
snake.direction_pending = false;
}
int32_t new_head_x = (snake.body[0].x + snake.dir.x * snake.size) % game_state->width;
if (new_head_x < 0) new_head_x = game_state->width - snake.size;
int32_t new_head_y = (snake.body[0].y + snake.dir.y * snake.size) % game_state->height;
if (new_head_y < 0) new_head_y = game_state->height - snake.size;
for (int i = snake.length - 1; i > 0; i--) {
snake.body[i] = snake.body[i - 1];
}
snake.body[0] = (v2){new_head_x, new_head_y};
}
void
spawn_apple_at_random_pos()
{
snake.apple_pos = (v2){
snake.size * (rand() % (game_state->width / snake.size)),
snake.size * (rand() % (game_state->height / snake.size))
};
snake.is_apple_alive = true;
}
void
calculate_apple_collisions()
{
if (snake.is_apple_alive &&
v2_eq(snake.body[0], snake.apple_pos)
) {
snake.is_apple_alive = false;
if (snake.length < SNAKE_MAX_LENGTH) {
snake.length++;
//snake.body[snake.length] = snake.body[snake.length - 1];
}
}
}
void
calculate_snake_death_collisions()
{
for (int i = 1; i < snake.length; i++) {
if (v2_eq(snake.body[0], snake.body[i])) {
put_string_game_over = true;
}
}
}
void
evaluate_win()
{
put_string_win = (snake.length >= SNAKE_MAX_LENGTH);
}
/**
* performed every frame
* runs on a separate worker thread - you can take advantage
* of sleep or usleep from <unistd.h> to create delays
*/
void
client_update_framebuffer()
{
uint64_t now = SDL_GetTicks();
if (now >= snake.next_move_time && !put_string_game_over) {
// ------- business logic --------
if (!snake.is_apple_alive) {
spawn_apple_at_random_pos();
}
calculate_apple_collisions();
calculate_snake_death_collisions();
evaluate_win();
v2 old_head = snake.body[0];
v2 old_tail = snake.body[snake.length - 1];
advance_snake_pos();
v2 new_head = snake.body[0];
clear_rect(
old_tail.x,
old_tail.y,
snake.size,
snake.size,
0x000000
);
if (snake.length > 1) {
put_rect(
old_head.x,
old_head.y,
snake.size,
snake.size,
0x00AA00
);
}
put_rect(
new_head.x,
new_head.y,
snake.size,
snake.size,
0x00FF00
);
mark_dirty_rect(
old_tail.x,
old_tail.y,
snake.size,
snake.size
);
mark_dirty_rect(
old_head.x,
old_head.y,
snake.size,
snake.size
);
mark_dirty_rect(
new_head.x,
new_head.y,
snake.size,
snake.size
);
// -------------------------------
snake.next_move_time += snake.move_interval;
if (snake.next_move_time < now) {
snake.next_move_time = now + snake.move_interval;
}
redraw = true;
game_state->framebuffer_dirty = true;
}
if (redraw) {
// draw the apple
if (snake.is_apple_alive) {
put_rect_v2(
snake.apple_pos,
snake.size,
snake.size,
0xFF0000
);
}
// draw the head
put_rect(
snake.body[0].x, snake.body[0].y,
snake.size, snake.size, 0x00FF00
);
// draw the tail
for (int i = 1; i < snake.length; i++) {
put_rect(
snake.body[i].x, snake.body[i].y,
snake.size, snake.size, 0x00AA00
);
}
// draw the splash texts
if (put_string_game_over) {
put_string("GAME OVER!\n<q> to quit",
0, 0, 5, 2, 3, 0xFF0000, 0x000000
);
}
if (put_string_win) {
put_string("YOU WIN!\n<q> to quit",
0, 100, 5, 2, 3, 0x00FF00, 0x000000
);
}
redraw = false;
}
}
void
snake_init()
{
srand(time(NULL));
// start at the left-top corner and go right
snake.body[0] = (v2){0, 0};
snake.dir = (v2){1, 0};
snake.length = 1;
snake.size = 20;
snake.move_interval = 50;
snake.next_move_time = snake.move_interval;
snake.is_apple_alive = false;
snake.pending_dir = snake.dir;
snake.direction_pending = false;
put_string_game_over = false;
put_string_win = false;
}
int
main()
{
// initialize the snake vars
snake_init();
// initialize the engine
if (engine_init(
client_poll_event,
client_update_framebuffer
)) {
engine_start();
engine_stop();
return 0;
} else {
return 1;
}
}
+16 -31
View File
@@ -1,10 +1,9 @@
#include "engine.h" #include "engine.h"
bool left = true;
// polling events every frame // polling events every frame
void void
client_poll_event() { client_poll_event()
{
SDL_Event ev = game_state->event; SDL_Event ev = game_state->event;
if (ev.type == SDL_EVENT_QUIT) { if (ev.type == SDL_EVENT_QUIT) {
@@ -15,43 +14,29 @@ client_poll_event() {
switch (ev.key.key) { switch (ev.key.key) {
case SDLK_Q: case SDLK_Q:
game_state->is_running = false; game_state->is_running = false;
break;
default:
break;
} }
} }
} }
// performed every frame // performed every frame
void void
client_update_framebuffer() { client_update_framebuffer()
// draw a green square {
// clear_framebuffer(0x000000); // single green demo pixel
// for (int i = 0; i < 20; i++) { game_state->framebuffer[0] = 0x00FF00;
// for (int j = 0; j < 20; j++) {
// put_pixel(i, j, 0x00FF00);
// }
// }
// draw alternating green squares put_rect(0, 0, 1100, 200, 0xFFFF00);
if (left) { put_string("Hello, traveller. You have come\nto the right place!",
clear_framebuffer(0x000000); 20, 20, 2, 2, 3, 0x000000, 0xFFFF00
for (int i = 0; i < 20; i++) { );
for (int j = 0; j < 20; j++) {
put_pixel(i, j, 0x00FF00);
}
}
left = false;
} else {
clear_framebuffer(0x000000);
for (int i = 20; i < 40; i++) {
for (int j = 20; j < 40; j++) {
put_pixel(i, j, 0x00FFFF);
}
}
left = true;
}
SDL_Delay(100);
} }
int main() { int
main()
{
// initialize the engine // initialize the engine
if (engine_init( if (engine_init(
client_poll_event, client_poll_event,