refactoring app name to spacetris and new icon
This commit is contained in:
6
.github/copilot-instructions.md
vendored
6
.github/copilot-instructions.md
vendored
@ -1,4 +1,4 @@
|
||||
# Copilot Instructions — Tetris (C++ SDL3)
|
||||
# Copilot Instructions — Spacetris (C++ SDL3)
|
||||
|
||||
Purpose: Speed up development on this native SDL3 Tetris. Follow these conventions to keep builds reproducible and packages shippable.
|
||||
|
||||
@ -9,11 +9,9 @@ Purpose: Speed up development on this native SDL3 Tetris. Follow these conventio
|
||||
- Assets: `assets/` (images/music/fonts), plus `FreeSans.ttf` at repo root.
|
||||
|
||||
## Build and run
|
||||
- Configure and build Release:
|
||||
- CMake picks up vcpkg toolchain if found (`VCPKG_ROOT`, local `vcpkg/`, or user path). Required packages: `sdl3`, `sdl3-ttf` (see `vcpkg.json`).
|
||||
- Typical sequence (PowerShell): `cmake -S . -B build-release -DCMAKE_BUILD_TYPE=Release` then `cmake --build build-release --config Release`.
|
||||
- Packaging: `.\build-production.ps1` creates `dist/TetrisGame/` with exe, DLLs, assets, README, and ZIP; it can clean via `-Clean` and package-only via `-PackageOnly`.
|
||||
- MSVC generator builds are under `build-msvc/` when using Visual Studio.
|
||||
Packaging: `.\build-production.ps1` creates `dist/SpacetrisGame/` with exe, DLLs, assets, README, and ZIP; it can clean via `-Clean` and package-only via `-PackageOnly`.
|
||||
|
||||
## Runtime dependencies
|
||||
- Links: `SDL3::SDL3`, `SDL3_ttf::SDL3_ttf`; on Windows also `mfplat`, `mfreadwrite`, `mfuuid` for media.
|
||||
|
||||
36
.github/workflows/build.yml
vendored
36
.github/workflows/build.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: Build and Package Tetris
|
||||
name: Build and Package Spacetris
|
||||
|
||||
on:
|
||||
push:
|
||||
@ -43,20 +43,20 @@ jobs:
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: tetris-windows-x64
|
||||
path: dist/TetrisGame/
|
||||
name: spacetris-windows-x64
|
||||
path: dist/SpacetrisGame/
|
||||
|
||||
- name: Create Release ZIP
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
run: |
|
||||
cd dist
|
||||
7z a ../TetrisGame-Windows-x64.zip TetrisGame/
|
||||
7z a ../SpacetrisGame-Windows-x64.zip SpacetrisGame/
|
||||
|
||||
- name: Release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: TetrisGame-Windows-x64.zip
|
||||
files: SpacetrisGame-Windows-x64.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@ -83,32 +83,32 @@ jobs:
|
||||
|
||||
- name: Package
|
||||
run: |
|
||||
mkdir -p dist/TetrisGame-Linux
|
||||
cp build/tetris dist/TetrisGame-Linux/
|
||||
cp -r assets dist/TetrisGame-Linux/
|
||||
cp FreeSans.ttf dist/TetrisGame-Linux/
|
||||
echo '#!/bin/bash' > dist/TetrisGame-Linux/launch-tetris.sh
|
||||
echo 'cd "$(dirname "$0")"' >> dist/TetrisGame-Linux/launch-tetris.sh
|
||||
echo './tetris' >> dist/TetrisGame-Linux/launch-tetris.sh
|
||||
chmod +x dist/TetrisGame-Linux/launch-tetris.sh
|
||||
chmod +x dist/TetrisGame-Linux/tetris
|
||||
mkdir -p dist/SpacetrisGame-Linux
|
||||
cp build/spacetris dist/SpacetrisGame-Linux/
|
||||
cp -r assets dist/SpacetrisGame-Linux/
|
||||
cp FreeSans.ttf dist/SpacetrisGame-Linux/
|
||||
echo '#!/bin/bash' > dist/SpacetrisGame-Linux/launch-spacetris.sh
|
||||
echo 'cd "$(dirname "$0")"' >> dist/SpacetrisGame-Linux/launch-spacetris.sh
|
||||
echo './spacetris' >> dist/SpacetrisGame-Linux/launch-spacetris.sh
|
||||
chmod +x dist/SpacetrisGame-Linux/launch-spacetris.sh
|
||||
chmod +x dist/SpacetrisGame-Linux/spacetris
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: tetris-linux-x64
|
||||
path: dist/TetrisGame-Linux/
|
||||
name: spacetris-linux-x64
|
||||
path: dist/SpacetrisGame-Linux/
|
||||
|
||||
- name: Create Release TAR
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
run: |
|
||||
cd dist
|
||||
tar -czf ../TetrisGame-Linux-x64.tar.gz TetrisGame-Linux/
|
||||
tar -czf ../SpacetrisGame-Linux-x64.tar.gz SpacetrisGame-Linux/
|
||||
|
||||
- name: Release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: TetrisGame-Linux-x64.tar.gz
|
||||
files: SpacetrisGame-Linux-x64.tar.gz
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,4 +1,4 @@
|
||||
# .gitignore for Tetris (native C++ project and web subproject)
|
||||
# .gitignore for Spacetris (native C++ project and web subproject)
|
||||
|
||||
# Visual Studio / VS artifacts
|
||||
.vs/
|
||||
|
||||
@ -15,7 +15,7 @@ if(DEFINED CMAKE_TOOLCHAIN_FILE)
|
||||
set(CMAKE_TOOLCHAIN_FILE "${CMAKE_TOOLCHAIN_FILE}" CACHE STRING "" FORCE)
|
||||
endif()
|
||||
|
||||
project(tetris_sdl3 LANGUAGES CXX)
|
||||
project(spacetris_sdl3 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
@ -72,27 +72,39 @@ set(TETRIS_SOURCES
|
||||
if(APPLE)
|
||||
set(APP_ICON "${CMAKE_SOURCE_DIR}/assets/favicon/AppIcon.icns")
|
||||
if(EXISTS "${APP_ICON}")
|
||||
add_executable(tetris MACOSX_BUNDLE ${TETRIS_SOURCES} "${APP_ICON}")
|
||||
add_executable(spacetris MACOSX_BUNDLE ${TETRIS_SOURCES} "${APP_ICON}")
|
||||
set_source_files_properties("${APP_ICON}" PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
|
||||
set_target_properties(tetris PROPERTIES
|
||||
set_target_properties(spacetris PROPERTIES
|
||||
MACOSX_BUNDLE_ICON_FILE "AppIcon"
|
||||
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_SOURCE_DIR}/cmake/MacBundleInfo.plist.in"
|
||||
)
|
||||
else()
|
||||
message(WARNING "App icon not found at ${APP_ICON}; bundle will use default icon")
|
||||
add_executable(tetris MACOSX_BUNDLE ${TETRIS_SOURCES})
|
||||
set_target_properties(tetris PROPERTIES
|
||||
add_executable(spacetris MACOSX_BUNDLE ${TETRIS_SOURCES})
|
||||
set_target_properties(spacetris PROPERTIES
|
||||
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_SOURCE_DIR}/cmake/MacBundleInfo.plist.in"
|
||||
)
|
||||
endif()
|
||||
else()
|
||||
add_executable(tetris ${TETRIS_SOURCES})
|
||||
add_executable(spacetris ${TETRIS_SOURCES})
|
||||
endif()
|
||||
|
||||
# Ensure the built executable is named `spacetris`.
|
||||
set_target_properties(spacetris PROPERTIES OUTPUT_NAME "spacetris")
|
||||
|
||||
if (WIN32)
|
||||
# No compatibility copy; built executable is `spacetris`.
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
# Embed the application icon into the executable
|
||||
set_source_files_properties(src/app_icon.rc PROPERTIES LANGUAGE RC)
|
||||
target_sources(tetris PRIVATE src/app_icon.rc)
|
||||
target_sources(spacetris PRIVATE src/app_icon.rc)
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
# Prevent PDB write contention on MSVC by enabling /FS for this target
|
||||
target_compile_options(spacetris PRIVATE /FS)
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
@ -107,14 +119,14 @@ if (WIN32)
|
||||
COMMENT "Copy favicon.ico to build dir for resource compilation"
|
||||
)
|
||||
add_custom_target(copy_favicon ALL DEPENDS ${FAVICON_DEST})
|
||||
add_dependencies(tetris copy_favicon)
|
||||
add_dependencies(spacetris copy_favicon)
|
||||
else()
|
||||
message(WARNING "Favicon not found at ${FAVICON_SRC}; app icon may not compile")
|
||||
endif()
|
||||
|
||||
# Also copy favicon into the runtime output folder (same dir as exe)
|
||||
add_custom_command(TARGET tetris POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${FAVICON_SRC} $<TARGET_FILE_DIR:tetris>/favicon.ico
|
||||
add_custom_command(TARGET spacetris POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${FAVICON_SRC} $<TARGET_FILE_DIR:spacetris>/favicon.ico
|
||||
COMMENT "Copy favicon.ico next to executable"
|
||||
)
|
||||
endif()
|
||||
@ -123,35 +135,35 @@ if(APPLE)
|
||||
set(_mac_copy_commands)
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/assets")
|
||||
list(APPEND _mac_copy_commands
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/assets" "$<TARGET_FILE_DIR:tetris>/assets"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/assets" "$<TARGET_FILE_DIR:spacetris>/assets"
|
||||
)
|
||||
endif()
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/fonts")
|
||||
list(APPEND _mac_copy_commands
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/fonts" "$<TARGET_FILE_DIR:tetris>/fonts"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/fonts" "$<TARGET_FILE_DIR:spacetris>/fonts"
|
||||
)
|
||||
endif()
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/FreeSans.ttf")
|
||||
list(APPEND _mac_copy_commands
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/FreeSans.ttf" "$<TARGET_FILE_DIR:tetris>/FreeSans.ttf"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/FreeSans.ttf" "$<TARGET_FILE_DIR:spacetris>/FreeSans.ttf"
|
||||
)
|
||||
endif()
|
||||
if(_mac_copy_commands)
|
||||
add_custom_command(TARGET tetris POST_BUILD
|
||||
add_custom_command(TARGET spacetris POST_BUILD
|
||||
${_mac_copy_commands}
|
||||
COMMENT "Copying game assets into macOS bundle"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
target_link_libraries(tetris PRIVATE SDL3::SDL3 SDL3_ttf::SDL3_ttf SDL3_image::SDL3_image cpr::cpr nlohmann_json::nlohmann_json)
|
||||
target_link_libraries(spacetris PRIVATE SDL3::SDL3 SDL3_ttf::SDL3_ttf SDL3_image::SDL3_image cpr::cpr nlohmann_json::nlohmann_json)
|
||||
|
||||
if (WIN32)
|
||||
target_link_libraries(tetris PRIVATE mfplat mfreadwrite mfuuid)
|
||||
target_link_libraries(spacetris PRIVATE mfplat mfreadwrite mfuuid)
|
||||
endif()
|
||||
if(APPLE)
|
||||
# Needed for MP3 decoding via AudioToolbox on macOS
|
||||
target_link_libraries(tetris PRIVATE "-framework AudioToolbox" "-framework CoreFoundation")
|
||||
target_link_libraries(spacetris PRIVATE "-framework AudioToolbox" "-framework CoreFoundation")
|
||||
endif()
|
||||
|
||||
# Include production build configuration
|
||||
@ -162,20 +174,20 @@ enable_testing()
|
||||
|
||||
# Unit tests (simple runner)
|
||||
find_package(Catch2 CONFIG REQUIRED)
|
||||
add_executable(tetris_tests
|
||||
add_executable(spacetris_tests
|
||||
tests/GravityTests.cpp
|
||||
src/core/GravityManager.cpp
|
||||
)
|
||||
target_include_directories(tetris_tests PRIVATE ${CMAKE_SOURCE_DIR}/src)
|
||||
target_link_libraries(tetris_tests PRIVATE Catch2::Catch2WithMain)
|
||||
add_test(NAME GravityTests COMMAND tetris_tests)
|
||||
target_include_directories(spacetris_tests PRIVATE ${CMAKE_SOURCE_DIR}/src)
|
||||
target_link_libraries(spacetris_tests PRIVATE Catch2::Catch2WithMain)
|
||||
add_test(NAME GravityTests COMMAND spacetris_tests)
|
||||
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/vcpkg_installed/x64-windows/include")
|
||||
target_include_directories(tetris_tests PRIVATE "${CMAKE_SOURCE_DIR}/vcpkg_installed/x64-windows/include")
|
||||
target_include_directories(spacetris_tests PRIVATE "${CMAKE_SOURCE_DIR}/vcpkg_installed/x64-windows/include")
|
||||
endif()
|
||||
|
||||
# Add new src subfolders to include path so old #includes continue to work
|
||||
target_include_directories(tetris PRIVATE
|
||||
target_include_directories(spacetris PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/src
|
||||
${CMAKE_SOURCE_DIR}/src/audio
|
||||
${CMAKE_SOURCE_DIR}/src/gameplay
|
||||
|
||||
760
CODE_ANALYSIS.md
760
CODE_ANALYSIS.md
@ -1,760 +0,0 @@
|
||||
# Tetris SDL3 - Code Analysis & Best Practices Review
|
||||
|
||||
**Generated:** 2025-12-03
|
||||
**Project:** Tetris Game (SDL3)
|
||||
|
||||
---
|
||||
|
||||
## 📊 Executive Summary
|
||||
|
||||
Your Tetris project is **well-structured and follows many modern C++ best practices**. The codebase demonstrates:
|
||||
- ✅ Clean separation of concerns with a state-based architecture
|
||||
- ✅ Modern C++20 features and RAII patterns
|
||||
- ✅ Centralized configuration management
|
||||
- ✅ Proper dependency management via vcpkg
|
||||
- ✅ Good documentation and code organization
|
||||
|
||||
However, there are opportunities for improvement in areas like memory management, error handling, and code duplication.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Strengths
|
||||
|
||||
### 1. **Architecture & Design Patterns**
|
||||
- **State Pattern Implementation**: Clean state management with `MenuState`, `PlayingState`, `OptionsState`, `LevelSelectorState`, and `LoadingState`
|
||||
- **Separation of Concerns**: Game logic (`Game.cpp`), rendering (`GameRenderer`, `UIRenderer`), audio (`Audio`, `SoundEffect`), and persistence (`Scores`) are well-separated
|
||||
- **Centralized Configuration**: `Config.h` provides a single source of truth for constants, eliminating magic numbers
|
||||
- **Service Locator Pattern**: `StateContext` acts as a dependency injection container
|
||||
|
||||
### 2. **Modern C++ Practices**
|
||||
- **C++20 Standard**: Using modern features like `std::filesystem`, `std::jthread`
|
||||
- **RAII**: Proper resource management with smart pointers and automatic cleanup
|
||||
- **Type Safety**: Strong typing with enums (`PieceType`, `AppState`, `LevelBackgroundPhase`)
|
||||
- **Const Correctness**: Good use of `const` methods and references
|
||||
|
||||
### 3. **Code Organization**
|
||||
```
|
||||
src/
|
||||
├── audio/ # Audio system (music, sound effects)
|
||||
├── core/ # Core systems (state management, settings, global state)
|
||||
├── gameplay/ # Game logic (Tetris mechanics, effects)
|
||||
├── graphics/ # Rendering (UI, game renderer, effects)
|
||||
├── persistence/ # Score management
|
||||
├── states/ # State implementations
|
||||
└── utils/ # Utilities
|
||||
```
|
||||
This structure is logical and easy to navigate.
|
||||
|
||||
### 4. **Build System**
|
||||
- **CMake**: Modern CMake with proper target configuration
|
||||
- **vcpkg**: Excellent dependency management
|
||||
- **Cross-platform**: Support for Windows and macOS
|
||||
- **Testing**: Catch2 integration for unit tests
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Areas for Improvement
|
||||
|
||||
### 1. **Memory Management Issues**
|
||||
|
||||
#### **Problem: Raw Pointer Usage**
|
||||
**Location:** `MenuState.h`, `main.cpp`
|
||||
```cpp
|
||||
// MenuState.h (lines 17-21)
|
||||
SDL_Texture* playIcon = nullptr;
|
||||
SDL_Texture* levelIcon = nullptr;
|
||||
SDL_Texture* optionsIcon = nullptr;
|
||||
SDL_Texture* exitIcon = nullptr;
|
||||
```
|
||||
|
||||
**Issue:** Raw pointers to SDL resources without proper cleanup in all code paths.
|
||||
|
||||
**Recommendation:**
|
||||
```cpp
|
||||
// Create a smart pointer wrapper for SDL_Texture
|
||||
struct SDL_TextureDeleter {
|
||||
void operator()(SDL_Texture* tex) const {
|
||||
if (tex) SDL_DestroyTexture(tex);
|
||||
}
|
||||
};
|
||||
using SDL_TexturePtr = std::unique_ptr<SDL_Texture, SDL_TextureDeleter>;
|
||||
|
||||
// Usage in MenuState.h
|
||||
private:
|
||||
SDL_TexturePtr playIcon;
|
||||
SDL_TexturePtr levelIcon;
|
||||
SDL_TexturePtr optionsIcon;
|
||||
SDL_TexturePtr exitIcon;
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Automatic cleanup
|
||||
- Exception safety
|
||||
- No manual memory management
|
||||
- Clear ownership semantics
|
||||
|
||||
---
|
||||
|
||||
### 2. **Error Handling**
|
||||
|
||||
#### **Problem: Inconsistent Error Handling**
|
||||
**Location:** `main.cpp` (lines 86-114)
|
||||
```cpp
|
||||
static SDL_Texture* loadTextureFromImage(SDL_Renderer* renderer, const std::string& path, int* outW = nullptr, int* outH = nullptr) {
|
||||
if (!renderer) {
|
||||
return nullptr; // Silent failure
|
||||
}
|
||||
|
||||
SDL_Surface* surface = IMG_Load(resolvedPath.c_str());
|
||||
if (!surface) {
|
||||
SDL_LogError(...); // Logs but returns nullptr
|
||||
return nullptr;
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**Issues:**
|
||||
- Silent failures make debugging difficult
|
||||
- Callers must check for `nullptr` (easy to forget)
|
||||
- No way to distinguish between different error types
|
||||
|
||||
**Recommendation:**
|
||||
```cpp
|
||||
#include <expected> // C++23, or use tl::expected for C++20
|
||||
|
||||
struct TextureLoadError {
|
||||
std::string message;
|
||||
std::string path;
|
||||
};
|
||||
|
||||
std::expected<SDL_TexturePtr, TextureLoadError>
|
||||
loadTextureFromImage(SDL_Renderer* renderer, const std::string& path,
|
||||
int* outW = nullptr, int* outH = nullptr) {
|
||||
if (!renderer) {
|
||||
return std::unexpected(TextureLoadError{
|
||||
"Renderer is null", path
|
||||
});
|
||||
}
|
||||
|
||||
const std::string resolvedPath = AssetPath::resolveImagePath(path);
|
||||
SDL_Surface* surface = IMG_Load(resolvedPath.c_str());
|
||||
if (!surface) {
|
||||
return std::unexpected(TextureLoadError{
|
||||
std::string("Failed to load: ") + SDL_GetError(),
|
||||
resolvedPath
|
||||
});
|
||||
}
|
||||
|
||||
// ... success case
|
||||
return SDL_TexturePtr(texture);
|
||||
}
|
||||
|
||||
// Usage:
|
||||
auto result = loadTextureFromImage(renderer, "path.png");
|
||||
if (result) {
|
||||
// Use result.value()
|
||||
} else {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to load %s: %s",
|
||||
result.error().path.c_str(),
|
||||
result.error().message.c_str());
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. **Code Duplication**
|
||||
|
||||
#### **Problem: Repeated Patterns**
|
||||
**Location:** `MenuState.cpp`, `PlayingState.cpp`, `OptionsState.cpp`
|
||||
|
||||
Similar lambda patterns for exit popup handling:
|
||||
```cpp
|
||||
auto setExitSelection = [&](int value) {
|
||||
if (ctx.exitPopupSelectedButton) {
|
||||
*ctx.exitPopupSelectedButton = value;
|
||||
}
|
||||
};
|
||||
|
||||
auto getExitSelection = [&]() -> int {
|
||||
return ctx.exitPopupSelectedButton ? *ctx.exitPopupSelectedButton : 1;
|
||||
};
|
||||
```
|
||||
|
||||
**Recommendation:**
|
||||
Create a helper class in `StateContext`:
|
||||
```cpp
|
||||
// StateContext.h
|
||||
class ExitPopupHelper {
|
||||
public:
|
||||
ExitPopupHelper(int* selectedButton, bool* showPopup)
|
||||
: m_selectedButton(selectedButton), m_showPopup(showPopup) {}
|
||||
|
||||
void setSelection(int value) {
|
||||
if (m_selectedButton) *m_selectedButton = value;
|
||||
}
|
||||
|
||||
int getSelection() const {
|
||||
return m_selectedButton ? *m_selectedButton : 1;
|
||||
}
|
||||
|
||||
void show() {
|
||||
if (m_showPopup) *m_showPopup = true;
|
||||
}
|
||||
|
||||
void hide() {
|
||||
if (m_showPopup) *m_showPopup = false;
|
||||
}
|
||||
|
||||
bool isVisible() const {
|
||||
return m_showPopup && *m_showPopup;
|
||||
}
|
||||
|
||||
private:
|
||||
int* m_selectedButton;
|
||||
bool* m_showPopup;
|
||||
};
|
||||
|
||||
// Usage in states:
|
||||
ExitPopupHelper exitPopup(ctx.exitPopupSelectedButton, ctx.showExitConfirmPopup);
|
||||
exitPopup.setSelection(0);
|
||||
if (exitPopup.isVisible()) { ... }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. **Magic Numbers**
|
||||
|
||||
#### **Problem: Some Magic Numbers Still Present**
|
||||
**Location:** `MenuState.cpp` (lines 269-273)
|
||||
```cpp
|
||||
float btnW = 200.0f; // Fixed width to match background buttons
|
||||
float btnH = 70.0f; // Fixed height to match background buttons
|
||||
float btnX = LOGICAL_W * 0.5f + contentOffsetX;
|
||||
float btnY = LOGICAL_H * 0.865f + contentOffsetY;
|
||||
```
|
||||
|
||||
**Recommendation:**
|
||||
Add to `Config.h`:
|
||||
```cpp
|
||||
namespace Config::UI {
|
||||
constexpr float MENU_BUTTON_WIDTH = 200.0f;
|
||||
constexpr float MENU_BUTTON_HEIGHT = 70.0f;
|
||||
constexpr float MENU_BUTTON_Y_FRACTION = 0.865f;
|
||||
constexpr float MENU_BUTTON_SPACING = 210.0f;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. **File I/O for Debugging**
|
||||
|
||||
#### **Problem: Debug Logging to Files**
|
||||
**Location:** `MenuState.cpp` (lines 182-184, 195-203, etc.)
|
||||
```cpp
|
||||
FILE* f = fopen("tetris_trace.log", "a");
|
||||
if (f) {
|
||||
fprintf(f, "MenuState::render entry\n");
|
||||
fclose(f);
|
||||
}
|
||||
```
|
||||
|
||||
**Issues:**
|
||||
- File handles not checked properly
|
||||
- No error handling
|
||||
- Performance overhead in production
|
||||
- Should use proper logging framework
|
||||
|
||||
**Recommendation:**
|
||||
```cpp
|
||||
// Create a simple logger utility
|
||||
// src/utils/Logger.h
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
|
||||
class Logger {
|
||||
public:
|
||||
enum class Level { TRACE, DEBUG, INFO, WARN, ERROR };
|
||||
|
||||
static Logger& instance();
|
||||
|
||||
void setLevel(Level level) { m_level = level; }
|
||||
void setFile(const std::string& path);
|
||||
|
||||
template<typename... Args>
|
||||
void trace(const char* fmt, Args... args) {
|
||||
log(Level::TRACE, fmt, args...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void debug(const char* fmt, Args... args) {
|
||||
log(Level::DEBUG, fmt, args...);
|
||||
}
|
||||
|
||||
private:
|
||||
Logger() = default;
|
||||
|
||||
template<typename... Args>
|
||||
void log(Level level, const char* fmt, Args... args);
|
||||
|
||||
Level m_level = Level::INFO;
|
||||
std::ofstream m_file;
|
||||
std::mutex m_mutex;
|
||||
};
|
||||
|
||||
// Usage:
|
||||
#ifdef DEBUG
|
||||
Logger::instance().trace("MenuState::render entry");
|
||||
#endif
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. **Const Correctness**
|
||||
|
||||
#### **Problem: Missing const in Some Places**
|
||||
**Location:** `StateContext` and various state methods
|
||||
|
||||
**Recommendation:**
|
||||
```cpp
|
||||
// State.h
|
||||
class State {
|
||||
public:
|
||||
virtual void render(SDL_Renderer* renderer, float logicalScale,
|
||||
SDL_Rect logicalVP) const = 0; // Add const
|
||||
// Render shouldn't modify state
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7. **Thread Safety**
|
||||
|
||||
#### **Problem: Potential Race Conditions**
|
||||
**Location:** `Audio.cpp` - Background loading
|
||||
|
||||
**Current:**
|
||||
```cpp
|
||||
std::vector<AudioTrack> tracks;
|
||||
std::mutex tracksMutex;
|
||||
```
|
||||
|
||||
**Recommendation:**
|
||||
- Document thread safety guarantees
|
||||
- Use `std::shared_mutex` for read-heavy operations
|
||||
- Consider using lock-free data structures for performance-critical paths
|
||||
|
||||
```cpp
|
||||
// Audio.h
|
||||
class Audio {
|
||||
private:
|
||||
std::vector<AudioTrack> tracks;
|
||||
mutable std::shared_mutex tracksMutex; // Allow concurrent reads
|
||||
|
||||
public:
|
||||
// Read operation - shared lock
|
||||
int getLoadedTrackCount() const {
|
||||
std::shared_lock lock(tracksMutex);
|
||||
return tracks.size();
|
||||
}
|
||||
|
||||
// Write operation - exclusive lock
|
||||
void addTrack(const std::string& path) {
|
||||
std::unique_lock lock(tracksMutex);
|
||||
tracks.push_back(loadTrack(path));
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8. **Testing Coverage**
|
||||
|
||||
#### **Current State:**
|
||||
Only one test file: `tests/GravityTests.cpp`
|
||||
|
||||
**Recommendation:**
|
||||
Add comprehensive tests:
|
||||
```
|
||||
tests/
|
||||
├── GravityTests.cpp ✅ Exists
|
||||
├── GameLogicTests.cpp ❌ Missing
|
||||
├── ScoreManagerTests.cpp ❌ Missing
|
||||
├── StateTransitionTests.cpp ❌ Missing
|
||||
└── AudioSystemTests.cpp ❌ Missing
|
||||
```
|
||||
|
||||
**Example Test Structure:**
|
||||
```cpp
|
||||
// tests/GameLogicTests.cpp
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include "gameplay/core/Game.h"
|
||||
|
||||
TEST_CASE("Game initialization", "[game]") {
|
||||
Game game(0);
|
||||
|
||||
SECTION("Board starts empty") {
|
||||
const auto& board = game.boardRef();
|
||||
REQUIRE(std::all_of(board.begin(), board.end(),
|
||||
[](int cell) { return cell == 0; }));
|
||||
}
|
||||
|
||||
SECTION("Score starts at zero") {
|
||||
REQUIRE(game.score() == 0);
|
||||
REQUIRE(game.lines() == 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Piece rotation", "[game]") {
|
||||
Game game(0);
|
||||
|
||||
SECTION("Clockwise rotation") {
|
||||
auto initialRot = game.current().rot;
|
||||
game.rotate(1);
|
||||
REQUIRE(game.current().rot == (initialRot + 1) % 4);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Line clearing", "[game]") {
|
||||
Game game(0);
|
||||
|
||||
SECTION("Single line clear awards correct score") {
|
||||
// Setup: Fill bottom row except one cell
|
||||
// ... test implementation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 9. **Documentation**
|
||||
|
||||
#### **Current State:**
|
||||
- Good inline comments
|
||||
- Config.h has excellent documentation
|
||||
- Missing: API documentation, architecture overview
|
||||
|
||||
**Recommendation:**
|
||||
Add Doxygen-style comments:
|
||||
```cpp
|
||||
/**
|
||||
* @class Game
|
||||
* @brief Core Tetris game logic engine
|
||||
*
|
||||
* Manages the game board, piece spawning, collision detection,
|
||||
* line clearing, and scoring. This class is independent of
|
||||
* rendering and input handling.
|
||||
*
|
||||
* @note Thread-safe for read operations, but write operations
|
||||
* (move, rotate, etc.) should only be called from the
|
||||
* main game thread.
|
||||
*
|
||||
* Example usage:
|
||||
* @code
|
||||
* Game game(5); // Start at level 5
|
||||
* game.tickGravity(16.67); // Update for one frame
|
||||
* if (game.isGameOver()) {
|
||||
* // Handle game over
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
class Game {
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
Create `docs/ARCHITECTURE.md`:
|
||||
```markdown
|
||||
# Architecture Overview
|
||||
|
||||
## State Machine
|
||||
[Diagram of state transitions]
|
||||
|
||||
## Data Flow
|
||||
[Diagram showing how data flows through the system]
|
||||
|
||||
## Threading Model
|
||||
- Main thread: Rendering, input, game logic
|
||||
- Background thread: Audio loading
|
||||
- Audio callback thread: Audio mixing
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 10. **Performance Considerations**
|
||||
|
||||
#### **Issue: Frequent String Allocations**
|
||||
**Location:** Various places using `std::string` for paths
|
||||
|
||||
**Recommendation:**
|
||||
```cpp
|
||||
// Use string_view for read-only string parameters
|
||||
#include <string_view>
|
||||
|
||||
SDL_Texture* loadTextureFromImage(SDL_Renderer* renderer,
|
||||
std::string_view path, // Changed
|
||||
int* outW = nullptr,
|
||||
int* outH = nullptr);
|
||||
|
||||
// For compile-time strings, use constexpr
|
||||
namespace AssetPaths {
|
||||
constexpr std::string_view LOGO = "assets/images/logo.bmp";
|
||||
constexpr std::string_view BACKGROUND = "assets/images/main_background.bmp";
|
||||
}
|
||||
```
|
||||
|
||||
#### **Issue: Vector Reallocations**
|
||||
**Location:** `fireworks` vector in `main.cpp`
|
||||
|
||||
**Recommendation:**
|
||||
```cpp
|
||||
// Reserve capacity upfront
|
||||
fireworks.reserve(5); // Max 5 fireworks at once
|
||||
|
||||
// Or use a fixed-size container
|
||||
std::array<std::optional<TetrisFirework>, 5> fireworks;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Specific Recommendations by Priority
|
||||
|
||||
### **High Priority** (Do These First)
|
||||
|
||||
1. **Replace raw SDL pointers with smart pointers**
|
||||
- Impact: Prevents memory leaks
|
||||
- Effort: Medium
|
||||
- Files: `MenuState.h`, `main.cpp`, all state files
|
||||
|
||||
2. **Remove debug file I/O from production code**
|
||||
- Impact: Performance, code cleanliness
|
||||
- Effort: Low
|
||||
- Files: `MenuState.cpp`, `main.cpp`
|
||||
|
||||
3. **Add error handling to asset loading**
|
||||
- Impact: Better debugging, crash prevention
|
||||
- Effort: Medium
|
||||
- Files: `main.cpp`, `AssetManager.cpp`
|
||||
|
||||
### **Medium Priority**
|
||||
|
||||
4. **Extract common patterns into helper classes**
|
||||
- Impact: Code maintainability
|
||||
- Effort: Medium
|
||||
- Files: All state files
|
||||
|
||||
5. **Move remaining magic numbers to Config.h**
|
||||
- Impact: Maintainability
|
||||
- Effort: Low
|
||||
- Files: `MenuState.cpp`, `UIRenderer.cpp`
|
||||
|
||||
6. **Add comprehensive unit tests**
|
||||
- Impact: Code quality, regression prevention
|
||||
- Effort: High
|
||||
- Files: New test files
|
||||
|
||||
### **Low Priority** (Nice to Have)
|
||||
|
||||
7. **Add Doxygen documentation**
|
||||
- Impact: Developer onboarding
|
||||
- Effort: Medium
|
||||
|
||||
8. **Performance profiling and optimization**
|
||||
- Impact: Depends on current performance
|
||||
- Effort: Medium
|
||||
|
||||
9. **Consider using `std::expected` for error handling**
|
||||
- Impact: Better error handling
|
||||
- Effort: High (requires C++23 or external library)
|
||||
|
||||
---
|
||||
|
||||
## 📝 Code Style Observations
|
||||
|
||||
### **Good Practices You're Already Following:**
|
||||
|
||||
✅ **Consistent naming conventions:**
|
||||
- Classes: `PascalCase` (e.g., `MenuState`, `GameRenderer`)
|
||||
- Functions: `camelCase` (e.g., `tickGravity`, `loadTexture`)
|
||||
- Constants: `UPPER_SNAKE_CASE` (e.g., `LOGICAL_W`, `DAS_DELAY`)
|
||||
- Member variables: `camelCase` with `m_` prefix in some places
|
||||
|
||||
✅ **Header guards:** Using `#pragma once`
|
||||
|
||||
✅ **Forward declarations:** Minimizing include dependencies
|
||||
|
||||
✅ **RAII:** Resources tied to object lifetime
|
||||
|
||||
### **Minor Style Inconsistencies:**
|
||||
|
||||
❌ **Inconsistent member variable naming:**
|
||||
```cpp
|
||||
// Some classes use m_ prefix
|
||||
float m_masterVolume = 1.0f;
|
||||
|
||||
// Others don't
|
||||
int selectedButton = 0;
|
||||
```
|
||||
|
||||
**Recommendation:** Pick one style and stick to it. I suggest:
|
||||
```cpp
|
||||
// Private members: m_ prefix
|
||||
float m_masterVolume = 1.0f;
|
||||
int m_selectedButton = 0;
|
||||
|
||||
// Public members: no prefix (rare in good design)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Architecture Suggestions
|
||||
|
||||
### **Consider Implementing:**
|
||||
|
||||
1. **Event System**
|
||||
Instead of callbacks, use an event bus:
|
||||
```cpp
|
||||
// events/GameEvents.h
|
||||
struct LineClearedEvent {
|
||||
int linesCleared;
|
||||
int newScore;
|
||||
};
|
||||
|
||||
struct LevelUpEvent {
|
||||
int newLevel;
|
||||
};
|
||||
|
||||
// EventBus.h
|
||||
class EventBus {
|
||||
public:
|
||||
template<typename Event>
|
||||
void subscribe(std::function<void(const Event&)> handler);
|
||||
|
||||
template<typename Event>
|
||||
void publish(const Event& event);
|
||||
};
|
||||
|
||||
// Usage in Game.cpp
|
||||
eventBus.publish(LineClearedEvent{linesCleared, _score});
|
||||
|
||||
// Usage in Audio system
|
||||
eventBus.subscribe<LineClearedEvent>([](const auto& e) {
|
||||
playLineClearSound(e.linesCleared);
|
||||
});
|
||||
```
|
||||
|
||||
2. **Component-Based UI**
|
||||
Extract UI components:
|
||||
```cpp
|
||||
class Button {
|
||||
public:
|
||||
void render(SDL_Renderer* renderer);
|
||||
bool isHovered(int mouseX, int mouseY) const;
|
||||
void onClick(std::function<void()> callback);
|
||||
};
|
||||
|
||||
class Panel {
|
||||
std::vector<std::unique_ptr<UIComponent>> children;
|
||||
};
|
||||
```
|
||||
|
||||
3. **Asset Manager**
|
||||
Centralize asset loading:
|
||||
```cpp
|
||||
class AssetManager {
|
||||
public:
|
||||
SDL_TexturePtr getTexture(std::string_view name);
|
||||
FontAtlas* getFont(std::string_view name);
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, SDL_TexturePtr> textures;
|
||||
std::unordered_map<std::string, std::unique_ptr<FontAtlas>> fonts;
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Security Considerations
|
||||
|
||||
1. **File Path Validation**
|
||||
```cpp
|
||||
// AssetPath::resolveImagePath should validate paths
|
||||
// to prevent directory traversal attacks
|
||||
std::string resolveImagePath(std::string_view path) {
|
||||
// Reject paths with ".."
|
||||
if (path.find("..") != std::string_view::npos) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Invalid path: %s", path.data());
|
||||
return "";
|
||||
}
|
||||
// ... rest of implementation
|
||||
}
|
||||
```
|
||||
|
||||
2. **Score File Tampering**
|
||||
Consider adding checksums to score files:
|
||||
```cpp
|
||||
// Scores.cpp
|
||||
void ScoreManager::save() const {
|
||||
nlohmann::json j;
|
||||
j["scores"] = scores;
|
||||
j["checksum"] = computeChecksum(scores);
|
||||
// ... save to file
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Metrics
|
||||
|
||||
Based on the codebase analysis:
|
||||
|
||||
| Metric | Value | Rating |
|
||||
|--------|-------|--------|
|
||||
| **Code Organization** | Excellent | ⭐⭐⭐⭐⭐ |
|
||||
| **Modern C++ Usage** | Very Good | ⭐⭐⭐⭐ |
|
||||
| **Error Handling** | Fair | ⭐⭐⭐ |
|
||||
| **Memory Safety** | Good | ⭐⭐⭐⭐ |
|
||||
| **Test Coverage** | Poor | ⭐ |
|
||||
| **Documentation** | Good | ⭐⭐⭐⭐ |
|
||||
| **Performance** | Good | ⭐⭐⭐⭐ |
|
||||
| **Maintainability** | Very Good | ⭐⭐⭐⭐ |
|
||||
|
||||
**Overall Score: 4/5 ⭐⭐⭐⭐**
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Wins (Easy Improvements)
|
||||
|
||||
1. **Add `.clang-format` file** for consistent formatting
|
||||
2. **Create `CONTRIBUTING.md`** with coding guidelines
|
||||
3. **Add pre-commit hooks** for formatting and linting
|
||||
4. **Set up GitHub Actions** for CI/CD
|
||||
5. **Add `README.md`** with build instructions and screenshots
|
||||
|
||||
---
|
||||
|
||||
## 📚 Recommended Resources
|
||||
|
||||
- **Modern C++ Best Practices:** https://isocpp.github.io/CppCoreGuidelines/
|
||||
- **SDL3 Migration Guide:** https://wiki.libsdl.org/SDL3/README/migration
|
||||
- **Game Programming Patterns:** https://gameprogrammingpatterns.com/
|
||||
- **C++ Testing with Catch2:** https://github.com/catchorg/Catch2
|
||||
|
||||
---
|
||||
|
||||
## ✅ Conclusion
|
||||
|
||||
Your Tetris project demonstrates **strong software engineering practices** with a clean architecture, modern C++ usage, and good separation of concerns. The main areas for improvement are:
|
||||
|
||||
1. Enhanced error handling
|
||||
2. Increased test coverage
|
||||
3. Elimination of raw pointers
|
||||
4. Removal of debug code from production
|
||||
|
||||
With these improvements, this codebase would be **production-ready** and serve as an excellent example of modern C++ game development.
|
||||
|
||||
**Keep up the excellent work!** 🎮
|
||||
@ -1,4 +1,4 @@
|
||||
# Tetris SDL3 - Production Deployment Guide
|
||||
# Spacetris SDL3 - Production Deployment Guide
|
||||
|
||||
## 🚀 Build Scripts Available
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
- Uses existing Debug/Release build from build-msvc
|
||||
- Creates distribution package with all dependencies
|
||||
- **Size: ~939 MB** (includes all assets and music)
|
||||
- **Output:** `dist/TetrisGame/` + ZIP file
|
||||
- **Output:** `dist/SpacetrisGame/` + ZIP file
|
||||
|
||||
### 2. Full Production Build
|
||||
```powershell
|
||||
@ -31,8 +31,8 @@ build-production.bat
|
||||
The distribution package includes:
|
||||
|
||||
### Essential Files
|
||||
- ✅ `tetris.exe` - Main game executable
|
||||
- ✅ `Launch-Tetris.bat` - Safe launcher with error handling
|
||||
- ✅ `spacetris.exe` - Main game executable
|
||||
- ✅ `Launch-Spacetris.bat` - Safe launcher with error handling
|
||||
- ✅ `README.txt` - User instructions
|
||||
|
||||
### Dependencies
|
||||
@ -49,9 +49,9 @@ The distribution package includes:
|
||||
## 🎯 Distribution Options
|
||||
|
||||
### Option 1: ZIP Archive (Recommended)
|
||||
- **File:** `TetrisGame-YYYY.MM.DD.zip`
|
||||
- **File:** `SpacetrisGame-YYYY.MM.DD.zip`
|
||||
- **Size:** ~939 MB
|
||||
- **Usage:** Users extract and run `Launch-Tetris.bat`
|
||||
- **Usage:** Users extract and run `Launch-Spacetris.bat`
|
||||
|
||||
### Option 2: Installer (Future)
|
||||
- Use CMake CPack to create NSIS installer
|
||||
@ -60,11 +60,11 @@ The distribution package includes:
|
||||
```
|
||||
|
||||
### Option 3: Portable Folder
|
||||
- Direct distribution of `dist/TetrisGame/` folder
|
||||
Direct distribution of `dist/SpacetrisGame/` folder
|
||||
- Users copy folder and run executable
|
||||
|
||||
[ ] Launch-Spacetris.bat works
|
||||
## 🔧 Build Requirements
|
||||
|
||||
**Solution:** Use `Launch-Spacetris.bat` for better error reporting
|
||||
### Development Environment
|
||||
- **CMake** 3.20+
|
||||
- **Visual Studio 2026 (VS 18)** with Desktop development with C++ workload
|
||||
@ -87,18 +87,21 @@ vcpkg install sdl3 sdl3-ttf --triplet=x64-windows
|
||||
- [ ] Font rendering works (both FreeSans and PressStart2P)
|
||||
- [ ] Game saves high scores
|
||||
|
||||
### Package Validation
|
||||
### Package Validation
|
||||
- [ ] All DLL files present
|
||||
- [ ] Assets folder complete
|
||||
- [ ] Launch-Tetris.bat works
|
||||
- [ ] Launch-Spacetris.bat works
|
||||
- [ ] README.txt is informative
|
||||
- [ ] Package size reasonable (~939 MB)
|
||||
|
||||
### Distribution
|
||||
- [ ] ZIP file created successfully
|
||||
### "spacetris.exe is not recognized"
|
||||
- **Solution:** Ensure all DLL files are in same folder as executable
|
||||
- [ ] Test extraction on clean system
|
||||
- [ ] Verify game runs on target machines
|
||||
- [ ] No missing dependencies
|
||||
### Game won't start
|
||||
- **Solution:** Use `Launch-Spacetris.bat` for better error reporting
|
||||
|
||||
## 📋 User System Requirements
|
||||
|
||||
@ -118,7 +121,7 @@ vcpkg install sdl3 sdl3-ttf --triplet=x64-windows
|
||||
|
||||
## 🐛 Common Issues & Solutions
|
||||
|
||||
### "tetris.exe is not recognized"
|
||||
### "spacetris.exe is not recognized"
|
||||
- **Solution:** Ensure all DLL files are in same folder as executable
|
||||
|
||||
### "Failed to initialize SDL"
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Tetris — Upgrade Roadmap
|
||||
# Spacetris — Upgrade Roadmap
|
||||
|
||||
This document lists recommended code, architecture, tooling, and runtime upgrades for the native SDL3 Tetris project. Items are grouped, prioritized, and mapped to target files and effort estimates so you can plan incremental work.
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 1.0 MiB |
@ -1,5 +1,5 @@
|
||||
@echo off
|
||||
REM Build and run debug executable for the Tetris project
|
||||
REM Build and run debug executable for the Spacetris project
|
||||
SETLOCAL
|
||||
cd /d "%~dp0"
|
||||
cmake --build build-msvc --config Debug
|
||||
@ -7,5 +7,5 @@ if errorlevel 1 (
|
||||
echo Build failed.
|
||||
exit /b %ERRORLEVEL%
|
||||
)
|
||||
"%~dp0build-msvc\Debug\tetris.exe"
|
||||
"%~dp0build-msvc\Debug\spacetris.exe"
|
||||
ENDLOCAL
|
||||
|
||||
@ -149,7 +149,7 @@ if ($NoRun) {
|
||||
exit 0
|
||||
}
|
||||
|
||||
$exePath = Join-Path $root "build-msvc\Debug\tetris.exe"
|
||||
$exePath = Join-Path $root "build-msvc\Debug\spacetris.exe"
|
||||
if (-not (Test-Path $exePath)) {
|
||||
Write-Error "Executable not found: $exePath"
|
||||
exit 1
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# macOS Production Build Script for the SDL3 Tetris project
|
||||
# macOS Production Build Script for the SDL3 Spacetris project
|
||||
# Mirrors the Windows PowerShell workflow but uses common POSIX tooling so it
|
||||
# can be executed on macOS runners or local developer machines.
|
||||
|
||||
PROJECT_NAME="tetris"
|
||||
PROJECT_NAME="spacetris"
|
||||
BUILD_DIR="build-release"
|
||||
OUTPUT_DIR="dist"
|
||||
PACKAGE_DIR=""
|
||||
@ -67,7 +67,7 @@ parse_args() {
|
||||
}
|
||||
|
||||
configure_paths() {
|
||||
PACKAGE_DIR="${OUTPUT_DIR}/TetrisGame-mac"
|
||||
PACKAGE_DIR="${OUTPUT_DIR}/SpacetrisGame-mac"
|
||||
}
|
||||
|
||||
generate_icns_if_needed() {
|
||||
@ -246,15 +246,15 @@ create_launchers() {
|
||||
launch_command="open \"./${app_dir}\""
|
||||
fi
|
||||
|
||||
cat > "$PACKAGE_DIR/Launch-Tetris.command" <<EOF
|
||||
cat > "$PACKAGE_DIR/Launch-Spacetris.command" <<EOF
|
||||
#!/usr/bin/env bash
|
||||
cd "\$(dirname \"\$0\")"
|
||||
${launch_command}
|
||||
EOF
|
||||
chmod +x "$PACKAGE_DIR/Launch-Tetris.command"
|
||||
chmod +x "$PACKAGE_DIR/Launch-Spacetris.command"
|
||||
|
||||
cat > "$PACKAGE_DIR/README-mac.txt" <<EOF
|
||||
Tetris SDL3 Game - macOS Release $VERSION
|
||||
Spacetris SDL3 Game - macOS Release $VERSION
|
||||
=========================================
|
||||
|
||||
Requirements:
|
||||
@ -262,12 +262,12 @@ Requirements:
|
||||
- GPU with Metal support
|
||||
|
||||
Installation:
|
||||
1. Unzip the archive anywhere (e.g., ~/Games/Tetris).
|
||||
1. Unzip the archive anywhere (e.g., ~/Games/Spacetris).
|
||||
2. Ensure the executable bit stays set (script already does this).
|
||||
3. Double-click Launch-Tetris.command or run the binary/app manually from Terminal or Finder.
|
||||
3. Double-click Launch-Spacetris.command or run the binary/app manually from Terminal or Finder.
|
||||
|
||||
Files:
|
||||
- tetris / Launch-Tetris.command (start the game)
|
||||
- spacetris / Launch-Spacetris.command (start the game)
|
||||
- assets/ (art, audio, fonts)
|
||||
- *.dylib from SDL3 and related dependencies
|
||||
- FreeSans.ttf font
|
||||
@ -298,7 +298,7 @@ validate_package() {
|
||||
|
||||
create_zip() {
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
local zip_name="TetrisGame-mac-${VERSION}.zip"
|
||||
local zip_name="SpacetrisGame-mac-${VERSION}.zip"
|
||||
local zip_path="$OUTPUT_DIR/$zip_name"
|
||||
log INFO "Creating zip archive $zip_path ..."
|
||||
if command -v ditto >/dev/null 2>&1; then
|
||||
@ -349,7 +349,7 @@ create_dmg() {
|
||||
fi
|
||||
|
||||
local app_name="${APP_BUNDLE_PATH##*/}"
|
||||
local dmg_name="TetrisGame-mac-${VERSION}.dmg"
|
||||
local dmg_name="SpacetrisGame-mac-${VERSION}.dmg"
|
||||
local dmg_path="$OUTPUT_DIR/$dmg_name"
|
||||
|
||||
if [[ ! -f "scripts/create-dmg.sh" ]]; then
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
@echo off
|
||||
REM Simple Production Build Script for Tetris SDL3
|
||||
REM Simple Production Build Script for Spacetris SDL3
|
||||
REM This batch file builds and packages the game for distribution
|
||||
|
||||
setlocal EnableDelayedExpansion
|
||||
|
||||
echo ======================================
|
||||
echo Tetris SDL3 Production Builder
|
||||
echo Spacetris SDL3 Production Builder
|
||||
echo ======================================
|
||||
echo.
|
||||
|
||||
@ -45,13 +45,13 @@ cd ..
|
||||
|
||||
REM Create distribution directory
|
||||
echo Creating distribution package...
|
||||
mkdir "dist\TetrisGame"
|
||||
mkdir "dist\SpacetrisGame"
|
||||
|
||||
REM Copy executable
|
||||
if exist "build-release\Release\tetris.exe" (
|
||||
copy "build-release\Release\tetris.exe" "dist\TetrisGame\"
|
||||
) else if exist "build-release\tetris.exe" (
|
||||
copy "build-release\tetris.exe" "dist\TetrisGame\"
|
||||
if exist "build-release\Release\spacetris.exe" (
|
||||
copy "build-release\Release\spacetris.exe" "dist\SpacetrisGame\"
|
||||
) else if exist "build-release\spacetris.exe" (
|
||||
copy "build-release\spacetris.exe" "dist\SpacetrisGame\"
|
||||
) else (
|
||||
echo Error: Executable not found!
|
||||
pause
|
||||
@ -60,12 +60,12 @@ if exist "build-release\Release\tetris.exe" (
|
||||
|
||||
REM Copy assets
|
||||
echo Copying game assets...
|
||||
if exist "assets" xcopy "assets" "dist\TetrisGame\assets\" /E /I /Y
|
||||
if exist "FreeSans.ttf" copy "FreeSans.ttf" "dist\TetrisGame\"
|
||||
if exist "assets" xcopy "assets" "dist\SpacetrisGame\assets\" /E /I /Y
|
||||
if exist "FreeSans.ttf" copy "FreeSans.ttf" "dist\SpacetrisGame\"
|
||||
|
||||
REM Copy SDL DLLs (if available) - SDL_image no longer needed
|
||||
echo Copying dependencies...
|
||||
set "PackageDir=dist\TetrisGame"
|
||||
set "PackageDir=dist\SpacetrisGame"
|
||||
set "copiedDependencies=0"
|
||||
|
||||
call :CopyDependencyDir "build-release\vcpkg_installed\x64-windows\bin"
|
||||
@ -76,19 +76,19 @@ if "%copiedDependencies%"=="0" (
|
||||
)
|
||||
|
||||
REM Create launcher batch file
|
||||
echo @echo off > "dist\TetrisGame\Launch-Tetris.bat"
|
||||
echo cd /d "%%~dp0" >> "dist\TetrisGame\Launch-Tetris.bat"
|
||||
echo tetris.exe >> "dist\TetrisGame\Launch-Tetris.bat"
|
||||
echo pause >> "dist\TetrisGame\Launch-Tetris.bat"
|
||||
echo @echo off > "dist\SpacetrisGame\Launch-Spacetris.bat"
|
||||
echo cd /d "%%~dp0" >> "dist\SpacetrisGame\Launch-Spacetris.bat"
|
||||
echo spacetris.exe >> "dist\SpacetrisGame\Launch-Spacetris.bat"
|
||||
echo pause >> "dist\SpacetrisGame\Launch-Spacetris.bat"
|
||||
|
||||
echo.
|
||||
echo ======================================
|
||||
echo Build Completed Successfully!
|
||||
echo ======================================
|
||||
echo Package location: dist\TetrisGame
|
||||
echo Package location: dist\SpacetrisGame
|
||||
echo.
|
||||
echo The game is ready for distribution!
|
||||
echo Users can run tetris.exe or Launch-Tetris.bat
|
||||
echo Users can run spacetris.exe or Launch-Spacetris.bat
|
||||
echo.
|
||||
pause
|
||||
goto :eof
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
#!/usr/bin/env pwsh
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Production Build Script for Tetris SDL3 Game
|
||||
Production Build Script for Spacetris SDL3 Game
|
||||
|
||||
.DESCRIPTION
|
||||
This script builds the Tetris game for production distribution, including:
|
||||
This script builds the Spacetris game for production distribution, including:
|
||||
- Clean Release build with optimizations
|
||||
- Dependency collection and packaging
|
||||
- Asset organization and validation
|
||||
@ -31,9 +31,9 @@ param(
|
||||
)
|
||||
|
||||
# Configuration
|
||||
$ProjectName = "tetris"
|
||||
$ProjectName = "spacetris"
|
||||
$BuildDir = "build-release"
|
||||
$PackageDir = Join-Path $OutputDir "TetrisGame"
|
||||
$PackageDir = Join-Path $OutputDir "SpacetrisGame"
|
||||
$Version = Get-Date -Format "yyyy.MM.dd"
|
||||
|
||||
# Colors for output
|
||||
@ -52,7 +52,7 @@ function Write-Warning { Write-ColorOutput Yellow $args }
|
||||
function Write-Error { Write-ColorOutput Red $args }
|
||||
|
||||
Write-Info "======================================"
|
||||
Write-Info " Tetris SDL3 Production Builder"
|
||||
Write-Info " Spacetris SDL3 Production Builder"
|
||||
Write-Info "======================================"
|
||||
Write-Info "Version: $Version"
|
||||
Write-Info "Output: $PackageDir"
|
||||
@ -184,7 +184,7 @@ Write-Info "Creating distribution files..."
|
||||
|
||||
# Create README
|
||||
$ReadmeContent = @"
|
||||
Tetris SDL3 Game - Release $Version
|
||||
Spacetris SDL3 Game - Release $Version
|
||||
=====================================
|
||||
|
||||
## System Requirements
|
||||
@ -194,7 +194,7 @@ Tetris SDL3 Game - Release $Version
|
||||
|
||||
## Installation
|
||||
1. Extract all files to a folder
|
||||
2. Run tetris.exe
|
||||
2. Run spacetris.exe
|
||||
|
||||
## Controls
|
||||
- Arrow Keys: Move pieces
|
||||
@ -206,17 +206,17 @@ Tetris SDL3 Game - Release $Version
|
||||
- Esc: Return to menu
|
||||
|
||||
## Troubleshooting
|
||||
- If the game doesn't start, ensure all DLL files are in the same folder as tetris.exe
|
||||
- If the game doesn't start, ensure all DLL files are in the same folder as spacetris.exe
|
||||
- For audio issues, check that your audio drivers are up to date
|
||||
- The game requires the assets folder to be in the same directory as the executable
|
||||
|
||||
## Files Included
|
||||
- tetris.exe - Main game executable
|
||||
- spacetris.exe - Main game executable
|
||||
- SDL3.dll, SDL3_ttf.dll, SDL3_image.dll - Required libraries
|
||||
- assets/ - Game assets (images, music, fonts)
|
||||
- FreeSans.ttf - Main font file
|
||||
|
||||
Enjoy playing Tetris!
|
||||
Enjoy playing Spacetris!
|
||||
"@
|
||||
|
||||
$ReadmeContent | Out-File -FilePath (Join-Path $PackageDir "README.txt") -Encoding UTF8
|
||||
@ -230,8 +230,8 @@ tetris.exe
|
||||
pause
|
||||
"@
|
||||
|
||||
$BatchContent | Out-File -FilePath (Join-Path $PackageDir "Launch-Tetris.bat") -Encoding ASCII
|
||||
Write-Success "Created Launch-Tetris.bat"
|
||||
$BatchContent | Out-File -FilePath (Join-Path $PackageDir "Launch-Spacetris.bat") -Encoding ASCII
|
||||
Write-Success "Created Launch-Spacetris.bat"
|
||||
|
||||
# Step 9: Validate package
|
||||
Write-Info "🔍 Validating package..."
|
||||
@ -259,7 +259,7 @@ $PackageSize = (Get-ChildItem $PackageDir -Recurse | Measure-Object -Property Le
|
||||
$PackageSizeMB = [math]::Round($PackageSize / 1MB, 2)
|
||||
|
||||
# Step 11: Create ZIP archive (optional)
|
||||
$ZipPath = Join-Path $OutputDir "TetrisGame-$Version.zip"
|
||||
$ZipPath = Join-Path $OutputDir "SpacetrisGame-$Version.zip"
|
||||
Write-Info "📁 Creating ZIP archive..."
|
||||
try {
|
||||
Compress-Archive -Path $PackageDir -DestinationPath $ZipPath -Force
|
||||
@ -280,5 +280,5 @@ if (Test-Path $ZipPath) {
|
||||
}
|
||||
Write-Info ""
|
||||
Write-Info "The game is ready for distribution!"
|
||||
Write-Info "Users can run tetris.exe or Launch-Tetris.bat"
|
||||
Write-Info "Users can run spacetris.exe or Launch-Spacetris.bat"
|
||||
Write-Info ""
|
||||
|
||||
@ -103,7 +103,7 @@ Optional additional scaling:
|
||||
- After level 100 completion: show completion screen + stats.
|
||||
|
||||
### Game Over
|
||||
- Standard Tetris game over: stack reaches spawn/top (existing behavior).
|
||||
- Standard Spacetris game over: stack reaches spawn/top (existing behavior).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -7,11 +7,11 @@
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.example.tetris</string>
|
||||
<string>com.example.spacetris</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Tetris</string>
|
||||
<string>Spacetris</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
|
||||
@ -12,12 +12,19 @@ if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /OPT:REF /OPT:ICF")
|
||||
|
||||
# Enable whole program optimization
|
||||
set_target_properties(tetris PROPERTIES
|
||||
# Detect game target (spacetris only)
|
||||
if(TARGET spacetris)
|
||||
set(GAME_TARGET spacetris)
|
||||
else()
|
||||
message(FATAL_ERROR "No game target found (expected 'spacetris')")
|
||||
endif()
|
||||
|
||||
set_target_properties(${GAME_TARGET} PROPERTIES
|
||||
INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE
|
||||
)
|
||||
|
||||
# Set subsystem to Windows (no console) for release
|
||||
set_target_properties(tetris PROPERTIES
|
||||
set_target_properties(${GAME_TARGET} PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup"
|
||||
)
|
||||
@ -30,61 +37,78 @@ if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Ensure we have a GAME_TARGET set (spacetris only)
|
||||
if(NOT DEFINED GAME_TARGET)
|
||||
if(TARGET spacetris)
|
||||
set(GAME_TARGET spacetris)
|
||||
else()
|
||||
message(FATAL_ERROR "No game target found (expected 'spacetris')")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Custom target for creating distribution package (renamed to avoid conflict with CPack)
|
||||
if(WIN32)
|
||||
# Windows-specific packaging
|
||||
if(GAME_TARGET)
|
||||
add_custom_target(dist_package
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "Creating Windows distribution package..."
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/package/TetrisGame"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "$<TARGET_FILE:tetris>" "${CMAKE_BINARY_DIR}/package/TetrisGame/"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/assets" "${CMAKE_BINARY_DIR}/package/TetrisGame/assets"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_SOURCE_DIR}/FreeSans.ttf" "${CMAKE_BINARY_DIR}/package/TetrisGame/"
|
||||
COMMENT "Packaging Tetris for distribution"
|
||||
DEPENDS tetris
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/package/SpacetrisGame"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "$<TARGET_FILE:${GAME_TARGET}>" "${CMAKE_BINARY_DIR}/package/SpacetrisGame/"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/assets" "${CMAKE_BINARY_DIR}/package/SpacetrisGame/assets"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_SOURCE_DIR}/FreeSans.ttf" "${CMAKE_BINARY_DIR}/package/SpacetrisGame/"
|
||||
COMMENT "Packaging Spacetris for distribution"
|
||||
DEPENDS ${GAME_TARGET}
|
||||
)
|
||||
else()
|
||||
message(WARNING "No game target detected; skipping dist_package target.")
|
||||
endif()
|
||||
|
||||
# Try to copy SDL DLLs automatically (SDL_image no longer needed)
|
||||
find_file(SDL3_DLL SDL3.dll PATHS "${CMAKE_BINARY_DIR}/vcpkg_installed/x64-windows/bin" NO_DEFAULT_PATH)
|
||||
find_file(SDL3_TTF_DLL SDL3_ttf.dll PATHS "${CMAKE_BINARY_DIR}/vcpkg_installed/x64-windows/bin" NO_DEFAULT_PATH)
|
||||
|
||||
if(GAME_TARGET)
|
||||
if(SDL3_DLL)
|
||||
add_custom_command(TARGET dist_package POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${SDL3_DLL}" "${CMAKE_BINARY_DIR}/package/TetrisGame/"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${SDL3_DLL}" "${CMAKE_BINARY_DIR}/package/SpacetrisGame/"
|
||||
COMMENT "Copying SDL3.dll"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(SDL3_TTF_DLL)
|
||||
add_custom_command(TARGET dist_package POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${SDL3_TTF_DLL}" "${CMAKE_BINARY_DIR}/package/TetrisGame/"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${SDL3_TTF_DLL}" "${CMAKE_BINARY_DIR}/package/SpacetrisGame/"
|
||||
COMMENT "Copying SDL3_ttf.dll"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Installation rules for system-wide installation
|
||||
if(APPLE)
|
||||
install(TARGETS tetris
|
||||
if(GAME_TARGET)
|
||||
if(APPLE)
|
||||
install(TARGETS ${GAME_TARGET}
|
||||
BUNDLE DESTINATION .
|
||||
COMPONENT Runtime
|
||||
)
|
||||
else()
|
||||
install(TARGETS tetris
|
||||
else()
|
||||
install(TARGETS ${GAME_TARGET}
|
||||
RUNTIME DESTINATION bin
|
||||
COMPONENT Runtime
|
||||
)
|
||||
endif()
|
||||
|
||||
install(DIRECTORY assets/
|
||||
DESTINATION share/${GAME_TARGET}/assets
|
||||
COMPONENT Runtime
|
||||
)
|
||||
|
||||
install(FILES FreeSans.ttf
|
||||
DESTINATION share/${GAME_TARGET}
|
||||
COMPONENT Runtime
|
||||
)
|
||||
endif()
|
||||
|
||||
install(DIRECTORY assets/
|
||||
DESTINATION share/tetris/assets
|
||||
COMPONENT Runtime
|
||||
)
|
||||
|
||||
install(FILES FreeSans.ttf
|
||||
DESTINATION share/tetris
|
||||
COMPONENT Runtime
|
||||
)
|
||||
|
||||
# CPack configuration for creating installers (commented out - requires LICENSE file)
|
||||
# set(CPACK_PACKAGE_NAME "Tetris")
|
||||
# set(CPACK_PACKAGE_VENDOR "TetrisGame")
|
||||
|
||||
174
cooperate_mode_plan.md
Normal file
174
cooperate_mode_plan.md
Normal file
@ -0,0 +1,174 @@
|
||||
# Spacetris — COOPERATE Mode (Two-Player Co-Op)
|
||||
## VS Code Copilot AI Agent Prompt
|
||||
|
||||
> Implement a new **COOPERATE** play mode for Spacetris.
|
||||
> This is a **two-player cooperative mode** with a shared board and synchronized line clears.
|
||||
|
||||
---
|
||||
|
||||
## 1. Mode Overview
|
||||
|
||||
### Mode Name
|
||||
- **COOPERATE**
|
||||
|
||||
### Core Concept
|
||||
- Two players play **together**, not versus.
|
||||
- One shared game board with **double width**.
|
||||
- Each player is responsible for **their own half** of the board.
|
||||
- A line clears **only when BOTH halves of the same row are full**.
|
||||
|
||||
---
|
||||
|
||||
## 2. Grid Layout
|
||||
|
||||
- Grid width: **20 columns**
|
||||
- Grid height: **standard height** (same as Endless/Challenge)
|
||||
- Column ownership:
|
||||
- Player 1 → columns `0–9` (left half)
|
||||
- Player 2 → columns `10–19` (right half)
|
||||
|
||||
### Visual Requirements
|
||||
- Draw a **vertical divider line** between columns 9 and 10.
|
||||
- Divider should be subtle but always visible.
|
||||
|
||||
---
|
||||
|
||||
## 3. Line Clear Rule (Critical)
|
||||
|
||||
A row clears **only if**:
|
||||
- Player 1 half of the row is completely filled
|
||||
- AND Player 2 half of the row is completely filled
|
||||
|
||||
If only one side is filled:
|
||||
- The row **does NOT clear**
|
||||
- Provide visual feedback:
|
||||
- Glow or pulse on the completed half
|
||||
- Optional hint on the incomplete half
|
||||
|
||||
---
|
||||
|
||||
## 4. Player Mechanics
|
||||
|
||||
### Piece Streams
|
||||
- Each player has their **own active piece**
|
||||
- Each player has their **own NEXT queue**
|
||||
- Both queues use the **same RNG seed** for fairness
|
||||
|
||||
### Controls
|
||||
- Player 1 controls only the left half
|
||||
- Player 2 controls only the right half
|
||||
- Players cannot move or rotate pieces into the other player’s half
|
||||
|
||||
---
|
||||
|
||||
## 5. Gravity & Timing
|
||||
|
||||
- Gravity applies globally (same speed for both)
|
||||
- Lock delay is handled **per player**
|
||||
- One player locking does NOT block the other player
|
||||
|
||||
---
|
||||
|
||||
## 6. Failure Conditions
|
||||
|
||||
Recommended:
|
||||
- Game over occurs only when **both players top out**
|
||||
|
||||
Alternative (optional):
|
||||
- If either player tops out → shared loss
|
||||
|
||||
---
|
||||
|
||||
## 7. Scoring System
|
||||
|
||||
- Score is **shared**
|
||||
- Line clears grant:
|
||||
- Base score
|
||||
- Cooperative bonus if both halves complete simultaneously
|
||||
- Combo bonus for consecutive cooperative clears
|
||||
|
||||
No competitive scoring between players.
|
||||
|
||||
---
|
||||
|
||||
## 8. UI / HUD Requirements
|
||||
|
||||
- Two NEXT panels (one per player)
|
||||
- Shared score display
|
||||
- Shared level display
|
||||
- Visual feedback for:
|
||||
- Half-filled rows
|
||||
- Successful cooperative clears
|
||||
|
||||
Optional:
|
||||
- SYNC meter for advanced cooperative mechanics (future)
|
||||
|
||||
---
|
||||
|
||||
## 9. Data Model Suggestions
|
||||
|
||||
```cpp
|
||||
enum class PlayerSide {
|
||||
Left,
|
||||
Right
|
||||
};
|
||||
|
||||
struct PlayerState {
|
||||
PlayerSide side;
|
||||
ActivePiece piece;
|
||||
bool isAlive;
|
||||
};
|
||||
|
||||
struct Cell {
|
||||
bool occupied;
|
||||
PlayerSide owner;
|
||||
};
|
||||
````
|
||||
|
||||
---
|
||||
|
||||
## 10. Line Clear Algorithm (Guidance)
|
||||
|
||||
When checking for full rows:
|
||||
|
||||
1. For each row:
|
||||
|
||||
* Check columns `0–9` for full (Player 1)
|
||||
* Check columns `10–19` for full (Player 2)
|
||||
2. Only if **both are full**, mark row for clearing
|
||||
3. Clear row normally and apply gravity to both halves
|
||||
4. Update shared score and combos
|
||||
|
||||
---
|
||||
|
||||
## 11. Constraints
|
||||
|
||||
* COOPERATE is a separate play mode
|
||||
* Do NOT reuse versus or garbage mechanics
|
||||
* Focus on clarity, fairness, and readability
|
||||
* Keep implementation modular (easy to expand later)
|
||||
|
||||
---
|
||||
|
||||
## 12. Acceptance Criteria
|
||||
|
||||
* Two players can play simultaneously on one board
|
||||
* Each player fills only their half
|
||||
* Lines clear only when both halves are filled
|
||||
* Visual feedback clearly shows cooperative dependency
|
||||
* Mode integrates cleanly into the main menu
|
||||
|
||||
---
|
||||
|
||||
## 13. Optional Future Hooks (Do Not Implement Now)
|
||||
|
||||
* Assist blocks
|
||||
* Shared power-ups
|
||||
* Cross-half interactions
|
||||
* Online co-op
|
||||
|
||||
---
|
||||
|
||||
## Short Summary for the Agent
|
||||
|
||||
Implement a two-player COOPERATE mode with a 20-column board split into two halves. Each player fills their half independently. A line clears only when both halves of the same row are full. Score, level, and progress are shared. Add clear visual feedback and a divider between player halves.
|
||||
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env pwsh
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Quick Production Package Creator for Tetris SDL3
|
||||
Quick Production Package Creator for Spacetris SDL3
|
||||
|
||||
.DESCRIPTION
|
||||
This script creates a production package using the existing build-msvc directory.
|
||||
@ -12,8 +12,8 @@ param(
|
||||
[string]$OutputDir = "dist"
|
||||
)
|
||||
|
||||
$ProjectName = "tetris"
|
||||
$PackageDir = Join-Path $OutputDir "TetrisGame"
|
||||
$ProjectName = "spacetris"
|
||||
$PackageDir = Join-Path $OutputDir "SpacetrisGame"
|
||||
$Version = Get-Date -Format "yyyy.MM.dd"
|
||||
|
||||
function Write-ColorOutput($ForegroundColor) {
|
||||
@ -29,13 +29,13 @@ function Write-Warning { Write-ColorOutput Yellow $args }
|
||||
function Write-Error { Write-ColorOutput Red $args }
|
||||
|
||||
Write-Info "======================================"
|
||||
Write-Info " Tetris Quick Package Creator"
|
||||
Write-Info " Spacetris Quick Package Creator"
|
||||
Write-Info "======================================"
|
||||
|
||||
# Check if build exists
|
||||
$ExecutablePath = "build-msvc\Debug\tetris.exe"
|
||||
$ExecutablePath = "build-msvc\Debug\spacetris.exe"
|
||||
if (!(Test-Path $ExecutablePath)) {
|
||||
$ExecutablePath = "build-msvc\Release\tetris.exe"
|
||||
$ExecutablePath = "build-msvc\Release\spacetris.exe"
|
||||
if (!(Test-Path $ExecutablePath)) {
|
||||
Write-Error "No executable found in build-msvc directory. Please build the project first."
|
||||
exit 1
|
||||
@ -52,7 +52,7 @@ New-Item -ItemType Directory -Path $PackageDir -Force | Out-Null
|
||||
|
||||
# Copy executable
|
||||
Copy-Item $ExecutablePath $PackageDir
|
||||
Write-Success "Copied tetris.exe"
|
||||
Write-Success "Copied spacetris.exe"
|
||||
|
||||
# Copy assets
|
||||
if (Test-Path "assets") {
|
||||
@ -82,7 +82,7 @@ if (Test-Path $VcpkgBin) {
|
||||
$LaunchContent = @"
|
||||
@echo off
|
||||
cd /d "%~dp0"
|
||||
tetris.exe
|
||||
spacetris.exe
|
||||
if %errorlevel% neq 0 (
|
||||
echo.
|
||||
echo Game crashed or failed to start!
|
||||
@ -91,22 +91,22 @@ if %errorlevel% neq 0 (
|
||||
pause
|
||||
)
|
||||
"@
|
||||
$LaunchContent | Out-File -FilePath (Join-Path $PackageDir "Launch-Tetris.bat") -Encoding ASCII
|
||||
Write-Success "Created Launch-Tetris.bat"
|
||||
$LaunchContent | Out-File -FilePath (Join-Path $PackageDir "Launch-Spacetris.bat") -Encoding ASCII
|
||||
Write-Success "Created Launch-Spacetris.bat"
|
||||
|
||||
# Create README
|
||||
$ReadmeContent = @"
|
||||
Tetris SDL3 Game
|
||||
================
|
||||
Spacetris SDL3 Game
|
||||
===================
|
||||
|
||||
## Quick Start
|
||||
1. Run Launch-Tetris.bat or tetris.exe
|
||||
1. Run Launch-Spacetris.bat or spacetris.exe
|
||||
2. Use arrow keys to move, Z/X to rotate, Space to drop
|
||||
3. Press F11 for fullscreen, Esc for menu
|
||||
|
||||
## Files
|
||||
- tetris.exe: Main game
|
||||
- Launch-Tetris.bat: Safe launcher with error handling
|
||||
- spacetris.exe: Main game
|
||||
- Launch-Spacetris.bat: Safe launcher with error handling
|
||||
- assets/: Game resources (music, images, fonts)
|
||||
- *.dll: Required libraries
|
||||
|
||||
@ -129,7 +129,7 @@ $PackageSize = (Get-ChildItem $PackageDir -Recurse | Measure-Object -Property Le
|
||||
$PackageSizeMB = [math]::Round($PackageSize / 1MB, 2)
|
||||
|
||||
# Create ZIP
|
||||
$ZipPath = Join-Path $OutputDir "TetrisGame-$Version.zip"
|
||||
$ZipPath = Join-Path $OutputDir "SpacetrisGame-$Version.zip"
|
||||
try {
|
||||
Compress-Archive -Path $PackageDir -DestinationPath $ZipPath -Force
|
||||
Write-Success "Created ZIP: $ZipPath"
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Create a distributable DMG for the macOS Tetris app
|
||||
# Create a distributable DMG for the macOS Spacetris app
|
||||
# Usage: ./scripts/create-dmg.sh <app-bundle-path> <output-dmg>
|
||||
# Example: ./scripts/create-dmg.sh dist/TetrisGame-mac/tetris.app dist/TetrisGame.dmg
|
||||
# Example: ./scripts/create-dmg.sh dist/SpacetrisGame-mac/spacetris.app dist/SpacetrisGame.dmg
|
||||
|
||||
if [[ $# -lt 2 ]]; then
|
||||
echo "Usage: $0 <app-bundle-path> <output-dmg>"
|
||||
echo "Example: $0 dist/TetrisGame-mac/tetris.app dist/TetrisGame.dmg"
|
||||
echo "Example: $0 dist/SpacetrisGame-mac/spacetris.app dist/SpacetrisGame.dmg"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@ -38,7 +38,7 @@
|
||||
ApplicationManager::ApplicationManager() = default;
|
||||
|
||||
static void traceFile(const char* msg) {
|
||||
std::ofstream f("tetris_trace.log", std::ios::app);
|
||||
std::ofstream f("spacetris_trace.log", std::ios::app);
|
||||
if (f) f << msg << "\n";
|
||||
}
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ void InputManager::processEvents() {
|
||||
while (SDL_PollEvent(&event)) {
|
||||
// Trace every polled event type for debugging abrupt termination
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "InputManager: polled event type=%d\n", (int)event.type); fclose(f); }
|
||||
FILE* f = fopen("spacetris_trace.log", "a"); if (f) { fprintf(f, "InputManager: polled event type=%d\n", (int)event.type); fclose(f); }
|
||||
}
|
||||
switch (event.type) {
|
||||
case SDL_EVENT_QUIT:
|
||||
@ -349,7 +349,7 @@ void InputManager::reset() {
|
||||
}
|
||||
|
||||
void InputManager::handleQuitEvent() {
|
||||
FILE* f = fopen("tetris_trace.log", "a");
|
||||
FILE* f = fopen("spacetris_trace.log", "a");
|
||||
if (f) {
|
||||
fprintf(f, "InputManager::handleQuitEvent invoked\n");
|
||||
fclose(f);
|
||||
|
||||
@ -86,7 +86,7 @@ bool StateManager::setState(AppState newState) {
|
||||
getStateName(m_currentState), getStateName(newState));
|
||||
// Persistent trace for debugging abrupt exits
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "setState start %s -> %s\n", getStateName(m_currentState), getStateName(newState)); fclose(f); }
|
||||
FILE* f = fopen("spacetris_trace.log", "a"); if (f) { fprintf(f, "setState start %s -> %s\n", getStateName(m_currentState), getStateName(newState)); fclose(f); }
|
||||
}
|
||||
|
||||
// Execute exit hooks for current state
|
||||
@ -101,7 +101,7 @@ bool StateManager::setState(AppState newState) {
|
||||
|
||||
// Trace completion
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "setState end %s\n", getStateName(m_currentState)); fclose(f); }
|
||||
FILE* f = fopen("spacetris_trace.log", "a"); if (f) { fprintf(f, "setState end %s\n", getStateName(m_currentState)); fclose(f); }
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -190,7 +190,7 @@ void StateManager::executeEnterHooks(AppState state) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Executing enter hook %d for state %s", idx, getStateName(state));
|
||||
// Also write to trace file for persistent record
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "executeEnterHook %d %s\n", idx, getStateName(state)); fclose(f); }
|
||||
FILE* f = fopen("spacetris_trace.log", "a"); if (f) { fprintf(f, "executeEnterHook %d %s\n", idx, getStateName(state)); fclose(f); }
|
||||
}
|
||||
try {
|
||||
hook();
|
||||
@ -212,7 +212,7 @@ void StateManager::executeExitHooks(AppState state) {
|
||||
for (auto& hook : it->second) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Executing exit hook %d for state %s", idx, getStateName(state));
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "executeExitHook %d %s\n", idx, getStateName(state)); fclose(f); }
|
||||
FILE* f = fopen("spacetris_trace.log", "a"); if (f) { fprintf(f, "executeExitHook %d %s\n", idx, getStateName(state)); fclose(f); }
|
||||
}
|
||||
try {
|
||||
hook();
|
||||
|
||||
@ -84,7 +84,7 @@ void RenderManager::beginFrame() {
|
||||
|
||||
// Trace beginFrame entry
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::beginFrame entry\n"); fclose(f); }
|
||||
FILE* f = fopen("spacetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::beginFrame entry\n"); fclose(f); }
|
||||
}
|
||||
|
||||
// Clear the screen (wrapped with trace)
|
||||
@ -92,7 +92,7 @@ void RenderManager::beginFrame() {
|
||||
|
||||
// Trace after clear
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::beginFrame after clear\n"); fclose(f); }
|
||||
FILE* f = fopen("spacetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::beginFrame after clear\n"); fclose(f); }
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,14 +102,14 @@ void RenderManager::endFrame() {
|
||||
}
|
||||
// Trace before present
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::endFrame before present\n"); fclose(f); }
|
||||
FILE* f = fopen("spacetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::endFrame before present\n"); fclose(f); }
|
||||
}
|
||||
|
||||
SDL_RenderPresent(m_renderer);
|
||||
|
||||
// Trace after present
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::endFrame after present\n"); fclose(f); }
|
||||
FILE* f = fopen("spacetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::endFrame after present\n"); fclose(f); }
|
||||
}
|
||||
}
|
||||
|
||||
@ -170,11 +170,11 @@ void RenderManager::renderTexture(SDL_Texture* texture, const SDL_FRect* src, co
|
||||
|
||||
// Trace renderTexture usage
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::renderTexture entry tex=%llu src=%p dst=%p\n", (unsigned long long)(uintptr_t)texture, (void*)src, (void*)dst); fclose(f); }
|
||||
FILE* f = fopen("spacetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::renderTexture entry tex=%llu src=%p dst=%p\n", (unsigned long long)(uintptr_t)texture, (void*)src, (void*)dst); fclose(f); }
|
||||
}
|
||||
SDL_RenderTexture(m_renderer, texture, src, dst);
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::renderTexture after SDL_RenderTexture tex=%llu\n", (unsigned long long)(uintptr_t)texture); fclose(f); }
|
||||
FILE* f = fopen("spacetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::renderTexture after SDL_RenderTexture tex=%llu\n", (unsigned long long)(uintptr_t)texture); fclose(f); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -84,7 +84,7 @@ void RenderManager::beginFrame() {
|
||||
|
||||
// Trace beginFrame entry
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::beginFrame entry\n"); fclose(f); }
|
||||
FILE* f = fopen("spacetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::beginFrame entry\n"); fclose(f); }
|
||||
}
|
||||
|
||||
// Clear the screen (wrapped with trace)
|
||||
@ -92,7 +92,7 @@ void RenderManager::beginFrame() {
|
||||
|
||||
// Trace after clear
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::beginFrame after clear\n"); fclose(f); }
|
||||
FILE* f = fopen("spacetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::beginFrame after clear\n"); fclose(f); }
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,14 +102,14 @@ void RenderManager::endFrame() {
|
||||
}
|
||||
// Trace before present
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::endFrame before present\n"); fclose(f); }
|
||||
FILE* f = fopen("spacetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::endFrame before present\n"); fclose(f); }
|
||||
}
|
||||
|
||||
SDL_RenderPresent(m_renderer);
|
||||
|
||||
// Trace after present
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::endFrame after present\n"); fclose(f); }
|
||||
FILE* f = fopen("spacetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::endFrame after present\n"); fclose(f); }
|
||||
}
|
||||
}
|
||||
|
||||
@ -200,11 +200,11 @@ void RenderManager::renderTexture(SDL_Texture* texture, const SDL_FRect* src, co
|
||||
|
||||
// Trace renderTexture usage
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::renderTexture entry tex=%llu src=%p dst=%p\n", (unsigned long long)(uintptr_t)texture, (void*)src, (void*)dst); fclose(f); }
|
||||
FILE* f = fopen("spacetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::renderTexture entry tex=%llu src=%p dst=%p\n", (unsigned long long)(uintptr_t)texture, (void*)src, (void*)dst); fclose(f); }
|
||||
}
|
||||
SDL_RenderTexture(m_renderer, texture, src, dst);
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::renderTexture after SDL_RenderTexture tex=%llu\n", (unsigned long long)(uintptr_t)texture); fclose(f); }
|
||||
FILE* f = fopen("spacetris_trace.log", "a"); if (f) { fprintf(f, "RenderManager::renderTexture after SDL_RenderTexture tex=%llu\n", (unsigned long long)(uintptr_t)texture); fclose(f); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ ScoreManager::ScoreManager(size_t maxScores) : maxEntries(maxScores) {}
|
||||
|
||||
std::string ScoreManager::filePath() const {
|
||||
static std::string path; if (!path.empty()) return path;
|
||||
char* base = SDL_GetPrefPath("example","tetris_sdl3");
|
||||
char* base = SDL_GetPrefPath("example","spacetris_sdl3");
|
||||
if (base) { path = std::string(base)+"highscores.txt"; SDL_free(base);} else path="highscores.txt";
|
||||
return path;
|
||||
}
|
||||
|
||||
@ -729,7 +729,7 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi
|
||||
|
||||
// Trace entry to persistent log for debugging abrupt exit/crash during render
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render entry\n"); fclose(f); }
|
||||
FILE* f = fopen("spacetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render entry\n"); fclose(f); }
|
||||
}
|
||||
|
||||
// Compute content offsets (same approach as main.cpp for proper centering)
|
||||
@ -745,7 +745,7 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi
|
||||
|
||||
// Background is drawn by main (stretched to the full window) to avoid double-draw.
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a");
|
||||
FILE* f = fopen("spacetris_trace.log", "a");
|
||||
if (f) {
|
||||
fprintf(f, "MenuState::render ctx.mainScreenTex=%llu (w=%d h=%d)\n",
|
||||
(unsigned long long)(uintptr_t)ctx.mainScreenTex,
|
||||
@ -921,7 +921,7 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi
|
||||
|
||||
if (ctx.pixelFont) {
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render drawing buttons; pixelFont=%llu\n", (unsigned long long)(uintptr_t)ctx.pixelFont); fclose(f); }
|
||||
FILE* f = fopen("spacetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render drawing buttons; pixelFont=%llu\n", (unsigned long long)(uintptr_t)ctx.pixelFont); fclose(f); }
|
||||
}
|
||||
char levelBtnText[32];
|
||||
int startLevel = ctx.startLevelSelection ? *ctx.startLevelSelection : 0;
|
||||
@ -1420,6 +1420,6 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi
|
||||
}
|
||||
// Trace exit
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render exit\n"); fclose(f); }
|
||||
FILE* f = fopen("spacetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render exit\n"); fclose(f); }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user