Compare commits
11
Commits
65382a1663
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5be3992a6 | ||
|
|
904b8c3a29 | ||
|
|
78ae90391a | ||
|
|
e72588f792 | ||
|
|
760d2abc6c | ||
|
|
0faf401f67 | ||
|
|
931f625311 | ||
|
|
5c42066f5e | ||
|
|
52e4043f87 | ||
|
|
433f625f41 | ||
|
|
4045ae89d7 |
@@ -1,18 +1,40 @@
|
||||
CC := gcc
|
||||
LIBS := `pkg-config --libs sdl3`
|
||||
INC := `pkg-config --cflags sdl3` -I./include
|
||||
SRC := src/main.c \
|
||||
src/engine/engine.c \
|
||||
SRC := src/engine/engine.c \
|
||||
src/engine/engine_init.c \
|
||||
src/engine/render.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) $(SRC) $(INC) $(LIBS)
|
||||
$(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)
|
||||
|
||||
|
||||
@@ -1,30 +1,42 @@
|
||||
# Dangerzone
|
||||
|
||||
## A tiny game engine / software renderer using SDL for framebuffer visualization
|
||||
## A tiny game engine / software renderer using SDL for framebuffer visualization.
|
||||
|
||||
#### Features
|
||||
|
||||
1. Put Pixel
|
||||
1. Put Pixel - Lets you color one given pixel in the framebuffer.
|
||||
|
||||
```
|
||||
void put_pixel(unsigned int x, unsigned int y, uint32_t color);
|
||||
```c
|
||||
void put_pixel(uint32_t x, uint32_t y, uint32_t color);
|
||||
```
|
||||
|
||||
- Lets you color one given pixel in the framebuffer.
|
||||
2. Clear Framebuffer - Lets you fill the entire framebuffer with one color.
|
||||
|
||||
2. Clear Framebuffer
|
||||
|
||||
```
|
||||
```c
|
||||
void clear_framebuffer(uint32_t color);
|
||||
```
|
||||
|
||||
- Lets you fill the entire framebuffer with one 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(
|
||||
@@ -50,7 +62,7 @@ Updating the framebuffer means changing color values in the respective struct an
|
||||
|
||||
An excerpt from the main game loop is below:
|
||||
|
||||
```
|
||||
```c
|
||||
...
|
||||
|
||||
// main game loop
|
||||
@@ -71,4 +83,4 @@ while(game_state->is_running) {
|
||||
...
|
||||
```
|
||||
|
||||
If you want to modify the values for things like the window size or window name, (for now) go into `src/engine/engine.h` and change them there on the `#define` lines.
|
||||
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.
|
||||
@@ -11,6 +11,8 @@
|
||||
#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];
|
||||
@@ -39,6 +41,8 @@ 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;
|
||||
|
||||
@@ -72,7 +76,44 @@ put_pixel(
|
||||
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
|
||||
);
|
||||
+135
-18
@@ -3,12 +3,144 @@
|
||||
engine_internal_state_t *internal_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
|
||||
engine_start() {
|
||||
// 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();
|
||||
@@ -18,11 +150,7 @@ engine_start() {
|
||||
internal_state->engine_update_framebuffer();
|
||||
|
||||
// update the texture with the modified framebuffer
|
||||
SDL_UpdateTexture(
|
||||
internal_state->sdl_state->texture,
|
||||
NULL, game_state->framebuffer,
|
||||
internal_state->sdl_state->width * sizeof(uint32_t)
|
||||
);
|
||||
upload_dirty_texture();
|
||||
|
||||
SDL_RenderClear(
|
||||
internal_state->sdl_state->renderer
|
||||
@@ -37,17 +165,6 @@ engine_start() {
|
||||
SDL_RenderPresent(
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,11 @@ engine_sdl_init_renderer(
|
||||
return RCODE_FAILURE;
|
||||
}
|
||||
|
||||
// enable VSync
|
||||
if (!SDL_SetRenderVSync(state->renderer, 1)) {
|
||||
eprintf("SDL_SetRenderVSync failed: %s\n", SDL_GetError());
|
||||
}
|
||||
|
||||
return RCODE_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -127,9 +132,16 @@ engine_init(
|
||||
) {
|
||||
internal_state = (engine_internal_state_t *)
|
||||
malloc(sizeof(engine_internal_state_t));
|
||||
if (internal_state == NULL) {
|
||||
return RCODE_FAILURE;
|
||||
}
|
||||
|
||||
game_state = (game_state_t *)
|
||||
malloc(sizeof(game_state_t));
|
||||
if (game_state == NULL) {
|
||||
free(internal_state);
|
||||
return RCODE_FAILURE;
|
||||
}
|
||||
|
||||
internal_state->engine_poll_events
|
||||
= engine_poll_events;
|
||||
@@ -138,11 +150,22 @@ engine_init(
|
||||
= engine_update_framebuffer;
|
||||
|
||||
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->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->height = internal_state->sdl_state->height;
|
||||
|
||||
return (game_state == NULL || internal_state == NULL)
|
||||
? RCODE_FAILURE : RCODE_SUCCESS;
|
||||
return RCODE_SUCCESS;
|
||||
}
|
||||
+52
-5
@@ -1,5 +1,6 @@
|
||||
#include "engine.h"
|
||||
#include "error.h"
|
||||
#include <stdint.h>
|
||||
|
||||
// alter a pixel at an imaginary x & y coordinates
|
||||
void
|
||||
@@ -19,19 +20,65 @@ put_pixel(
|
||||
} 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
|
||||
* 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
|
||||
void
|
||||
clear_framebuffer(
|
||||
uint32_t color
|
||||
) {
|
||||
uint16_t w = game_state->width;
|
||||
uint16_t h = game_state->height;
|
||||
for (size_t i = 0;
|
||||
i < (size_t)game_state->width * game_state->height;
|
||||
++i) {
|
||||
game_state->framebuffer[i] = color;
|
||||
}
|
||||
|
||||
for (int i = 0; i < w * h; i++) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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++;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -1,10 +1,9 @@
|
||||
#include "engine.h"
|
||||
|
||||
bool left = true;
|
||||
|
||||
// polling events every frame
|
||||
void
|
||||
client_poll_event() {
|
||||
client_poll_event()
|
||||
{
|
||||
SDL_Event ev = game_state->event;
|
||||
|
||||
if (ev.type == SDL_EVENT_QUIT) {
|
||||
@@ -15,43 +14,29 @@ client_poll_event() {
|
||||
switch (ev.key.key) {
|
||||
case SDLK_Q:
|
||||
game_state->is_running = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// performed every frame
|
||||
void
|
||||
client_update_framebuffer() {
|
||||
// draw a green square
|
||||
// clear_framebuffer(0x000000);
|
||||
// for (int i = 0; i < 20; i++) {
|
||||
// for (int j = 0; j < 20; j++) {
|
||||
// put_pixel(i, j, 0x00FF00);
|
||||
// }
|
||||
// }
|
||||
client_update_framebuffer()
|
||||
{
|
||||
// single green demo pixel
|
||||
game_state->framebuffer[0] = 0x00FF00;
|
||||
|
||||
// draw alternating green squares
|
||||
if (left) {
|
||||
clear_framebuffer(0x000000);
|
||||
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);
|
||||
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() {
|
||||
int
|
||||
main()
|
||||
{
|
||||
// initialize the engine
|
||||
if (engine_init(
|
||||
client_poll_event,
|
||||
|
||||
Reference in New Issue
Block a user