Initial commit basically lol

This commit is contained in:
Martin Dvořák 2026-08-28 20:51:26 +02:00
commit 6d77491228
5 changed files with 316 additions and 2 deletions

14
Makefile Normal file
View file

@ -0,0 +1,14 @@
makeCC = gcc
CFLAGS = -Wall -Wextra -std=c11 $(shell sdl2-config --cflags)
LDFLAGS = $(shell sdl2-config --libs) -lSDL2_image -lSDL2_ttf -lSDL2_mixer
TARGET = game
SRC = src/main.c
all: $(TARGET)
$(TARGET): $(SRC)
$(CC) $(CFLAGS) $(SRC) -o $(TARGET) $(LDFLAGS)
clean:
rm -f $(TARGET)

View file

@ -1,3 +1,66 @@
# sdk-experiment # C/SDL2 Game Framework
just testing out ollama cloud for vibecoding.. A lightweight, professional game template built with C and SDL2, designed for macOS. This project implements a robust game loop architecture with a cinematic introductory sequence and precise frame-rate management.
## 🚀 Features
- **Fixed Time-Step Loop**: Locked at 60 FPS using `SDL_Delay` and execution time calculation to ensure consistent gameplay across different hardware.
- **Cinematic Intro System**:
- Typewriter-style text animation driven by millisecond timers (`SDL_GetTicks`).
- Dynamic centering algorithm for text rendering regardless of string length.
- "Hold-to-Quit" mechanism utilizing `delta_time` for mathematical precision.
- **State Management**: Centralized `Game` struct acting as the single source of truth for the application state.
- **macOS Optimized**: Configured to work seamlessly with Homebrew-installed SDL2 libraries.
## 🛠️ Technical Stack
- **Language**: C (C11)
- **Libraries**:
- `SDL2`: Core windowing, input, and rendering.
- `SDL2_ttf`: TrueType font rendering.
- `SDL2_image` & `SDL2_mixer`: Linked and ready for asset expansion.
## 📋 Prerequisites
You must have Homebrew installed on your macOS system. Install the required SDL2 dependencies using:
```bash
brew install sdl2 sdl2_ttf sdl2_image sdl2_mixer
```
## 🔨 Building and Running
The project includes a `Makefile` to automate the compilation process.
1. **Compile the project**:
```bash
make
```
2. **Run the game**:
```bash
./game
```
3. **Clean build files**:
```bash
make clean
```
## 📂 Project Structure
- `src/main.c`: Core game logic, including the game loop, input handling, update, and render functions.
- `Makefile`: Build configuration using `sdl2-config` for path resolution.
- `assets/`: Directory for game assets (e.g., `font.ttf`).
## 🎮 Controls
- **Hold ESC**: Exit the game (requires a 1-second hold during the intro).
- **Arrow Keys**: Movement logic is implemented (currently disabled during the intro sequence).
## 🗺️ Roadmap
- [ ] Implement transition from Intro Sequence to Active Gameplay.
- [ ] Integrate sprite rendering for the player character.
- [ ] Expand game world with interactive objects.
- [ ] Add background music and sound effects using `SDL2_mixer`.

BIN
assets/font.ttf Normal file

Binary file not shown.

26
prompt.md Normal file
View file

@ -0,0 +1,26 @@
Project Summary: C/SDL2 Cinematic Game Template
# 🛠 Technical Stack
Language: C (C11 standard)
Libraries: SDL2, SDL2_ttf
OS: macOS (Homebrew environment)
Build System: Makefile using sdl2-config for cross-platform header/library paths.
# 🚀 Current Implementation
1. Core Architecture
Game Loop: Implemented a professional Input $\rightarrow$ Update $\rightarrow$ Render cycle.
FPS Locking: Locked at 60 FPS using a dynamic frame-capper (SDL_Delay based on frame start/end timestamps) to ensure CPU efficiency and consistent timing.
Delta Time: Implemented time-based calculations instead of frame-based ones to ensure gameplay speed is identical regardless of hardware performance.
2. Cinematic Text System
Typewriter Effect: A custom-built system that reveals a message ("Hello, human.") letter-by-letter.
Timing: Uses SDL_GetTicks() to trigger a new character reveal every 150ms.
Rendering: Uses SDL_ttf for high-quality text. The text is dynamically centered on the screen by calculating the surface dimensions relative to the window size.
Memory Safety: Implemented strict null-termination (\0) on the text buffer to prevent memory leakage/artifacts.
3. Game Mechanics & State
Game State Struct: A centralized Game struct that manages the loop status, timers, font pointers, and player data.
Hold-to-Quit: A safety exit mechanism where the user must hold the Escape key for exactly 1000ms (1 second) to terminate the program.
Player Object: A Player struct exists with position and speed data (currently invisible but fully functional in the background).
# 📂 Project Structure
Makefile: Handles compilation and linking of SDL2 libraries.
src/main.c: The entire game logic, fully documented with header and inline comments.
assets/font.ttf: External TrueType font used for rendering.
# 🎯 Current Status
The game currently launches a black window, reveals a centered message with a cinematic typewriter animation, and allows the user to exit via a timed Escape-key hold. The codebase is cleaned, documented, and optimized for macOS.

211
src/main.c Normal file
View file

