summaryrefslogtreecommitdiff
path: root/examples/game.c
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-12 22:43:23 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-12 22:43:23 +0300
commitd26bd78644544151b42600c5358d0fecc84b14f1 (patch)
treee394f96305674068a21619940c6b717d8eb3d628 /examples/game.c
parent94c5e361fbb229b0cd6853e2102a7d95f1f358b3 (diff)
feat: implement modern Approach A app runner, unified events, notepad, and game examples
Diffstat (limited to 'examples/game.c')
-rw-r--r--examples/game.c358
1 files changed, 358 insertions, 0 deletions
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 <yasl/app.h>
+
+#include <math.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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);
+}