updated prompt.md :3
This commit is contained in:
parent
86608e8cb3
commit
e91f9e023d
2 changed files with 150 additions and 22 deletions
|
|
@ -12,7 +12,7 @@ Delta Time: Fixed time-step used for consistent updates; delta time passed to in
|
||||||
2. Cinematic Text System
|
2. Cinematic Text System
|
||||||
Typewriter Effect: A custom-built system that reveals two messages letter-by-letter.
|
Typewriter Effect: A custom-built system that reveals two messages letter-by-letter.
|
||||||
Message 1: "Hello, human." (large font, 120pt)
|
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).
|
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.
|
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.
|
Memory Safety: Implemented strict null-termination (\0) on text buffers to prevent memory leakage/artifacts.
|
||||||
|
|
|
||||||
168
src/main.c
168
src/main.c
|
|
@ -5,6 +5,7 @@
|
||||||
#include <SDL_ttf.h>
|
#include <SDL_ttf.h>
|
||||||
#include <SDL_mixer.h>
|
#include <SDL_mixer.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Global Constants
|
* Global Constants
|
||||||
|
|
@ -38,6 +39,7 @@ typedef struct {
|
||||||
const char* message2;
|
const char* message2;
|
||||||
int visible_chars2; // Number of characters revealed in message2
|
int visible_chars2; // Number of characters revealed in message2
|
||||||
uint32_t last_char_time2; // Timestamp of last character 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
|
int message_stage; // 0: showing message1, 1: showing message2
|
||||||
uint32_t message1_finish_time; // Timestamp when message1 finished revealing (0 if not finished)
|
uint32_t message1_finish_time; // Timestamp when message1 finished revealing (0 if not finished)
|
||||||
// Escape key handling
|
// Escape key handling
|
||||||
|
|
@ -55,6 +57,35 @@ typedef struct {
|
||||||
Player player; // The player object
|
Player player; // The player object
|
||||||
} Game;
|
} 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.
|
* Initializes SDL, TTF, and the game state.
|
||||||
* Returns true if everything started correctly, false otherwise.
|
* Returns true if everything started correctly, false otherwise.
|
||||||
|
|
@ -105,6 +136,35 @@ bool init_game(Game *game) {
|
||||||
game->visible_chars1 = 0;
|
game->visible_chars1 = 0;
|
||||||
game->last_char_time1 = SDL_GetTicks();
|
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";
|
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->visible_chars2 = 0;
|
||||||
game->last_char_time2 = 0; // Not used until we switch to message2
|
game->last_char_time2 = 0; // Not used until we switch to message2
|
||||||
game->message_stage = 0; // Start with message1
|
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
|
// First pass: count lines to compute total height
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while (i < visible_chars_to_render) {
|
while (i < visible_chars_to_render) {
|
||||||
const char* line_start = message_to_render + i;
|
|
||||||
// Find end of line (newline or end of visible chars)
|
// Find end of line (newline or end of visible chars)
|
||||||
int line_len = 0;
|
int line_len = 0;
|
||||||
while (i + line_len < visible_chars_to_render && message_to_render[i + line_len] != '\n') {
|
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 total_height = line_count * line_height;
|
||||||
int start_y = (SCREEN_HEIGHT - total_height) / 2;
|
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
|
// Second pass: render each line
|
||||||
i = 0;
|
i = 0;
|
||||||
int line_y = 0;
|
int line_y = 0;
|
||||||
while (i < visible_chars_to_render) {
|
while (i < visible_chars_to_render) {
|
||||||
const char* line_start = message_to_render + i;
|
|
||||||
int line_len = 0;
|
int line_len = 0;
|
||||||
while (i + line_len < visible_chars_to_render && message_to_render[i + line_len] != '\n') {
|
while (i + line_len < visible_chars_to_render && message_to_render[i + line_len] != '\n') {
|
||||||
line_len++;
|
line_len++;
|
||||||
}
|
}
|
||||||
// Create temporary buffer for this line
|
// Process line for character-by-character highlighting
|
||||||
char line_buf[256] = {0};
|
SDL_Color white = {255, 255, 255, 255};
|
||||||
strncpy(line_buf, line_start, line_len);
|
SDL_Color yellow = {255, 255, 0, 255};
|
||||||
line_buf[line_len] = '\0';
|
#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);
|
int line_start_global = i;
|
||||||
if (surface) {
|
int pos = 0; // offset within line
|
||||||
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
|
while (pos < line_len) {
|
||||||
int shake_offset_x = (rand() % (int)(game->shake_intensity * 2 + 1)) - game->shake_intensity;
|
// Determine color for this character
|
||||||
int shake_offset_y = (rand() % (int)(game->shake_intensity * 2 + 1)) - game->shake_intensity;
|
SDL_Color cur_col = white;
|
||||||
SDL_Rect text_rect = {
|
if (game->message_stage == 1 && game->highlight2) {
|
||||||
(SCREEN_WIDTH - surface->w) / 2 + shake_offset_x,
|
int global_pos = line_start_global + pos;
|
||||||
start_y + line_y + shake_offset_y,
|
if (global_pos >= 0 && global_pos < (int)strlen(game->message2)) {
|
||||||
surface->w,
|
if (game->highlight2[global_pos]) {
|
||||||
surface->h
|
cur_col = yellow;
|
||||||
};
|
}
|
||||||
SDL_RenderCopy(renderer, texture, NULL, &text_rect);
|
}
|
||||||
SDL_FreeSurface(surface);
|
}
|
||||||
SDL_DestroyTexture(texture);
|
// 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;
|
line_y += line_height;
|
||||||
i += line_len;
|
i += line_len;
|
||||||
if (i < visible_chars_to_render && message_to_render[i] == '\n') {
|
if (i < visible_chars_to_render && message_to_render[i] == '\n') {
|
||||||
|
|
@ -352,6 +476,10 @@ int main(int argc, char* args[]) {
|
||||||
// Cleanup memory
|
// Cleanup memory
|
||||||
if (game.font_large) TTF_CloseFont(game.font_large);
|
if (game.font_large) TTF_CloseFont(game.font_large);
|
||||||
if (game.font_small) TTF_CloseFont(game.font_small);
|
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);
|
if (game.beep_chunk) Mix_FreeChunk(game.beep_chunk);
|
||||||
Mix_CloseAudio();
|
Mix_CloseAudio();
|
||||||
Mix_Quit();
|
Mix_Quit();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue