Console Jack is built using a multi-threaded, subsystem-based architecture with an Entity Component System (ECS) for game logic and a custom UI framework for terminal rendering.
All core subsystems use the enum singleton pattern (Effective Java, Item 3) for JVM-wide singleton enforcement:
public enum MasterSubsystem implements Subsystem {
INSTANCE;
// implementation
}Benefits:
- Thread-safe by default
- Serialization-safe
- Reflection-proof
- Memory efficient
Each major concern runs in its own thread to prevent blocking:
- Master Thread: Game loop and ECS updates
- Render Thread: Terminal UI rendering
- Input Thread: Keyboard event processing
- Audio Thread: Sound management
Internal engine components — game code must not access these directly:
MasterSubsystem: Orchestrates the main game loop at 8 UPSRenderSubsystem: Manages Lanterna terminal renderingInputSubsystem: Handles keyboard input eventsAudioSubsystem: Manages sound effects and background musicStateMachineCoordinator: Coordinates game state transitions (replaces the formerStateMachineSubsystem)EntityCoordinator: Manages the sharedEntityPooland ordered ECS system list
Static facade classes — the public API surface for game code:
EntityManager: Primary API for creating/destroying entities and registering ECS systemsMasterManager: Engine lifecycle control (graceful shutdown)StateMachineManager: State push/pop/replace operationsRenderManager: Rendering commands and utilitiesInputManager: Input event distributionAudioManager: Sound playback control
Design rule: game code (states, components) calls managers only. Managers delegate to the internal coordinators/subsystems. This boundary lets engine internals change without touching game-side call sites.
Entity Component System implementation:
EntityPool
├── Entity (ID-based)
├── Component (data containers)
│ ├── Position
│ ├── Visual
│ └── Layer
└── EcsSystem (logic processors)
└── DisplayListSystem
Custom terminal UI components:
UIComponent (base)
├── UIWidget (single components)
│ ├── Label
│ └── MenuItem
└── UIContainer (composite components)
└── Menu
Key Interfaces:
Focusable: Components that can receive input focusInputHandler: Components that process input events
Concrete implementations of game screens:
MainMenuState: Main menu with navigationGameplayState: Blackjack game logicPauseState: Pause menu overlay
Blackjack-specific ECS components:
Card: Playing card dataCardArt: Visual representationCardSprite: Rendering information
while (running) {
long startTime = System.nanoTime();
// Update ECS systems
for (EcsSystem system : ecsSystems) {
system.update(entityPool);
}
// Update current game state
StateMachineManager.getCurrentState().update();
// Sleep to maintain target UPS
sleepForTargetUps(startTime);
}- Lock-free queues for inter-thread communication
- Immutable records for data transfer (
RenderCmd,ZLayerData) - Volatile flags for state coordination
StateMachineManager
├── State Stack (LIFO)
├── Push State (overlay)
├── Pop State (return)
└── Replace State (transition)
interface LoopableState {
void onEnter(); // Initialize state
void update(); // Per-frame logic
void onExit(); // Cleanup
}- Entities: Unique integer IDs
- Component Storage: Type-indexed maps
- System Processing: Component iteration
// Data-only components
public record Position(int x, int y) implements Component {}
public record Visual(String text, TextColor color) implements Component {}public class DisplayListSystem implements EcsSystem {
@Override
public void update(EntityPool entityPool) {
// Query entities with Position + Visual
// Generate render commands
// Submit to render subsystem
}
}public enum ZLayer {
BACKGROUND(0),
GAME_OBJECTS(100),
UI_BACKGROUND(200),
UI_FOREGROUND(300),
DEBUG_OVERLAY(400);
}ECS Systems → RenderCmd → RenderSubsystem → Lanterna → Terminal
interface Subsystem {
void init() throws ResourceInitializationException;
void start();
void stop();
void cleanUp() throws ResourceCleanupException;
}- Fonts: TTF files in
resources/fonts/ - Audio: WAV files in
resources/audio/ - Configurations: Properties files in
resources/
RuntimeException
├── ResourceInitializationException
└── ResourceCleanupException
- Graceful degradation for non-critical failures
- Logging at appropriate levels
- Resource cleanup in finally blocks
- Game loop: 8 UPS target (125ms budget)
- Render loop: 60 FPS target when possible
- ECS queries: Optimized for component iteration
- Object pooling for frequently created objects
- Immutable records to reduce garbage collection
- Primitive collections where appropriate
- Create record implementing
Component - Add to relevant entities in
EntityPool - Create system to process the component
- Implement
LoopableState - Add to game package
- Register transitions in existing states
- Extend
UIWidgetorUIContainer - Implement
Focusableif interactive - Handle input in
handleInput()method
- Component logic: Isolated component behavior
- System logic: ECS system processing
- State logic: State transition behavior
- Subsystem coordination: Thread interaction
- State machine: State transition flows
- UI framework: Component interaction
- Game loop timing: UPS consistency
- Memory usage: Garbage collection impact
- Thread contention: Lock-free communication