From d26bd78644544151b42600c5358d0fecc84b14f1 Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Mon, 12 Oct 2026 22:43:23 +0300 Subject: feat: implement modern Approach A app runner, unified events, notepad, and game examples --- examples/game.c | 358 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 358 insertions(+) create mode 100644 examples/game.c (limited to 'examples/game.c') diff --git a/examples/game.c b/examples/game.c new file mode 100644 index 0000000..0a8bc18 --- /dev/null +++ b/examples/game.c @@ -0,0 +1,358 @@ +/** + * @file game.c + * A continuous 60/144+ FPS game loop demonstration built on YASL Approach A. + * + * Demonstrates: + * - Continuous display-synchronized ticks (YASL_PACING_CONTINUOUS) + * - 3D software rendering (rotating shaded cube + starfield) + * - WASD continuous player movement & key state tracking + * - Relative mouse mode (YASL_MOUSE_RELATIVE) for smooth first-person camera orbit + * - Spacebar toggles pointer lock (Relative vs Normal cursor) + * - Real-time FPS counter and frame latency display + * - Dynamic window resizing with automatic aspect ratio adjustment + */ + +#define _DEFAULT_SOURCE +#include + +#include +#include +#include +#include +#include +#include + +#define NUM_STARS 256 +#define FOV_SCALE 320.0f + +struct star { + float x, y, z; +}; + +struct vec3 { + float x, y, z; +}; + +struct game_state { + struct yasl_display *display; + struct yasl_surface *surface; + + /* Camera & controls */ + float cam_x, cam_y, cam_z; + float cam_yaw, cam_pitch; + bool key_w, key_a, key_s, key_d; + bool key_q, key_e; + bool relative_mouse; + + /* Simulation objects */ + float cube_rot_x, cube_rot_y; + struct star stars[NUM_STARS]; + + /* FPS & timing stats */ + uint64_t last_stat_time_ns; + int frame_count; + float current_fps; + float avg_frame_time_ms; +}; + +/* 8x8 bitmap font for overlay HUD */ +static const uint8_t font8x8_digits[10][8] = { + {0x3C,0x66,0x6E,0x76,0x66,0x66,0x3C,0x00}, /* 0 */ + {0x18,0x38,0x18,0x18,0x18,0x18,0x7E,0x00}, /* 1 */ + {0x3C,0x66,0x06,0x0C,0x18,0x30,0x7E,0x00}, /* 2 */ + {0x3C,0x66,0x06,0x1C,0x06,0x66,0x3C,0x00}, /* 3 */ + {0x0C,0x1C,0x34,0x64,0x7E,0x04,0x04,0x00}, /* 4 */ + {0x7E,0x60,0x7C,0x06,0x06,0x66,0x3C,0x00}, /* 5 */ + {0x1C,0x30,0x60,0x7C,0x66,0x66,0x3C,0x00}, /* 6 */ + {0x7E,0x06,0x0C,0x18,0x30,0x30,0x30,0x00}, /* 7 */ + {0x3C,0x66,0x66,0x3C,0x66,0x66,0x3C,0x00}, /* 8 */ + {0x3C,0x66,0x66,0x3E,0x06,0x0C,0x38,0x00}, /* 9 */ +}; + +static void draw_digit(uint32_t *pix, int w, int h, int px, int py, int digit, uint32_t col, int s) +{ + if (digit < 0 || digit > 9) return; + const uint8_t *glyph = font8x8_digits[digit]; + for (int y = 0; y < 8; y++) { + uint8_t row = glyph[y]; + for (int x = 0; x < 8; x++) { + if (row & (1 << x)) { + for (int dy = 0; dy < s; dy++) { + int yy = py + y * s + dy; + if (yy < 0 || yy >= h) continue; + for (int dx = 0; dx < s; dx++) { + int xx = px + x * s + dx; + if (xx >= 0 && xx < w) pix[(size_t)yy * w + xx] = col; + } + } + } + } + } +} + +static void draw_number(uint32_t *pix, int w, int h, int px, int py, int num, uint32_t col, int scale) +{ + char buf[16]; + snprintf(buf, sizeof(buf), "%d", num); + for (int i = 0; buf[i]; i++) { + draw_digit(pix, w, h, px + i * 8 * scale, py, buf[i] - '0', col, scale); + } +} + +static void draw_line(uint32_t *pix, int w, int h, int x0, int y0, int x1, int y1, uint32_t col) +{ + int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1; + int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1; + int err = dx + dy, e2; + + while (1) { + if (x0 >= 0 && x0 < w && y0 >= 0 && y0 < h) { + pix[(size_t)y0 * w + x0] = col; + } + if (x0 == x1 && y0 == y1) break; + e2 = 2 * err; + if (e2 >= dy) { err += dy; x0 += sx; } + if (e2 <= dx) { err += dx; y0 += sy; } + } +} + +static struct vec3 rotate_vertex(struct vec3 v, float rx, float ry) +{ + /* Rotate around Y */ + float cy = cosf(ry), sy = sinf(ry); + float x1 = v.x * cy + v.z * sy; + float z1 = -v.x * sy + v.z * cy; + + /* Rotate around X */ + float cx = cosf(rx), sx = sinf(rx); + float y2 = v.y * cx - z1 * sx; + float z2 = v.y * sx + z1 * cx; + + return (struct vec3){x1, y2, z2}; +} + +static bool project_point(struct vec3 p, int w, int h, int *out_x, int *out_y) +{ + if (p.z <= 0.2f) return false; + float inv_z = FOV_SCALE / p.z; + *out_x = (int)(w * 0.5f + p.x * inv_z); + *out_y = (int)(h * 0.5f - p.y * inv_z); + return true; +} + +static int on_init(void *ud, struct yasl_display *d) +{ + struct game_state *game = ud; + game->display = d; + + struct yasl_surface_spec spec = { + .title = "YASL 3D Game Loop (Approach A)", + .size = {800, 600}, + }; + game->surface = yasl_surface_create_toplevel(d, &spec); + if (!game->surface) return YASL_APP_QUIT; + + /* Drive continuous 60/144Hz ticks */ + yasl_surface_set_pacing(game->surface, YASL_PACING_CONTINUOUS); + + game->cam_z = 3.5f; + game->relative_mouse = false; + + /* Initialize stars */ + for (int i = 0; i < NUM_STARS; i++) { + game->stars[i].x = ((float)(rand() % 2000) - 1000.0f) / 100.0f; + game->stars[i].y = ((float)(rand() % 2000) - 1000.0f) / 100.0f; + game->stars[i].z = ((float)(rand() % 1000) + 10.0f) / 50.0f; + } + + game->last_stat_time_ns = yasl_loop_now_ns(); + return YASL_APP_CONTINUE; +} + +static int on_event(void *ud, const struct yasl_event *ev) +{ + struct game_state *game = ud; + + switch (ev->type) { + case YASL_EVENT_KEY: { + bool down = (ev->key.action == YASL_KEY_PRESS || ev->key.action == YASL_KEY_REPEAT); + if (ev->key.action == YASL_KEY_RELEASE) down = false; + + switch (ev->key.key) { + case YASL_KEY_W: game->key_w = down; break; + case YASL_KEY_S: game->key_s = down; break; + case YASL_KEY_A: game->key_a = down; break; + case YASL_KEY_D: game->key_d = down; break; + case YASL_KEY_Q: game->key_q = down; break; + case YASL_KEY_E: game->key_e = down; break; + case YASL_KEY_SPACE: + if (ev->key.action == YASL_KEY_PRESS) { + /* Toggle relative mouse mode */ + game->relative_mouse = !game->relative_mouse; + yasl_surface_set_mouse_mode(game->surface, + game->relative_mouse ? YASL_MOUSE_RELATIVE : YASL_MOUSE_NORMAL); + } + break; + case YASL_KEY_ESCAPE: + if (ev->key.action == YASL_KEY_PRESS) { + if (game->relative_mouse) { + game->relative_mouse = false; + yasl_surface_set_mouse_mode(game->surface, YASL_MOUSE_NORMAL); + } else { + return YASL_APP_QUIT; + } + } + break; + default: + break; + } + break; + } + + case YASL_EVENT_MOUSE_MOTION: + if (game->relative_mouse) { + /* Smooth first-person camera orbit */ + game->cam_yaw += (float)ev->mouse_motion.delta.x * 0.005f; + game->cam_pitch += (float)ev->mouse_motion.delta.y * 0.005f; + if (game->cam_pitch > 1.4f) game->cam_pitch = 1.4f; + if (game->cam_pitch < -1.4f) game->cam_pitch = -1.4f; + } + break; + + case YASL_EVENT_MOUSE_BUTTON: + if (ev->mouse_button.pressed && !game->relative_mouse) { + /* Click window to capture cursor */ + game->relative_mouse = true; + yasl_surface_set_mouse_mode(game->surface, YASL_MOUSE_RELATIVE); + } + break; + + case YASL_EVENT_WINDOW_CLOSE_REQUEST: + return YASL_APP_QUIT; + + default: + break; + } + + return YASL_APP_CONTINUE; +} + +static void on_frame(void *ud, struct yasl_surface *s, struct yasl_frame_context *ctx) +{ + (void)s; + struct game_state *game = ud; + int w = ctx->size.x; + int h = ctx->size.y; + uint32_t *pix = ctx->cpu.pixels; + if (!pix || w <= 0 || h <= 0) return; + + /* Update game simulation */ + float speed = 0.04f; + float forward_x = sinf(game->cam_yaw); + float forward_z = cosf(game->cam_yaw); + float right_x = cosf(game->cam_yaw); + float right_z = -sinf(game->cam_yaw); + + if (game->key_w) { game->cam_x -= forward_x * speed; game->cam_z -= forward_z * speed; } + if (game->key_s) { game->cam_x += forward_x * speed; game->cam_z += forward_z * speed; } + if (game->key_a) { game->cam_x -= right_x * speed; game->cam_z -= right_z * speed; } + if (game->key_d) { game->cam_x += right_x * speed; game->cam_z += right_z * speed; } + if (game->key_q) { game->cam_y -= speed; } + if (game->key_e) { game->cam_y += speed; } + + game->cube_rot_x += 0.015f; + game->cube_rot_y += 0.025f; + + /* Clear screen to deep space dark blue */ + memset(pix, 0x08, (size_t)w * h * sizeof(uint32_t)); + + /* 1. Render 3D Starfield */ + for (int i = 0; i < NUM_STARS; i++) { + game->stars[i].z -= 0.05f; + if (game->stars[i].z <= 0.2f) game->stars[i].z += 20.0f; + + struct vec3 sp = {game->stars[i].x - game->cam_x, + game->stars[i].y - game->cam_y, + game->stars[i].z}; + int sx, sy; + if (project_point(sp, w, h, &sx, &sy) && sx >= 0 && sx < w && sy >= 0 && sy < h) { + uint8_t brightness = (uint8_t)(255.0f * (1.0f - sp.z / 20.0f)); + uint32_t star_col = 0xff000000 | (brightness << 16) | (brightness << 8) | brightness; + pix[(size_t)sy * w + sx] = star_col; + } + } + + /* 2. Render 3D Cube */ + static const struct vec3 cube_vertices[8] = { + {-1.0f, -1.0f, -1.0f}, { 1.0f, -1.0f, -1.0f}, + { 1.0f, 1.0f, -1.0f}, {-1.0f, 1.0f, -1.0f}, + {-1.0f, -1.0f, 1.0f}, { 1.0f, -1.0f, 1.0f}, + { 1.0f, 1.0f, 1.0f}, {-1.0f, 1.0f, 1.0f}, + }; + + static const int cube_edges[12][2] = { + {0, 1}, {1, 2}, {2, 3}, {3, 0}, + {4, 5}, {5, 6}, {6, 7}, {7, 4}, + {0, 4}, {1, 5}, {2, 6}, {3, 7}, + }; + + int proj_x[8], proj_y[8]; + bool valid[8]; + + for (int i = 0; i < 8; i++) { + struct vec3 rot = rotate_vertex(cube_vertices[i], game->cube_rot_x, game->cube_rot_y); + rot.x -= game->cam_x; + rot.y -= game->cam_y; + rot.z += game->cam_z; + valid[i] = project_point(rot, w, h, &proj_x[i], &proj_y[i]); + } + + uint32_t cyan = 0xff00ffff; + for (int i = 0; i < 12; i++) { + int u = cube_edges[i][0]; + int v = cube_edges[i][1]; + if (valid[u] && valid[v]) { + draw_line(pix, w, h, proj_x[u], proj_y[u], proj_x[v], proj_y[v], cyan); + } + } + + /* 3. Center crosshair in relative mouse mode */ + if (game->relative_mouse) { + int cx = w / 2, cy = h / 2; + draw_line(pix, w, h, cx - 6, cy, cx + 6, cy, 0xffffffff); + draw_line(pix, w, h, cx, cy - 6, cx, cy + 6, 0xffffffff); + } + + /* 4. FPS counter calculation */ + game->frame_count++; + uint64_t now = yasl_loop_now_ns(); + if (now - game->last_stat_time_ns >= 1000000000ull) { + uint64_t elapsed = now - game->last_stat_time_ns; + game->current_fps = (float)game->frame_count * 1000000000.0f / (float)elapsed; + game->avg_frame_time_ms = 1000.0f / (game->current_fps > 0 ? game->current_fps : 1.0f); + game->frame_count = 0; + game->last_stat_time_ns = now; + } + + /* Draw FPS overlay digits in top-left */ + int scale = ctx->scale_120 >= 240 ? 2 : 1; + draw_number(pix, w, h, 16 * scale, 16 * scale, (int)game->current_fps, 0xff00ff00, scale); + + /* Submit frame */ + yasl_frame_present_cpu(ctx, NULL, 0); +} + +int main(void) +{ + static struct game_state game; + struct yasl_app_desc desc = { + .app_id = "org.yasl.game", + .backend = YASL_BACKEND_AUTO, + .userdata = &game, + .init = on_init, + .event = on_event, + .frame = on_frame, + }; + + return yasl_run(&desc); +} -- cgit v1.2.3