Last active
December 6, 2025 22:07
-
-
Save pawel-dubiel/dcfef86130dd338a1d0ed4dd2c6ed2ef to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| C Implementation (cland) Instructions | |
| Setup | |
| Install SDL2 Development Libraries: | |
| * macOS (Homebrew): | |
| brew install sdl2 | |
| * Linux (apt-get): | |
| sudo apt-get install libsdl2-dev | |
| * Windows (MSYS2/vcpkg): Installation varies, but you'll need the SDL2 development headers and libraries. | |
| Navigate to the project directory: | |
| Compile the project: | |
| - make | |
| This will create an executable named `galaxy` in the `cland` directory. | |
| Running | |
| Basic Run: | |
| ./galaxy | |
| Force Star System Type (1 for single, 2 for binary, 3 for trinary): | |
| ./galaxy --stars=2 | |
| Controls | |
| Mouse Left-Click (Galaxy View): Enter a star system. | |
| Mouse Left-Click (System View): Select a star or planet to view details. | |
| Mouse Double Left-Click (System View): Center the camera on the selected planet or star. | |
| Mouse Right-Click (System View): Exit the current star system and return to the Galaxy Map. | |
| Arrow Keys (Both Views): Pan the camera. | |
| Mouse Scroll Wheel (Both Views): Zoom in and out. | |
| `UP` / `DOWN` (System View): Speed up / Slow down time. | |
| `SPACE` (System View): Pause / Resume the simulation. | |
| `+` / `=` (Both Views): Zoom in. | |
| `-` (Both Views): Zoom out. | |
| */ | |
| --- cland/font.h --- | |
| #ifndef FONT_H | |
| #define FONT_H | |
| #include <SDL2/SDL.h> | |
| // Simple 5x7 font data (ASC 32-127) | |
| static const unsigned char font_data[] = { | |
| 0x00,0x00,0x00,0x00,0x00, // space | |
| 0x00,0x00,0x5F,0x00,0x00, // ! | |
| 0x00,0x07,0x00,0x07,0x00, // " | |
| 0x14,0x7F,0x14,0x7F,0x14, // # | |
| 0x24,0x2A,0x7F,0x2A,0x12, // $ | |
| 0x23,0x13,0x08,0x64,0x62, // % | |
| 0x36,0x49,0x55,0x22,0x50, // & | |
| 0x00,0x05,0x03,0x00,0x00, // ' | |
| 0x00,0x1C,0x22,0x41,0x00, // ( | |
| 0x00,0x41,0x22,0x1C,0x00, // ) | |
| 0x14,0x08,0x3E,0x08,0x14, // * | |
| 0x08,0x08,0x3E,0x08,0x08, // + | |
| 0x00,0x50,0x30,0x00,0x00, // , | |
| 0x08,0x08,0x08,0x08,0x08, // - | |
| 0x00,0x60,0x60,0x00,0x00, // . | |
| 0x20,0x10,0x08,0x04,0x02, // / | |
| 0x3E,0x51,0x49,0x45,0x3E, // 0 | |
| 0x00,0x42,0x7F,0x40,0x00, // 1 | |
| 0x42,0x61,0x51,0x49,0x46, // 2 | |
| 0x21,0x41,0x45,0x4B,0x31, // 3 | |
| 0x18,0x14,0x12,0x7F,0x10, // 4 | |
| 0x27,0x45,0x45,0x45,0x39, // 5 | |
| 0x3C,0x4A,0x49,0x49,0x30, // 6 | |
| 0x01,0x71,0x09,0x05,0x03, // 7 | |
| 0x36,0x49,0x49,0x49,0x36, // 8 | |
| 0x06,0x49,0x49,0x29,0x1E, // 9 | |
| 0x00,0x36,0x36,0x00,0x00, // : | |
| 0x00,0x56,0x36,0x00,0x00, // ; | |
| 0x08,0x14,0x22,0x41,0x00, // < | |
| 0x14,0x14,0x14,0x14,0x14, // = | |
| 0x00,0x41,0x22,0x14,0x08, // > | |
| 0x02,0x01,0x51,0x09,0x06, // ? | |
| 0x32,0x49,0x79,0x41,0x3E, // @ | |
| 0x7E,0x11,0x11,0x11,0x7E, // A | |
| 0x7F,0x49,0x49,0x49,0x36, // B | |
| 0x3E,0x41,0x41,0x41,0x22, // C | |
| 0x7F,0x41,0x41,0x22,0x1C, // D | |
| 0x7F,0x49,0x49,0x49,0x41, // E | |
| 0x7F,0x09,0x09,0x09,0x01, // F | |
| 0x3E,0x41,0x49,0x49,0x7A, // G | |
| 0x7F,0x08,0x08,0x08,0x7F, // H | |
| 0x00,0x41,0x7F,0x41,0x00, // I | |
| 0x20,0x40,0x41,0x3F,0x01, // J | |
| 0x7F,0x08,0x14,0x22,0x41, // K | |
| 0x7F,0x40,0x40,0x40,0x40, // L | |
| 0x7F,0x02,0x0C,0x02,0x7F, // M | |
| 0x7F,0x04,0x08,0x10,0x7F, // N | |
| 0x3E,0x41,0x41,0x41,0x3E, // O | |
| 0x7F,0x09,0x09,0x09,0x06, // P | |
| 0x3E,0x41,0x51,0x21,0x5E, // Q | |
| 0x7F,0x09,0x19,0x29,0x46, // R | |
| 0x46,0x49,0x49,0x49,0x31, // S | |
| 0x01,0x01,0x7F,0x01,0x01, // T | |
| 0x3F,0x40,0x40,0x40,0x3F, // U | |
| 0x1F,0x20,0x40,0x20,0x1F, // V | |
| 0x3F,0x40,0x38,0x40,0x3F, // W | |
| 0x63,0x14,0x08,0x14,0x63, // X | |
| 0x07,0x08,0x70,0x08,0x07, // Y | |
| 0x61,0x51,0x49,0x45,0x43, // Z | |
| 0x00,0x7F,0x41,0x41,0x00, // [ | |
| 0x02,0x04,0x08,0x10,0x20, // backslash | |
| 0x00,0x41,0x41,0x7F,0x00, // ] | |
| 0x04,0x02,0x01,0x02,0x04, // ^ | |
| 0x40,0x40,0x40,0x40,0x40, // _ | |
| 0x00,0x01,0x02,0x04,0x00, // ` | |
| 0x20,0x54,0x54,0x54,0x78, // a | |
| 0x7F,0x48,0x44,0x44,0x38, // b | |
| 0x38,0x44,0x44,0x44,0x20, // c | |
| 0x38,0x44,0x44,0x48,0x7F, // d | |
| 0x38,0x54,0x54,0x54,0x18, // e | |
| 0x08,0x7E,0x09,0x01,0x02, // f | |
| 0x0C,0x52,0x52,0x52,0x3E, // g | |
| 0x7F,0x08,0x04,0x04,0x78, // h | |
| 0x00,0x44,0x7D,0x40,0x00, // i | |
| 0x20,0x40,0x44,0x3D,0x00, // j | |
| 0x7F,0x10,0x28,0x44,0x00, // k | |
| 0x00,0x41,0x7F,0x40,0x00, // l | |
| 0x7C,0x04,0x18,0x04,0x78, // m | |
| 0x7C,0x08,0x04,0x04,0x78, // n | |
| 0x38,0x44,0x44,0x44,0x38, // o | |
| 0x7C,0x14,0x14,0x14,0x08, // p | |
| 0x08,0x14,0x14,0x18,0x7C, // q | |
| 0x7C,0x08,0x04,0x04,0x08, // r | |
| 0x48,0x54,0x54,0x54,0x20, // s | |
| 0x04,0x3F,0x44,0x40,0x20, // t | |
| 0x3C,0x40,0x40,0x20,0x7C, // u | |
| 0x1C,0x20,0x40,0x20,0x1C, // v | |
| 0x3C,0x40,0x30,0x40,0x3C, // w | |
| 0x44,0x28,0x10,0x28,0x44, // x | |
| 0x0C,0x50,0x50,0x50,0x3C, // y | |
| 0x44,0x64,0x54,0x4C,0x44, // z | |
| 0x00,0x08,0x36,0x41,0x00, // { | |
| 0x00,0x00,0x7F,0x00,0x00, // | | |
| 0x00,0x41,0x36,0x08,0x00, // } | |
| 0x10,0x08,0x08,0x10,0x08, // ~ | |
| }; | |
| void draw_char(SDL_Renderer *renderer, int x, int y, char c, int scale) { | |
| if (c < 32 || c > 126) c = '?'; | |
| const unsigned char *glyph = &font_data[(c - 32) * 5]; | |
| for (int i = 0; i < 5; i++) { | |
| unsigned char line = glyph[i]; | |
| for (int j = 0; j < 7; j++) { | |
| if (line & (1 << j)) { | |
| if (scale == 1) { | |
| SDL_RenderDrawPoint(renderer, x + i, y + j); | |
| } else { | |
| SDL_Rect rect = {x + i * scale, y + j * scale, scale, scale}; | |
| SDL_RenderFillRect(renderer, &rect); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| void draw_text(SDL_Renderer *renderer, int x, int y, const char *text, int scale) { | |
| while (*text) { | |
| draw_char(renderer, x, y, *text, scale); | |
| x += 6 * scale; | |
| text++; | |
| } | |
| } | |
| #endif | |
| --- cland/galaxy.h --- | |
| #ifndef GALAXY_H | |
| #define GALAXY_H | |
| #include <SDL2/SDL.h> | |
| #include <stdbool.h> | |
| #define SCREEN_WIDTH 1200 | |
| #define SCREEN_HEIGHT 900 | |
| #define MAX(x, y) (((x) > (y)) ? (x) : (y)) // Shared macro | |
| // Math | |
| typedef struct { | |
| float x, y; | |
| } Vec2; | |
| // Data Structures | |
| typedef struct { | |
| char name[32]; | |
| float mass; | |
| float radius; | |
| float temp; | |
| float luminosity; | |
| float metallicity; | |
| Uint32 color; | |
| // Physics | |
| float x, y; // AU | |
| float vx, vy; // AU/year | |
| } Star; | |
| typedef enum { | |
| TYPE_ROCKY, | |
| TYPE_TERRESTRIAL, | |
| TYPE_GAS_GIANT, | |
| TYPE_ICE_GIANT, | |
| TYPE_MOLTEN, | |
| TYPE_ICY_WORLD | |
| } PlanetType; | |
| typedef struct { | |
| char name[32]; | |
| float radius; | |
| float orbit_dist; // Relative visual distance | |
| float x, y; // Angle/Speed in simulation or actual coords | |
| float vx, vy; | |
| } Moon; | |
| typedef struct { | |
| char name[32]; | |
| float mass; | |
| float radius; | |
| Uint32 color; | |
| PlanetType type; | |
| // Physics | |
| float x, y; // AU | |
| float vx, vy; // AU/year | |
| // Details | |
| float density; | |
| float surface_temp; | |
| char atmosphere[32]; | |
| float albedo; | |
| float greenhouse; | |
| bool is_tidally_locked; | |
| float gravity; | |
| float rotation_period; | |
| float rotation_angle; // Current axial rotation | |
| // Resources (Percentages 0.0 - 100.0) | |
| // 0:Iron, 1:Silicon, 2:Nickel, 3:Water, 4:Hydrogen, 5:Helium, 6:Gold, 7:RareEarths | |
| float resources[8]; | |
| int moon_count; | |
| Moon moons[4]; | |
| } Planet; | |
| typedef struct { | |
| int x, y; | |
| unsigned int seed; | |
| int star_count; | |
| Star stars[3]; | |
| int planet_count; | |
| Planet planets[16]; | |
| } StarSystem; | |
| // Game State | |
| typedef enum { | |
| VIEW_GALAXY, | |
| VIEW_SYSTEM | |
| } ViewMode; | |
| typedef struct { | |
| ViewMode mode; | |
| Vec2 camera_pos; // Current camera position (System or Galaxy) | |
| Vec2 galaxy_camera_pos; // Saved galaxy position | |
| float zoom; | |
| float galaxy_zoom; // Saved galaxy zoom | |
| StarSystem current_system; | |
| bool running; | |
| bool debug; | |
| bool paused; | |
| float time_speed; | |
| int selected_planet_idx; // -1 for none | |
| bool selected_star; | |
| int centered_planet_idx; // -1 for none (Star centered) | |
| Vec2 system_center_offset; // Offset to center view on a body | |
| // Cache | |
| StarSystem *visited_systems; | |
| int visited_count; | |
| int visited_capacity; | |
| } GameState; | |
| // Functions | |
| unsigned int get_seed(int x, int y); | |
| bool star_exists(int x, int y); | |
| StarSystem generate_system(int x, int y); | |
| float rand_float(float min, float max); | |
| void set_forced_star_count(int count); // New | |
| #endif | |
| --- cland/gen.c --- | |
| #include "galaxy.h" | |
| #include <stdlib.h> | |
| #include <math.h> | |
| #include <stdio.h> | |
| // Deterministic Hash | |
| unsigned int get_seed(int x, int y) { | |
| unsigned int seed = (x * 73856093) ^ (y * 19349663); | |
| return seed; | |
| } | |
| bool star_exists(int x, int y) { | |
| unsigned int seed = get_seed(x, y); | |
| srand(seed); | |
| return (rand() % 100) < 5; | |
| } | |
| float rand_float(float min, float max) { | |
| return min + (float)rand() / (float)(RAND_MAX / (max - min)); | |
| } | |
| // Helper | |
| static int g_forced_star_count = 0; | |
| void set_forced_star_count(int count) { | |
| g_forced_star_count = count; | |
| } | |
| void generate_resources(Planet *p, float metallicity) { | |
| // 0:Fe, 1:Si, 2:Ni, 3:H2O, 4:H, 5:He, 6:Au, 7:REE | |
| for(int i=0; i<8; i++) p->resources[i] = 0.0f; | |
| float metal_boost = powf(10.0, metallicity); | |
| float weights[8] = {0}; | |
| if (p->type == TYPE_GAS_GIANT) { | |
| weights[4] = 75.0; | |
| weights[5] = 24.0; | |
| weights[3] = 1.0 * metal_boost; | |
| } else if (p->type == TYPE_ICE_GIANT) { | |
| weights[3] = 50.0; | |
| weights[4] = 15.0; | |
| weights[5] = 5.0; | |
| weights[1] = 5.0; | |
| } else if (p->type == TYPE_MOLTEN) { | |
| weights[0] = 40.0; | |
| weights[1] = 30.0; | |
| weights[2] = 10.0; | |
| weights[6] = 0.1 * metal_boost; | |
| } else if (p->type == TYPE_TERRESTRIAL || p->type == TYPE_ROCKY) { | |
| weights[1] = 30.0; | |
| weights[0] = 25.0; | |
| weights[3] = (p->type == TYPE_TERRESTRIAL) ? 5.0 : 0.1; | |
| weights[2] = 5.0; | |
| weights[7] = 0.05 * metal_boost; | |
| weights[6] = 0.01 * metal_boost; | |
| } else { // Icy | |
| weights[3] = 70.0; | |
| weights[1] = 10.0; | |
| } | |
| float total = 0; | |
| for(int i=0; i<8; i++) { | |
| if (weights[i] > 0) weights[i] *= rand_float(0.8, 1.2); | |
| total += weights[i]; | |
| } | |
| if (total > 0) { | |
| for(int i=0; i<8; i++) p->resources[i] = (weights[i] / total) * 100.0; | |
| } | |
| } | |
| StarSystem generate_system(int x, int y) { | |
| StarSystem sys; | |
| sys.x = x; | |
| sys.y = y; | |
| sys.seed = get_seed(x, y); | |
| srand(sys.seed); | |
| if (g_forced_star_count > 0) sys.star_count = g_forced_star_count; | |
| else { | |
| int r = rand() % 100; | |
| sys.star_count = (r < 60) ? 1 : ((r < 90) ? 2 : 3); | |
| } | |
| float total_mass = 0; | |
| float total_lum = 0; | |
| for (int i = 0; i < sys.star_count; i++) { | |
| Star *s = &sys.stars[i]; | |
| sprintf(s->name, "Star-%c", 'A' + i); | |
| s->mass = rand_float(0.1, 5.0); | |
| if (i > 0) s->mass *= 0.5; | |
| s->radius = powf(s->mass, 0.8); | |
| s->luminosity = powf(s->mass, 3.5); | |
| s->metallicity = rand_float(-0.5, 0.5); | |
| s->temp = 5778 * powf(s->mass, 0.5); | |
| if (s->temp > 10000) s->color = 0x8888FFFF; | |
| else if (s->temp > 6000) s->color = 0xFFFFFFFF; | |
| else if (s->temp > 4000) s->color = 0xFFFF00FF; | |
| else s->color = 0xFF4400FF; | |
| total_mass += s->mass; | |
| total_lum += s->luminosity; | |
| } | |
| if (sys.star_count == 1) { | |
| sys.stars[0].x = 0; sys.stars[0].y = 0; | |
| sys.stars[0].vx = 0; sys.stars[0].vy = 0; | |
| } else if (sys.star_count == 2) { | |
| Star *s1 = &sys.stars[0]; Star *s2 = &sys.stars[1]; | |
| float dist = 0.2; | |
| float v_circ = sqrtf(39.478 * (s1->mass + s2->mass) / dist); | |
| float r1 = dist * s2->mass / (s1->mass + s2->mass); | |
| float r2 = dist * s1->mass / (s1->mass + s2->mass); | |
| s1->x = -r1; s1->y = 0; s1->vx = 0; s1->vy = v_circ * (r1 / dist); | |
| s2->x = r2; s2->y = 0; s2->vx = 0; s2->vy = -v_circ * (r2 / dist); | |
| } else { | |
| float dist = 0.3; | |
| float v = sqrtf(39.478 * total_mass / dist) * 0.5; | |
| sys.stars[0].x = dist; sys.stars[0].y = 0; sys.stars[0].vx = 0; sys.stars[0].vy = v; | |
| sys.stars[1].x = -dist*0.5; sys.stars[1].y = dist*0.866; sys.stars[1].vx = -v*0.866; sys.stars[1].vy = -v*0.5; | |
| sys.stars[2].x = -dist*0.5; sys.stars[2].y = -dist*0.866; sys.stars[2].vx = v*0.866; sys.stars[2].vy = -v*0.5; | |
| } | |
| sys.planet_count = rand() % 10; | |
| float current_dist = 0.5 + sys.star_count * 0.2; | |
| float frost_line = 2.7 * sqrtf(total_lum); | |
| for (int i = 0; i < sys.planet_count; i++) { | |
| Planet *p = &sys.planets[i]; | |
| current_dist *= rand_float(1.3, 1.8); | |
| float angle = rand_float(0, 6.28); | |
| p->x = cos(angle) * current_dist; | |
| p->y = sin(angle) * current_dist; | |
| float v = sqrtf(39.478 * total_mass / current_dist); | |
| p->vx = -sin(angle) * v; | |
| p->vy = cos(angle) * v; | |
| p->rotation_angle = rand_float(0, 6.28); | |
| bool is_outer = current_dist > frost_line * 0.8; | |
| if (is_outer && rand()%100 < 60) { | |
| p->mass = rand_float(10.0, 300.0); | |
| if (p->mass > 50.0) { | |
| p->type = TYPE_GAS_GIANT; | |
| p->radius = 11.0; | |
| p->color = (rand()%2==0) ? 0xDDCCAAFF : 0xDDAA88FF; | |
| sprintf(p->atmosphere, "H/He"); | |
| } else { | |
| p->type = TYPE_ICE_GIANT; | |
| p->radius = 4.0; | |
| p->color = 0xAABBFFFF; | |
| sprintf(p->atmosphere, "H/He/CH4"); | |
| } | |
| } else { | |
| p->mass = rand_float(0.1, 5.0); | |
| if (current_dist < 0.1 * total_mass) { | |
| p->type = TYPE_MOLTEN; | |
| p->radius = powf(p->mass, 0.27); | |
| p->color = 0xFF4400FF; | |
| sprintf(p->atmosphere, "Metal Vapor"); | |
| } else if (is_outer) { | |
| p->type = TYPE_ICY_WORLD; | |
| p->radius = powf(p->mass, 0.3); | |
| p->color = 0xEEEEFFFF; | |
| sprintf(p->atmosphere, "Thin N2"); | |
| } else if (current_dist > 0.8 * sqrtf(total_lum) && current_dist < 1.5 * sqrtf(total_lum) && p->mass > 0.3) { | |
| p->type = TYPE_TERRESTRIAL; | |
| p->radius = powf(p->mass, 0.27); | |
| p->color = 0x008800FF; | |
| sprintf(p->atmosphere, "N2/O2"); | |
| } else { | |
| p->type = TYPE_ROCKY; | |
| p->radius = powf(p->mass, 0.27); | |
| p->color = 0x888888FF; | |
| sprintf(p->atmosphere, "Thin CO2"); | |
| } | |
| } | |
| // Resources & Density (Scientific Model) | |
| generate_resources(p, sys.stars[0].metallicity); | |
| // Calculate Bulk Density from Composition | |
| // densities: Fe=7.8, Si=3.3, Ni=8.9, H2O=1.0, H/He=~0.2 (highly compressible), Au=19.3, REE=6.0 | |
| // Simplified weighted average for density | |
| float w_sum = 0; | |
| float vol_sum = 0; // Volume units | |
| float dens_map[8] = {7.8, 3.3, 8.9, 1.0, 0.1, 0.15, 19.3, 6.0}; | |
| for (int r=0; r<8; r++) { | |
| float pct = p->resources[r]; | |
| if (pct > 0) { | |
| w_sum += pct; | |
| vol_sum += pct / dens_map[r]; | |
| } | |
| } | |
| float uncompressed_density = (vol_sum > 0) ? (w_sum / vol_sum) : 3.0; | |
| // Compression Factor: Larger planets are denser due to gravity compressing the core | |
| // Earth (5.5) vs uncompressed rock (3.3). Factor ~1.6 for 1 Me. | |
| // Simple compression model: D = D_0 * (1 + 0.5 * log10(M_earth)) | |
| // Gas Giants: compression is huge. Jupiter density 1.33, Saturn 0.69. | |
| if (p->type == TYPE_GAS_GIANT || p->type == TYPE_ICE_GIANT) { | |
| // Gas physics is complex (degeneracy). Use empirical fit. | |
| if (p->mass > 100) { | |
| // Jupiter-like: Density increases with mass | |
| p->density = 1.33 * powf(p->mass/318.0, 0.3); | |
| } else { | |
| // Saturn-like: Fluffy | |
| p->density = 0.7 * powf(p->mass/95.0, 0.1); | |
| } | |
| } else { | |
| // Solid body compression | |
| float compression = 1.0 + 0.4 * log10f(MAX(0.1, p->mass)); | |
| p->density = uncompressed_density * compression; | |
| } | |
| // CALCULATE RADIUS from Mass and Density | |
| // M in Earths, D in g/cm3. | |
| // R_earth = 1.0. M_earth = 1.0. D_earth = 5.51. | |
| // R = (M / (D/5.51))^(1/3) | |
| p->radius = powf(p->mass / (p->density / 5.51), 0.333); | |
| p->gravity = p->mass / powf(p->radius, 2); | |
| p->surface_temp = sys.stars[0].temp * sqrtf(sys.stars[0].radius / (2 * current_dist * 215)); | |
| p->is_tidally_locked = (current_dist < 0.4 * total_mass); | |
| if (p->is_tidally_locked) p->rotation_period = sqrtf(powf(current_dist, 3) / total_mass) * 365.25; | |
| else if (p->type == TYPE_GAS_GIANT || p->type == TYPE_ICE_GIANT) p->rotation_period = rand_float(0.3, 0.6); | |
| else p->rotation_period = (rand()%10 < 8) ? rand_float(0.5, 2.0) : rand_float(10.0, 243.0); | |
| sprintf(p->name, "P-%c", 'A' + i); | |
| p->moon_count = (p->type == TYPE_GAS_GIANT) ? (rand() % 4 + 1) : (rand()%100 < 30); | |
| for (int m=0; m<p->moon_count; m++) { | |
| Moon *mn = &p->moons[m]; | |
| mn->orbit_dist = p->radius * (1.5 + rand_float(0.5, 2.0)); | |
| mn->radius = p->radius * 0.25; | |
| mn->x = rand_float(0, 6.28); | |
| mn->vx = rand_float(1.0, 3.0); | |
| sprintf(mn->name, "M-%d", m+1); | |
| } | |
| } | |
| return sys; | |
| } | |
| --- cland/main.c --- | |
| #include <SDL2/SDL.h> | |
| #include "galaxy.h" | |
| #include "font.h" | |
| #include <math.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #define MAX(x, y) (((x) > (y)) ? (x) : (y)) | |
| #define G 39.478 // Gravity Constant | |
| // Cache Helpers | |
| void init_cache(GameState *state) { | |
| state->visited_count = 0; | |
| state->visited_capacity = 16; | |
| state->visited_systems = (StarSystem*)malloc(sizeof(StarSystem) * state->visited_capacity); | |
| } | |
| int find_cached_system(GameState *state, int x, int y) { | |
| for (int i = 0; i < state->visited_count; i++) { | |
| if (state->visited_systems[i].x == x && state->visited_systems[i].y == y) return i; | |
| } | |
| return -1; | |
| } | |
| void cache_current_system(GameState *state) { | |
| int idx = find_cached_system(state, state->current_system.x, state->current_system.y); | |
| if (idx != -1) state->visited_systems[idx] = state->current_system; | |
| else { | |
| if (state->visited_count >= state->visited_capacity) { | |
| state->visited_capacity *= 2; | |
| state->visited_systems = (StarSystem*)realloc(state->visited_systems, sizeof(StarSystem) * state->visited_capacity); | |
| } | |
| state->visited_systems[state->visited_count++] = state->current_system; | |
| } | |
| } | |
| void draw_circle(SDL_Renderer *renderer, int cx, int cy, int radius) { | |
| for (int w = 0; w < radius * 2; w++) { | |
| for (int h = 0; h < radius * 2; h++) { | |
| int dx = radius - w; | |
| int dy = radius - h; | |
| if ((dx*dx + dy*dy) <= (radius * radius)) { | |
| SDL_RenderDrawPoint(renderer, cx + dx, cy + dy); | |
| } | |
| } | |
| } | |
| } | |
| #include <string.h> | |
| // ... | |
| int main(int argc, char* argv[]) { | |
| // Parse Arguments | |
| for (int i=1; i<argc; i++) { | |
| if (strncmp(argv[i], "--stars=", 8) == 0) { | |
| int count = atoi(argv[i] + 8); | |
| if (count >= 1 && count <= 3) { | |
| set_forced_star_count(count); | |
| printf("Forcing Star Count: %d\n", count); | |
| } | |
| } | |
| } | |
| if (SDL_Init(SDL_INIT_VIDEO) < 0) return 1; | |
| SDL_Window* window = SDL_CreateWindow("C-Galaxy N-Body", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN); | |
| SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); | |
| GameState state; | |
| state.mode = VIEW_GALAXY; | |
| state.camera_pos.x = 0; state.camera_pos.y = 0; | |
| state.zoom = 20.0; | |
| state.running = true; | |
| state.selected_planet_idx = -1; | |
| state.selected_star = false; | |
| state.centered_planet_idx = -1; | |
| state.system_center_offset.x = 0; state.system_center_offset.y = 0; | |
| state.paused = false; | |
| state.time_speed = 1.0f; | |
| state.galaxy_camera_pos.x = 0; state.galaxy_camera_pos.y = 0; state.galaxy_zoom = 20.0; | |
| init_cache(&state); | |
| SDL_Event e; | |
| Uint32 last_time = SDL_GetTicks(); | |
| while (state.running) { | |
| Uint32 current_time = SDL_GetTicks(); | |
| float dt = (current_time - last_time) / 1000.0f; | |
| last_time = current_time; | |
| while (SDL_PollEvent(&e) != 0) { | |
| if (e.type == SDL_QUIT) state.running = false; | |
| if (e.type == SDL_MOUSEBUTTONDOWN) { | |
| int mx, my; SDL_GetMouseState(&mx, &my); | |
| if (e.button.button == SDL_BUTTON_LEFT) { | |
| if (state.mode == VIEW_GALAXY) { | |
| float wx = state.camera_pos.x + (mx - SCREEN_WIDTH/2) / state.zoom; | |
| float wy = state.camera_pos.y + (my - SCREEN_HEIGHT/2) / state.zoom; | |
| int ix = round(wx); int iy = round(wy); | |
| if (star_exists(ix, iy)) { | |
| state.galaxy_camera_pos = state.camera_pos; state.galaxy_zoom = state.zoom; | |
| int idx = find_cached_system(&state, ix, iy); | |
| if (idx != -1) state.current_system = state.visited_systems[idx]; | |
| else state.current_system = generate_system(ix, iy); | |
| state.mode = VIEW_SYSTEM; state.zoom = 100.0; | |
| state.selected_planet_idx = -1; state.selected_star = true; | |
| state.centered_planet_idx = -1; state.system_center_offset.x = 0; state.system_center_offset.y = 0; | |
| } | |
| } else { | |
| // System Click | |
| int cx = SCREEN_WIDTH/2 - state.system_center_offset.x * state.zoom; | |
| int cy = SCREEN_HEIGHT/2 - state.system_center_offset.y * state.zoom; | |
| state.selected_planet_idx = -1; state.selected_star = false; | |
| // Click Star? | |
| for (int s=0; s<state.current_system.star_count; s++) { | |
| Star *star = &state.current_system.stars[s]; | |
| int sx = cx + star->x * state.zoom; | |
| int sy = cy + star->y * state.zoom; | |
| if (sqrt(pow(mx-sx,2)+pow(my-sy,2)) < 30) { | |
| state.selected_star = true; | |
| if (e.button.clicks == 2) { state.centered_planet_idx = -1; state.system_center_offset.x = star->x; state.system_center_offset.y = star->y; } | |
| goto clicked; | |
| } | |
| } | |
| // Click Planet? | |
| for (int i=0; i<state.current_system.planet_count; i++) { | |
| Planet *p = &state.current_system.planets[i]; | |
| int px = cx + p->x * state.zoom; | |
| int py = cy + p->y * state.zoom; | |
| if (sqrt(pow(mx-px,2)+pow(my-py,2)) < 20) { | |
| state.selected_planet_idx = i; | |
| if (e.button.clicks == 2) state.centered_planet_idx = i; | |
| goto clicked; | |
| } | |
| } | |
| clicked:; | |
| } | |
| } | |
| if (e.button.button == SDL_BUTTON_RIGHT && state.mode == VIEW_SYSTEM) { | |
| cache_current_system(&state); | |
| state.mode = VIEW_GALAXY; | |
| state.camera_pos = state.galaxy_camera_pos; state.zoom = state.galaxy_zoom; | |
| } | |
| } | |
| if (e.type == SDL_KEYDOWN) { | |
| if (e.key.keysym.sym == SDLK_LEFT) state.camera_pos.x -= 10.0/state.zoom*20; | |
| if (e.key.keysym.sym == SDLK_RIGHT) state.camera_pos.x += 10.0/state.zoom*20; | |
| if (e.key.keysym.sym == SDLK_UP) { | |
| if (state.mode == VIEW_GALAXY) state.camera_pos.y -= 10.0/state.zoom*20; | |
| else state.time_speed *= 1.5; | |
| } | |
| if (e.key.keysym.sym == SDLK_DOWN) { | |
| if (state.mode == VIEW_GALAXY) state.camera_pos.y += 10.0/state.zoom*20; | |
| else state.time_speed /= 1.5; | |
| } | |
| if (e.key.keysym.sym == SDLK_SPACE) state.paused = !state.paused; | |
| if (e.key.keysym.sym == SDLK_EQUALS) state.zoom *= 1.1; | |
| if (e.key.keysym.sym == SDLK_MINUS) state.zoom /= 1.1; | |
| } | |
| if (e.type == SDL_MOUSEWHEEL) state.zoom *= (e.wheel.y > 0 ? 1.1 : 0.9); | |
| } | |
| // --- N-BODY PHYSICS --- | |
| if (state.mode == VIEW_SYSTEM && !state.paused) { | |
| float sim_dt = dt * state.time_speed * 0.5; | |
| // Update Stars | |
| if (state.current_system.star_count > 1) { | |
| for (int i=0; i<state.current_system.star_count; i++) { | |
| Star *s1 = &state.current_system.stars[i]; | |
| float ax = 0, ay = 0; | |
| for (int j=0; j<state.current_system.star_count; j++) { | |
| if (i==j) continue; | |
| Star *s2 = &state.current_system.stars[j]; | |
| float dx = s2->x - s1->x; | |
| float dy = s2->y - s1->y; | |
| float r2 = dx*dx + dy*dy; | |
| float r = sqrt(r2); | |
| float f = G * s2->mass / r2; | |
| ax += f * dx / r; | |
| ay += f * dy / r; | |
| } | |
| s1->vx += ax * sim_dt; s1->vy += ay * sim_dt; | |
| } | |
| for (int i=0; i<state.current_system.star_count; i++) { | |
| state.current_system.stars[i].x += state.current_system.stars[i].vx * sim_dt; | |
| state.current_system.stars[i].y += state.current_system.stars[i].vy * sim_dt; | |
| } | |
| } | |
| // Update Planets | |
| for (int i=0; i<state.current_system.planet_count; i++) { | |
| Planet *p = &state.current_system.planets[i]; | |
| float ax = 0, ay = 0; | |
| for (int s=0; s<state.current_system.star_count; s++) { | |
| Star *star = &state.current_system.stars[s]; | |
| float dx = star->x - p->x; | |
| float dy = star->y - p->y; | |
| float r2 = dx*dx + dy*dy; | |
| float r = sqrt(r2); | |
| float f = G * star->mass / r2; | |
| ax += f * dx / r; | |
| ay += f * dy / r; | |
| } | |
| p->vx += ax * sim_dt; p->vy += ay * sim_dt; | |
| p->x += p->vx * sim_dt; p->y += p->vy * sim_dt; | |
| // Update Rotation | |
| float rot_speed = 6.28 / (p->rotation_period / 365.25); | |
| p->rotation_angle += rot_speed * sim_dt; | |
| for (int m=0; m<p->moon_count; m++) { | |
| p->moons[m].x += p->moons[m].vx * sim_dt * 5.0; | |
| } | |
| } | |
| if (state.centered_planet_idx != -1) { | |
| Planet *p = &state.current_system.planets[state.centered_planet_idx]; | |
| state.system_center_offset.x = p->x; | |
| state.system_center_offset.y = p->y; | |
| } | |
| } | |
| // Render | |
| SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); | |
| SDL_RenderClear(renderer); | |
| if (state.mode == VIEW_GALAXY) { | |
| int view_w = SCREEN_WIDTH / state.zoom; | |
| int view_h = SCREEN_HEIGHT / state.zoom; | |
| for (int x = (int)state.camera_pos.x - view_w/2; x <= (int)state.camera_pos.x + view_w/2; x++) { | |
| for (int y = (int)state.camera_pos.y - view_h/2; y <= (int)state.camera_pos.y + view_h/2; y++) { | |
| if (star_exists(x, y)) { | |
| int sx = (x - state.camera_pos.x) * state.zoom + SCREEN_WIDTH/2; | |
| int sy = (y - state.camera_pos.y) * state.zoom + SCREEN_HEIGHT/2; | |
| unsigned int s = get_seed(x,y); srand(s); | |
| float temp = 5778 * powf(rand_float(0.1, 5.0), 0.5); | |
| int r=255,g=255,b=255; | |
| if (temp > 10000) {r=136;g=136;} else if (temp<4000) {g=68;b=0;} else if (temp<6000) {b=0;} | |
| SDL_SetRenderDrawColor(renderer, r, g, b, 255); | |
| draw_circle(renderer, sx, sy, MAX(2, state.zoom/5)); | |
| } | |
| } | |
| } | |
| SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); | |
| draw_text(renderer, 10, 10, "GALAXY N-BODY - Click star", 2); | |
| } else { | |
| // Draw System | |
| int cx = SCREEN_WIDTH/2 - state.system_center_offset.x * state.zoom; | |
| int cy = SCREEN_HEIGHT/2 - state.system_center_offset.y * state.zoom; | |
| // Stars | |
| for (int i=0; i<state.current_system.star_count; i++) { | |
| Star *s = &state.current_system.stars[i]; | |
| int sx = cx + s->x * state.zoom; | |
| int sy = cy + s->y * state.zoom; | |
| Uint32 c = s->color; | |
| SDL_SetRenderDrawColor(renderer, (c>>24)&0xFF, (c>>16)&0xFF, (c>>8)&0xFF, 255); | |
| draw_circle(renderer, sx, sy, MAX(10, s->radius * 5 * (state.zoom/100.0))); | |
| } | |
| // Planets | |
| for (int i=0; i<state.current_system.planet_count; i++) { | |
| Planet *p = &state.current_system.planets[i]; | |
| int px = cx + p->x * state.zoom; | |
| int py = cy + p->y * state.zoom; | |
| // Draw Body | |
| Uint32 c = p->color; | |
| SDL_SetRenderDrawColor(renderer, (c>>24)&0xFF, (c>>16)&0xFF, (c>>8)&0xFF, 255); | |
| int radius = (p->type==TYPE_GAS_GIANT)?6:3; | |
| draw_circle(renderer, px, py, radius); | |
| // Draw Rotation Line | |
| int rx = px + cos(p->rotation_angle) * radius; | |
| int ry = py + sin(p->rotation_angle) * radius; | |
| SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); | |
| SDL_RenderDrawLine(renderer, px, py, rx, ry); | |
| // Moons | |
| for (int m=0; m<p->moon_count; m++) { | |
| Moon *mn = &p->moons[m]; | |
| int mx = px + cos(mn->x) * mn->orbit_dist * (state.zoom/50.0 + 2.0); | |
| int my = py + sin(mn->x) * mn->orbit_dist * (state.zoom/50.0 + 2.0); | |
| SDL_SetRenderDrawColor(renderer, 200, 200, 200, 255); | |
| draw_circle(renderer, mx, my, 1); | |
| } | |
| SDL_SetRenderDrawColor(renderer, 200, 200, 200, 255); | |
| draw_text(renderer, px, py+10, p->name, 1); | |
| } | |
| // UI | |
| char buf[256]; | |
| SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); | |
| sprintf(buf, "SYSTEM: %s (%d Stars) | Speed: %.1f", state.current_system.stars[0].name, state.current_system.star_count, state.time_speed); | |
| draw_text(renderer, 10, 10, buf, 2); | |
| int y=50; | |
| if (state.selected_star) { | |
| Star *s = &state.current_system.stars[0]; | |
| draw_text(renderer, 10, y+=15, "SELECTED STAR:", 1); | |
| sprintf(buf, "Name: %s", s->name); draw_text(renderer, 10, y+=15, buf, 1); | |
| sprintf(buf, "Mass: %.2f Sol", s->mass); draw_text(renderer, 10, y+=15, buf, 1); | |
| sprintf(buf, "Lum: %.2f Lsol", s->luminosity); draw_text(renderer, 10, y+=15, buf, 1); | |
| sprintf(buf, "Temp: %.0f K", s->temp); draw_text(renderer, 10, y+=15, buf, 1); | |
| } else if (state.selected_planet_idx != -1) { | |
| Planet *p = &state.current_system.planets[state.selected_planet_idx]; | |
| draw_text(renderer, 10, y+=15, "SELECTED PLANET:", 1); | |
| sprintf(buf, "Name: %s", p->name); draw_text(renderer, 10, y+=15, buf, 1); | |
| sprintf(buf, "Mass: %.2f", p->mass); draw_text(renderer, 10, y+=15, buf, 1); | |
| sprintf(buf, "Temp: %.0f K", p->surface_temp); draw_text(renderer, 10, y+=15, buf, 1); | |
| sprintf(buf, "Atm: %s", p->atmosphere); draw_text(renderer, 10, y+=15, buf, 1); | |
| sprintf(buf, "Locked: %s", p->is_tidally_locked?"YES":"NO"); draw_text(renderer, 10, y+=15, buf, 1); | |
| sprintf(buf, "Day: %.2f Days", p->rotation_period); draw_text(renderer, 10, y+=15, buf, 1); | |
| sprintf(buf, "Moons: %d", p->moon_count); draw_text(renderer, 10, y+=15, buf, 1); | |
| draw_text(renderer, 10, y+=15, "RESOURCES:", 1); | |
| // Find Top 3 | |
| for (int k=0; k<3; k++) { | |
| int max_idx = -1; | |
| float max_val = -1.0; | |
| for (int r=0; r<8; r++) { | |
| if (p->resources[r] > max_val) { | |
| bool used = false; | |
| // Check if already displayed (hacky sort) | |
| // To do this properly without modifying struct, we need a temp array or just print > threshold | |
| // Let's just print any > 10% for now or fix sort | |
| } | |
| } | |
| } | |
| // Simpler approach: Iterate and print all > 15% | |
| const char* res_names[] = {"Iron", "Silicon", "Nickel", "Water", "Hydrogen", "Helium", "Gold", "RareEarths"}; | |
| int printed = 0; | |
| for (int r=0; r<8; r++) { | |
| if (p->resources[r] > 15.0) { | |
| sprintf(buf, " %s: %.1f%%", res_names[r], p->resources[r]); | |
| draw_text(renderer, 10, y+=15, buf, 1); | |
| printed++; | |
| } | |
| } | |
| if (printed == 0) draw_text(renderer, 10, y+=15, " Trace Elements", 1); | |
| } | |
| } | |
| SDL_RenderPresent(renderer); | |
| } | |
| free(state.visited_systems); | |
| SDL_DestroyRenderer(renderer); | |
| SDL_DestroyWindow(window); | |
| SDL_Quit(); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment