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:
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