diff --git a/src/common.h b/src/common.h index 474e133..9fda4d3 100644 --- a/src/common.h +++ b/src/common.h @@ -173,6 +173,8 @@ typedef struct { int potions_used; int floors_reached; int final_score; + // Seed for this run + unsigned int run_seed; } GameState; diff --git a/src/main.c b/src/main.c index ed3cb51..6bd5351 100644 --- a/src/main.c +++ b/src/main.c @@ -10,9 +10,12 @@ #include "render.h" #include "rng.h" #include "settings.h" +#include #include #include +#include #include +#include // Add message to action log static void add_log(GameState *gs, const char *msg) { @@ -102,11 +105,14 @@ static void update_effects(GameState *gs) { // Initialize a new floor static void init_floor(GameState *gs, int floor_num) { + // Seed RNG with run seed combined with floor number for deterministic generation + rng_seed(gs->run_seed + floor_num * 54321); + // Generate dungeon dungeon_generate(&gs->dungeon, &gs->map, floor_num); // Seed rng for this floor's content - rng_seed(floor_num * 54321); + rng_seed(gs->run_seed + floor_num * 98765); // Find spawn position int start_x, start_y; @@ -478,7 +484,13 @@ static int handle_input(GameState *gs) { // Check for restart (works during game over) if (IsKeyPressed(KEY_R) && gs->game_over) { memset(gs, 0, sizeof(GameState)); + // Generate a new random seed for the new run + gs->run_seed = (unsigned int)time(NULL); init_floor(gs, 1); + // Update window title with new seed + char title[128]; + snprintf(title, sizeof(title), "Roguelike - Seed: %u", gs->run_seed); + SetWindowTitle(title); return 0; } @@ -508,12 +520,12 @@ void load_audio_assets(GameState *gs) { } // Main game loop -static void game_loop(void) { +static void game_loop(unsigned int run_seed) { GameState gs; memset(&gs, 0, sizeof(GameState)); + gs.run_seed = run_seed; load_audio_assets(&gs); // Initialize first floor - rng_seed(12345); init_floor(&gs, 1); // Disable esc to exit @@ -533,10 +545,16 @@ static void game_loop(void) { break; if (IsKeyPressed(KEY_R)) { memset(&gs, 0, sizeof(GameState)); + // Generate a new random seed for the new run + gs.run_seed = (unsigned int)time(NULL); gs.game_over = 0; gs.game_won = 0; load_audio_assets(&gs); init_floor(&gs, 1); + // Update window title with new seed + char title[128]; + snprintf(title, sizeof(title), "Roguelike - Seed: %u", gs.run_seed); + SetWindowTitle(title); } } @@ -579,6 +597,9 @@ static void game_loop(void) { render_message(gs.last_message); } + // Draw persistent seed display in top right + render_seed_display(gs.run_seed); + // Draw game over screen if (gs.game_over) { // Compute final score @@ -589,7 +610,7 @@ static void game_loop(void) { } render_end_screen(gs.game_won, gs.total_kills, gs.items_collected, gs.damage_dealt, gs.damage_taken, gs.crits_landed, gs.times_hit, gs.potions_used, gs.floors_reached, gs.turn_count, - gs.final_score); + gs.final_score, gs.run_seed); } EndDrawing(); @@ -599,17 +620,67 @@ static void game_loop(void) { } } -int main(void) { +// Check if a string is a valid unsigned integer +static int is_valid_uint(const char *str) { + if (str == NULL || *str == '\0') + return 0; + // Check for optional leading + + if (*str == '+') + str++; + // Must have at least one digit + if (*str == '\0') + return 0; + // All characters must be digits + for (const char *p = str; *p != '\0'; p++) { + if (!isdigit((unsigned char)*p)) + return 0; + } + return 1; +} + +int main(int argc, char **argv) { + // Parse command-line arguments + unsigned int run_seed = 0; + int seed_provided = 0; + + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--seed") == 0) { + if (i + 1 >= argc) { + fprintf(stderr, "Error: --seed requires a value\n"); + fprintf(stderr, "Usage: %s [--seed ]\n", argv[0]); + return 1; + } + const char *seed_str = argv[i + 1]; + if (!is_valid_uint(seed_str)) { + fprintf(stderr, "Error: Invalid seed value: %s\n", seed_str); + fprintf(stderr, "Seed must be a non-negative integer\n"); + return 1; + } + run_seed = (unsigned int)strtoul(seed_str, NULL, 10); + seed_provided = 1; + i++; // Skip the value + } + } + + // If no seed provided, generate random seed from time + if (!seed_provided) { + run_seed = (unsigned int)time(NULL); + } + + printf("Starting game with seed: %u\n", run_seed); + // Initialize audio audio_init(); // Initialize random number generator SetRandomSeed(88435); - // Initialize window - InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT + 60, "Roguelike"); + // Initialize window with seed in title + char title[128]; + snprintf(title, sizeof(title), "Roguelike - Seed: %u", run_seed); + InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT + 60, title); SetTargetFPS(60); // Run game - game_loop(); + game_loop(run_seed); // Cleanup CloseWindow(); diff --git a/src/map.c b/src/map.c index b9d5bbc..011c06b 100644 --- a/src/map.c +++ b/src/map.c @@ -170,9 +170,6 @@ void get_random_floor_tile(Map *map, int *x, int *y, int attempts) { } void dungeon_generate(Dungeon *d, Map *map, int floor_num) { - // Seed RNG with floor number for deterministic generation - rng_seed(floor_num * 12345); - // Initialize map to all walls map_init(map); diff --git a/src/render.c b/src/render.c index 430b3e3..8d7bbaa 100644 --- a/src/render.c +++ b/src/render.c @@ -507,7 +507,7 @@ void render_floating_texts(FloatingText *texts, int count, int shake_x, int shak } void render_end_screen(int is_victory, int kills, int items, int damage_dealt, int damage_taken, int crits, - int times_hit, int potions, int floors, int turns, int score) { + int times_hit, int potions, int floors, int turns, int score, unsigned int seed) { // Semi-transparent overlay Rectangle overlay = {0, 0, (float)SCREEN_WIDTH, (float)SCREEN_HEIGHT}; DrawRectangleRec(overlay, (Color){0, 0, 0, 210}); @@ -523,7 +523,7 @@ void render_end_screen(int is_victory, int kills, int items, int damage_dealt, i int box_x = SCREEN_WIDTH / 2 - 200; int box_y = 110; int box_w = 400; - int box_h = 320; + int box_h = 350; DrawRectangle(box_x, box_y, box_w, box_h, (Color){20, 20, 20, 240}); DrawRectangleLines(box_x, box_y, box_w, box_h, (Color){100, 100, 100, 255}); @@ -589,7 +589,14 @@ void render_end_screen(int is_victory, int kills, int items, int damage_dealt, i DrawText("SCORE:", col1_x, row_y, 22, GOLD); snprintf(line, sizeof(line), "%d", score); DrawText(line, col1_x + 90, row_y, 22, GOLD); + row_y += 35; + // Seed display + DrawText("SEED:", col1_x, row_y, 18, label_color); + snprintf(line, sizeof(line), "%u", seed); + DrawText(line, col1_x + 60, row_y, 18, END_SEED); + + // Instructions if (is_victory) { const char *subtitle = "Press R to play again or Q to quit"; int sub_width = MeasureText(subtitle, 20); @@ -672,3 +679,18 @@ void render_message(const char *message) { DrawText(message, text_x, text_y, font_size, WHITE); } + +void render_seed_display(unsigned int seed) { + char seed_text[64]; + snprintf(seed_text, sizeof(seed_text), "Seed: %u", seed); + + const int font_size = 14; + int text_width = MeasureText(seed_text, font_size); + + // Position at top right with padding + int x = SCREEN_WIDTH - text_width - 10; + int y = 5; + + // Draw with non-obstructive dim text color + DrawText(seed_text, x, y, font_size, TEXT_DIM); +} diff --git a/src/render.h b/src/render.h index d934ffc..aa5c181 100644 --- a/src/render.h +++ b/src/render.h @@ -1,3 +1,4 @@ + #ifndef RENDER_H #define RENDER_H @@ -68,6 +69,7 @@ #define END_OVERLAY (Color){0, 0, 0, 210} #define END_BOX_BG (Color){20, 20, 20, 240} #define END_BOX_BORDER (Color){100, 100, 100, 255} +#define END_SEED (Color){150, 200, 255, 255} // Portrait placeholder // FIXME: remove when player sprites are available @@ -99,9 +101,12 @@ void render_floating_texts(FloatingText *texts, int count, int shake_x, int shak // Render end screen (victory or death) with stats breakdown void render_end_screen(int is_victory, int kills, int items, int damage_dealt, int damage_taken, int crits, - int times_hit, int potions, int floors, int turns, int score); + int times_hit, int potions, int floors, int turns, int score, unsigned int seed); // Render a message popup void render_message(const char *message); +// Render seed display at top right of screen +void render_seed_display(unsigned int seed); + #endif // RENDER_H