Python command emulation
This commit is contained in:
parent
a44b8c93e9
commit
763fa201f0
|
@ -0,0 +1,54 @@
|
|||
# Convenience Makefile wrapper for McRogueFace
|
||||
# This delegates to CMake build in the build directory
|
||||
|
||||
.PHONY: all build clean run test dist help
|
||||
|
||||
# Default target
|
||||
all: build
|
||||
|
||||
# Build the project
|
||||
build:
|
||||
@./build.sh
|
||||
|
||||
# Clean build artifacts
|
||||
clean:
|
||||
@./clean.sh
|
||||
|
||||
# Run the game
|
||||
run: build
|
||||
@cd build && ./mcrogueface
|
||||
|
||||
# Run in Python mode
|
||||
python: build
|
||||
@cd build && ./mcrogueface -i
|
||||
|
||||
# Test basic functionality
|
||||
test: build
|
||||
@echo "Testing McRogueFace..."
|
||||
@cd build && ./mcrogueface -V
|
||||
@cd build && ./mcrogueface -c "print('Test passed')"
|
||||
@cd build && ./mcrogueface --headless -c "import mcrfpy; print('mcrfpy imported successfully')"
|
||||
|
||||
# Create distribution archive
|
||||
dist: build
|
||||
@echo "Creating distribution archive..."
|
||||
@cd build && zip -r ../McRogueFace-$$(date +%Y%m%d).zip . -x "*.o" "CMakeFiles/*" "Makefile" "*.cmake"
|
||||
@echo "Distribution archive created: McRogueFace-$$(date +%Y%m%d).zip"
|
||||
|
||||
# Show help
|
||||
help:
|
||||
@echo "McRogueFace Build System"
|
||||
@echo "======================="
|
||||
@echo ""
|
||||
@echo "Available targets:"
|
||||
@echo " make - Build the project (default)"
|
||||
@echo " make build - Build the project"
|
||||
@echo " make clean - Remove all build artifacts"
|
||||
@echo " make run - Build and run the game"
|
||||
@echo " make python - Build and run in Python interactive mode"
|
||||
@echo " make test - Run basic tests"
|
||||
@echo " make dist - Create distribution archive"
|
||||
@echo " make help - Show this help message"
|
||||
@echo ""
|
||||
@echo "Build output goes to: ./build/"
|
||||
@echo "Distribution archives are created in project root"
|
|
@ -0,0 +1,54 @@
|
|||
#!/bin/bash
|
||||
# Build script for McRogueFace - compiles everything into ./build directory
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${GREEN}McRogueFace Build Script${NC}"
|
||||
echo "========================="
|
||||
|
||||
# Create build directory if it doesn't exist
|
||||
if [ ! -d "build" ]; then
|
||||
echo -e "${YELLOW}Creating build directory...${NC}"
|
||||
mkdir build
|
||||
fi
|
||||
|
||||
# Change to build directory
|
||||
cd build
|
||||
|
||||
# Run CMake to generate build files
|
||||
echo -e "${YELLOW}Running CMake...${NC}"
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
# Check if CMake succeeded
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}CMake configuration failed!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Run make with parallel jobs
|
||||
echo -e "${YELLOW}Building with make...${NC}"
|
||||
make -j$(nproc)
|
||||
|
||||
# Check if make succeeded
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}Build failed!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}Build completed successfully!${NC}"
|
||||
echo ""
|
||||
echo "The build directory contains:"
|
||||
ls -la
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}To run McRogueFace:${NC}"
|
||||
echo " cd build"
|
||||
echo " ./mcrogueface"
|
||||
echo ""
|
||||
echo -e "${GREEN}To create a distribution archive:${NC}"
|
||||
echo " cd build"
|
||||
echo " zip -r ../McRogueFace-$(date +%Y%m%d).zip ."
|
|
@ -0,0 +1,33 @@
|
|||
#!/bin/bash
|
||||
# Clean script for McRogueFace - removes build artifacts
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${YELLOW}Cleaning McRogueFace build artifacts...${NC}"
|
||||
|
||||
# Remove build directory
|
||||
if [ -d "build" ]; then
|
||||
echo "Removing build directory..."
|
||||
rm -rf build
|
||||
fi
|
||||
|
||||
# Remove CMake artifacts from project root
|
||||
echo "Removing CMake artifacts from project root..."
|
||||
rm -f CMakeCache.txt
|
||||
rm -f cmake_install.cmake
|
||||
rm -f Makefile
|
||||
rm -rf CMakeFiles
|
||||
|
||||
# Remove compiled executable from project root
|
||||
rm -f mcrogueface
|
||||
|
||||
# Remove any test artifacts
|
||||
rm -f test_script.py
|
||||
rm -rf test_venv
|
||||
rm -f python3 # symlink
|
||||
|
||||
echo -e "${GREEN}Clean complete!${NC}"
|
|
@ -1,54 +0,0 @@
|
|||
# Install script for directory: /home/john/Development/McRogueFace
|
||||
|
||||
# Set the install prefix
|
||||
if(NOT DEFINED CMAKE_INSTALL_PREFIX)
|
||||
set(CMAKE_INSTALL_PREFIX "/usr/local")
|
||||
endif()
|
||||
string(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}")
|
||||
|
||||
# Set the install configuration name.
|
||||
if(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME)
|
||||
if(BUILD_TYPE)
|
||||
string(REGEX REPLACE "^[^A-Za-z0-9_]+" ""
|
||||
CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}")
|
||||
else()
|
||||
set(CMAKE_INSTALL_CONFIG_NAME "")
|
||||
endif()
|
||||
message(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"")
|
||||
endif()
|
||||
|
||||
# Set the component getting installed.
|
||||
if(NOT CMAKE_INSTALL_COMPONENT)
|
||||
if(COMPONENT)
|
||||
message(STATUS "Install component: \"${COMPONENT}\"")
|
||||
set(CMAKE_INSTALL_COMPONENT "${COMPONENT}")
|
||||
else()
|
||||
set(CMAKE_INSTALL_COMPONENT)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Install shared libraries without execute permission?
|
||||
if(NOT DEFINED CMAKE_INSTALL_SO_NO_EXE)
|
||||
set(CMAKE_INSTALL_SO_NO_EXE "1")
|
||||
endif()
|
||||
|
||||
# Is this installation the result of a crosscompile?
|
||||
if(NOT DEFINED CMAKE_CROSSCOMPILING)
|
||||
set(CMAKE_CROSSCOMPILING "FALSE")
|
||||
endif()
|
||||
|
||||
# Set default install directory permissions.
|
||||
if(NOT DEFINED CMAKE_OBJDUMP)
|
||||
set(CMAKE_OBJDUMP "/usr/bin/objdump")
|
||||
endif()
|
||||
|
||||
if(CMAKE_INSTALL_COMPONENT)
|
||||
set(CMAKE_INSTALL_MANIFEST "install_manifest_${CMAKE_INSTALL_COMPONENT}.txt")
|
||||
else()
|
||||
set(CMAKE_INSTALL_MANIFEST "install_manifest.txt")
|
||||
endif()
|
||||
|
||||
string(REPLACE ";" "\n" CMAKE_INSTALL_MANIFEST_CONTENT
|
||||
"${CMAKE_INSTALL_MANIFEST_FILES}")
|
||||
file(WRITE "/home/john/Development/McRogueFace/${CMAKE_INSTALL_MANIFEST}"
|
||||
"${CMAKE_INSTALL_MANIFEST_CONTENT}")
|
|
@ -0,0 +1,157 @@
|
|||
#include "CommandLineParser.h"
|
||||
#include <iostream>
|
||||
#include <filesystem>
|
||||
#include <algorithm>
|
||||
|
||||
CommandLineParser::CommandLineParser(int argc, char* argv[])
|
||||
: argc(argc), argv(argv) {}
|
||||
|
||||
CommandLineParser::ParseResult CommandLineParser::parse(McRogueFaceConfig& config) {
|
||||
ParseResult result;
|
||||
current_arg = 1; // Reset for each parse
|
||||
|
||||
// Detect if running as Python interpreter
|
||||
std::filesystem::path exec_name = std::filesystem::path(argv[0]).filename();
|
||||
if (exec_name.string().find("python") == 0) {
|
||||
config.headless = true;
|
||||
config.python_mode = true;
|
||||
}
|
||||
|
||||
while (current_arg < argc) {
|
||||
std::string arg = argv[current_arg];
|
||||
|
||||
// Handle Python-style single-letter flags
|
||||
if (arg == "-h" || arg == "--help") {
|
||||
print_help();
|
||||
result.should_exit = true;
|
||||
result.exit_code = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (arg == "-V" || arg == "--version") {
|
||||
print_version();
|
||||
result.should_exit = true;
|
||||
result.exit_code = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Python execution modes
|
||||
if (arg == "-c") {
|
||||
config.python_mode = true;
|
||||
current_arg++;
|
||||
if (current_arg >= argc) {
|
||||
std::cerr << "Argument expected for the -c option" << std::endl;
|
||||
result.should_exit = true;
|
||||
result.exit_code = 1;
|
||||
return result;
|
||||
}
|
||||
config.python_command = argv[current_arg];
|
||||
current_arg++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg == "-m") {
|
||||
config.python_mode = true;
|
||||
current_arg++;
|
||||
if (current_arg >= argc) {
|
||||
std::cerr << "Argument expected for the -m option" << std::endl;
|
||||
result.should_exit = true;
|
||||
result.exit_code = 1;
|
||||
return result;
|
||||
}
|
||||
config.python_module = argv[current_arg];
|
||||
current_arg++;
|
||||
// Collect remaining args as module args
|
||||
while (current_arg < argc) {
|
||||
config.script_args.push_back(argv[current_arg]);
|
||||
current_arg++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg == "-i") {
|
||||
config.interactive_mode = true;
|
||||
config.python_mode = true;
|
||||
current_arg++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// McRogueFace specific flags
|
||||
if (arg == "--headless") {
|
||||
config.headless = true;
|
||||
config.audio_enabled = false;
|
||||
current_arg++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg == "--audio-off") {
|
||||
config.audio_enabled = false;
|
||||
current_arg++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg == "--audio-on") {
|
||||
config.audio_enabled = true;
|
||||
current_arg++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg == "--screenshot") {
|
||||
config.take_screenshot = true;
|
||||
current_arg++;
|
||||
if (current_arg < argc && argv[current_arg][0] != '-') {
|
||||
config.screenshot_path = argv[current_arg];
|
||||
current_arg++;
|
||||
} else {
|
||||
config.screenshot_path = "screenshot.png";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// If no flags matched, treat as positional argument (script name)
|
||||
if (arg[0] != '-') {
|
||||
config.script_path = arg;
|
||||
config.python_mode = true;
|
||||
current_arg++;
|
||||
// Remaining args are script args
|
||||
while (current_arg < argc) {
|
||||
config.script_args.push_back(argv[current_arg]);
|
||||
current_arg++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Unknown flag
|
||||
std::cerr << "Unknown option: " << arg << std::endl;
|
||||
result.should_exit = true;
|
||||
result.exit_code = 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void CommandLineParser::print_help() {
|
||||
std::cout << "usage: mcrogueface [option] ... [-c cmd | -m mod | file | -] [arg] ...\n"
|
||||
<< "Options:\n"
|
||||
<< " -c cmd : program passed in as string (terminates option list)\n"
|
||||
<< " -h : print this help message and exit (also --help)\n"
|
||||
<< " -i : inspect interactively after running script\n"
|
||||
<< " -m mod : run library module as a script (terminates option list)\n"
|
||||
<< " -V : print the Python version number and exit (also --version)\n"
|
||||
<< "\n"
|
||||
<< "McRogueFace specific options:\n"
|
||||
<< " --headless : run without creating a window (implies --audio-off)\n"
|
||||
<< " --audio-off : disable audio\n"
|
||||
<< " --audio-on : enable audio (even in headless mode)\n"
|
||||
<< " --screenshot [path] : take a screenshot in headless mode\n"
|
||||
<< "\n"
|
||||
<< "Arguments:\n"
|
||||
<< " file : program read from script file\n"
|
||||
<< " - : program read from stdin\n"
|
||||
<< " arg ...: arguments passed to program in sys.argv[1:]\n";
|
||||
}
|
||||
|
||||
void CommandLineParser::print_version() {
|
||||
std::cout << "Python 3.12.0 (McRogueFace embedded)\n";
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
#ifndef COMMAND_LINE_PARSER_H
|
||||
#define COMMAND_LINE_PARSER_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "McRogueFaceConfig.h"
|
||||
|
||||
class CommandLineParser {
|
||||
public:
|
||||
struct ParseResult {
|
||||
bool should_exit = false;
|
||||
int exit_code = 0;
|
||||
};
|
||||
|
||||
CommandLineParser(int argc, char* argv[]);
|
||||
ParseResult parse(McRogueFaceConfig& config);
|
||||
|
||||
private:
|
||||
int argc;
|
||||
char** argv;
|
||||
int current_arg = 1; // Skip program name
|
||||
|
||||
bool has_flag(const std::string& short_flag, const std::string& long_flag = "");
|
||||
std::string get_next_arg(const std::string& flag_name);
|
||||
void parse_positional_args(McRogueFaceConfig& config);
|
||||
void print_help();
|
||||
void print_version();
|
||||
};
|
||||
|
||||
#endif // COMMAND_LINE_PARSER_H
|
|
@ -5,14 +5,32 @@
|
|||
#include "UITestScene.h"
|
||||
#include "Resources.h"
|
||||
|
||||
GameEngine::GameEngine()
|
||||
GameEngine::GameEngine() : GameEngine(McRogueFaceConfig{})
|
||||
{
|
||||
}
|
||||
|
||||
GameEngine::GameEngine(const McRogueFaceConfig& cfg)
|
||||
: config(cfg), headless(cfg.headless)
|
||||
{
|
||||
Resources::font.loadFromFile("./assets/JetbrainsMono.ttf");
|
||||
Resources::game = this;
|
||||
window_title = "Crypt of Sokoban - 7DRL 2025, McRogueface Engine";
|
||||
window.create(sf::VideoMode(1024, 768), window_title, sf::Style::Titlebar | sf::Style::Close);
|
||||
visible = window.getDefaultView();
|
||||
window.setFramerateLimit(60);
|
||||
|
||||
// Initialize rendering based on headless mode
|
||||
if (headless) {
|
||||
headless_renderer = std::make_unique<HeadlessRenderer>();
|
||||
if (!headless_renderer->init(1024, 768)) {
|
||||
throw std::runtime_error("Failed to initialize headless renderer");
|
||||
}
|
||||
render_target = &headless_renderer->getRenderTarget();
|
||||
} else {
|
||||
window = std::make_unique<sf::RenderWindow>();
|
||||
window->create(sf::VideoMode(1024, 768), window_title, sf::Style::Titlebar | sf::Style::Close);
|
||||
window->setFramerateLimit(60);
|
||||
render_target = window.get();
|
||||
}
|
||||
|
||||
visible = render_target->getDefaultView();
|
||||
scene = "uitest";
|
||||
scenes["uitest"] = new UITestScene(this);
|
||||
|
||||
|
@ -25,6 +43,13 @@ GameEngine::GameEngine()
|
|||
runtime.restart();
|
||||
}
|
||||
|
||||
GameEngine::~GameEngine()
|
||||
{
|
||||
for (auto& [name, scene] : scenes) {
|
||||
delete scene;
|
||||
}
|
||||
}
|
||||
|
||||
Scene* GameEngine::currentScene() { return scenes[scene]; }
|
||||
void GameEngine::changeScene(std::string s)
|
||||
{
|
||||
|
@ -37,13 +62,24 @@ void GameEngine::changeScene(std::string s)
|
|||
void GameEngine::quit() { running = false; }
|
||||
void GameEngine::setPause(bool p) { paused = p; }
|
||||
sf::Font & GameEngine::getFont() { /*return font; */ return Resources::font; }
|
||||
sf::RenderWindow & GameEngine::getWindow() { return window; }
|
||||
sf::RenderWindow & GameEngine::getWindow() {
|
||||
if (!window) {
|
||||
throw std::runtime_error("Window not available in headless mode");
|
||||
}
|
||||
return *window;
|
||||
}
|
||||
|
||||
sf::RenderTarget & GameEngine::getRenderTarget() {
|
||||
return *render_target;
|
||||
}
|
||||
|
||||
void GameEngine::createScene(std::string s) { scenes[s] = new PyScene(this); }
|
||||
|
||||
void GameEngine::setWindowScale(float multiplier)
|
||||
{
|
||||
window.setSize(sf::Vector2u(1024 * multiplier, 768 * multiplier)); // 7DRL 2024: window scaling
|
||||
if (!headless && window) {
|
||||
window->setSize(sf::Vector2u(1024 * multiplier, 768 * multiplier)); // 7DRL 2024: window scaling
|
||||
}
|
||||
//window.create(sf::VideoMode(1024 * multiplier, 768 * multiplier), window_title, sf::Style::Titlebar | sf::Style::Close);
|
||||
}
|
||||
|
||||
|
@ -55,18 +91,40 @@ void GameEngine::run()
|
|||
{
|
||||
currentScene()->update();
|
||||
testTimers();
|
||||
if (!headless) {
|
||||
sUserInput();
|
||||
}
|
||||
if (!paused)
|
||||
{
|
||||
}
|
||||
currentScene()->render();
|
||||
|
||||
// Display the frame
|
||||
if (headless) {
|
||||
headless_renderer->display();
|
||||
// Take screenshot if requested
|
||||
if (config.take_screenshot) {
|
||||
headless_renderer->saveScreenshot(config.screenshot_path.empty() ? "screenshot.png" : config.screenshot_path);
|
||||
config.take_screenshot = false; // Only take one screenshot
|
||||
}
|
||||
} else {
|
||||
window->display();
|
||||
}
|
||||
|
||||
currentFrame++;
|
||||
frameTime = clock.restart().asSeconds();
|
||||
fps = 1 / frameTime;
|
||||
int whole_fps = (int)fps;
|
||||
int tenth_fps = int(fps * 100) % 10;
|
||||
//window.setTitle(window_title + " " + std::to_string(fps) + " FPS");
|
||||
window.setTitle(window_title + " " + std::to_string(whole_fps) + "." + std::to_string(tenth_fps) + " FPS");
|
||||
|
||||
if (!headless && window) {
|
||||
window->setTitle(window_title + " " + std::to_string(whole_fps) + "." + std::to_string(tenth_fps) + " FPS");
|
||||
}
|
||||
|
||||
// In windowed mode, check if window was closed
|
||||
if (!headless && window && !window->isOpen()) {
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -111,7 +169,7 @@ void GameEngine::testTimers()
|
|||
void GameEngine::sUserInput()
|
||||
{
|
||||
sf::Event event;
|
||||
while (window.pollEvent(event))
|
||||
while (window && window->pollEvent(event))
|
||||
{
|
||||
std::string actionType;
|
||||
int actionCode = 0;
|
||||
|
|
|
@ -6,10 +6,16 @@
|
|||
#include "IndexTexture.h"
|
||||
#include "Timer.h"
|
||||
#include "PyCallable.h"
|
||||
#include "McRogueFaceConfig.h"
|
||||
#include "HeadlessRenderer.h"
|
||||
#include <memory>
|
||||
|
||||
class GameEngine
|
||||
{
|
||||
sf::RenderWindow window;
|
||||
std::unique_ptr<sf::RenderWindow> window;
|
||||
std::unique_ptr<HeadlessRenderer> headless_renderer;
|
||||
sf::RenderTarget* render_target;
|
||||
|
||||
sf::Font font;
|
||||
std::map<std::string, Scene*> scenes;
|
||||
bool running = true;
|
||||
|
@ -20,6 +26,9 @@ class GameEngine
|
|||
float frameTime;
|
||||
std::string window_title;
|
||||
|
||||
bool headless = false;
|
||||
McRogueFaceConfig config;
|
||||
|
||||
sf::Clock runtime;
|
||||
//std::map<std::string, Timer> timers;
|
||||
std::map<std::string, std::shared_ptr<PyTimerCallable>> timers;
|
||||
|
@ -28,6 +37,8 @@ class GameEngine
|
|||
public:
|
||||
std::string scene;
|
||||
GameEngine();
|
||||
GameEngine(const McRogueFaceConfig& cfg);
|
||||
~GameEngine();
|
||||
Scene* currentScene();
|
||||
void changeScene(std::string);
|
||||
void createScene(std::string);
|
||||
|
@ -35,6 +46,7 @@ public:
|
|||
void setPause(bool);
|
||||
sf::Font & getFont();
|
||||
sf::RenderWindow & getWindow();
|
||||
sf::RenderTarget & getRenderTarget();
|
||||
void run();
|
||||
void sUserInput();
|
||||
int getFrame() { return currentFrame; }
|
||||
|
@ -42,6 +54,7 @@ public:
|
|||
sf::View getView() { return visible; }
|
||||
void manageTimer(std::string, PyObject*, int);
|
||||
void setWindowScale(float);
|
||||
bool isHeadless() const { return headless; }
|
||||
|
||||
// global textures for scripts to access
|
||||
std::vector<IndexTexture> textures;
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
#include "HeadlessRenderer.h"
|
||||
#include <iostream>
|
||||
|
||||
bool HeadlessRenderer::init(int width, int height) {
|
||||
if (!render_texture.create(width, height)) {
|
||||
std::cerr << "Failed to create headless render texture" << std::endl;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
sf::RenderTarget& HeadlessRenderer::getRenderTarget() {
|
||||
return render_texture;
|
||||
}
|
||||
|
||||
void HeadlessRenderer::saveScreenshot(const std::string& path) {
|
||||
sf::Image screenshot = render_texture.getTexture().copyToImage();
|
||||
if (!screenshot.saveToFile(path)) {
|
||||
std::cerr << "Failed to save screenshot to: " << path << std::endl;
|
||||
} else {
|
||||
std::cout << "Screenshot saved to: " << path << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void HeadlessRenderer::display() {
|
||||
render_texture.display();
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
#ifndef HEADLESS_RENDERER_H
|
||||
#define HEADLESS_RENDERER_H
|
||||
|
||||
#include <SFML/Graphics.hpp>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
class HeadlessRenderer {
|
||||
private:
|
||||
sf::RenderTexture render_texture;
|
||||
|
||||
public:
|
||||
bool init(int width = 1024, int height = 768);
|
||||
sf::RenderTarget& getRenderTarget();
|
||||
void saveScreenshot(const std::string& path);
|
||||
void display(); // Finalize the current frame
|
||||
bool isOpen() const { return true; } // Always "open" in headless mode
|
||||
};
|
||||
|
||||
#endif // HEADLESS_RENDERER_H
|
|
@ -3,6 +3,8 @@
|
|||
#include "GameEngine.h"
|
||||
#include "UI.h"
|
||||
#include "Resources.h"
|
||||
#include <filesystem>
|
||||
#include <cstring>
|
||||
|
||||
std::map<std::string, PyObject*> McRFPy_API::callbacks;
|
||||
std::vector<sf::SoundBuffer> McRFPy_API::soundbuffers;
|
||||
|
@ -160,6 +162,68 @@ PyStatus init_python(const char *program_name)
|
|||
return status;
|
||||
}
|
||||
|
||||
PyStatus McRFPy_API::init_python_with_config(const McRogueFaceConfig& config, int argc, char** argv)
|
||||
{
|
||||
PyStatus status;
|
||||
PyConfig pyconfig;
|
||||
PyConfig_InitIsolatedConfig(&pyconfig);
|
||||
|
||||
// CRITICAL: Pass actual command line arguments to Python
|
||||
status = PyConfig_SetBytesArgv(&pyconfig, argc, argv);
|
||||
if (PyStatus_Exception(status)) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Check if we're in a virtual environment
|
||||
auto exe_path = std::filesystem::path(argv[0]);
|
||||
auto exe_dir = exe_path.parent_path();
|
||||
auto venv_root = exe_dir.parent_path();
|
||||
|
||||
if (std::filesystem::exists(venv_root / "pyvenv.cfg")) {
|
||||
// We're running from within a venv!
|
||||
// Add venv's site-packages to module search paths
|
||||
auto site_packages = venv_root / "lib" / "python3.12" / "site-packages";
|
||||
PyWideStringList_Append(&pyconfig.module_search_paths,
|
||||
site_packages.wstring().c_str());
|
||||
pyconfig.module_search_paths_set = 1;
|
||||
}
|
||||
|
||||
// Set Python home to our bundled Python
|
||||
auto python_home = executable_path() + L"/lib/Python";
|
||||
PyConfig_SetString(&pyconfig, &pyconfig.home, python_home.c_str());
|
||||
|
||||
// Set up module search paths
|
||||
#if __PLATFORM_SET_PYTHON_SEARCH_PATHS == 1
|
||||
if (!pyconfig.module_search_paths_set) {
|
||||
pyconfig.module_search_paths_set = 1;
|
||||
}
|
||||
|
||||
// search paths for python libs/modules/scripts
|
||||
const wchar_t* str_arr[] = {
|
||||
L"/scripts",
|
||||
L"/lib/Python/lib.linux-x86_64-3.12",
|
||||
L"/lib/Python",
|
||||
L"/lib/Python/Lib",
|
||||
L"/venv/lib/python3.12/site-packages"
|
||||
};
|
||||
|
||||
for(auto s : str_arr) {
|
||||
status = PyWideStringList_Append(&pyconfig.module_search_paths, (executable_path() + s).c_str());
|
||||
if (PyStatus_Exception(status)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Register mcrfpy module before initialization
|
||||
PyImport_AppendInittab("mcrfpy", &PyInit_mcrfpy);
|
||||
|
||||
status = Py_InitializeFromConfig(&pyconfig);
|
||||
PyConfig_Clear(&pyconfig);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
void McRFPy_API::setSpriteTexture(int ti)
|
||||
{
|
||||
|
@ -200,6 +264,33 @@ void McRFPy_API::api_init() {
|
|||
//setSpriteTexture(0);
|
||||
}
|
||||
|
||||
void McRFPy_API::api_init(const McRogueFaceConfig& config, int argc, char** argv) {
|
||||
// Initialize Python with proper argv - this is CRITICAL
|
||||
PyStatus status = init_python_with_config(config, argc, argv);
|
||||
if (PyStatus_Exception(status)) {
|
||||
Py_ExitStatusException(status);
|
||||
}
|
||||
|
||||
McRFPy_API::mcrf_module = PyImport_ImportModule("mcrfpy");
|
||||
|
||||
// For -m module execution, let Python handle it
|
||||
if (!config.python_module.empty() && config.python_module != "venv") {
|
||||
// Py_RunMain() will handle -m execution
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute based on mode - this is handled in main.cpp now
|
||||
// The actual execution logic is in run_python_interpreter()
|
||||
|
||||
// Set up default resources only if in game mode
|
||||
if (!config.python_mode) {
|
||||
//PyModule_AddObject(McRFPy_API::mcrf_module, "default_font", McRFPy_API::default_font->pyObject());
|
||||
PyObject_SetAttrString(McRFPy_API::mcrf_module, "default_font", McRFPy_API::default_font->pyObject());
|
||||
//PyModule_AddObject(McRFPy_API::mcrf_module, "default_texture", McRFPy_API::default_texture->pyObject());
|
||||
PyObject_SetAttrString(McRFPy_API::mcrf_module, "default_texture", McRFPy_API::default_texture->pyObject());
|
||||
}
|
||||
}
|
||||
|
||||
void McRFPy_API::executeScript(std::string filename)
|
||||
{
|
||||
FILE* PScriptFile = fopen(filename.c_str(), "r");
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include "PyFont.h"
|
||||
#include "PyTexture.h"
|
||||
#include "McRogueFaceConfig.h"
|
||||
|
||||
class GameEngine; // forward declared (circular members)
|
||||
|
||||
|
@ -27,6 +28,8 @@ public:
|
|||
//static void setSpriteTexture(int);
|
||||
inline static GameEngine* game;
|
||||
static void api_init();
|
||||
static void api_init(const McRogueFaceConfig& config, int argc, char** argv);
|
||||
static PyStatus init_python_with_config(const McRogueFaceConfig& config, int argc, char** argv);
|
||||
static void api_shutdown();
|
||||
// Python API functionality - use mcrfpy.* in scripts
|
||||
//static PyObject* _drawSprite(PyObject*, PyObject*);
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
#ifndef MCROGUEFACE_CONFIG_H
|
||||
#define MCROGUEFACE_CONFIG_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
|
||||
struct McRogueFaceConfig {
|
||||
// McRogueFace specific
|
||||
bool headless = false;
|
||||
bool audio_enabled = true;
|
||||
|
||||
// Python interpreter emulation
|
||||
bool python_mode = false;
|
||||
std::string python_command; // -c command
|
||||
std::string python_module; // -m module
|
||||
bool interactive_mode = false; // -i flag
|
||||
bool show_version = false; // -V flag
|
||||
bool show_help = false; // -h flag
|
||||
|
||||
// Script execution
|
||||
std::filesystem::path script_path;
|
||||
std::vector<std::string> script_args;
|
||||
|
||||
// Screenshot functionality for headless mode
|
||||
std::string screenshot_path;
|
||||
bool take_screenshot = false;
|
||||
};
|
||||
|
||||
#endif // MCROGUEFACE_CONFIG_H
|
|
@ -21,6 +21,11 @@ void PyScene::update()
|
|||
|
||||
void PyScene::do_mouse_input(std::string button, std::string type)
|
||||
{
|
||||
// In headless mode, mouse input is not available
|
||||
if (game->isHeadless()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto unscaledmousepos = sf::Mouse::getPosition(game->getWindow());
|
||||
auto mousepos = game->getWindow().mapPixelToCoords(unscaledmousepos);
|
||||
UIDrawable* target;
|
||||
|
@ -62,7 +67,7 @@ void PyScene::doAction(std::string name, std::string type)
|
|||
|
||||
void PyScene::render()
|
||||
{
|
||||
game->getWindow().clear();
|
||||
game->getRenderTarget().clear();
|
||||
|
||||
auto vec = *ui_elements;
|
||||
for (auto e: vec)
|
||||
|
@ -71,5 +76,5 @@ void PyScene::render()
|
|||
e->render();
|
||||
}
|
||||
|
||||
game->getWindow().display();
|
||||
// Display is handled by GameEngine
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ void UIDrawable::click_unregister()
|
|||
|
||||
void UIDrawable::render()
|
||||
{
|
||||
render(sf::Vector2f(), Resources::game->getWindow());
|
||||
render(sf::Vector2f(), Resources::game->getRenderTarget());
|
||||
}
|
||||
|
||||
PyObject* UIDrawable::get_click(PyObject* self, void* closure) {
|
||||
|
|
|
@ -156,8 +156,8 @@ void UITestScene::doAction(std::string name, std::string type)
|
|||
|
||||
void UITestScene::render()
|
||||
{
|
||||
game->getWindow().clear();
|
||||
game->getWindow().draw(text);
|
||||
game->getRenderTarget().clear();
|
||||
game->getRenderTarget().draw(text);
|
||||
|
||||
// draw all UI elements
|
||||
//for (auto e: ui_elements)
|
||||
|
@ -175,7 +175,7 @@ void UITestScene::render()
|
|||
|
||||
//e1.render(sf::Vector2f(-100, -100));
|
||||
|
||||
game->getWindow().display();
|
||||
// Display is handled by GameEngine
|
||||
|
||||
//McRFPy_API::REPL();
|
||||
}
|
||||
|
|
115
src/main.cpp
115
src/main.cpp
|
@ -1,8 +1,117 @@
|
|||
#include <SFML/Graphics.hpp>
|
||||
#include "GameEngine.h"
|
||||
#include "CommandLineParser.h"
|
||||
#include "McRogueFaceConfig.h"
|
||||
#include "McRFPy_API.h"
|
||||
#include <Python.h>
|
||||
#include <iostream>
|
||||
#include <filesystem>
|
||||
|
||||
int main()
|
||||
// Forward declarations
|
||||
int run_game_engine(const McRogueFaceConfig& config);
|
||||
int run_python_interpreter(const McRogueFaceConfig& config, int argc, char* argv[]);
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
GameEngine g;
|
||||
g.run();
|
||||
McRogueFaceConfig config;
|
||||
CommandLineParser parser(argc, argv);
|
||||
|
||||
// Parse arguments
|
||||
auto parse_result = parser.parse(config);
|
||||
if (parse_result.should_exit) {
|
||||
return parse_result.exit_code;
|
||||
}
|
||||
|
||||
// Special handling for -m module: let Python handle modules properly
|
||||
if (!config.python_module.empty()) {
|
||||
config.python_mode = true;
|
||||
}
|
||||
|
||||
// Initialize based on configuration
|
||||
if (config.python_mode) {
|
||||
return run_python_interpreter(config, argc, argv);
|
||||
} else {
|
||||
return run_game_engine(config);
|
||||
}
|
||||
}
|
||||
|
||||
int run_game_engine(const McRogueFaceConfig& config)
|
||||
{
|
||||
GameEngine g(config);
|
||||
g.run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int run_python_interpreter(const McRogueFaceConfig& config, int argc, char* argv[])
|
||||
{
|
||||
// Initialize Python with configuration
|
||||
McRFPy_API::init_python_with_config(config, argc, argv);
|
||||
|
||||
// Handle different Python modes
|
||||
if (!config.python_command.empty()) {
|
||||
// Execute command from -c
|
||||
int result = PyRun_SimpleString(config.python_command.c_str());
|
||||
Py_Finalize();
|
||||
return result;
|
||||
}
|
||||
else if (!config.python_module.empty()) {
|
||||
// Execute module using runpy
|
||||
std::string run_module_code =
|
||||
"import sys\n"
|
||||
"import runpy\n"
|
||||
"sys.argv = ['" + config.python_module + "'";
|
||||
|
||||
for (const auto& arg : config.script_args) {
|
||||
run_module_code += ", '" + arg + "'";
|
||||
}
|
||||
run_module_code += "]\n";
|
||||
run_module_code += "runpy.run_module('" + config.python_module + "', run_name='__main__', alter_sys=True)\n";
|
||||
|
||||
int result = PyRun_SimpleString(run_module_code.c_str());
|
||||
Py_Finalize();
|
||||
return result;
|
||||
}
|
||||
else if (!config.script_path.empty()) {
|
||||
// Execute script file
|
||||
FILE* fp = fopen(config.script_path.string().c_str(), "r");
|
||||
if (!fp) {
|
||||
std::cerr << "mcrogueface: can't open file '" << config.script_path << "': ";
|
||||
std::cerr << "[Errno " << errno << "] " << strerror(errno) << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Set up sys.argv
|
||||
wchar_t** python_argv = new wchar_t*[config.script_args.size() + 1];
|
||||
python_argv[0] = Py_DecodeLocale(config.script_path.string().c_str(), nullptr);
|
||||
for (size_t i = 0; i < config.script_args.size(); i++) {
|
||||
python_argv[i + 1] = Py_DecodeLocale(config.script_args[i].c_str(), nullptr);
|
||||
}
|
||||
PySys_SetArgvEx(config.script_args.size() + 1, python_argv, 0);
|
||||
|
||||
int result = PyRun_SimpleFile(fp, config.script_path.string().c_str());
|
||||
fclose(fp);
|
||||
|
||||
// Clean up
|
||||
for (size_t i = 0; i <= config.script_args.size(); i++) {
|
||||
PyMem_RawFree(python_argv[i]);
|
||||
}
|
||||
delete[] python_argv;
|
||||
|
||||
if (config.interactive_mode && result == 0) {
|
||||
// Run interactive mode after script
|
||||
PyRun_InteractiveLoop(stdin, "<stdin>");
|
||||
}
|
||||
|
||||
Py_Finalize();
|
||||
return result;
|
||||
}
|
||||
else if (config.interactive_mode || config.python_mode) {
|
||||
// Interactive Python interpreter
|
||||
Py_InspectFlag = 1;
|
||||
PyRun_InteractiveLoop(stdin, "<stdin>");
|
||||
Py_Finalize();
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue