A Lightweight, Modular Framework for Building Console Applications in C
TL;DR
- What is it? A lightweight, modular C framework for building console apps (especially interactive/TUI/games).
- Key Features: Component-oriented, interface-based, declarative macros (
INTERFACES
,MAP_LAYER
), Arena-based Allocator, advanced color rendering, Observer/Emitter signals. - Why use it? Build efficient console apps/games in C with a structured, flexible architecture.
- Status: Actively developed (API may change).
- Try it:
git clone https://github.com/gooderfreed/Zen.git && cd Zen && make
(builds library & all examples).
Zen is a streamlined C framework designed for developing robust and efficient console-based applications. It employs a flexible, component-oriented architecture centered around interfaces and utilizes C macros to provide a declarative API for configuring application structure and object capabilities.
Zen offers a solid foundation for projects ranging from simple utilities to complex interactive console applications, including text-based games. It emphasizes performance through careful memory management and aims for good color fidelity across different terminal types.
Key architectural concepts include:
- Component-Oriented Design: Encourages code reusability and maintainability through composition.
- Interface-Based System: Defines object capabilities and interactions via structures containing function pointers, promoting flexibility and decoupling.
- Declarative Macro API: Provides C macros (like
INTERFACES
,MAP_LAYER
,OBSERVER
) that enable a declarative style for defining object interfaces and application structure. - Arena Memory Management: Leverages an arena allocator for efficient memory handling, suitable for both static and dynamic allocation scenarios.
- Console Screen Rendering: Offers functionalities for text-based output with color (including RGB with automatic fallback) and formatting.
- Event System: Includes a signal/slot (Observer/Emitter) mechanism for inter-object communication.
- Input Handling: Manages keyboard input for interactive applications.
Zen aims to be lightweight, producing compact executables while offering these features.
Zen's architecture revolves around a few key concepts that enable its modularity and declarative style.
The core library headers are organized into three main directories:
primitives/
: Contains forward declarations (typedef struct ... TypeName;
) of all major framework structs. This is used to resolve circular dependencies between headers.components/
: Defines concrete data structures and core functionalities likeScreen
(rendering),Map
(layered map),Cursor
,Arena
(memory),Container
,Color
, time managers (TimeManager
,TickCounter
,FrameTimer
), etc.interfaces/
: Defines the interfaces (contracts) that objects can implement to interact with the framework and each other. Examples includeDrawable
,Updateable
,InputHandler
,Observer
,Emitter
,CursorInteractable
,ButtonHandler
,CardHandler
.
The primary way to create framework-aware objects and attach interfaces is using the INTERFACES
macro:
#include "my_object.h" // Header defining MyObject and its interface functions
MyObject *create_my_object(Arena *arena) {
// Allocate object memory from the arena
MyObject *obj = arena_alloc(arena, sizeof(MyObject));
obj->some_data = 42;
// Initialize framework interfaces using the INTERFACES macro
INTERFACES(arena, obj, {
// Inside this block, use interface-specific initializer macros
DRAWABLE(my_object_draw_func); // Attach Drawable interface
UPDATEABLE(my_object_update_func); // Attach Updateable interface
INPUT_HANDLER(my_object_input_func); // Attach InputHandler
OBSERVER({ // Attach Observer
NEW_OBSERVER("some_signal", my_object_on_signal); // Subscribe
});
EMITTER({ // Attach Emitter
NEW_EMITTER("my_own_signal"); // Declare signal
});
// ... other interfaces ...
});
The INTERFACES
macro handles:
- Setting the object's name
- Managing interface struct allocation via the arena
- Setting capability flags
- Storing function pointers
Define map layers, their properties (like loop callbacks), and the objects they contain:
// Define a game layer and place objects on it
MapLayer *game_layer = NULL;
MAP_LAYER(arena, game_layer, {
// Layer properties/callbacks
prepare_screen = prepare_game_screen;
loop = game_loop;
}, {
// Objects placed on the layer using OBJECT(object_ptr, coords, [params])
OBJECT(player_object, COORDS(10, 5), {is_main = true;});
OBJECT(enemy_object, COORDS(20, 8));
});
Zen relies heavily on its Arena-based Allocator (components/arena_alloc.h
). All dynamic memory within the framework is managed through an Arena instance provided during initialization (zen_init
).
- Supports both static (
arena_new_static
) and dynamic (arena_new_dynamic
) arenas - Reduces fragmentation and potentially improves allocation/deallocation speed compared to standard
malloc
/free
- Allows for easy cleanup of entire states using
arena_reset
, and also supports freeing individual blocks (arena_free_block
) for memory reuse within the arena. - For dynamic arenas, memory is automatically freed when calling
arena_free
or when the arena is reset
Provides a publish-subscribe mechanism for decoupled communication (interfaces/observer.h
, interfaces/emitter.h
).
- Uses string names for signals.
- Observers subscribe to signals with callbacks using
OBSERVER
/NEW_OBSERVER
. - Emitters declare signals they emit using
EMITTER
/NEW_EMITTER
. - A central registry (managed internally) connects observers to emitters efficiently during a linking phase (
link_observers_to_emitters
). - Signals are sent using
emit_signal(emitter_object, "signal_name", data_payload)
.
Interfaces like CoreDependent
and TickDependent
allow objects to declare dependencies on core framework components (Zen
, TickCounter
).
- The object uses the
CORE_DEPENDENT()
orTICK_DEPENDENT()
macro during initialization. - The framework (during setup) injects the necessary pointers (e.g.,
Zen *zen
) into the object'sObjectInterfaces
. - The object can then use helper macros (
GET_CORE()
,CORE_ACTION()
,GET_CURRENT_TICK()
) to access core functionality safely.
Zen implements a standard game loop (time_manager.h
, tick_counter.h
, frame_timer.h
, updatable.h
) suitable for interactive applications.
- Logic updates happen at a fixed rate (
ticks_per_second
) via theUpdateable
interface. - Rendering happens as fast as possible up to a target FPS (
target_fps
) via theDrawable
interface. - This ensures game logic remains consistent regardless of rendering speed.
Zen provides a range of components and interfaces (detailed in Core Concepts) to facilitate application development:
- Core (
Zen
struct,zen.h
): Central orchestrator managing the main loop, components, and event dispatch. - Memory Management: Integrated Arena-based Allocator (
arena_alloc.h
) for efficient static or dynamic memory management. - Screen Rendering (
Screen
,screen.h
): Double-buffered terminal rendering with support for Unicode,TextEffect
s (bold, etc.), and RGB color (usingColor
struct fromcolor.h
). Includes drawing primitives. - Color Handling (
Color
,color.h
): Simpleuint32_t
based RGB color representation with many predefinedCOLOR_*
constants (CSS/X11 names). Color output is automatically adapted to terminal capabilities (TrueColor, 256-color, 16-color) detected via internal methods or optionally using thetput
command if available. - Layered Map (
Map
,MapLayer
,map.h
): A component for organizing objects (void*
) in a 2D grid with multiple layers. Supports declarative definition viaMAP_LAYER
/OBJECT
macros and layer-specific callbacks. - Cursor (
Cursor
,cursor.h
): Manages cursor position, appearance (CursorConfig
), and interaction state (including aContainer
for selected items). - Generic Container (
Container
,container.h
): A simple dynamic array forvoid*
. - Input Handling: Raw keyboard input reading (
getwchar
), terminal mode setting (set_noncanonical_mode
), and dispatching via theInputHandler
interface orMapLayer
callbacks. - Event System (
Observer
/Emitter
,observer.h
/emitter.h
): Signal/slot mechanism for event-based communication using string identifiers. - Game Loop: Fixed timestep logic updates (
TickCounter
,Updateable
interface) decoupled from variable FPS rendering (FrameTimer
,Drawable
interface). - FPS Counter (
FpsStats
,fps_stats.h
): Optional component to track and display FPS metrics. - Interaction Interfaces: A suite of interfaces for building interactive elements:
CursorInteractable
: Defines how an object interacts with cursor movement and placement.ButtonHandler
: Handles activation (e.g., Enter/Space press) on objects, often used withCursorInteractable
for UI buttons. Supports declarative definition viaBUTTONS_HANDLER
/BUTTON
macros.CardHandler
: Specific interface for selecting/moving card-like objects (used in Solitaire).
- Dependency Injection:
CoreDependent
andTickDependent
interfaces provide controlled access to core systems.
Here are some of the planned features and improvements for Zen (subject to change):
- Refinement & Consistency: General improvements to existing components and ensuring a consistent API/style across the framework.
- Screen Module Extraction: Enhancing the screen rendering functionality and extracting it into a standalone library.
- Input System Enhancement: Improving the input system, adding an abstraction layer for input locales, and extracting it into a standalone library.
- Advanced Signal System: Implementing a more flexible signal system (e.g., "wide, narrow, deep" signals) for unified inter-object communication.
- Request/Response System: Extend the signal system to support request-response patterns (getting a return value from a signal/call).
- Dynamic Context System: Introducing a system for managing dynamic context at different levels (application, layer).
- File I/O Utilities: Adding helpers or components for file operations.
- Resource Bundling & Localization: Support for packaging resources and internationalization.
- Logging System: Implementing a robust logging system (e.g., logging to a file) for diagnostics and analysis.
- Serialization/Deserialization: Adding mechanisms for saving and loading application state or data.
- Theming/Color Schemes: Developing a system for easily defining and applying color themes.
- Socket/Network Communication: Basic support for network operations.
Zen is designed as a collection of modular components that can be used independently:
-
Arena-based Allocator: A header-only arena-based memory allocation library extracted from Zen. Provides efficient memory management with both static and dynamic allocation options.
-
Screen Renderer (coming soon): A standalone library for terminal rendering with color support and automatic terminal capability detection.
This modular approach allows you to:
- Use only the components you need
- Integrate individual parts into existing projects
- Benefit from Zen's optimized implementations without adopting the entire framework
The framework includes the following example applications in the examples
directory to showcase different types of console applications that can be built with Zen:
-
Solitaire Game: A complete Klondike Solitaire card game implementation demonstrating advanced framework features like complex game logic, user interface elements (menus, win screen), Unicode card rendering, and terminal-based user interaction.
-
Snake Game: A classic Snake game implementation, illustrating game loop management, real-time input handling, and dynamic object updates within the Zen framework.
-
Rotating 3D Donut: A visually impressive, rotating 3D donut rendered using ASCII art and grayscale shading. This example demonstrates:
- Advanced Rendering Techniques: Projection of a 3D object (a torus) onto a 2D terminal screen.
- Real-time Animation: Smooth rotation of the donut using frame-based updates.
- Grayscale Shading: Dynamic calculation of brightness levels to create a 3D effect using only background colors.
- Efficient Algorithms: Optimized calculations for inverse distance (z-buffering) and normalized luminance.
Zen is designed to be lightweight and portable, with minimal external dependencies.
Required dependencies:
- Standard C Library (libc)
- Math Library (libm)
Optional tools:
tput
- Used for enhanced terminal capability detection. If not available, Zen will fall back to a simpler detection method that works in most environments.
No third-party libraries are required, making Zen easy to integrate into any project or environment.
Zen is designed with performance and resource efficiency in mind. The framework generates highly optimized executables with minimal overhead:
Example Application | Executable Size (Static Release) |
---|---|
Solitaire | 47.6KB |
Snake | 35.3KB |
3D Donut | 35.3KB |
Simple Demo | 35.2KB |
These compact sizes make Zen suitable for resource-constrained environments while still providing rich functionality and developer-friendly abstractions.
# Clone the repository
git clone https://github.com/gooderfreed/Zen.git
cd Zen
# Show available targets
make list
# Build the core libraries
make compile
# Build all examples
make
To see all available targets:
make list
To build a specific example (e.g., Solitaire):
cd examples/solitaire
make list # Show available targets for this example
make static # Build with static library (release mode)
Each example has multiple build targets:
make static
- Build with static library (release)make dynamic
- Build with dynamic library (release)make static_debug
- Build with static library (debug)make dynamic_debug
- Build with dynamic library (debug)
To use Zen in your own project:
- Build the Zen libraries (
make compile
) - Copy the
zen/inc
directory to your project's include path - Link against either
libzen.a
(static) orlibzen.so
(dynamic) - See the examples directory for implementation patterns
For a step-by-step guide and a basic code example demonstrating the fundamental usage of Zen, please refer to the examples/simple_demo directory. This example provides a clear illustration of how to initialize the core, create objects, implement interfaces, and integrate them into the main application loop.
Licensed under the MIT License. See LICENSE for details.
Contributions are welcome! Please feel free to submit a Pull Request.