diff --git a/prompt.md b/prompt.md index 93391bf..1fcb030 100644 --- a/prompt.md +++ b/prompt.md @@ -12,7 +12,7 @@ Delta Time: Fixed time-step used for consistent updates; delta time passed to in 2. Cinematic Text System Typewriter Effect: A custom-built system that reveals two messages letter-by-letter. Message 1: "Hello, human." (large font, 120pt) -Message 2: Multi-line Undertale-themed message about C being the best programming language (small font, 24pt) +Message 2: Multi-line Undertale-themed message about C being the best programming language (small font, 24pt) with the words "Undertale", "rewritten", "opensourced", "C", "GPL3.0" rendered in yellow and shifted up for better centering Timing: Uses SDL_GetTicks() to trigger a new character reveal every ~333ms for message 1 and ~36ms for message 2 (after 3.5s delay). Rendering: Uses SDL_ttf for high-quality text. The text is dynamically centered on the screen by calculating surface dimensions relative to window size, supporting multi-line strings. Memory Safety: Implemented strict null-termination (\0) on text buffers to prevent memory leakage/artifacts. diff --git a/src/main.c b/src/main.c index f029668..ce330b2 100644 --- a/src/main.c +++ b/src/main.c @@ -5,6 +5,7 @@ #include #include #include +#include /* * Global Constants @@ -38,6 +39,7 @@ typedef struct { const char* message2; int visible_chars2; // Number of characters revealed in message2 uint32_t last_char_time2; // Timestamp of last character revealed in message2 + bool* highlight2; // Boolean array for each char in message2: true if should be yellow int message_stage; // 0: showing message1, 1: showing message2 uint32_t message1_finish_time; // Timestamp when message1 finished revealing (0 if not finished) // Escape key handling @@ -55,6 +57,35 @@ typedef struct { Player player; // The player object } Game; +// Helper functions for text highlighting +static bool is_delimiter(char c) { + return !isalnum((unsigned char)c); +} + +static int match_target_word(const char *str, int pos, const char **word_out, int *len_out) { + static const char *targets[] = { + "Undertale", "rewritten", "opensourced", "C", "GPL3.0" + }; + for (size_t ti = 0; ti < sizeof(targets)/sizeof(targets[0]); ++ti) { + const char *word = targets[ti]; + size_t wlen = strlen(word); + // Check bounds + if (pos + (int)wlen > (int)strlen(str)) continue; + // Case-insensitive compare + if (strncasecmp(str + pos, word, wlen) == 0) { + // Check word boundaries + bool left_ok = (pos == 0) || is_delimiter(str[pos-1]); + bool right_ok = (pos + (int)wlen == (int)strlen(str)) || is_delimiter(str[pos + wlen]); + if (left_ok && right_ok) { + *word_out = word; + *len_out = (int)wlen; + return (int)wlen; + } + } + } + return 0; +} + /** * Initializes SDL, TTF, and the game state. * Returns true if everything started correctly, false otherwise. @@ -105,6 +136,35 @@ bool init_game(Game *game) { game->visible_chars1 = 0; game->last_char_time1 = SDL_GetTicks(); game->message2 = "This is the remake of the popular game\nUndertale, but rewritten and opensourced in\nthe best programming language known\nto man, C. Enjoy this peak of a GPL3.0 game :3"; + // Allocate and compute highlight array for message2 + size_t msg2_len = strlen(game->message2); + game->highlight2 = (bool*)malloc(msg2_len * sizeof(bool)); + if (game->highlight2) { + // Initialize all to false + for (size_t i = 0; i < msg2_len; ++i) { + game->highlight2[i] = false; + } + // Target words to highlight (case-insensitive) + static const char *targets[] = { + "Undertale", "rewritten", "opensourced", "C", "GPL3.0" + }; + for (size_t ti = 0; ti < sizeof(targets)/sizeof(targets[0]); ++ti) { + const char *word = targets[ti]; + size_t wlen = strlen(word); + // Search for occurrences case-insensitive + for (size_t i = 0; i + wlen <= msg2_len; ++i) { + if (strncasecmp(game->message2 + i, word, wlen) == 0) { + // Mark each character of this occurrence as true + for (size_t j = 0; j < wlen; ++j) { + game->highlight2[i + j] = true; + } + } + } + } + } else { + // If allocation fails, treat as no highlighting (all false) + game->highlight2 = NULL; + } game->visible_chars2 = 0; game->last_char_time2 = 0; // Not used until we switch to message2 game->message_stage = 0; // Start with message1 @@ -259,7 +319,6 @@ void render(SDL_Renderer *renderer, Game *game) { // First pass: count lines to compute total height int i = 0; while (i < visible_chars_to_render) { - const char* line_start = message_to_render + i; // Find end of line (newline or end of visible chars) int line_len = 0; while (i + line_len < visible_chars_to_render && message_to_render[i + line_len] != '\n') { @@ -276,36 +335,101 @@ void render(SDL_Renderer *renderer, Game *game) { int total_height = line_count * line_height; int start_y = (SCREEN_HEIGHT - total_height) / 2; + // Shift second message up a bit for better visual centering + if (game->message_stage == 1) { + start_y -= 20; // Adjust as needed + } // Second pass: render each line i = 0; int line_y = 0; while (i < visible_chars_to_render) { - const char* line_start = message_to_render + i; int line_len = 0; while (i + line_len < visible_chars_to_render && message_to_render[i + line_len] != '\n') { line_len++; } - // Create temporary buffer for this line - char line_buf[256] = {0}; - strncpy(line_buf, line_start, line_len); - line_buf[line_len] = '\0'; + // Process line for character-by-character highlighting + SDL_Color white = {255, 255, 255, 255}; + SDL_Color yellow = {255, 255, 0, 255}; + #define MAX_SEGMENTS 16 + char seg_text[MAX_SEGMENTS][256]; + int seg_len[MAX_SEGMENTS]; + SDL_Color seg_col[MAX_SEGMENTS]; + int seg_count = 0; - SDL_Surface* surface = TTF_RenderText_Blended(game->current_font, line_buf, white); - if (surface) { - SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); - int shake_offset_x = (rand() % (int)(game->shake_intensity * 2 + 1)) - game->shake_intensity; - int shake_offset_y = (rand() % (int)(game->shake_intensity * 2 + 1)) - game->shake_intensity; - SDL_Rect text_rect = { - (SCREEN_WIDTH - surface->w) / 2 + shake_offset_x, - start_y + line_y + shake_offset_y, - surface->w, - surface->h - }; - SDL_RenderCopy(renderer, texture, NULL, &text_rect); - SDL_FreeSurface(surface); - SDL_DestroyTexture(texture); + int line_start_global = i; + int pos = 0; // offset within line + while (pos < line_len) { + // Determine color for this character + SDL_Color cur_col = white; + if (game->message_stage == 1 && game->highlight2) { + int global_pos = line_start_global + pos; + if (global_pos >= 0 && global_pos < (int)strlen(game->message2)) { + if (game->highlight2[global_pos]) { + cur_col = yellow; + } + } + } + // Find run of same color + int run_start = pos; + while (pos < line_len) { + int global_pos = line_start_global + pos; + SDL_Color next_col = white; + if (game->message_stage == 1 && game->highlight2) { + if (global_pos >= 0 && global_pos < (int)strlen(game->message2)) { + if (game->highlight2[global_pos]) { + next_col = yellow; + } + } + } + if (next_col.r != cur_col.r || next_col.g != cur_col.g || next_col.b != cur_col.b) { + break; + } + pos++; + } + int run_len = pos - run_start; + if (run_len > 0 && seg_count < MAX_SEGMENTS) { + strncpy(seg_text[seg_count], message_to_render + (line_start_global + run_start), run_len); + seg_text[seg_count][run_len] = '\0'; + seg_len[seg_count] = run_len; + seg_col[seg_count] = cur_col; + seg_count++; + } } + + // Compute total width of line + int total_width = 0; + int temp_w; + for (int s = 0; s < seg_count; ++s) { + TTF_SizeText(game->current_font, seg_text[s], &temp_w, NULL); + total_width += temp_w; + } + + // Base x position for centering + int base_x = (SCREEN_WIDTH - total_width) / 2; + int shake_offset_x = (rand() % (int)(game->shake_intensity * 2 + 1)) - game->shake_intensity; + int shake_offset_y = (rand() % (int)(game->shake_intensity * 2 + 1)) - game->shake_intensity; + int render_x = base_x + shake_offset_x; + + // Render each segment + int x_offset = 0; + for (int s = 0; s < seg_count; ++s) { + SDL_Surface* surface = TTF_RenderText_Blended(game->current_font, seg_text[s], seg_col[s]); + if (surface) { + SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); + SDL_Rect text_rect = { + render_x + x_offset, + start_y + line_y + shake_offset_y, + surface->w, + surface->h + }; + SDL_RenderCopy(renderer, texture, NULL, &text_rect); + x_offset += surface->w; + SDL_FreeSurface(surface); + SDL_DestroyTexture(texture); + } + } + line_y += line_height; i += line_len; if (i < visible_chars_to_render && message_to_render[i] == '\n') { @@ -352,6 +476,10 @@ int main(int argc, char* args[]) { // Cleanup memory if (game.font_large) TTF_CloseFont(game.font_large); if (game.font_small) TTF_CloseFont(game.font_small); + if (game.highlight2) { + free(game.highlight2); + game.highlight2 = NULL; + } if (game.beep_chunk) Mix_FreeChunk(game.beep_chunk); Mix_CloseAudio(); Mix_Quit();