feat: implement textured line clear effects and refine UI alignment
- **Visual Effects**: Upgraded line clear particles to use the game's block texture instead of simple circles, matching the reference web game's aesthetic. - **Particle Physics**: Tuned particle velocity, gravity, and fade rates for a more dynamic explosion effect. - **Rendering Integration**: Updated [main.cpp](cci:7://file:///d:/Sites/Work/tetris/src/main.cpp:0:0-0:0) and `GameRenderer` to pass the block texture to the effect system and correctly trigger animations upon line completion. - **Menu UI**: Fixed [MenuState](cci:1://file:///d:/Sites/Work/tetris/src/states/MenuState.cpp:19:0-19:55) layout calculations to use fixed logical dimensions (1200x1000), ensuring consistent centering and alignment of the logo, buttons, and settings icon across different window sizes. - **Code Cleanup**: Refactored `PlayingState` to delegate effect triggering to the rendering layer where correct screen coordinates are available.
This commit is contained in:
425
docs/CODE_ORGANIZATION.md
Normal file
425
docs/CODE_ORGANIZATION.md
Normal file
@ -0,0 +1,425 @@
|
||||
# Code Organization & Structure Improvements
|
||||
|
||||
## ✅ Progress Tracker
|
||||
|
||||
### Phase 1: Core Reorganization - IN PROGRESS ⚠️
|
||||
|
||||
**✅ Completed:**
|
||||
|
||||
- ✅ Created new directory structure (interfaces/, application/, assets/, input/, state/, memory/)
|
||||
- ✅ Created core interfaces (IRenderer.h, IAudioSystem.h, IAssetLoader.h, IInputHandler.h, IGameRules.h)
|
||||
- ✅ Created ServiceContainer for dependency injection
|
||||
- ✅ Moved ApplicationManager to core/application/
|
||||
- ✅ Moved AssetManager to core/assets/
|
||||
- ✅ Moved InputManager to core/input/
|
||||
- ✅ Moved StateManager to core/state/
|
||||
- ✅ Moved Game files to gameplay/core/
|
||||
- ✅ Moved Font files to graphics/ui/
|
||||
- ✅ Moved Starfield files to graphics/effects/
|
||||
- ✅ Moved RenderManager and GameRenderer to graphics/renderers/
|
||||
- ✅ Moved LineEffect to gameplay/effects/
|
||||
- ✅ Cleaned up duplicate files
|
||||
- ✅ Audio and Scores files already properly located
|
||||
|
||||
**⚠️ Currently In Progress:**
|
||||
|
||||
- ✅ Updated critical include paths in main.cpp, state files, graphics renderers
|
||||
- ✅ Fixed RenderManager duplicate method declarations
|
||||
- ✅ Resolved GameRenderer.h and LoadingState.cpp include paths
|
||||
- ⚠️ Still fixing remaining include path issues (ongoing)
|
||||
- ⚠️ Still debugging Game.h redefinition errors (ongoing)
|
||||
|
||||
**❌ Next Steps:**
|
||||
|
||||
- ❌ Complete all remaining #include statement updates
|
||||
- ❌ Resolve Game.h redefinition compilation errors
|
||||
- ❌ Test successful compilation of both tetris and tetris_refactored targets
|
||||
- ❌ Update documentation
|
||||
- ❌ Begin Phase 2 - Interface implementation
|
||||
|
||||
### Phase 2: Interface Extraction - NOT STARTED ❌
|
||||
|
||||
### Phase 3: Module Separation - NOT STARTED ❌
|
||||
|
||||
### Phase 4: Documentation & Standards - NOT STARTED ❌
|
||||
|
||||
## Current Structure Analysis
|
||||
|
||||
### Strengths
|
||||
|
||||
- ✅ Clear domain separation (core/, gameplay/, graphics/, audio/, etc.)
|
||||
- ✅ Consistent naming conventions
|
||||
- ✅ Modern C++ header organization
|
||||
- ✅ Proper forward declarations
|
||||
|
||||
### Areas for Improvement
|
||||
|
||||
- ⚠️ Some files in root src/ should be moved to appropriate subdirectories
|
||||
- ⚠️ Missing interfaces/contracts
|
||||
- ⚠️ Some circular dependencies
|
||||
- ⚠️ CMakeLists.txt has duplicate entries
|
||||
|
||||
## Proposed Directory Restructure
|
||||
|
||||
```text
|
||||
src/
|
||||
├── core/ # Core engine systems
|
||||
│ ├── interfaces/ # Abstract interfaces (NEW)
|
||||
│ │ ├── IRenderer.h
|
||||
│ │ ├── IAudioSystem.h
|
||||
│ │ ├── IAssetLoader.h
|
||||
│ │ ├── IInputHandler.h
|
||||
│ │ └── IGameRules.h
|
||||
│ ├── application/ # Application lifecycle (NEW)
|
||||
│ │ ├── ApplicationManager.cpp/h
|
||||
│ │ ├── ServiceContainer.cpp/h
|
||||
│ │ └── SystemCoordinator.cpp/h
|
||||
│ ├── assets/ # Asset management
|
||||
│ │ ├── AssetManager.cpp/h
|
||||
│ │ └── AssetLoader.cpp/h
|
||||
│ ├── input/ # Input handling
|
||||
│ │ └── InputManager.cpp/h
|
||||
│ ├── state/ # State management
|
||||
│ │ └── StateManager.cpp/h
|
||||
│ ├── memory/ # Memory management (NEW)
|
||||
│ │ ├── ObjectPool.h
|
||||
│ │ └── MemoryTracker.h
|
||||
│ └── Config.h
|
||||
│ └── GlobalState.cpp/h
|
||||
│ └── GravityManager.cpp/h
|
||||
├── gameplay/ # Game logic
|
||||
│ ├── core/ # Core game mechanics
|
||||
│ │ ├── Game.cpp/h
|
||||
│ │ ├── Board.cpp/h # Extract from Game.cpp
|
||||
│ │ ├── Piece.cpp/h # Extract from Game.cpp
|
||||
│ │ └── PieceFactory.cpp/h # Extract from Game.cpp
|
||||
│ ├── rules/ # Game rules (NEW)
|
||||
│ │ ├── ClassicTetrisRules.cpp/h
|
||||
│ │ ├── ModernTetrisRules.cpp/h
|
||||
│ │ └── ScoringSystem.cpp/h
|
||||
│ ├── effects/ # Visual effects
|
||||
│ │ └── LineEffect.cpp/h
|
||||
│ └── mechanics/ # Game mechanics (NEW)
|
||||
│ ├── RotationSystem.cpp/h
|
||||
│ ├── KickTable.cpp/h
|
||||
│ └── BagRandomizer.cpp/h
|
||||
├── graphics/ # Rendering and visual
|
||||
│ ├── renderers/ # Different renderers
|
||||
│ │ ├── RenderManager.cpp/h
|
||||
│ │ ├── GameRenderer.cpp/h
|
||||
│ │ ├── UIRenderer.cpp/h # Extract from various places
|
||||
│ │ └── EffectRenderer.cpp/h # New
|
||||
│ ├── effects/ # Visual effects
|
||||
│ │ ├── Starfield.cpp/h
|
||||
│ │ ├── Starfield3D.cpp/h
|
||||
│ │ └── ParticleSystem.cpp/h # New
|
||||
│ ├── ui/ # UI components
|
||||
│ │ ├── Font.cpp/h
|
||||
│ │ ├── Button.cpp/h # New
|
||||
│ │ ├── Panel.cpp/h # New
|
||||
│ │ └── ScoreDisplay.cpp/h # New
|
||||
│ └── resources/ # Graphics resources
|
||||
│ ├── TextureAtlas.cpp/h # New
|
||||
│ └── SpriteManager.cpp/h # New
|
||||
├── audio/ # Audio system
|
||||
│ ├── Audio.cpp/h
|
||||
│ ├── SoundEffect.cpp/h
|
||||
│ ├── MusicManager.cpp/h # New
|
||||
│ └── AudioMixer.cpp/h # New
|
||||
├── persistence/ # Data persistence
|
||||
│ ├── Scores.cpp/h
|
||||
│ ├── Settings.cpp/h # New
|
||||
│ ├── SaveGame.cpp/h # New
|
||||
│ └── Serialization.cpp/h # New
|
||||
├── states/ # Game states
|
||||
│ ├── State.h # Base interface
|
||||
│ ├── LoadingState.cpp/h
|
||||
│ ├── MenuState.cpp/h
|
||||
│ ├── LevelSelectorState.cpp/h
|
||||
│ ├── PlayingState.cpp/h
|
||||
│ ├── PausedState.cpp/h # New
|
||||
│ ├── GameOverState.cpp/h # New
|
||||
│ └── SettingsState.cpp/h # New
|
||||
├── network/ # Future: Multiplayer (NEW)
|
||||
│ ├── NetworkManager.h
|
||||
│ ├── Protocol.h
|
||||
│ └── MultiplayerGame.h
|
||||
├── utils/ # Utilities (NEW)
|
||||
│ ├── Logger.cpp/h
|
||||
│ ├── Timer.cpp/h
|
||||
│ ├── MathUtils.h
|
||||
│ └── StringUtils.h
|
||||
├── platform/ # Platform-specific (NEW)
|
||||
│ ├── Platform.h
|
||||
│ ├── Windows/
|
||||
│ ├── Linux/
|
||||
│ └── macOS/
|
||||
└── main.cpp # Keep original main
|
||||
└── main_new.cpp # Refactored main
|
||||
```
|
||||
|
||||
## Module Dependencies
|
||||
|
||||
### Clean Dependency Graph
|
||||
|
||||
```text
|
||||
Application Layer: main.cpp → ApplicationManager
|
||||
Core Layer: ServiceContainer → All Managers
|
||||
Gameplay Layer: Game → Rules → Mechanics
|
||||
Graphics Layer: RenderManager → Renderers → Resources
|
||||
Audio Layer: AudioSystem → Concrete Implementations
|
||||
Persistence Layer: SaveSystem → Serialization
|
||||
Platform Layer: Platform Abstraction (lowest level)
|
||||
```
|
||||
|
||||
### Dependency Rules
|
||||
|
||||
1. **No circular dependencies**
|
||||
2. **Higher layers can depend on lower layers only**
|
||||
3. **Use interfaces for cross-layer communication**
|
||||
4. **Platform layer has no dependencies on other layers**
|
||||
|
||||
## Header Organization
|
||||
|
||||
### 1. Consistent Header Structure
|
||||
|
||||
```cpp
|
||||
// Standard template for all headers
|
||||
#pragma once
|
||||
|
||||
// System includes
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
// External library includes
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
// Internal includes (from most general to most specific)
|
||||
#include "core/interfaces/IRenderer.h"
|
||||
#include "graphics/resources/Texture.h"
|
||||
#include "MyClass.h"
|
||||
|
||||
// Forward declarations
|
||||
class GameRenderer;
|
||||
class TextureAtlas;
|
||||
|
||||
// Class definition
|
||||
class MyClass {
|
||||
// Public interface first
|
||||
public:
|
||||
// Constructors/Destructors
|
||||
MyClass();
|
||||
~MyClass();
|
||||
|
||||
// Core functionality
|
||||
void update(double deltaTime);
|
||||
void render();
|
||||
|
||||
// Getters/Setters
|
||||
int getValue() const { return value; }
|
||||
void setValue(int v) { value = v; }
|
||||
|
||||
// Private implementation
|
||||
private:
|
||||
// Member variables
|
||||
int value{0};
|
||||
std::unique_ptr<GameRenderer> renderer;
|
||||
|
||||
// Private methods
|
||||
void initializeRenderer();
|
||||
};
|
||||
```
|
||||
|
||||
### 2. Include Guards and PCH
|
||||
|
||||
```cpp
|
||||
// PrecompiledHeaders.h (NEW)
|
||||
#pragma once
|
||||
|
||||
// Standard library
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
|
||||
// External libraries (stable)
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3_ttf/SDL_ttf.h>
|
||||
|
||||
// Common project headers
|
||||
#include "core/Config.h"
|
||||
#include "core/interfaces/IRenderer.h"
|
||||
```
|
||||
|
||||
## Code Style Improvements
|
||||
|
||||
### 1. Consistent Naming Conventions
|
||||
|
||||
```cpp
|
||||
// Classes: PascalCase
|
||||
class GameRenderer;
|
||||
class TextureAtlas;
|
||||
|
||||
// Functions/Methods: camelCase
|
||||
void updateGameLogic();
|
||||
bool isValidPosition();
|
||||
|
||||
// Variables: camelCase
|
||||
int currentScore;
|
||||
double deltaTime;
|
||||
|
||||
// Constants: UPPER_SNAKE_CASE
|
||||
const int MAX_LEVEL = 30;
|
||||
const double GRAVITY_MULTIPLIER = 1.0;
|
||||
|
||||
// Private members: camelCase with suffix
|
||||
class MyClass {
|
||||
private:
|
||||
int memberVariable_; // or m_memberVariable
|
||||
static int staticCounter_;
|
||||
};
|
||||
```
|
||||
|
||||
### 2. Documentation Standards
|
||||
|
||||
```cpp
|
||||
/**
|
||||
* @brief Manages the core game state and logic for Tetris
|
||||
*
|
||||
* The Game class handles piece movement, rotation, line clearing,
|
||||
* and scoring according to classic Tetris rules.
|
||||
*
|
||||
* @example
|
||||
* ```cpp
|
||||
* Game game(startLevel);
|
||||
* game.reset(0);
|
||||
* game.move(-1); // Move left
|
||||
* game.rotate(1); // Rotate clockwise
|
||||
* ```
|
||||
*/
|
||||
class Game {
|
||||
public:
|
||||
/**
|
||||
* @brief Moves the current piece horizontally
|
||||
* @param dx Direction to move (-1 for left, +1 for right)
|
||||
* @return true if the move was successful, false if blocked
|
||||
*/
|
||||
bool move(int dx);
|
||||
|
||||
/**
|
||||
* @brief Gets the current score
|
||||
* @return Current score value
|
||||
* @note Score never decreases during gameplay
|
||||
*/
|
||||
int score() const noexcept { return score_; }
|
||||
};
|
||||
```
|
||||
|
||||
## CMake Improvements
|
||||
|
||||
### 1. Modular CMakeLists.txt
|
||||
|
||||
```cmake
|
||||
# CMakeLists.txt (main)
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(tetris_sdl3 LANGUAGES CXX)
|
||||
|
||||
# Global settings
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Find packages
|
||||
find_package(SDL3 CONFIG REQUIRED)
|
||||
find_package(SDL3_ttf CONFIG REQUIRED)
|
||||
|
||||
# Add subdirectories
|
||||
add_subdirectory(src/core)
|
||||
add_subdirectory(src/gameplay)
|
||||
add_subdirectory(src/graphics)
|
||||
add_subdirectory(src/audio)
|
||||
add_subdirectory(src/persistence)
|
||||
add_subdirectory(src/states)
|
||||
|
||||
# Main executable
|
||||
add_executable(tetris src/main.cpp)
|
||||
target_link_libraries(tetris PRIVATE
|
||||
tetris::core
|
||||
tetris::gameplay
|
||||
tetris::graphics
|
||||
tetris::audio
|
||||
tetris::persistence
|
||||
tetris::states
|
||||
)
|
||||
|
||||
# Tests
|
||||
if(BUILD_TESTING)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
```
|
||||
|
||||
### 2. Module CMakeLists.txt
|
||||
|
||||
```cmake
|
||||
# src/core/CMakeLists.txt
|
||||
add_library(tetris_core
|
||||
ApplicationManager.cpp
|
||||
StateManager.cpp
|
||||
InputManager.cpp
|
||||
AssetManager.cpp
|
||||
GlobalState.cpp
|
||||
GravityManager.cpp
|
||||
)
|
||||
|
||||
add_library(tetris::core ALIAS tetris_core)
|
||||
|
||||
target_include_directories(tetris_core
|
||||
PUBLIC ${CMAKE_SOURCE_DIR}/src
|
||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(tetris_core
|
||||
PUBLIC SDL3::SDL3 SDL3_ttf::SDL3_ttf
|
||||
)
|
||||
|
||||
# Export for use by other modules
|
||||
target_compile_features(tetris_core PUBLIC cxx_std_20)
|
||||
```
|
||||
|
||||
## Implementation Timeline
|
||||
|
||||
### Phase 1: Core Reorganization (Week 1-2)
|
||||
|
||||
1. Create new directory structure
|
||||
2. Move files to appropriate locations
|
||||
3. Update CMakeLists.txt files
|
||||
4. Fix include paths
|
||||
|
||||
### Phase 2: Interface Extraction (Week 3-4)
|
||||
|
||||
1. Create interface headers
|
||||
2. Update implementations to use interfaces
|
||||
3. Add dependency injection container
|
||||
|
||||
### Phase 3: Module Separation (Week 5-6)
|
||||
|
||||
1. Split large classes (Game, ApplicationManager)
|
||||
2. Create separate CMake modules
|
||||
3. Establish clean dependency graph
|
||||
|
||||
### Phase 4: Documentation & Standards (Week 7-8)
|
||||
|
||||
1. Add comprehensive documentation
|
||||
2. Implement coding standards
|
||||
3. Add static analysis tools
|
||||
4. Update build scripts
|
||||
|
||||
## Benefits
|
||||
|
||||
1. **Maintainability**: Clear module boundaries and responsibilities
|
||||
2. **Testability**: Easy to mock and test individual components
|
||||
3. **Scalability**: Easy to add new features without affecting existing code
|
||||
4. **Team Development**: Multiple developers can work on different modules
|
||||
5. **Code Reuse**: Modular design enables component reuse
|
||||
163
docs/PERFORMANCE_OPTIMIZATION.md
Normal file
163
docs/PERFORMANCE_OPTIMIZATION.md
Normal file
@ -0,0 +1,163 @@
|
||||
# Performance Optimization Recommendations
|
||||
|
||||
## Current Performance Analysis
|
||||
|
||||
### Memory Management
|
||||
- **Good**: Proper RAII patterns, smart pointers
|
||||
- **Improvement**: Object pooling for frequently created/destroyed objects
|
||||
|
||||
### Rendering Performance
|
||||
- **Current**: SDL3 with immediate mode rendering
|
||||
- **Optimization Opportunities**: Batch rendering, texture atlasing
|
||||
|
||||
### Game Logic Performance
|
||||
- **Current**: Simple collision detection, adequate for Tetris
|
||||
- **Good**: Efficient board representation using flat array
|
||||
|
||||
## Specific Optimizations
|
||||
|
||||
### 1. Object Pooling for Game Pieces
|
||||
|
||||
```cpp
|
||||
// src/gameplay/PiecePool.h
|
||||
class PiecePool {
|
||||
private:
|
||||
std::vector<std::unique_ptr<Piece>> available;
|
||||
std::vector<std::unique_ptr<Piece>> inUse;
|
||||
|
||||
public:
|
||||
std::unique_ptr<Piece> acquire(PieceType type);
|
||||
void release(std::unique_ptr<Piece> piece);
|
||||
void preAllocate(size_t count);
|
||||
};
|
||||
```
|
||||
|
||||
### 2. Texture Atlas for UI Elements
|
||||
|
||||
```cpp
|
||||
// src/graphics/TextureAtlas.h
|
||||
class TextureAtlas {
|
||||
private:
|
||||
SDL_Texture* atlasTexture;
|
||||
std::unordered_map<std::string, SDL_Rect> regions;
|
||||
|
||||
public:
|
||||
void loadAtlas(const std::string& atlasPath, const std::string& configPath);
|
||||
SDL_Rect getRegion(const std::string& name) const;
|
||||
SDL_Texture* getTexture() const { return atlasTexture; }
|
||||
};
|
||||
```
|
||||
|
||||
### 3. Batch Rendering System
|
||||
|
||||
```cpp
|
||||
// src/graphics/BatchRenderer.h
|
||||
class BatchRenderer {
|
||||
private:
|
||||
struct RenderCommand {
|
||||
SDL_Texture* texture;
|
||||
SDL_Rect srcRect;
|
||||
SDL_Rect dstRect;
|
||||
};
|
||||
|
||||
std::vector<RenderCommand> commands;
|
||||
|
||||
public:
|
||||
void addSprite(SDL_Texture* texture, const SDL_Rect& src, const SDL_Rect& dst);
|
||||
void flush();
|
||||
void clear();
|
||||
};
|
||||
```
|
||||
|
||||
### 4. Memory-Efficient Board Representation
|
||||
|
||||
```cpp
|
||||
// Current: std::array<int, COLS*ROWS> board (40 integers = 160 bytes)
|
||||
// Optimized: Bitset representation for filled/empty + color array for occupied cells
|
||||
|
||||
class OptimizedBoard {
|
||||
private:
|
||||
std::bitset<COLS * ROWS> occupied; // 25 bytes (200 bits)
|
||||
std::array<uint8_t, COLS * ROWS> colors; // 200 bytes, but only for occupied cells
|
||||
|
||||
public:
|
||||
bool isOccupied(int x, int y) const;
|
||||
uint8_t getColor(int x, int y) const;
|
||||
void setCell(int x, int y, uint8_t color);
|
||||
void clearCell(int x, int y);
|
||||
};
|
||||
```
|
||||
|
||||
### 5. Cache-Friendly Data Structures
|
||||
|
||||
```cpp
|
||||
// Group related data together for better cache locality
|
||||
struct GameState {
|
||||
// Hot data (frequently accessed)
|
||||
std::array<uint8_t, COLS * ROWS> board;
|
||||
Piece currentPiece;
|
||||
int score;
|
||||
int level;
|
||||
int lines;
|
||||
|
||||
// Cold data (less frequently accessed)
|
||||
std::vector<PieceType> bag;
|
||||
Piece holdPiece;
|
||||
bool gameOver;
|
||||
bool paused;
|
||||
};
|
||||
```
|
||||
|
||||
## Performance Measurement
|
||||
|
||||
### 1. Add Profiling Infrastructure
|
||||
|
||||
```cpp
|
||||
// src/core/Profiler.h
|
||||
class Profiler {
|
||||
private:
|
||||
std::unordered_map<std::string, std::chrono::high_resolution_clock::time_point> startTimes;
|
||||
std::unordered_map<std::string, double> averageTimes;
|
||||
|
||||
public:
|
||||
void beginTimer(const std::string& name);
|
||||
void endTimer(const std::string& name);
|
||||
void printStats();
|
||||
};
|
||||
|
||||
// Usage:
|
||||
// profiler.beginTimer("GameLogic");
|
||||
// game.update(deltaTime);
|
||||
// profiler.endTimer("GameLogic");
|
||||
```
|
||||
|
||||
### 2. Frame Rate Optimization
|
||||
|
||||
```cpp
|
||||
// Target 60 FPS with consistent frame timing
|
||||
class FrameRateManager {
|
||||
private:
|
||||
std::chrono::high_resolution_clock::time_point lastFrame;
|
||||
double targetFrameTime = 1000.0 / 60.0; // 16.67ms
|
||||
|
||||
public:
|
||||
void beginFrame();
|
||||
void endFrame();
|
||||
double getDeltaTime() const;
|
||||
bool shouldSkipFrame() const;
|
||||
};
|
||||
```
|
||||
|
||||
## Expected Performance Gains
|
||||
|
||||
1. **Object Pooling**: 30-50% reduction in allocation overhead
|
||||
2. **Texture Atlas**: 20-30% improvement in rendering performance
|
||||
3. **Batch Rendering**: 40-60% reduction in draw calls
|
||||
4. **Optimized Board**: 60% reduction in memory usage
|
||||
5. **Cache Optimization**: 10-20% improvement in game logic performance
|
||||
|
||||
## Implementation Priority
|
||||
|
||||
1. **High Impact, Low Effort**: Profiling infrastructure, frame rate management
|
||||
2. **Medium Impact, Medium Effort**: Object pooling, optimized board representation
|
||||
3. **High Impact, High Effort**: Texture atlas, batch rendering system
|
||||
128
docs/REFACTORING_SOLID_PRINCIPLES.md
Normal file
128
docs/REFACTORING_SOLID_PRINCIPLES.md
Normal file
@ -0,0 +1,128 @@
|
||||
# SOLID Principles Refactoring Plan
|
||||
|
||||
## Current Architecture Issues
|
||||
|
||||
### 1. Single Responsibility Principle (SRP) Violations
|
||||
- `ApplicationManager` handles initialization, coordination, rendering coordination, and asset management
|
||||
- `Game` class mixes game logic with some presentation concerns
|
||||
|
||||
### 2. Open/Closed Principle (OCP) Opportunities
|
||||
- Hard-coded piece types and behaviors
|
||||
- Limited extensibility for new game modes or rule variations
|
||||
|
||||
### 3. Dependency Inversion Principle (DIP) Missing
|
||||
- Concrete dependencies instead of interfaces
|
||||
- Direct instantiation rather than dependency injection
|
||||
|
||||
## Proposed Improvements
|
||||
|
||||
### 1. Extract Interfaces
|
||||
|
||||
```cpp
|
||||
// src/core/interfaces/IRenderer.h
|
||||
class IRenderer {
|
||||
public:
|
||||
virtual ~IRenderer() = default;
|
||||
virtual void clear(uint8_t r, uint8_t g, uint8_t b, uint8_t a) = 0;
|
||||
virtual void present() = 0;
|
||||
virtual SDL_Renderer* getSDLRenderer() = 0;
|
||||
};
|
||||
|
||||
// src/core/interfaces/IAudioSystem.h
|
||||
class IAudioSystem {
|
||||
public:
|
||||
virtual ~IAudioSystem() = default;
|
||||
virtual void playSound(const std::string& name) = 0;
|
||||
virtual void playMusic(const std::string& name) = 0;
|
||||
virtual void setMasterVolume(float volume) = 0;
|
||||
};
|
||||
|
||||
// src/core/interfaces/IAssetLoader.h
|
||||
class IAssetLoader {
|
||||
public:
|
||||
virtual ~IAssetLoader() = default;
|
||||
virtual SDL_Texture* loadTexture(const std::string& path) = 0;
|
||||
virtual void loadFont(const std::string& name, const std::string& path, int size) = 0;
|
||||
};
|
||||
```
|
||||
|
||||
### 2. Dependency Injection Container
|
||||
|
||||
```cpp
|
||||
// src/core/ServiceContainer.h
|
||||
class ServiceContainer {
|
||||
private:
|
||||
std::unordered_map<std::type_index, std::shared_ptr<void>> services;
|
||||
|
||||
public:
|
||||
template<typename T>
|
||||
void registerService(std::shared_ptr<T> service) {
|
||||
services[std::type_index(typeid(T))] = service;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::shared_ptr<T> getService() {
|
||||
auto it = services.find(std::type_index(typeid(T)));
|
||||
if (it != services.end()) {
|
||||
return std::static_pointer_cast<T>(it->second);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 3. Break Down ApplicationManager
|
||||
|
||||
```cpp
|
||||
// src/core/ApplicationLifecycle.h
|
||||
class ApplicationLifecycle {
|
||||
public:
|
||||
bool initialize(int argc, char* argv[]);
|
||||
void run();
|
||||
void shutdown();
|
||||
};
|
||||
|
||||
// src/core/SystemCoordinator.h
|
||||
class SystemCoordinator {
|
||||
public:
|
||||
void initializeSystems(ServiceContainer& container);
|
||||
void updateSystems(double deltaTime);
|
||||
void shutdownSystems();
|
||||
};
|
||||
```
|
||||
|
||||
### 4. Strategy Pattern for Game Rules
|
||||
|
||||
```cpp
|
||||
// src/gameplay/interfaces/IGameRules.h
|
||||
class IGameRules {
|
||||
public:
|
||||
virtual ~IGameRules() = default;
|
||||
virtual int calculateScore(int linesCleared, int level) = 0;
|
||||
virtual double getGravitySpeed(int level) = 0;
|
||||
virtual bool shouldLevelUp(int lines) = 0;
|
||||
};
|
||||
|
||||
// src/gameplay/rules/ClassicTetrisRules.h
|
||||
class ClassicTetrisRules : public IGameRules {
|
||||
public:
|
||||
int calculateScore(int linesCleared, int level) override;
|
||||
double getGravitySpeed(int level) override;
|
||||
bool shouldLevelUp(int lines) override;
|
||||
};
|
||||
```
|
||||
|
||||
## Implementation Priority
|
||||
|
||||
1. **Phase 1**: Extract core interfaces (IRenderer, IAudioSystem)
|
||||
2. **Phase 2**: Implement dependency injection container
|
||||
3. **Phase 3**: Break down ApplicationManager responsibilities
|
||||
4. **Phase 4**: Add strategy patterns for game rules
|
||||
5. **Phase 5**: Improve testability with mock implementations
|
||||
|
||||
## Benefits
|
||||
|
||||
- **Testability**: Easy to mock dependencies for unit tests
|
||||
- **Extensibility**: New features without modifying existing code
|
||||
- **Maintainability**: Clear responsibilities and loose coupling
|
||||
- **Flexibility**: Easy to swap implementations (e.g., different renderers)
|
||||
309
docs/TESTING_STRATEGY.md
Normal file
309
docs/TESTING_STRATEGY.md
Normal file
@ -0,0 +1,309 @@
|
||||
# Testing Strategy Enhancement
|
||||
|
||||
## Current Testing State
|
||||
|
||||
### Existing Tests
|
||||
- ✅ GravityTests.cpp - Basic gravity manager testing
|
||||
- ✅ Catch2 framework integration
|
||||
- ✅ CTest integration in CMake
|
||||
|
||||
### Coverage Gaps
|
||||
- ❌ Game logic testing (piece movement, rotation, line clearing)
|
||||
- ❌ Collision detection testing
|
||||
- ❌ Scoring system testing
|
||||
- ❌ State management testing
|
||||
- ❌ Integration tests
|
||||
- ❌ Performance tests
|
||||
|
||||
## Comprehensive Testing Strategy
|
||||
|
||||
### 1. Unit Tests Expansion
|
||||
|
||||
```cpp
|
||||
// tests/GameLogicTests.cpp
|
||||
TEST_CASE("Piece Movement", "[game][movement]") {
|
||||
Game game(0);
|
||||
Piece originalPiece = game.current();
|
||||
|
||||
SECTION("Move left when possible") {
|
||||
game.move(-1);
|
||||
REQUIRE(game.current().x == originalPiece.x - 1);
|
||||
}
|
||||
|
||||
SECTION("Cannot move left at boundary") {
|
||||
// Move piece to left edge
|
||||
while (game.current().x > 0) {
|
||||
game.move(-1);
|
||||
}
|
||||
int edgeX = game.current().x;
|
||||
game.move(-1);
|
||||
REQUIRE(game.current().x == edgeX); // Should not move further
|
||||
}
|
||||
}
|
||||
|
||||
// tests/CollisionTests.cpp
|
||||
TEST_CASE("Collision Detection", "[game][collision]") {
|
||||
Game game(0);
|
||||
|
||||
SECTION("Piece collides with bottom") {
|
||||
// Force piece to bottom
|
||||
while (!game.isGameOver()) {
|
||||
game.hardDrop();
|
||||
if (game.isGameOver()) break;
|
||||
}
|
||||
// Verify collision behavior
|
||||
}
|
||||
|
||||
SECTION("Piece collides with placed blocks") {
|
||||
// Place a block manually
|
||||
// Test collision with new piece
|
||||
}
|
||||
}
|
||||
|
||||
// tests/ScoringTests.cpp
|
||||
TEST_CASE("Scoring System", "[game][scoring]") {
|
||||
Game game(0);
|
||||
int initialScore = game.score();
|
||||
|
||||
SECTION("Single line clear") {
|
||||
// Set up board with almost complete line
|
||||
// Clear line and verify score increase
|
||||
}
|
||||
|
||||
SECTION("Tetris (4 lines)") {
|
||||
// Set up board for Tetris
|
||||
// Verify bonus scoring
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Mock Objects for Testing
|
||||
|
||||
```cpp
|
||||
// tests/mocks/MockRenderer.h
|
||||
class MockRenderer : public IRenderer {
|
||||
private:
|
||||
mutable std::vector<std::string> calls;
|
||||
|
||||
public:
|
||||
void clear(uint8_t r, uint8_t g, uint8_t b, uint8_t a) override {
|
||||
calls.push_back("clear");
|
||||
}
|
||||
|
||||
void present() override {
|
||||
calls.push_back("present");
|
||||
}
|
||||
|
||||
SDL_Renderer* getSDLRenderer() override {
|
||||
return nullptr; // Mock implementation
|
||||
}
|
||||
|
||||
const std::vector<std::string>& getCalls() const { return calls; }
|
||||
void clearCalls() { calls.clear(); }
|
||||
};
|
||||
|
||||
// tests/mocks/MockAudioSystem.h
|
||||
class MockAudioSystem : public IAudioSystem {
|
||||
private:
|
||||
std::vector<std::string> playedSounds;
|
||||
|
||||
public:
|
||||
void playSound(const std::string& name) override {
|
||||
playedSounds.push_back(name);
|
||||
}
|
||||
|
||||
void playMusic(const std::string& name) override {
|
||||
playedSounds.push_back("music:" + name);
|
||||
}
|
||||
|
||||
void setMasterVolume(float volume) override {
|
||||
// Mock implementation
|
||||
}
|
||||
|
||||
const std::vector<std::string>& getPlayedSounds() const { return playedSounds; }
|
||||
};
|
||||
```
|
||||
|
||||
### 3. Integration Tests
|
||||
|
||||
```cpp
|
||||
// tests/integration/StateTransitionTests.cpp
|
||||
TEST_CASE("State Transitions", "[integration][states]") {
|
||||
ApplicationManager app;
|
||||
// Mock dependencies
|
||||
|
||||
SECTION("Loading to Menu transition") {
|
||||
// Simulate loading completion
|
||||
// Verify menu state activation
|
||||
}
|
||||
|
||||
SECTION("Menu to Game transition") {
|
||||
// Simulate start game action
|
||||
// Verify game state initialization
|
||||
}
|
||||
}
|
||||
|
||||
// tests/integration/GamePlayTests.cpp
|
||||
TEST_CASE("Complete Game Session", "[integration][gameplay]") {
|
||||
Game game(0);
|
||||
|
||||
SECTION("Play until first line clear") {
|
||||
// Simulate complete game session
|
||||
// Verify all systems work together
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Performance Tests
|
||||
|
||||
```cpp
|
||||
// tests/performance/PerformanceTests.cpp
|
||||
TEST_CASE("Game Logic Performance", "[performance]") {
|
||||
Game game(0);
|
||||
|
||||
SECTION("1000 piece drops should complete in reasonable time") {
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
game.hardDrop();
|
||||
if (game.isGameOver()) {
|
||||
game.reset(0);
|
||||
}
|
||||
}
|
||||
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||
|
||||
REQUIRE(duration.count() < 100); // Should complete in under 100ms
|
||||
}
|
||||
}
|
||||
|
||||
// tests/performance/MemoryTests.cpp
|
||||
TEST_CASE("Memory Usage", "[performance][memory]") {
|
||||
SECTION("No memory leaks during gameplay") {
|
||||
size_t initialMemory = getCurrentMemoryUsage();
|
||||
|
||||
{
|
||||
Game game(0);
|
||||
// Simulate gameplay
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
game.hardDrop();
|
||||
if (game.isGameOver()) game.reset(0);
|
||||
}
|
||||
}
|
||||
|
||||
size_t finalMemory = getCurrentMemoryUsage();
|
||||
REQUIRE(finalMemory <= initialMemory + 1024); // Allow small overhead
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Property-Based Testing
|
||||
|
||||
```cpp
|
||||
// tests/property/PropertyTests.cpp
|
||||
TEST_CASE("Property: Game state consistency", "[property]") {
|
||||
Game game(0);
|
||||
|
||||
SECTION("Score never decreases") {
|
||||
int previousScore = game.score();
|
||||
|
||||
// Perform random valid actions
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
performRandomValidAction(game);
|
||||
REQUIRE(game.score() >= previousScore);
|
||||
previousScore = game.score();
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Board state remains valid") {
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
performRandomValidAction(game);
|
||||
REQUIRE(isBoardStateValid(game));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Test Data Management
|
||||
|
||||
```cpp
|
||||
// tests/fixtures/GameFixtures.h
|
||||
class GameFixtures {
|
||||
public:
|
||||
static Game createGameWithAlmostFullLine() {
|
||||
Game game(0);
|
||||
// Set up specific board state
|
||||
return game;
|
||||
}
|
||||
|
||||
static Game createGameNearGameOver() {
|
||||
Game game(0);
|
||||
// Fill board almost to top
|
||||
return game;
|
||||
}
|
||||
|
||||
static std::vector<PieceType> createTetrisPieceSequence() {
|
||||
return {I, O, T, S, Z, J, L};
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Test Automation & CI
|
||||
|
||||
### 1. GitHub Actions Configuration
|
||||
|
||||
```yaml
|
||||
# .github/workflows/tests.yml
|
||||
name: Tests
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||
build-type: [Debug, Release]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
# Install vcpkg and dependencies
|
||||
- name: Configure CMake
|
||||
run: cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.build-type }}
|
||||
- name: Build
|
||||
run: cmake --build build --config ${{ matrix.build-type }}
|
||||
- name: Test
|
||||
run: ctest --test-dir build --build-config ${{ matrix.build-type }}
|
||||
```
|
||||
|
||||
### 2. Code Coverage
|
||||
|
||||
```cmake
|
||||
# Add to CMakeLists.txt
|
||||
option(ENABLE_COVERAGE "Enable code coverage" OFF)
|
||||
|
||||
if(ENABLE_COVERAGE)
|
||||
target_compile_options(tetris PRIVATE --coverage)
|
||||
target_link_libraries(tetris PRIVATE --coverage)
|
||||
endif()
|
||||
```
|
||||
|
||||
## Quality Metrics Targets
|
||||
|
||||
- **Unit Test Coverage**: > 80%
|
||||
- **Integration Test Coverage**: > 60%
|
||||
- **Performance Regression**: < 5% per release
|
||||
- **Memory Leak Detection**: 0 leaks in test suite
|
||||
- **Static Analysis**: 0 critical issues
|
||||
|
||||
## Implementation Priority
|
||||
|
||||
1. **Phase 1**: Core game logic unit tests (movement, rotation, collision)
|
||||
2. **Phase 2**: Mock objects and dependency injection for testability
|
||||
3. **Phase 3**: Integration tests for state management
|
||||
4. **Phase 4**: Performance and memory tests
|
||||
5. **Phase 5**: Property-based testing and fuzzing
|
||||
6. **Phase 6**: CI/CD pipeline with automated testing
|
||||
Reference in New Issue
Block a user