@ -0,0 +1,211 @@
#include <SDL.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <SDL_ttf.h>
/*
* Global Constants
* These define the window dimensions and are used for centering graphics.
*/
const int SCREEN_WIDTH = 800;
const int SCREEN_HEIGHT = 600;
/*
* Player Structure
* Even if invisible, we keep the player state to allow for future gameplay logic.
*/
typedef struct {
float x, y;
float speed;
int size;
SDL_Color color;
} Player;
/*
* Game State Structure
* This is the 'Brain' of the game, containing everything needed to track the current session.
*/
typedef struct {
bool is_running; // Controls the main game loop
float escape_timer; // Tracks how long the Escape key has been held (in ms)
int visible_chars; // Number of characters currently revealed in the typewriter text
uint32_t last_char_time; // Timestamp of the last character revealed
const char* message; // The string to be displayed
TTF_Font* font; // Pointer to the loaded TrueType font
Player player; // The player object
} Game;
/**
* Initializes SDL, TTF, and the game state.
* Returns true if everything started correctly, false otherwise.
*/
bool init_game(Game *game) {
// Initialize SDL Video subsystem
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError());
return false;
}
// Initialize SDL_ttf for text rendering
if (TTF_Init() == -1) {
printf("SDL_ttf could not initialize! TTF_Error: %s\n", TTF_GetError());
return false;
}
// Load the font file from assets. Size is 48 for a cinematic look.
game->font = TTF_OpenFont("assets/font.ttf", 48);
if (!game->font) {
printf("CRITICAL: Failed to load font! TTF_Error: %s\n", TTF_GetError());
} else {
printf("SUCCESS: Font loaded successfully!\n");
}
// Setup initial state
game->is_running = true;
game->escape_timer = 0.0f;
game->visible_chars = 0;
game->last_char_time = SDL_GetTicks();
game->message = "Hello, human.";
game->player = (Player){ .x = 100, .y = 100, .speed = 0.5f, .size = 32, .color = {255, 0, 0, 255} };
return true;
}
/**
* Processes keyboard and window events.
* Handles the 'hold to quit' logic for the Escape key.
*/
void handle_input(Game *game, float delta_time) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
game->is_running = false;
}
}
const uint8_t *state = SDL_GetKeyboardState(NULL);
// ESCAPE KEY LOGIC:
// If Escape is held, we add the actual time passed (delta_time) to the timer.
if (state[SDL_SCANCODE_ESCAPE]) {
game->escape_timer += delta_time;
if (game->escape_timer >= 1000.0f) { // Exactly 1000ms = 1 second
game->is_running = false;
}
} else {
// Reset timer immediately if key is released
game->escape_timer = 0.0f;
}
// Player Movement (Invisible for now)
if (state[SDL_SCANCODE_UP]) game->player.y -= game->player.speed;
if (state[SDL_SCANCODE_DOWN]) game->player.y += game->player.speed;
if (state[SDL_SCANCODE_LEFT]) game->player.x -= game->player.speed;
if (state[SDL_SCANCODE_RIGHT]) game->player.x += game->player.speed;
}
/**
* Updates game logic independent of rendering.
* Handles the typewriter timing.
*/
void update(Game *game) {
// TYPEWRITER LOGIC:
// Check if enough time has passed since the last letter appeared.
uint32_t current_time = SDL_GetTicks();
if (current_time - game->last_char_time >= 150) {
if (game->visible_chars < (int)strlen(game->message)) {
game->visible_chars++;
game->last_char_time = current_time;
}
}
// Keep player within screen bounds
if (game->player.x < 0) game->player.x = 0;
if (game->player.x > SCREEN_WIDTH - game->player.size) game->player.x = SCREEN_WIDTH - game->player.size;
if (game->player.y < 0) game->player.y = 0;
if (game->player.y > SCREEN_HEIGHT - game->player.size) game->player.y = SCREEN_HEIGHT - game->player.size;
}
/**
* Handles all drawing operations.
* Clears screen and renders centered text.
*/
void render(SDL_Renderer *renderer, Game *game) {
// 1. Clear the screen to black
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
// 2. Render the revealed portion of the text
if (game->font) {
char buffer[64] = {0};
if (game->visible_chars > 0) {
// Copy only the number of characters currently 'revealed'
strncpy(buffer, game->message, game->visible_chars);
buffer[game->visible_chars] = '\0';
SDL_Color white = {255, 255, 255, 255};
SDL_Surface* surface = TTF_RenderText_Blended(game->font, buffer, white);
if (surface) {
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
// CENTER THE TEXT:
// Subtract half the text width/height from center screen coordinates.
SDL_Rect text_rect = {
(SCREEN_WIDTH - surface->w) / 2,
(SCREEN_HEIGHT - surface->h) / 2,
surface->w,
surface->h
};
SDL_RenderCopy(renderer, texture, NULL, &text_rect);
SDL_FreeSurface(surface);
SDL_DestroyTexture(texture);
}
}
}
// 3. Swap the backbuffer to the screen
SDL_RenderPresent(renderer);
}
int main(int argc, char* args[]) {
Game game;
if (!init_game(&game)) return 1;
SDL_Window *window = SDL_CreateWindow("C Game Template", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
// FPS Locking variables
const int TARGET_FPS = 60;
const uint32_t FRAME_DELAY = 1000 / TARGET_FPS;
while (game.is_running) {
uint32_t frame_start = SDL_GetTicks();
// Calculate Delta Time (time since last frame) in milliseconds
// Since we lock to 60FPS, this is usually around 16.6ms.
float delta_time = 1000.0f / TARGET_FPS;
handle_input(&game, delta_time);
update(&game);
render(renderer, &game);
// FRAME CAPPER:
// Calculate how long the frame took and sleep the remaining time.
uint32_t frame_time = SDL_GetTicks() - frame_start;
if (FRAME_DELAY > frame_time) {
SDL_Delay(FRAME_DELAY - frame_time);
}
}
// Cleanup memory
if (game.font) TTF_CloseFont(game.font);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
TTF_Quit();
SDL_Quit();
return 0;
}