Given the context of creating a file tree for an Entity Component System (ECS) within an NX MonoRepo environment, and utilizing Excalibur.js for game development with TypeScript, we will adopt a functional programming paradigm. We’ll structure this within an NX Shell library organization, adhering to principles of clean architecture, DRY (Don't Repeat Yourself), and SOLID principles, albeit adjusted for functional programming as opposed to OOP (Object-Oriented Programming).
Project Structure The NX standalone configuration allows for a modular approach, supporting multiple projects within a single repository. For an ECS system using Excalibur.js, the structure will need to accommodate components, systems, entities, and possibly utility libraries for the game logic, all while ensuring a clean separation of concerns.
File Tree Below is a proposed file tree, considering a clean architecture and functional programming principles:
/libs
/ecs-core
/src
/lib
/components
index.ts
transform.ts
renderable.ts
/systems
index.ts
movementSystem.ts
renderingSystem.ts
/entities
index.ts
entityFactory.ts
/utils
index.ts
index.ts
/game-logic
/src
/lib
/levels
index.ts
level1.ts
/actors
index.ts
player.ts
enemy.ts
/hooks
index.ts
useGameLoop.ts
/state
index.ts
gameState.ts
index.ts
/ui-components
/src
/lib
/buttons
index.ts
/overlays
index.ts
gameOverOverlay.ts
index.ts
/game-app
/src
main.ts
In this project, we adhere to the principles of functional programming and ES6 standards. Ensure your contributions reflect the following guidelines:
let
and const
for variable declarations, arrow functions, template literals, destructuring, and the spread operator.While SOLID principles are traditionally OOP concepts, their essence can be adapted to functional programming:
Single Responsibility: Functions and modules should have a single responsibility. This is naturally encouraged in functional programming by composing complex operations from smaller, reusable functions. Open/Closed: Functions are open for extension but closed for modification. This can be achieved by designing functions that accept callbacks or higher-order functions for extensions. Liskov Substitution: In a functional context, this translates to ensuring that function signatures are consistent and predictable, allowing functions to be replaced as needed without breaking the system. Interface Segregation and Dependency Inversion: While interfaces and dependency inversion are OOP constructs, the essence is to depend on abstractions rather than concrete implementations. This can be achieved by using function signatures and higher-order functions.
graph TD;
A[Develop Branch] --> B[Create Feature Branch];
B --> C{Work on Feature};
C --> D[Commit Changes];
D --> E[Push to Feature Branch];
E --> F[Open Pull Request];
F --> G[Code Review & Adjustments];
G --> H{PR Approved?};
H -->|Yes| I[Merge PR to Develop];
H -->|No| C;