189 lines
6.0 KiB
C++
189 lines
6.0 KiB
C++
// Example implementation of --exec flag for McRogueFace
|
|
// This shows the minimal changes needed to support multiple script execution
|
|
|
|
// === In McRogueFaceConfig.h ===
|
|
struct McRogueFaceConfig {
|
|
// ... existing fields ...
|
|
|
|
// Scripts to execute after main script (McRogueFace style)
|
|
std::vector<std::filesystem::path> exec_scripts;
|
|
};
|
|
|
|
// === In CommandLineParser.cpp ===
|
|
CommandLineParser::ParseResult CommandLineParser::parse(McRogueFaceConfig& config) {
|
|
// ... existing parsing code ...
|
|
|
|
for (int i = 1; i < argc; i++) {
|
|
std::string arg = argv[i];
|
|
|
|
// ... existing flag handling ...
|
|
|
|
else if (arg == "--exec") {
|
|
// Add script to exec list
|
|
if (i + 1 < argc) {
|
|
config.exec_scripts.push_back(argv[++i]);
|
|
} else {
|
|
std::cerr << "Error: --exec requires a script path\n";
|
|
return {true, 1};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// === In GameEngine.cpp ===
|
|
GameEngine::GameEngine(const McRogueFaceConfig& cfg) : config(cfg) {
|
|
// ... existing initialization ...
|
|
|
|
// Only load game.py if no custom script/command/module is specified
|
|
bool should_load_game = config.script_path.empty() &&
|
|
config.python_command.empty() &&
|
|
config.python_module.empty() &&
|
|
!config.interactive_mode &&
|
|
!config.python_mode &&
|
|
config.exec_scripts.empty(); // Add this check
|
|
|
|
if (should_load_game) {
|
|
if (!Py_IsInitialized()) {
|
|
McRFPy_API::api_init();
|
|
}
|
|
McRFPy_API::executePyString("import mcrfpy");
|
|
McRFPy_API::executeScript("scripts/game.py");
|
|
}
|
|
|
|
// Execute any --exec scripts
|
|
for (const auto& exec_script : config.exec_scripts) {
|
|
std::cout << "Executing script: " << exec_script << std::endl;
|
|
McRFPy_API::executeScript(exec_script.string());
|
|
}
|
|
}
|
|
|
|
// === Usage Examples ===
|
|
|
|
// Example 1: Run game with automation
|
|
// ./mcrogueface game.py --exec automation.py
|
|
|
|
// Example 2: Run game with multiple automation scripts
|
|
// ./mcrogueface game.py --exec test_suite.py --exec monitor.py --exec logger.py
|
|
|
|
// Example 3: Run only automation (no game)
|
|
// ./mcrogueface --exec standalone_test.py
|
|
|
|
// Example 4: Headless automation
|
|
// ./mcrogueface --headless game.py --exec automation.py
|
|
|
|
// === Python Script Example (automation.py) ===
|
|
/*
|
|
import mcrfpy
|
|
from mcrfpy import automation
|
|
|
|
def periodic_test():
|
|
"""Run automated tests every 5 seconds"""
|
|
# Take screenshot
|
|
automation.screenshot(f"test_{mcrfpy.getFrame()}.png")
|
|
|
|
# Check game state
|
|
scene = mcrfpy.currentScene()
|
|
if scene == "main_menu":
|
|
# Click start button
|
|
automation.click(400, 300)
|
|
elif scene == "game":
|
|
# Perform game tests
|
|
automation.hotkey("i") # Open inventory
|
|
|
|
print(f"Test completed at frame {mcrfpy.getFrame()}")
|
|
|
|
# Register timer for periodic testing
|
|
mcrfpy.setTimer("automation_test", periodic_test, 5000)
|
|
|
|
print("Automation script loaded - tests will run every 5 seconds")
|
|
|
|
# Script returns here - giving control back to C++
|
|
*/
|
|
|
|
// === Advanced Example: Event-Driven Automation ===
|
|
/*
|
|
# automation_advanced.py
|
|
|
|
import mcrfpy
|
|
from mcrfpy import automation
|
|
import json
|
|
|
|
class AutomationFramework:
|
|
def __init__(self):
|
|
self.test_queue = []
|
|
self.results = []
|
|
self.load_test_suite()
|
|
|
|
def load_test_suite(self):
|
|
"""Load test definitions from JSON"""
|
|
with open("test_suite.json") as f:
|
|
self.test_queue = json.load(f)["tests"]
|
|
|
|
def run_next_test(self):
|
|
"""Execute next test in queue"""
|
|
if not self.test_queue:
|
|
self.finish_testing()
|
|
return
|
|
|
|
test = self.test_queue.pop(0)
|
|
|
|
try:
|
|
if test["type"] == "click":
|
|
automation.click(test["x"], test["y"])
|
|
elif test["type"] == "key":
|
|
automation.keyDown(test["key"])
|
|
automation.keyUp(test["key"])
|
|
elif test["type"] == "screenshot":
|
|
automation.screenshot(test["filename"])
|
|
elif test["type"] == "wait":
|
|
# Re-queue this test for later
|
|
self.test_queue.insert(0, test)
|
|
return
|
|
|
|
self.results.append({"test": test, "status": "pass"})
|
|
except Exception as e:
|
|
self.results.append({"test": test, "status": "fail", "error": str(e)})
|
|
|
|
def finish_testing(self):
|
|
"""Save test results and cleanup"""
|
|
with open("test_results.json", "w") as f:
|
|
json.dump(self.results, f, indent=2)
|
|
print(f"Testing complete: {len(self.results)} tests executed")
|
|
mcrfpy.delTimer("automation_framework")
|
|
|
|
# Create and start automation
|
|
framework = AutomationFramework()
|
|
mcrfpy.setTimer("automation_framework", framework.run_next_test, 100)
|
|
*/
|
|
|
|
// === Thread Safety Considerations ===
|
|
|
|
// The --exec approach requires NO thread safety changes because:
|
|
// 1. All scripts run in the same Python interpreter
|
|
// 2. Scripts execute sequentially during initialization
|
|
// 3. After initialization, only callbacks run (timer/input based)
|
|
// 4. C++ maintains control of the render loop
|
|
|
|
// This is the "honor system" - scripts must:
|
|
// - Set up their callbacks/timers
|
|
// - Return control to C++
|
|
// - Not block or run infinite loops
|
|
// - Use timers for periodic tasks
|
|
|
|
// === Future Extensions ===
|
|
|
|
// 1. Script communication via shared Python modules
|
|
// game.py:
|
|
// import mcrfpy
|
|
// mcrfpy.game_state = {"level": 1, "score": 0}
|
|
//
|
|
// automation.py:
|
|
// import mcrfpy
|
|
// if mcrfpy.game_state["level"] == 1:
|
|
// # Test level 1 specific features
|
|
|
|
// 2. Priority-based script execution
|
|
// ./mcrogueface game.py --exec-priority high:critical.py --exec-priority low:logging.py
|
|
|
|
// 3. Conditional execution
|
|
// ./mcrogueface game.py --exec-if-scene menu:menu_test.py --exec-if-scene game:game_test.py
|