diff --git a/CMakeLists.txt b/CMakeLists.txt index 628df30..105d920 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,7 +42,9 @@ pico_sdk_init() add_executable(basic1 basic1.cpp lib/input_manager.cpp + lib/game_launcher.cpp games/tic_tac_toe.cpp + games/demo_game.cpp lib/st7796/st7796.c lib/ft6336u/ft6336u.c lib/sd_card/sd_card.c diff --git a/basic1.cpp b/basic1.cpp index df70f56..edfa4f6 100644 --- a/basic1.cpp +++ b/basic1.cpp @@ -49,7 +49,9 @@ #include "display/low_level_touch.h" #include "input_manager.h" #include "game.h" +#include "game_launcher.h" #include "tic_tac_toe.h" +#include "demo_game.h" // Binary info for RP2350 - ensures proper boot image structure @@ -318,16 +320,24 @@ int main() // Create InputManager for processing inputs InputManager input_manager(touch, &config); - // Create game instance (polymorphic - can swap for other games later) - Game* current_game = new TicTacToeGame(V_WIDTH, V_HEIGHT, &renderer, &gui); + // Create GameLauncher + GameLauncher launcher(V_WIDTH, V_HEIGHT, &renderer, &gui); - // Initialize game - current_game->init(); + // Register available games + launcher.register_game("Tic-Tac-Toe", "Classic 2-player game", + [](uint16_t w, uint16_t h, LowLevelRenderer* r, LowLevelGUI* g) -> Game* { + return new TicTacToeGame(w, h, r, g); + }); - // Draw initial game graphics - current_game->draw(); + launcher.register_game("Demo Game", "Simple test game", + [](uint16_t w, uint16_t h, LowLevelRenderer* r, LowLevelGUI* g) -> Game* { + return new DemoGame(w, h, r, g); + }); - // Refresh the screen with the rendered GUI (async on Core 1) + // Draw launcher menu + launcher.draw(); + + // Refresh the screen with the launcher menu (async on Core 1) refresh_screen_async(bit_buffer, display); printf("Initial screen refresh queued on Core 1\n"); @@ -408,6 +418,9 @@ int main() printf("\nEntering reactive game loop (Core 0 - input & logic)\n"); printf("Display refreshes handled by Core 1\n\n"); + Game* current_game = nullptr; + uint32_t game_start_time = 0; + while (1) { // Sleep until interrupt wakes us up (very power efficient!) __wfi(); // Wait For Interrupt - CPU sleeps until any interrupt occurs @@ -417,23 +430,58 @@ int main() // 1. Process button input first (higher priority) input = input_manager.process_button_input(); - if (input.valid) { - needs_refresh = current_game->update(input); - } // 2. Process touch input (if no button was pressed) if (!input.valid) { input = input_manager.process_touch_input(&last_touch_time); - if (input.valid) { + } + + // 3. Process input based on current state + if (input.valid) { + if (launcher.is_game_selected()) { + // In game mode - process game input + current_game = launcher.get_selected_game(); needs_refresh = current_game->update(input); + + // Check if player wants to exit (hold for 2+ seconds or special gesture) + // For now, we'll add a simple long-press detection + if (input.type == INPUT_TOUCH_DOWN) { + // Record start time on first touch + if (game_start_time == 0) { + game_start_time = to_ms_since_boot(get_absolute_time()); + } + } else if (input.type == INPUT_TOUCH_UP) { + uint32_t now = to_ms_since_boot(get_absolute_time()); + if (game_start_time > 0 && (now - game_start_time) > 2000) { + // Long press detected - return to menu + printf("Long press detected - returning to launcher\n"); + launcher.reset(); + needs_refresh = true; + } + game_start_time = 0; + } + } else { + // In launcher mode - process menu input + bool game_selected = launcher.update(input); + if (game_selected) { + printf("Game launched successfully\n"); + game_start_time = 0; + } + needs_refresh = true; } } - // 3. Redraw and queue async refresh on Core 1 + // 4. Redraw and queue async refresh on Core 1 if (needs_refresh || pending_refresh) { // Clear buffer and redraw entire UI with updated state memset(bit_buffer, 0, V_WIDTH * V_HEIGHT / 8); - current_game->draw(); + + if (launcher.is_game_selected()) { + current_game = launcher.get_selected_game(); + current_game->draw(); + } else { + launcher.draw(); + } // Request async refresh (non-blocking - handled by Core 1) bool refresh_started = refresh_screen_async(bit_buffer, display); diff --git a/display/low_level_gui.h b/display/low_level_gui.h index 4c3423d..ae2e438 100644 --- a/display/low_level_gui.h +++ b/display/low_level_gui.h @@ -1,3 +1,6 @@ +#ifndef LOW_LEVEL_GUI_H +#define LOW_LEVEL_GUI_H + #include "low_level_render.h" class LowLevelWindow @@ -32,4 +35,6 @@ public: void draw_circular_gauge(LowLevelWindow* window, int x, int y, int width, const char* label, int percentage); void draw_notification(LowLevelWindow* window, int x, int y, int width, const char* time, const char* message); void draw_large_clock(LowLevelWindow* window, int x, int y, const char* time_str); -}; \ No newline at end of file +}; + +#endif // LOW_LEVEL_GUI_H \ No newline at end of file diff --git a/games/demo_game.cpp b/games/demo_game.cpp new file mode 100644 index 0000000..e9aa42c --- /dev/null +++ b/games/demo_game.cpp @@ -0,0 +1,60 @@ +// ============================================================================ +// DEMO GAME IMPLEMENTATION +// ============================================================================ +// Simple demo game to test the launcher + +#include "demo_game.h" +#include +#include + +extern Font font_5x5_obj; + +DemoGame::DemoGame(uint16_t width, uint16_t height, LowLevelRenderer* renderer, LowLevelGUI* gui) + : Game(width, height, renderer, gui), tap_count(0), exit_requested(false) { +} + +void DemoGame::init() { + tap_count = 0; + exit_requested = false; +} + +bool DemoGame::update(const InputEvent& event) { + bool needs_refresh = false; + + if (event.type == INPUT_TOUCH_DOWN || event.type == INPUT_BUTTON_0 || event.type == INPUT_BUTTON_1) { + tap_count++; + needs_refresh = true; + + // After 3 taps, signal to exit + if (tap_count >= 3) { + exit_requested = true; + } + } + + return needs_refresh; +} + +void DemoGame::draw() { + // Draw main window + gui->draw_new_window(10, 10, width - 20, height - 20, "Demo Game"); + + renderer->set_font(&font_5x5_obj); + + // Draw centered message + int y = height / 2 - 40; + + renderer->draw_string(width/2 - 60, y, "Demo Game!", true); + renderer->draw_string(width/2 - 80, y + 20, "This is a placeholder", true); + + // Show tap count + char count_text[40]; + snprintf(count_text, sizeof(count_text), "Taps: %d", tap_count); + renderer->draw_string(width/2 - 40, y + 50, count_text, true); + + // Instructions + if (tap_count < 3) { + renderer->draw_string(width/2 - 80, y + 80, "Tap 3 times to exit", true); + } else { + renderer->draw_string(width/2 - 70, y + 80, "Exiting to menu...", true); + } +} diff --git a/games/demo_game.h b/games/demo_game.h new file mode 100644 index 0000000..d91f9e2 --- /dev/null +++ b/games/demo_game.h @@ -0,0 +1,30 @@ +// ============================================================================ +// DEMO GAME HEADER +// ============================================================================ +// Simple demo game to test the launcher + +#ifndef DEMO_GAME_H +#define DEMO_GAME_H + +#include "game.h" + +/** + * @brief Demo Game - Simple test game for launcher + * + * Displays a welcome message and simple instructions. + * Touch or press button to exit back to launcher. + */ +class DemoGame : public Game { +public: + DemoGame(uint16_t width, uint16_t height, LowLevelRenderer* renderer, LowLevelGUI* gui); + + void init() override; + bool update(const InputEvent& event) override; + void draw() override; + +private: + int tap_count; + bool exit_requested; +}; + +#endif // DEMO_GAME_H diff --git a/lib/game_launcher.cpp b/lib/game_launcher.cpp new file mode 100644 index 0000000..6263043 --- /dev/null +++ b/lib/game_launcher.cpp @@ -0,0 +1,131 @@ +// ============================================================================ +// GAME LAUNCHER IMPLEMENTATION +// ============================================================================ +// Menu system for selecting and launching games + +#include "game_launcher.h" +#include "display/low_level_render.h" +#include "display/low_level_gui.h" +#include + +GameLauncher::GameLauncher(uint16_t width, uint16_t height, + LowLevelRenderer* renderer, LowLevelGUI* gui) + : width(width), height(height), renderer(renderer), gui(gui), + selected_index(0), selected_game(nullptr) { +} + +void GameLauncher::register_game(const char* name, const char* description, + Game* (*factory)(uint16_t, uint16_t, LowLevelRenderer*, LowLevelGUI*)) { + GameEntry entry; + entry.name = name; + entry.description = description; + entry.factory = factory; + games.push_back(entry); + + printf("Registered game: %s - %s\n", name, description); +} + +void GameLauncher::draw() { + // Draw main window + gui->draw_new_window(10, 10, width - 20, height - 20, "Game Launcher"); + + // Draw title + renderer->draw_string(30, 30, "Select a Game:", true); + + // Draw game list + for (size_t i = 0; i < games.size(); i++) { + int y = MENU_Y_START + (i * MENU_ITEM_HEIGHT); + + // Highlight selected item + if ((int)i == selected_index) { + // Draw selection box + renderer->draw_rectangle(20, y - 5, width - 40, MENU_ITEM_HEIGHT - 10, true, 2); + renderer->draw_string(30, y + 2, ">", true); + } + + // Draw game name + renderer->draw_string(45, y + 2, games[i].name, true); + + // Draw description (smaller, below name) + renderer->draw_string(45, y + 15, games[i].description, true); + } + + // Draw instructions at bottom + const char* instructions; +#ifdef BUTTON_KEY0_PIN + instructions = "Touch game or use KEY0/KEY1"; +#else + instructions = "Touch game to play"; +#endif + renderer->draw_string(30, height - 35, instructions, true); +} + +bool GameLauncher::update(const InputEvent& event) { + bool needs_refresh = false; + + switch (event.type) { + case INPUT_TOUCH_DOWN: { + printf("Touch at (%d,%d) in launcher\n", event.x, event.y); + + // Check if touch is on a game entry + for (size_t i = 0; i < games.size(); i++) { + int y = MENU_Y_START + (i * MENU_ITEM_HEIGHT); + + // Touch area is the entire menu item + if (event.y >= y - 5 && event.y < y + MENU_ITEM_HEIGHT - 5) { + // Game selected - create instance + printf("Selected game: %s\n", games[i].name); + selected_game = games[i].factory(width, height, renderer, gui); + if (selected_game) { + selected_game->init(); + return true; // Signal game selected + } + break; + } + } + break; + } + + case INPUT_BUTTON_0: { + // Navigate menu + if (games.size() > 0) { + selected_index = (selected_index + 1) % games.size(); + needs_refresh = true; + printf("Menu selection: %d (%s)\n", selected_index, games[selected_index].name); + } + break; + } + + case INPUT_BUTTON_1: { + // Select current game + if (selected_index >= 0 && selected_index < (int)games.size()) { + printf("Selected game: %s\n", games[selected_index].name); + selected_game = games[selected_index].factory(width, height, renderer, gui); + if (selected_game) { + selected_game->init(); + return true; // Signal game selected + } + } + break; + } + + default: + break; + } + + return needs_refresh; +} + +Game* GameLauncher::get_selected_game() { + return selected_game; +} + +void GameLauncher::reset() { + // Clean up current game if any + if (selected_game) { + delete selected_game; + selected_game = nullptr; + } + selected_index = 0; + printf("Launcher reset - returning to menu\n"); +} diff --git a/lib/game_launcher.h b/lib/game_launcher.h new file mode 100644 index 0000000..196c9f1 --- /dev/null +++ b/lib/game_launcher.h @@ -0,0 +1,102 @@ +// ============================================================================ +// GAME LAUNCHER HEADER +// ============================================================================ +// Menu system for selecting and launching games + +#ifndef GAME_LAUNCHER_H +#define GAME_LAUNCHER_H + +#include +#include +#include "input_event.h" +#include "game.h" + +// Forward declarations +class LowLevelRenderer; +class LowLevelGUI; + +/** + * @brief Game entry in launcher menu + */ +struct GameEntry { + const char* name; // Display name + const char* description; // Short description + Game* (*factory)(uint16_t width, uint16_t height, + LowLevelRenderer* renderer, LowLevelGUI* gui); // Factory function +}; + +/** + * @brief Game Launcher - Menu system for game selection + * + * Displays a list of available games and allows selection via: + * - Touch: Tap on game name + * - Buttons: KEY0 to navigate, KEY1 to select + * + * Returns the selected game instance for the main loop to run. + */ +class GameLauncher { +public: + /** + * @brief Construct GameLauncher + * @param width Display width in pixels + * @param height Display height in pixels + * @param renderer Pointer to low-level rendering interface + * @param gui Pointer to GUI drawing primitives + */ + GameLauncher(uint16_t width, uint16_t height, LowLevelRenderer* renderer, LowLevelGUI* gui); + + /** + * @brief Register a game in the launcher + * @param name Game display name + * @param description Short description + * @param factory Function pointer to create game instance + */ + void register_game(const char* name, const char* description, + Game* (*factory)(uint16_t, uint16_t, LowLevelRenderer*, LowLevelGUI*)); + + /** + * @brief Draw the launcher menu + */ + void draw(); + + /** + * @brief Process input event in launcher + * @param event Input event from InputManager + * @return true if a game was selected (check get_selected_game()) + */ + bool update(const InputEvent& event); + + /** + * @brief Get the currently selected game instance + * @return Pointer to selected game, or nullptr if none selected + */ + Game* get_selected_game(); + + /** + * @brief Reset launcher to show menu again + */ + void reset(); + + /** + * @brief Check if a game is currently selected + * @return true if game selected, false if in menu + */ + bool is_game_selected() const { return selected_game != nullptr; } + +private: + uint16_t width; + uint16_t height; + LowLevelRenderer* renderer; + LowLevelGUI* gui; + + std::vector games; + int selected_index; // Currently highlighted game + Game* selected_game; // Currently running game (nullptr = in menu) + + // Menu layout constants + static const int MENU_Y_START = 60; + static const int MENU_ITEM_HEIGHT = 40; + static const int MENU_PADDING = 10; +}; + +#endif // GAME_LAUNCHER_H