Initial game launcher

This commit is contained in:
Adolfo Reyna
2026-01-30 21:39:09 -05:00
parent 2a6861fdf5
commit e3445b545d
7 changed files with 392 additions and 14 deletions

View File

@@ -42,7 +42,9 @@ pico_sdk_init()
add_executable(basic1 add_executable(basic1
basic1.cpp basic1.cpp
lib/input_manager.cpp lib/input_manager.cpp
lib/game_launcher.cpp
games/tic_tac_toe.cpp games/tic_tac_toe.cpp
games/demo_game.cpp
lib/st7796/st7796.c lib/st7796/st7796.c
lib/ft6336u/ft6336u.c lib/ft6336u/ft6336u.c
lib/sd_card/sd_card.c lib/sd_card/sd_card.c

View File

@@ -49,7 +49,9 @@
#include "display/low_level_touch.h" #include "display/low_level_touch.h"
#include "input_manager.h" #include "input_manager.h"
#include "game.h" #include "game.h"
#include "game_launcher.h"
#include "tic_tac_toe.h" #include "tic_tac_toe.h"
#include "demo_game.h"
// Binary info for RP2350 - ensures proper boot image structure // Binary info for RP2350 - ensures proper boot image structure
@@ -318,16 +320,24 @@ int main()
// Create InputManager for processing inputs // Create InputManager for processing inputs
InputManager input_manager(touch, &config); InputManager input_manager(touch, &config);
// Create game instance (polymorphic - can swap for other games later) // Create GameLauncher
Game* current_game = new TicTacToeGame(V_WIDTH, V_HEIGHT, &renderer, &gui); GameLauncher launcher(V_WIDTH, V_HEIGHT, &renderer, &gui);
// Initialize game // Register available games
current_game->init(); 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 launcher.register_game("Demo Game", "Simple test game",
current_game->draw(); [](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); refresh_screen_async(bit_buffer, display);
printf("Initial screen refresh queued on Core 1\n"); 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("\nEntering reactive game loop (Core 0 - input & logic)\n");
printf("Display refreshes handled by Core 1\n\n"); printf("Display refreshes handled by Core 1\n\n");
Game* current_game = nullptr;
uint32_t game_start_time = 0;
while (1) { while (1) {
// Sleep until interrupt wakes us up (very power efficient!) // Sleep until interrupt wakes us up (very power efficient!)
__wfi(); // Wait For Interrupt - CPU sleeps until any interrupt occurs __wfi(); // Wait For Interrupt - CPU sleeps until any interrupt occurs
@@ -417,23 +430,58 @@ int main()
// 1. Process button input first (higher priority) // 1. Process button input first (higher priority)
input = input_manager.process_button_input(); input = input_manager.process_button_input();
if (input.valid) {
needs_refresh = current_game->update(input);
}
// 2. Process touch input (if no button was pressed) // 2. Process touch input (if no button was pressed)
if (!input.valid) { if (!input.valid) {
input = input_manager.process_touch_input(&last_touch_time); input = input_manager.process_touch_input(&last_touch_time);
}
// 3. Process input based on current state
if (input.valid) { 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); 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) { if (needs_refresh || pending_refresh) {
// Clear buffer and redraw entire UI with updated state // Clear buffer and redraw entire UI with updated state
memset(bit_buffer, 0, V_WIDTH * V_HEIGHT / 8); memset(bit_buffer, 0, V_WIDTH * V_HEIGHT / 8);
if (launcher.is_game_selected()) {
current_game = launcher.get_selected_game();
current_game->draw(); current_game->draw();
} else {
launcher.draw();
}
// Request async refresh (non-blocking - handled by Core 1) // Request async refresh (non-blocking - handled by Core 1)
bool refresh_started = refresh_screen_async(bit_buffer, display); bool refresh_started = refresh_screen_async(bit_buffer, display);

View File

@@ -1,3 +1,6 @@
#ifndef LOW_LEVEL_GUI_H
#define LOW_LEVEL_GUI_H
#include "low_level_render.h" #include "low_level_render.h"
class LowLevelWindow class LowLevelWindow
@@ -33,3 +36,5 @@ public:
void draw_notification(LowLevelWindow* window, int x, int y, int width, const char* time, const char* message); 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); void draw_large_clock(LowLevelWindow* window, int x, int y, const char* time_str);
}; };
#endif // LOW_LEVEL_GUI_H

60
games/demo_game.cpp Normal file
View File

@@ -0,0 +1,60 @@
// ============================================================================
// DEMO GAME IMPLEMENTATION
// ============================================================================
// Simple demo game to test the launcher
#include "demo_game.h"
#include <stdio.h>
#include <string.h>
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);
}
}

30
games/demo_game.h Normal file
View File

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

131
lib/game_launcher.cpp Normal file
View File

@@ -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 <stdio.h>
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");
}

102
lib/game_launcher.h Normal file
View File

@@ -0,0 +1,102 @@
// ============================================================================
// GAME LAUNCHER HEADER
// ============================================================================
// Menu system for selecting and launching games
#ifndef GAME_LAUNCHER_H
#define GAME_LAUNCHER_H
#include <stdint.h>
#include <vector>
#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<GameEntry> 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