#if 0 /* #/ ================================================================ #/ #/ gravity.c #/ #/ ================================================================ #/ #/ Self-compilation shell script #/ SRC=${0##*./} BIN=${SRC%.*} gcc \ -Wall -Wextra -Werror -pedantic \ -Wno-old-style-declaration \ -Wno-missing-braces \ -Wno-unused-variable \ -Wno-unused-but-set-variable \ -Wno-unused-parameter \ -O3 \ -fsanitize=undefined,address,leak -mshstk \ -lX11 -lm \ -o $BIN $SRC && \ ./$BIN $@ && rm $BIN exit $? # */ #endif // ================================================================ // // Basic declarations // // ================================================================ #define _GNU_SOURCE typedef signed char i8; typedef signed short i16; typedef signed i32; typedef signed long long i64; typedef unsigned char u8; typedef unsigned short u16; typedef unsigned u32; typedef unsigned long long u64; typedef char c8; typedef signed char b8; typedef float f32; typedef double f64; // ================================================================ // // PLATFORM API // // ================================================================ enum { KEY_NONE, BUTTON_LEFT, BUTTON_MIDDLE, BUTTON_RIGHT, BUTTON_X1, BUTTON_X2, KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0, KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z, KEY_SPACE, KEY_BRACE_LEFT, KEY_BRACE_RIGHT, KEY_SEMICOLON, KEY_APOSTROPHE, KEY_TILDE, KEY_BACKSLASH, KEY_COMMA, KEY_PERIOD, KEY_SLASH, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, KEY_LCTRL, KEY_RCTRL, KEY_LSHIFT, KEY_RSHIFT, KEY_LALT, KEY_RALT, KEY_ESCAPE, KEY_BACKSPACE, KEY_TAB, KEY_ENTER, KEY_PRINTSCREEN, KEY_DELETE, KEY_PAUSE, KEY_INSERT, KEY_HOME, KEY_END, KEY_PAGEUP, KEY_PAGEDOWN, KEY_KP_0, KEY_KP_1, KEY_KP_2, KEY_KP_3, KEY_KP_4, KEY_KP_5, KEY_KP_6, KEY_KP_7, KEY_KP_8, KEY_KP_9, KEY_KP_ENTER, KEY_KP_DIVIDE, KEY_KP_MULTIPLY, KEY_KP_PLUS, KEY_KP_MINUS, KEY_KP_DECIMAL, KEY_KP_SEPARATOR, }; typedef struct { c8 *title; i32 frame_width; i32 frame_height; u32 *pixels; b8 done; b8 has_focus; b8 has_cursor; i32 cursor_x; i32 cursor_y; b8 key_down[512]; b8 key_pressed[512]; } Platform; Platform platform = {0}; void p_init(void); void p_cleanup(void); i32 p_handle_events(void); // ================================================================ // // GAME // // ================================================================ #include #include #include #include #include #include typedef struct { f64 x; f64 y; f64 vx; f64 vy; } Entity; typedef struct { i64 time; i64 num_entities; Entity entities[10 * 1024 * 1024]; } World; i64 time_milliseconds() { struct timespec t; timespec_get(&t, TIME_UTC); return (i64) t.tv_sec * 1000ll + (i64) t.tv_nsec / 1000000ll; } u32 u32_from_rgb(f32 red, f32 green, f32 blue) { i32 r = (i32) floor(red * 255.f); i32 g = (i32) floor(green * 255.f); i32 b = (i32) floor(blue * 255.f); if (r < 0) r = 0; if (r > 255) r = 255; if (g < 0) g = 0; if (g > 255) g = 255; if (b < 0) b = 0; if (b > 255) b = 255; return (r << 16) | (g << 8) | b; } i32 main(i32 argc, c8 **argv) { u32 background = u32_from_rgb(.0f, .0f, .08f); platform = (Platform) { .title = "Gravity", .frame_width = 960, .frame_height = 720, }; p_init(); srand(time(0)); static World world = {0}; i64 time_0 = time_milliseconds(); while (!platform.done) { i32 num_events = p_handle_events(); i64 time_elapsed = time_milliseconds() - time_0; time_0 += time_elapsed; if (platform.key_pressed[BUTTON_LEFT]) { i64 n = world.num_entities++; world.entities[n] = (Entity) { .x = (f64) platform.cursor_x - (f64) platform.frame_width / 2, .y = -((f64) platform.cursor_y - (f64) platform.frame_height / 2), }; } if (time_elapsed > 0) { if (platform.key_down[BUTTON_RIGHT]) { for (i64 n = 0; n < time_elapsed; ++n) world.entities[world.num_entities + n] = (Entity) { .x = (f64) platform.cursor_x - (f64) platform.frame_width / 2, .y = -((f64) platform.cursor_y - (f64) platform.frame_height / 2), }; world.num_entities += time_elapsed; } for (i64 n = 0; n < world.num_entities; ++n) { Entity *e = &world.entities[n]; for (i64 k = 0; k < n; ++k) { Entity *u = &world.entities[k]; f64 dx = e->x - u->x; f64 dy = e->y - u->y; f64 d = dx * dx + dy * dy; if (d < 1000.) continue; f64 r = sqrt(d); dx /= r; dy /= r; f64 a = (1. / d) * time_elapsed; if (d < 5000.) a = -a; e->vx -= dx * a; e->vy -= dy * a; u->vx += dx * a; u->vy += dy * a; } } for (i64 n = 0; n < world.num_entities; ++n) { Entity *e = &world.entities[n]; e->x += e->vx * time_elapsed; e->y += e->vy * time_elapsed; f64 z = (M_PI * 2.0) * (rand() % 10000) * .0001; e->vx += .0001 * cos(z); e->vy += .0001 * sin(z); } world.time += time_elapsed; } for (i32 j = 0; j < platform.frame_height; ++j) for (i32 i = 0; i < platform.frame_width; ++i) platform.pixels[j * platform.frame_width + i] = background; for (i64 n = 0; n < world.num_entities; ++n) { Entity *e = &world.entities[n]; i32 x = platform.frame_width / 2 + (i32) floor(e->x + .5); i32 y = platform.frame_height / 2 - (i32) floor(e->y + .5); for (i32 j = y - 10; j <= y + 10; ++j) { if (j < 0 || j >= platform.frame_height) continue; for (i32 i = x - 10; i <= x + 10; ++i) { if (i < 0 || i >= platform.frame_width) continue; if ((i - x) * (i - x) + (j - y) * (j - y) > 100) continue; f64 v = (e->vx * e->vx + e->vy * e->vy) * 8.; platform.pixels[j * platform.frame_width + i] = u32_from_rgb(-.2 + v * 2., .1 + v * .7, 1. - v); } } } } p_cleanup(); return 0; } // ================================================================ // // LINUX X11 PLATFORM IMPLEMENTATION // // ================================================================ #include #include static i16 key_table[512]; static u32 buffer[10 * 1024 * 1024]; static XImage image = { .xoffset = 0, .format = ZPixmap, .data = (c8 *) buffer, .byte_order = LSBFirst, .bitmap_unit = 32, .bitmap_bit_order = LSBFirst, .bitmap_pad = 32, .bits_per_pixel = 32, .red_mask = 0xff0000, .green_mask = 0x00ff00, .blue_mask = 0x0000ff, }; static Display * display; static i32 screen; static i32 depth; static Visual * visual; static GC gc; static Window window; static Pixmap pixmap; static Atom wm_delete_window; static XEvent ev; static XWindowAttributes attrs; void p_init(void) { display = XOpenDisplay(NULL); assert(display != NULL); key_table[XKeysymToKeycode(display, XK_Left)] = KEY_LEFT; key_table[XKeysymToKeycode(display, XK_Right)] = KEY_RIGHT; key_table[XKeysymToKeycode(display, XK_Up)] = KEY_UP; key_table[XKeysymToKeycode(display, XK_Down)] = KEY_DOWN; key_table[XKeysymToKeycode(display, XK_1)] = KEY_1; key_table[XKeysymToKeycode(display, XK_2)] = KEY_2; key_table[XKeysymToKeycode(display, XK_3)] = KEY_3; key_table[XKeysymToKeycode(display, XK_4)] = KEY_4; key_table[XKeysymToKeycode(display, XK_5)] = KEY_5; key_table[XKeysymToKeycode(display, XK_6)] = KEY_6; key_table[XKeysymToKeycode(display, XK_7)] = KEY_7; key_table[XKeysymToKeycode(display, XK_8)] = KEY_8; key_table[XKeysymToKeycode(display, XK_9)] = KEY_9; key_table[XKeysymToKeycode(display, XK_0)] = KEY_0; key_table[XKeysymToKeycode(display, XK_A)] = KEY_A; key_table[XKeysymToKeycode(display, XK_B)] = KEY_B; key_table[XKeysymToKeycode(display, XK_C)] = KEY_C; key_table[XKeysymToKeycode(display, XK_D)] = KEY_D; key_table[XKeysymToKeycode(display, XK_E)] = KEY_E; key_table[XKeysymToKeycode(display, XK_F)] = KEY_F; key_table[XKeysymToKeycode(display, XK_G)] = KEY_G; key_table[XKeysymToKeycode(display, XK_H)] = KEY_H; key_table[XKeysymToKeycode(display, XK_I)] = KEY_I; key_table[XKeysymToKeycode(display, XK_J)] = KEY_J; key_table[XKeysymToKeycode(display, XK_K)] = KEY_K; key_table[XKeysymToKeycode(display, XK_L)] = KEY_L; key_table[XKeysymToKeycode(display, XK_M)] = KEY_M; key_table[XKeysymToKeycode(display, XK_N)] = KEY_N; key_table[XKeysymToKeycode(display, XK_O)] = KEY_O; key_table[XKeysymToKeycode(display, XK_P)] = KEY_P; key_table[XKeysymToKeycode(display, XK_Q)] = KEY_Q; key_table[XKeysymToKeycode(display, XK_R)] = KEY_R; key_table[XKeysymToKeycode(display, XK_S)] = KEY_S; key_table[XKeysymToKeycode(display, XK_T)] = KEY_T; key_table[XKeysymToKeycode(display, XK_V)] = KEY_V; key_table[XKeysymToKeycode(display, XK_W)] = KEY_W; key_table[XKeysymToKeycode(display, XK_X)] = KEY_X; key_table[XKeysymToKeycode(display, XK_Y)] = KEY_Y; key_table[XKeysymToKeycode(display, XK_Z)] = KEY_Z; key_table[XKeysymToKeycode(display, XK_space)] = KEY_SPACE; key_table[XKeysymToKeycode(display, XK_braceleft)] = KEY_BRACE_LEFT; key_table[XKeysymToKeycode(display, XK_braceright)] = KEY_BRACE_RIGHT; key_table[XKeysymToKeycode(display, XK_colon)] = KEY_SEMICOLON; key_table[XKeysymToKeycode(display, XK_quotedbl)] = KEY_APOSTROPHE; key_table[XKeysymToKeycode(display, XK_asciitilde)] = KEY_TILDE; key_table[XKeysymToKeycode(display, XK_backslash)] = KEY_BACKSLASH; key_table[XKeysymToKeycode(display, XK_comma)] = KEY_COMMA; key_table[XKeysymToKeycode(display, XK_greater)] = KEY_PERIOD; key_table[XKeysymToKeycode(display, XK_question)] = KEY_SLASH; key_table[XKeysymToKeycode(display, XK_F1)] = KEY_F1; key_table[XKeysymToKeycode(display, XK_F2)] = KEY_F2; key_table[XKeysymToKeycode(display, XK_F3)] = KEY_F3; key_table[XKeysymToKeycode(display, XK_F4)] = KEY_F4; key_table[XKeysymToKeycode(display, XK_F5)] = KEY_F5; key_table[XKeysymToKeycode(display, XK_F6)] = KEY_F6; key_table[XKeysymToKeycode(display, XK_F7)] = KEY_F7; key_table[XKeysymToKeycode(display, XK_F8)] = KEY_F8; key_table[XKeysymToKeycode(display, XK_F9)] = KEY_F9; key_table[XKeysymToKeycode(display, XK_F10)] = KEY_F10; key_table[XKeysymToKeycode(display, XK_F11)] = KEY_F11; key_table[XKeysymToKeycode(display, XK_F12)] = KEY_F12; key_table[XKeysymToKeycode(display, XK_Control_L)] = KEY_LCTRL; key_table[XKeysymToKeycode(display, XK_Control_R)] = KEY_RCTRL; key_table[XKeysymToKeycode(display, XK_Shift_L)] = KEY_LSHIFT; key_table[XKeysymToKeycode(display, XK_Shift_R)] = KEY_RSHIFT; key_table[XKeysymToKeycode(display, XK_Alt_L)] = KEY_LALT; key_table[XKeysymToKeycode(display, XK_Alt_R)] = KEY_RALT; key_table[XKeysymToKeycode(display, XK_Escape)] = KEY_ESCAPE; key_table[XKeysymToKeycode(display, XK_BackSpace)] = KEY_BACKSPACE; key_table[XKeysymToKeycode(display, XK_Tab)] = KEY_TAB; key_table[XKeysymToKeycode(display, XK_Return)] = KEY_ENTER; key_table[XKeysymToKeycode(display, XK_Print)] = KEY_PRINTSCREEN; key_table[XKeysymToKeycode(display, XK_Delete)] = KEY_DELETE; key_table[XKeysymToKeycode(display, XK_Pause)] = KEY_PAUSE; key_table[XKeysymToKeycode(display, XK_Insert)] = KEY_INSERT; key_table[XKeysymToKeycode(display, XK_Home)] = KEY_HOME; key_table[XKeysymToKeycode(display, XK_End)] = KEY_END; key_table[XKeysymToKeycode(display, XK_Page_Up)] = KEY_PAGEUP; key_table[XKeysymToKeycode(display, XK_Page_Down)] = KEY_PAGEDOWN; key_table[XKeysymToKeycode(display, XK_KP_0)] = KEY_KP_0; key_table[XKeysymToKeycode(display, XK_KP_1)] = KEY_KP_1; key_table[XKeysymToKeycode(display, XK_KP_2)] = KEY_KP_2; key_table[XKeysymToKeycode(display, XK_KP_3)] = KEY_KP_3; key_table[XKeysymToKeycode(display, XK_KP_4)] = KEY_KP_4; key_table[XKeysymToKeycode(display, XK_KP_5)] = KEY_KP_5; key_table[XKeysymToKeycode(display, XK_KP_6)] = KEY_KP_6; key_table[XKeysymToKeycode(display, XK_KP_7)] = KEY_KP_7; key_table[XKeysymToKeycode(display, XK_KP_8)] = KEY_KP_8; key_table[XKeysymToKeycode(display, XK_KP_9)] = KEY_KP_9; key_table[XKeysymToKeycode(display, XK_KP_Enter)] = KEY_KP_ENTER; key_table[XKeysymToKeycode(display, XK_KP_Divide)] = KEY_KP_DIVIDE; key_table[XKeysymToKeycode(display, XK_KP_Multiply)] = KEY_KP_MULTIPLY; key_table[XKeysymToKeycode(display, XK_KP_Add)] = KEY_KP_PLUS; key_table[XKeysymToKeycode(display, XK_KP_Subtract)] = KEY_KP_MINUS; key_table[XKeysymToKeycode(display, XK_KP_Decimal)] = KEY_KP_DECIMAL; key_table[XKeysymToKeycode(display, XK_KP_Separator)] = KEY_KP_SEPARATOR; screen = DefaultScreen(display); depth = DefaultDepth (display, screen); visual = DefaultVisual(display, screen); gc = DefaultGC (display, screen); XSetGraphicsExposures(display, gc, False); i32 display_width = DisplayWidth (display, screen); i32 display_height = DisplayHeight(display, screen); i32 x = (display_width - platform.frame_width) / 2; i32 y = (display_height - platform.frame_height) / 2; window = XCreateWindow(display, XDefaultRootWindow(display), x, y, platform.frame_width, platform.frame_height, 0, depth, InputOutput, visual, CWEventMask, &(XSetWindowAttributes) { .event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask | VisibilityChangeMask | FocusChangeMask | StructureNotifyMask | SubstructureNotifyMask, }); platform.pixels = buffer; image.width = platform.frame_width; image.height = platform.frame_height; image.depth = depth; image.bytes_per_line = 4 * platform.frame_width; XInitImage(&image); pixmap = XCreatePixmap(display, window, platform.frame_width, platform.frame_height, depth); wm_delete_window = XInternAtom(display, "WM_DELETE_WINDOW", False); XSetWMProtocols(display, window, &wm_delete_window, 1); XStoreName(display, window, platform.title); XMapWindow(display, window); } void p_cleanup(void) { XFreePixmap (display, pixmap); XDestroyWindow(display, window); XCloseDisplay (display); } i32 p_handle_events(void) { XGetWindowAttributes(display, window, &attrs); if (attrs.width == platform.frame_width && attrs.height == platform.frame_height) { XPutImage(display, pixmap, gc, &image, 0, 0, 0, 0, attrs.width, attrs.height); XCopyArea(display, pixmap, window, gc, 0, 0, attrs.width, attrs.height, 0, 0); } XFlush(display); i32 num_events = XEventsQueued(display, QueuedAlready); memset(platform.key_pressed, 0, sizeof platform.key_pressed); for (i32 i = 0; i < num_events; ++i) { XNextEvent(display, &ev); switch (ev.type) { case DestroyNotify: platform.done = 1; break; case MotionNotify: platform.cursor_x = ev.xmotion.x; platform.cursor_y = ev.xmotion.y; break; case ButtonPress: platform.cursor_x = ev.xbutton.x; platform.cursor_y = ev.xbutton.y; switch (ev.xbutton.button) { case Button1: platform.key_down[BUTTON_LEFT] = 1; platform.key_pressed[BUTTON_LEFT]++; break; case Button2: platform.key_down[BUTTON_MIDDLE] = 1; platform.key_pressed[BUTTON_MIDDLE]++; break; case Button3: platform.key_down[BUTTON_RIGHT] = 1; platform.key_pressed[BUTTON_RIGHT]++; break; case Button4: platform.key_down[BUTTON_X1] = 1; platform.key_pressed[BUTTON_X1]++; break; case Button5: platform.key_down[BUTTON_X2] = 1; platform.key_pressed[BUTTON_X2]++; break; default:; } break; case ButtonRelease: platform.cursor_x = ev.xbutton.x; platform.cursor_y = ev.xbutton.y; switch (ev.xbutton.button) { case Button1: platform.key_down[BUTTON_LEFT] = 0; break; case Button2: platform.key_down[BUTTON_MIDDLE] = 0; break; case Button3: platform.key_down[BUTTON_RIGHT] = 0; break; case Button4: platform.key_down[BUTTON_X1] = 0; break; case Button5: platform.key_down[BUTTON_X2] = 0; break; default:; } break; case KeyPress: platform.cursor_x = ev.xkey.x; platform.cursor_y = ev.xkey.y; platform.key_down [key_table[ev.xkey.keycode]] = 1; platform.key_pressed[key_table[ev.xkey.keycode]]++; break; case KeyRelease: platform.cursor_x = ev.xkey.x; platform.cursor_y = ev.xkey.y; platform.key_down [key_table[ev.xkey.keycode]] = 0; platform.key_pressed[key_table[ev.xkey.keycode]]--; break; case EnterNotify: platform.has_cursor = 1; break; case LeaveNotify: platform.has_cursor = 0; break; case FocusIn: platform.has_focus = 1; break; case FocusOut: platform.has_focus = 0; break; case ClientMessage: if ((Atom) ev.xclient.data.l[0] == wm_delete_window) platform.done = 1; break; default:; } } if ((platform.frame_width != attrs.width || platform.frame_height != attrs.height) && attrs.width * attrs.height * 4 <= (i32) sizeof buffer) { if (attrs.width > 0 && attrs.height > 0) { image.width = attrs.width; image.height = attrs.height; image.bytes_per_line = 4 * attrs.width; XFreePixmap(display, pixmap); pixmap = XCreatePixmap(display, window, attrs.width, attrs.height, depth); } platform.frame_width = attrs.width; platform.frame_height = attrs.height; } return num_events; }