Add Lua scripting support to desktop emulator
- Created emulator-specific lua_game_emulator.cpp using filesystem instead of FatFS - Created lua_game_loader_emulator.cpp to scan games/lua_examples directory - Updated CMakeLists.txt to include Lua 5.4 engine and bindings - Updated to SFML 3.0 API compatibility (event handling, sprite initialization) - Updated Game class to use public members for Lua bindings - Updated GameLauncher to use std::function for lambda captures - Added continuous 60 FPS rendering for smooth display - Emulator now loads and runs all three example Lua games
This commit is contained in:
@@ -3,17 +3,41 @@ project(basic1_emulator)
|
|||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
|
||||||
find_package(SFML 2.5 COMPONENTS graphics window system REQUIRED)
|
find_package(SFML 3.0 COMPONENTS Graphics Window System REQUIRED)
|
||||||
|
|
||||||
|
# Lua source files
|
||||||
|
file(GLOB LUA_SOURCES "../lib/lua/*.c")
|
||||||
|
list(FILTER LUA_SOURCES EXCLUDE REGEX "lua\\.c$|luac\\.c$|loslib\\.c$|liolib\\.c$")
|
||||||
|
|
||||||
|
# Game source files
|
||||||
|
set(GAME_SOURCES
|
||||||
|
../games/lua_bindings.cpp
|
||||||
|
../games/demo_game.cpp
|
||||||
|
../games/tic_tac_toe.cpp
|
||||||
|
../games/monopoly/monopoly_game.cpp
|
||||||
|
../games/monopoly/player.c
|
||||||
|
../lib/game_launcher.cpp
|
||||||
|
../display/low_level_render.cpp
|
||||||
|
../display/low_level_gui.cpp
|
||||||
|
)
|
||||||
|
|
||||||
# Add source files
|
# Add source files
|
||||||
set(SOURCES
|
set(SOURCES
|
||||||
main.cpp
|
main.cpp
|
||||||
low_level_display_sfml.cpp
|
low_level_display_sfml.cpp
|
||||||
|
lua_game_emulator.cpp
|
||||||
|
lua_game_loader_emulator.cpp
|
||||||
|
input_manager.cpp
|
||||||
|
${GAME_SOURCES}
|
||||||
|
${LUA_SOURCES}
|
||||||
# Add more emulator-specific sources here
|
# Add more emulator-specific sources here
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(basic1_emulator ${SOURCES})
|
add_executable(basic1_emulator ${SOURCES})
|
||||||
|
|
||||||
target_include_directories(basic1_emulator PRIVATE ../display ../fonts ../games .)
|
# Define LUA_32BITS for 32-bit embedded mode
|
||||||
|
target_compile_definitions(basic1_emulator PRIVATE LUA_32BITS=1)
|
||||||
|
|
||||||
target_link_libraries(basic1_emulator sfml-graphics sfml-window sfml-system)
|
target_include_directories(basic1_emulator PRIVATE . .. ../display ../fonts ../games ../lib ../lib/lua)
|
||||||
|
|
||||||
|
target_link_libraries(basic1_emulator SFML::Graphics SFML::Window SFML::System)
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ public:
|
|||||||
virtual bool update(const InputEvent& event) = 0;
|
virtual bool update(const InputEvent& event) = 0;
|
||||||
virtual void draw() = 0;
|
virtual void draw() = 0;
|
||||||
virtual bool wants_to_exit() const { return false; }
|
virtual bool wants_to_exit() const { return false; }
|
||||||
protected:
|
|
||||||
|
// Public members for Lua bindings access
|
||||||
uint16_t width;
|
uint16_t width;
|
||||||
uint16_t height;
|
uint16_t height;
|
||||||
LowLevelRenderer* renderer;
|
LowLevelRenderer* renderer;
|
||||||
|
|||||||
@@ -1,25 +1,33 @@
|
|||||||
// Copy of game_launcher.h for emulator build
|
// Copy of game_launcher.h for emulator build
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
#include "input_event.h"
|
#include "input_event.h"
|
||||||
#include "game.h"
|
#include "game.h"
|
||||||
|
|
||||||
class LowLevelRenderer;
|
class LowLevelRenderer;
|
||||||
class LowLevelGUI;
|
class LowLevelGUI;
|
||||||
class InputManager;
|
class InputManager;
|
||||||
|
|
||||||
struct GameEntry {
|
struct GameEntry {
|
||||||
const char* name;
|
const char* name;
|
||||||
const char* description;
|
const char* description;
|
||||||
Game* (*factory)(uint16_t width, uint16_t height, LowLevelRenderer* renderer, LowLevelGUI* gui, InputManager* input_manager);
|
std::function<Game*(uint16_t, uint16_t, LowLevelRenderer*, LowLevelGUI*, InputManager*)> factory;
|
||||||
};
|
};
|
||||||
|
|
||||||
class GameLauncher {
|
class GameLauncher {
|
||||||
public:
|
public:
|
||||||
GameLauncher(uint16_t width, uint16_t height, LowLevelRenderer* renderer, LowLevelGUI* gui, InputManager* input_manager);
|
GameLauncher(uint16_t width, uint16_t height, LowLevelRenderer* renderer, LowLevelGUI* gui, InputManager* input_manager);
|
||||||
void register_game(const char* name, const char* description, Game* (*factory)(uint16_t, uint16_t, LowLevelRenderer*, LowLevelGUI*, InputManager*));
|
|
||||||
|
void register_game(const char* name, const char* description,
|
||||||
|
std::function<Game*(uint16_t, uint16_t, LowLevelRenderer*, LowLevelGUI*, InputManager*)> factory);
|
||||||
|
|
||||||
void draw();
|
void draw();
|
||||||
bool update(const InputEvent& event);
|
bool update(const InputEvent& event);
|
||||||
Game* get_selected_game();
|
Game* get_selected_game();
|
||||||
void reset();
|
void reset();
|
||||||
bool is_game_selected() const { return selected_game != nullptr; }
|
bool is_game_selected() const { return selected_game != nullptr; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint16_t width;
|
uint16_t width;
|
||||||
uint16_t height;
|
uint16_t height;
|
||||||
@@ -29,6 +37,7 @@ private:
|
|||||||
std::vector<GameEntry> games;
|
std::vector<GameEntry> games;
|
||||||
int selected_index;
|
int selected_index;
|
||||||
Game* selected_game;
|
Game* selected_game;
|
||||||
|
|
||||||
static const int MENU_Y_START = 60;
|
static const int MENU_Y_START = 60;
|
||||||
static const int MENU_ITEM_HEIGHT = 40;
|
static const int MENU_ITEM_HEIGHT = 40;
|
||||||
static const int MENU_PADDING = 10;
|
static const int MENU_PADDING = 10;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
// Emulator stub for InputManager implementation
|
// Emulator stub for InputManager implementation
|
||||||
#include "input_manager.h"
|
#include "input_manager.h"
|
||||||
// No implementation needed for stub
|
|
||||||
|
// Methods are all defined inline in the header
|
||||||
|
// This file exists just to ensure the class has a compilation unit
|
||||||
|
|||||||
@@ -7,21 +7,21 @@
|
|||||||
// Minimal stub for emulator build
|
// Minimal stub for emulator build
|
||||||
class InputManager {
|
class InputManager {
|
||||||
public:
|
public:
|
||||||
bool has_buttons() const { return false; }
|
inline bool has_buttons() const { return false; }
|
||||||
bool has_touch() const { return false; }
|
inline bool has_touch() const { return false; }
|
||||||
|
|
||||||
void get_virtual_button_regions(int* a_rect, int* b_rect) const {
|
inline void get_virtual_button_regions(int* a_rect, int* b_rect) const {
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
a_rect[i] = v_button_a[i];
|
a_rect[i] = v_button_a[i];
|
||||||
b_rect[i] = v_button_b[i];
|
b_rect[i] = v_button_b[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void set_virtual_button_regions(int ax, int ay, int aw, int ah, int bx, int by, int bw, int bh) {
|
inline void set_virtual_button_regions(int ax, int ay, int aw, int ah, int bx, int by, int bw, int bh) {
|
||||||
v_button_a[0] = ax; v_button_a[1] = ay; v_button_a[2] = aw; v_button_a[3] = ah;
|
v_button_a[0] = ax; v_button_a[1] = ay; v_button_a[2] = aw; v_button_a[3] = ah;
|
||||||
v_button_b[0] = bx; v_button_b[1] = by; v_button_b[2] = bw; v_button_b[3] = bh;
|
v_button_b[0] = bx; v_button_b[1] = by; v_button_b[2] = bw; v_button_b[3] = bh;
|
||||||
v_buttons_active = true;
|
v_buttons_active = true;
|
||||||
}
|
}
|
||||||
void clear_virtual_button_regions() {
|
inline void clear_virtual_button_regions() {
|
||||||
v_buttons_active = false;
|
v_buttons_active = false;
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
v_button_a[i] = 0;
|
v_button_a[i] = 0;
|
||||||
@@ -29,7 +29,7 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool check_virtual_buttons(int16_t x, int16_t y, InputType& out_type) const {
|
inline bool check_virtual_buttons(int16_t x, int16_t y, InputType& out_type) const {
|
||||||
if (!v_buttons_active) return false;
|
if (!v_buttons_active) return false;
|
||||||
|
|
||||||
if (x >= v_button_a[0] && x <= v_button_a[0] + v_button_a[2] &&
|
if (x >= v_button_a[0] && x <= v_button_a[0] + v_button_a[2] &&
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
// Add missing method implementations for emulator linkage
|
// Add missing method implementations for emulator linkage
|
||||||
bool LowLevelDisplaySFML::pollEvent(sf::Event& event) {
|
std::optional<sf::Event> LowLevelDisplaySFML::pollEvent() {
|
||||||
return window.pollEvent(event);
|
return window.pollEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LowLevelDisplaySFML::close() {
|
void LowLevelDisplaySFML::close() {
|
||||||
@@ -14,18 +14,22 @@ void LowLevelDisplaySFML::close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LowLevelDisplaySFML::LowLevelDisplaySFML(int w, int h)
|
LowLevelDisplaySFML::LowLevelDisplaySFML(int w, int h)
|
||||||
: width(w), height(h), window(sf::VideoMode(w, h), "basic1 Emulator"), framebuffer((w * h + 7) / 8, 0) {}
|
: width(w), height(h),
|
||||||
|
window(sf::VideoMode({(unsigned)w, (unsigned)h}), "basic1 Emulator"),
|
||||||
|
framebuffer((w * h + 7) / 8, 0) {}
|
||||||
|
|
||||||
bool LowLevelDisplaySFML::init() {
|
bool LowLevelDisplaySFML::init() {
|
||||||
texture.create(width, height);
|
if (!texture.resize({(unsigned)width, (unsigned)height})) {
|
||||||
sprite.setTexture(texture);
|
return false;
|
||||||
|
}
|
||||||
|
sprite.emplace(texture);
|
||||||
return window.isOpen();
|
return window.isOpen();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LowLevelDisplaySFML::draw_buffer(const uint8_t* bit_buffer) {
|
void LowLevelDisplaySFML::draw_buffer(const uint8_t* bit_buffer) {
|
||||||
// Convert 1-bit buffer to 8-bit grayscale (or RGBA) for SFML
|
// Convert 1-bit buffer to 8-bit grayscale (or RGBA) for SFML
|
||||||
// Each bit in bit_buffer represents a pixel (0=black, 1=white)
|
// Each bit in bit_buffer represents a pixel (0=black, 1=white)
|
||||||
std::vector<sf::Uint8> pixels(width * height * 4, 0);
|
std::vector<std::uint8_t> pixels(width * height * 4, 0);
|
||||||
for (int y = 0; y < height; ++y) {
|
for (int y = 0; y < height; ++y) {
|
||||||
for (int x = 0; x < width; ++x) {
|
for (int x = 0; x < width; ++x) {
|
||||||
int bit_index = y * width + x;
|
int bit_index = y * width + x;
|
||||||
@@ -33,7 +37,7 @@ void LowLevelDisplaySFML::draw_buffer(const uint8_t* bit_buffer) {
|
|||||||
int bit_offset = 7 - (bit_index % 8);
|
int bit_offset = 7 - (bit_index % 8);
|
||||||
bool on = (bit_buffer[byte_index] >> bit_offset) & 0x1;
|
bool on = (bit_buffer[byte_index] >> bit_offset) & 0x1;
|
||||||
int idx = (y * width + x) * 4;
|
int idx = (y * width + x) * 4;
|
||||||
sf::Uint8 color = on ? 255 : 0;
|
std::uint8_t color = on ? 255 : 0;
|
||||||
pixels[idx + 0] = color; // R
|
pixels[idx + 0] = color; // R
|
||||||
pixels[idx + 1] = color; // G
|
pixels[idx + 1] = color; // G
|
||||||
pixels[idx + 2] = color; // B
|
pixels[idx + 2] = color; // B
|
||||||
@@ -44,14 +48,10 @@ void LowLevelDisplaySFML::draw_buffer(const uint8_t* bit_buffer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LowLevelDisplaySFML::refresh() {
|
void LowLevelDisplaySFML::refresh() {
|
||||||
sf::Event event;
|
|
||||||
while (window.pollEvent(event)) {
|
|
||||||
if (event.type == sf::Event::Closed)
|
|
||||||
window.close();
|
|
||||||
// TODO: Handle mouse/keyboard input here
|
|
||||||
}
|
|
||||||
window.clear(sf::Color::Black);
|
window.clear(sf::Color::Black);
|
||||||
window.draw(sprite);
|
if (sprite) {
|
||||||
|
window.draw(*sprite);
|
||||||
|
}
|
||||||
window.display();
|
window.display();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <SFML/Graphics.hpp>
|
#include <SFML/Graphics.hpp>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
class LowLevelDisplaySFML {
|
class LowLevelDisplaySFML {
|
||||||
public:
|
public:
|
||||||
@@ -8,12 +9,12 @@ public:
|
|||||||
void draw_buffer(const uint8_t* bit_buffer);
|
void draw_buffer(const uint8_t* bit_buffer);
|
||||||
void refresh();
|
void refresh();
|
||||||
bool isOpen() const;
|
bool isOpen() const;
|
||||||
bool pollEvent(sf::Event& event);
|
std::optional<sf::Event> pollEvent();
|
||||||
void close();
|
void close();
|
||||||
private:
|
private:
|
||||||
int width, height;
|
int width, height;
|
||||||
sf::RenderWindow window;
|
sf::RenderWindow window;
|
||||||
sf::Texture texture;
|
sf::Texture texture;
|
||||||
sf::Sprite sprite;
|
std::optional<sf::Sprite> sprite;
|
||||||
std::vector<uint8_t> framebuffer;
|
std::vector<uint8_t> framebuffer;
|
||||||
};
|
};
|
||||||
|
|||||||
193
emulator/lua_game_emulator.cpp
Normal file
193
emulator/lua_game_emulator.cpp
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
// ============================================================================
|
||||||
|
// LUA GAME WRAPPER - EMULATOR IMPLEMENTATION
|
||||||
|
// ============================================================================
|
||||||
|
// Manages Lua VM lifecycle and script execution for desktop emulator
|
||||||
|
|
||||||
|
#include "../games/lua_game.h"
|
||||||
|
#include "../games/lua_bindings.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
LuaGame::LuaGame(const char* script_path, uint16_t width, uint16_t height,
|
||||||
|
LowLevelRenderer* renderer, LowLevelGUI* gui, InputManager* input_manager)
|
||||||
|
: Game(width, height, renderer, gui, input_manager),
|
||||||
|
L(nullptr),
|
||||||
|
script_path(script_path),
|
||||||
|
loaded(false) {
|
||||||
|
|
||||||
|
// Create new Lua state
|
||||||
|
L = luaL_newstate();
|
||||||
|
if (!L) {
|
||||||
|
error_message = "Failed to create Lua state";
|
||||||
|
printf("LuaGame: %s\n", error_message.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open standard Lua libraries (math, string, table, coroutine)
|
||||||
|
luaL_openlibs(L);
|
||||||
|
|
||||||
|
// Register game API bindings
|
||||||
|
lua_bindings_register(L, this);
|
||||||
|
|
||||||
|
// Load the script
|
||||||
|
loaded = load_script();
|
||||||
|
|
||||||
|
if (!loaded) {
|
||||||
|
printf("LuaGame: Failed to load %s: %s\n", script_path, error_message.c_str());
|
||||||
|
} else {
|
||||||
|
printf("LuaGame: Successfully loaded %s\n", script_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LuaGame::~LuaGame() {
|
||||||
|
if (L) {
|
||||||
|
lua_close(L);
|
||||||
|
L = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LuaGame::load_script() {
|
||||||
|
// Open Lua script from filesystem (emulator)
|
||||||
|
std::ifstream file(script_path);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
error_message = "Failed to open file: " + script_path;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read entire file into string
|
||||||
|
std::stringstream buffer;
|
||||||
|
buffer << file.rdbuf();
|
||||||
|
std::string script_content = buffer.str();
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
if (script_content.empty()) {
|
||||||
|
error_message = "Script file is empty";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (script_content.size() > 64 * 1024) { // Limit to 64KB
|
||||||
|
error_message = "Script file too large (> 64KB)";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load script into Lua
|
||||||
|
int result = luaL_loadbuffer(L, script_content.c_str(), script_content.size(), script_path.c_str());
|
||||||
|
|
||||||
|
if (result != LUA_OK) {
|
||||||
|
report_error("load script");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute script (loads functions into global namespace)
|
||||||
|
result = lua_pcall(L, 0, 0, 0);
|
||||||
|
if (result != LUA_OK) {
|
||||||
|
report_error("execute script");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LuaGame::init() {
|
||||||
|
if (!loaded) return;
|
||||||
|
|
||||||
|
// Call Lua init() function if it exists
|
||||||
|
lua_getglobal(L, "init");
|
||||||
|
if (lua_isfunction(L, -1)) {
|
||||||
|
call_lua_function("init", 0, 0);
|
||||||
|
} else {
|
||||||
|
lua_pop(L, 1); // Pop non-function value
|
||||||
|
printf("LuaGame: Warning - no init() function found\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LuaGame::update(const InputEvent& event) {
|
||||||
|
if (!loaded) return false;
|
||||||
|
|
||||||
|
// Call Lua update(event) function if it exists
|
||||||
|
lua_getglobal(L, "update");
|
||||||
|
if (!lua_isfunction(L, -1)) {
|
||||||
|
lua_pop(L, 1);
|
||||||
|
return false; // No update function, no redraw needed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push event table to Lua
|
||||||
|
lua_newtable(L);
|
||||||
|
|
||||||
|
lua_pushstring(L, "type");
|
||||||
|
lua_pushinteger(L, (int)event.type);
|
||||||
|
lua_settable(L, -3);
|
||||||
|
|
||||||
|
lua_pushstring(L, "x");
|
||||||
|
lua_pushinteger(L, event.x);
|
||||||
|
lua_settable(L, -3);
|
||||||
|
|
||||||
|
lua_pushstring(L, "y");
|
||||||
|
lua_pushinteger(L, event.y);
|
||||||
|
lua_settable(L, -3);
|
||||||
|
|
||||||
|
lua_pushstring(L, "button_id");
|
||||||
|
lua_pushinteger(L, event.button_id);
|
||||||
|
lua_settable(L, -3);
|
||||||
|
|
||||||
|
lua_pushstring(L, "valid");
|
||||||
|
lua_pushboolean(L, event.valid);
|
||||||
|
lua_settable(L, -3);
|
||||||
|
|
||||||
|
// Call update(event) with 1 arg, expecting 1 result (needs_redraw)
|
||||||
|
if (!call_lua_function("update", 1, 1)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get return value (needs redraw?)
|
||||||
|
bool needs_redraw = lua_toboolean(L, -1);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
return needs_redraw;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LuaGame::draw() {
|
||||||
|
if (!loaded) return;
|
||||||
|
|
||||||
|
// Call Lua draw() function if it exists
|
||||||
|
lua_getglobal(L, "draw");
|
||||||
|
if (lua_isfunction(L, -1)) {
|
||||||
|
call_lua_function("draw", 0, 0);
|
||||||
|
} else {
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LuaGame::wants_to_exit() const {
|
||||||
|
if (!L) return false;
|
||||||
|
|
||||||
|
// Check if Lua script requested exit
|
||||||
|
lua_pushstring(L, "__exit_requested");
|
||||||
|
lua_gettable(L, LUA_REGISTRYINDEX);
|
||||||
|
bool exit = lua_toboolean(L, -1);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
return exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LuaGame::call_lua_function(const char* func_name, int nargs, int nresults) {
|
||||||
|
int result = lua_pcall(L, nargs, nresults, 0);
|
||||||
|
if (result != LUA_OK) {
|
||||||
|
report_error(func_name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LuaGame::report_error(const char* context) {
|
||||||
|
const char* msg = lua_tostring(L, -1);
|
||||||
|
if (msg) {
|
||||||
|
error_message = context;
|
||||||
|
error_message += ": ";
|
||||||
|
error_message += msg;
|
||||||
|
printf("LuaGame Error [%s]: %s\n", context, msg);
|
||||||
|
}
|
||||||
|
lua_pop(L, 1); // Pop error message
|
||||||
|
}
|
||||||
159
emulator/lua_game_loader_emulator.cpp
Normal file
159
emulator/lua_game_loader_emulator.cpp
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
// ============================================================================
|
||||||
|
// LUA GAME LOADER - EMULATOR IMPLEMENTATION
|
||||||
|
// ============================================================================
|
||||||
|
// Discovers Lua scripts from filesystem and integrates with game launcher
|
||||||
|
|
||||||
|
#include "../games/lua_game_loader.h"
|
||||||
|
#include "../games/lua_game.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
// Structure to hold script path for factory closure
|
||||||
|
struct LuaGameFactoryData {
|
||||||
|
char script_path[256];
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::vector<LuaGameFactoryData*> factory_data_list;
|
||||||
|
|
||||||
|
bool LuaGameLoader::parse_metadata(const char* script_path, char* name, char* description) {
|
||||||
|
// Default name from filename
|
||||||
|
fs::path path(script_path);
|
||||||
|
std::string filename = path.stem().string(); // Get filename without extension
|
||||||
|
strncpy(name, filename.c_str(), 63);
|
||||||
|
name[63] = '\0';
|
||||||
|
|
||||||
|
// Default empty description
|
||||||
|
description[0] = '\0';
|
||||||
|
|
||||||
|
// Try to open file and parse metadata comments
|
||||||
|
std::ifstream file(script_path);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
printf("LuaGameLoader: Warning - could not open %s for metadata\n", script_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read first 512 bytes to look for metadata comments
|
||||||
|
char buffer[512];
|
||||||
|
file.read(buffer, sizeof(buffer) - 1);
|
||||||
|
std::streamsize bytes_read = file.gcount();
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
if (bytes_read == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer[bytes_read] = '\0';
|
||||||
|
|
||||||
|
// Parse metadata comments: -- NAME: Game Name
|
||||||
|
char* line = buffer;
|
||||||
|
while (line && (line - buffer) < bytes_read) {
|
||||||
|
char* next_line = strchr(line, '\n');
|
||||||
|
if (next_line) *next_line = '\0';
|
||||||
|
|
||||||
|
// Check for -- NAME:
|
||||||
|
if (strncmp(line, "-- NAME:", 8) == 0) {
|
||||||
|
const char* value = line + 8;
|
||||||
|
while (*value == ' ') value++; // Skip spaces
|
||||||
|
strncpy(name, value, 63);
|
||||||
|
name[63] = '\0';
|
||||||
|
}
|
||||||
|
// Check for -- DESC:
|
||||||
|
else if (strncmp(line, "-- DESC:", 8) == 0) {
|
||||||
|
const char* value = line + 8;
|
||||||
|
while (*value == ' ') value++;
|
||||||
|
strncpy(description, value, 127);
|
||||||
|
description[127] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next_line) {
|
||||||
|
line = next_line + 1;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaGameLoader::register_all_games(GameLauncher* launcher) {
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
printf("LuaGameLoader: Scanning games/lua_examples directory for .lua scripts...\n");
|
||||||
|
|
||||||
|
// Path to lua examples relative to emulator binary
|
||||||
|
const char* search_paths[] = {
|
||||||
|
"../games/lua_examples",
|
||||||
|
"games/lua_examples",
|
||||||
|
"./lua_examples"
|
||||||
|
};
|
||||||
|
|
||||||
|
fs::path games_dir;
|
||||||
|
bool found_dir = false;
|
||||||
|
|
||||||
|
// Try to find the lua_examples directory
|
||||||
|
for (const char* search_path : search_paths) {
|
||||||
|
if (fs::exists(search_path) && fs::is_directory(search_path)) {
|
||||||
|
games_dir = fs::path(search_path);
|
||||||
|
found_dir = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found_dir) {
|
||||||
|
printf("LuaGameLoader: Could not find games/lua_examples directory\n");
|
||||||
|
printf("LuaGameLoader: Tried: ../games/lua_examples, games/lua_examples, ./lua_examples\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("LuaGameLoader: Found directory: %s\n", games_dir.string().c_str());
|
||||||
|
|
||||||
|
// Scan for .lua files
|
||||||
|
try {
|
||||||
|
for (const auto& entry : fs::directory_iterator(games_dir)) {
|
||||||
|
if (!entry.is_regular_file()) continue;
|
||||||
|
|
||||||
|
// Check for .lua extension
|
||||||
|
if (entry.path().extension() != ".lua") continue;
|
||||||
|
|
||||||
|
std::string script_path = entry.path().string();
|
||||||
|
|
||||||
|
// Parse metadata
|
||||||
|
char name[64];
|
||||||
|
char description[128];
|
||||||
|
parse_metadata(script_path.c_str(), name, description);
|
||||||
|
|
||||||
|
printf("LuaGameLoader: Found %s - '%s'\n", entry.path().filename().string().c_str(), name);
|
||||||
|
|
||||||
|
// Create factory data (persistent for game lifetime)
|
||||||
|
LuaGameFactoryData* data = new LuaGameFactoryData();
|
||||||
|
strncpy(data->script_path, script_path.c_str(), sizeof(data->script_path) - 1);
|
||||||
|
data->script_path[sizeof(data->script_path) - 1] = '\0';
|
||||||
|
factory_data_list.push_back(data);
|
||||||
|
|
||||||
|
// Register with launcher - using lambda factory pattern
|
||||||
|
launcher->register_game(
|
||||||
|
name,
|
||||||
|
description[0] ? description : "Lua Script",
|
||||||
|
[data](uint16_t width, uint16_t height, LowLevelRenderer* renderer,
|
||||||
|
LowLevelGUI* gui, InputManager* input_manager) -> Game* {
|
||||||
|
return new LuaGame(data->script_path, width, height, renderer, gui, input_manager);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
} catch (const fs::filesystem_error& e) {
|
||||||
|
printf("LuaGameLoader: Error scanning directory: %s\n", e.what());
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("LuaGameLoader: Registered %d Lua games\n", count);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
@@ -2,10 +2,11 @@
|
|||||||
#include "low_level_display_sfml.h"
|
#include "low_level_display_sfml.h"
|
||||||
#include "../display/low_level_render.h"
|
#include "../display/low_level_render.h"
|
||||||
#include "../display/low_level_gui.h"
|
#include "../display/low_level_gui.h"
|
||||||
#include "game_launcher.h"
|
#include "../lib/game_launcher.h"
|
||||||
#include "../games/demo_game.h"
|
#include "../games/demo_game.h"
|
||||||
#include "../games/tic_tac_toe.h"
|
#include "../games/tic_tac_toe.h"
|
||||||
#include "../games/monopoly/monopoly_game.h"
|
#include "../games/monopoly/monopoly_game.h"
|
||||||
|
#include "../games/lua_game_loader.h"
|
||||||
#include "input_manager.h"
|
#include "input_manager.h"
|
||||||
#include <SFML/Window.hpp>
|
#include <SFML/Window.hpp>
|
||||||
#include <SFML/Graphics.hpp>
|
#include <SFML/Graphics.hpp>
|
||||||
@@ -34,6 +35,11 @@ int main() {
|
|||||||
|
|
||||||
// Create GameLauncher
|
// Create GameLauncher
|
||||||
GameLauncher launcher(WIDTH, HEIGHT, &renderer, &gui, &input_manager);
|
GameLauncher launcher(WIDTH, HEIGHT, &renderer, &gui, &input_manager);
|
||||||
|
|
||||||
|
// Register Lua games from lua_examples directory
|
||||||
|
LuaGameLoader::register_all_games(&launcher);
|
||||||
|
|
||||||
|
// Register built-in C++ games
|
||||||
launcher.register_game("Tic-Tac-Toe", "Classic 2-player game",
|
launcher.register_game("Tic-Tac-Toe", "Classic 2-player game",
|
||||||
[](uint16_t w, uint16_t h, LowLevelRenderer* r, LowLevelGUI* g, InputManager* im) -> Game* {
|
[](uint16_t w, uint16_t h, LowLevelRenderer* r, LowLevelGUI* g, InputManager* im) -> Game* {
|
||||||
return new TicTacToeGame(w, h, r, g, im);
|
return new TicTacToeGame(w, h, r, g, im);
|
||||||
@@ -54,15 +60,15 @@ int main() {
|
|||||||
while (display.isOpen() && running) {
|
while (display.isOpen() && running) {
|
||||||
// Handle SFML events and translate to InputEvent
|
// Handle SFML events and translate to InputEvent
|
||||||
InputEvent event = {INPUT_NONE, 0, 0, 0, 0, 0, false};
|
InputEvent event = {INPUT_NONE, 0, 0, 0, 0, 0, false};
|
||||||
sf::Event sfEvent;
|
|
||||||
while (display.pollEvent(sfEvent)) {
|
while (const auto sfEvent = display.pollEvent()) {
|
||||||
if (sfEvent.type == sf::Event::Closed) {
|
if (const auto* closed = sfEvent->getIf<sf::Event::Closed>()) {
|
||||||
display.close();
|
display.close();
|
||||||
running = false;
|
running = false;
|
||||||
} else if (sfEvent.type == sf::Event::MouseButtonPressed) {
|
} else if (const auto* mousePressed = sfEvent->getIf<sf::Event::MouseButtonPressed>()) {
|
||||||
event.type = INPUT_TOUCH_DOWN;
|
event.type = INPUT_TOUCH_DOWN;
|
||||||
event.x = sfEvent.mouseButton.x;
|
event.x = mousePressed->position.x;
|
||||||
event.y = sfEvent.mouseButton.y;
|
event.y = mousePressed->position.y;
|
||||||
event.valid = true;
|
event.valid = true;
|
||||||
|
|
||||||
// Check for virtual buttons
|
// Check for virtual buttons
|
||||||
@@ -70,14 +76,14 @@ int main() {
|
|||||||
if (input_manager.check_virtual_buttons(event.x, event.y, virtual_type)) {
|
if (input_manager.check_virtual_buttons(event.x, event.y, virtual_type)) {
|
||||||
event.type = virtual_type;
|
event.type = virtual_type;
|
||||||
}
|
}
|
||||||
} else if (sfEvent.type == sf::Event::KeyPressed) {
|
} else if (const auto* keyPressed = sfEvent->getIf<sf::Event::KeyPressed>()) {
|
||||||
if (sfEvent.key.code == sf::Keyboard::Space) {
|
if (keyPressed->code == sf::Keyboard::Key::Space) {
|
||||||
event.type = INPUT_BUTTON_0;
|
event.type = INPUT_BUTTON_0;
|
||||||
event.valid = true;
|
event.valid = true;
|
||||||
} else if (sfEvent.key.code == sf::Keyboard::Enter) {
|
} else if (keyPressed->code == sf::Keyboard::Key::Enter) {
|
||||||
event.type = INPUT_BUTTON_1;
|
event.type = INPUT_BUTTON_1;
|
||||||
event.valid = true;
|
event.valid = true;
|
||||||
} else if (sfEvent.key.code == sf::Keyboard::Escape) {
|
} else if (keyPressed->code == sf::Keyboard::Key::Escape) {
|
||||||
// Simulate long-press exit
|
// Simulate long-press exit
|
||||||
if (launcher.is_game_selected()) {
|
if (launcher.is_game_selected()) {
|
||||||
launcher.reset();
|
launcher.reset();
|
||||||
@@ -103,18 +109,19 @@ int main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needs_redraw) {
|
// Always redraw every frame for emulator
|
||||||
renderer.clear_buffer();
|
renderer.clear_buffer();
|
||||||
if (launcher.is_game_selected()) {
|
if (launcher.is_game_selected()) {
|
||||||
current_game = launcher.get_selected_game();
|
current_game = launcher.get_selected_game();
|
||||||
current_game->draw();
|
current_game->draw();
|
||||||
} else {
|
} else {
|
||||||
launcher.draw();
|
launcher.draw();
|
||||||
}
|
|
||||||
display.draw_buffer(framebuffer.data());
|
|
||||||
display.refresh();
|
|
||||||
needs_redraw = false;
|
|
||||||
}
|
}
|
||||||
|
display.draw_buffer(framebuffer.data());
|
||||||
|
display.refresh();
|
||||||
|
|
||||||
|
// Small delay to prevent busy-waiting (60 FPS)
|
||||||
|
sf::sleep(sf::milliseconds(16));
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user