From 50d040fb6d6b722e9b9eaff2e0e61105d576f8e9 Mon Sep 17 00:00:00 2001 From: fyex <33452346+fyex@users.noreply.github.com> Date: Tue, 25 Aug 2026 19:02:07 +0200 Subject: [PATCH 1/7] RSE: ConfirmSlotDetector --- .../Dialogs/PokemonRSE_DialogDetector.cpp | 9 + .../Dialogs/PokemonRSE_DialogDetector.h | 19 ++ .../PokemonRSE_SelectionDetector.cpp | 178 ++++++++++++++++++ .../Inference/PokemonRSE_SelectionDetector.h | 142 ++++++++++++++ .../Source/PokemonRSE/PokemonRSE_Settings.cpp | 10 +- .../Source/PokemonRSE/PokemonRSE_Settings.h | 1 + SerialPrograms/cmake/SourceFiles.cmake | 2 + 7 files changed, 360 insertions(+), 1 deletion(-) create mode 100644 SerialPrograms/Source/PokemonRSE/Inference/PokemonRSE_SelectionDetector.cpp create mode 100644 SerialPrograms/Source/PokemonRSE/Inference/PokemonRSE_SelectionDetector.h diff --git a/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp b/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp index 66f53f1770..885d0327bf 100644 --- a/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp +++ b/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp @@ -201,6 +201,15 @@ bool SelectionDialogDetector::detect(const ImageViewRGB32& screen){ } +ConfirmSlotDetector::ConfirmSlotDetector(Color color, VideoOverlay* overlay, ConfirmSlot slot) + : SelectionSlotDetector(color, overlay + , { + { 0.69, 0.45 + SELECTION_SLOT_OFFSETS[(int)slot], SelectionIndicator::ARROW }, + { 0.685, 0.458 + SELECTION_SLOT_OFFSETS[(int)slot], SelectionIndicator::BORDER } + }) +{} + + } } diff --git a/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h b/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h index 64d5bcbcbb..0a0413346c 100644 --- a/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h +++ b/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h @@ -15,6 +15,7 @@ #include "CommonTools/VisualDetector.h" #include "CommonTools/VisualDetectors/BlackScreenDetector.h" #include "CommonTools/InferenceCallbacks/VisualInferenceCallback.h" +#include "PokemonRSE/Inference/PokemonRSE_SelectionDetector.h" #include "PokemonRSE/PokemonRSE_Settings.h" namespace PokemonAutomation{ @@ -161,6 +162,24 @@ class SelectionDialogWatcher : public DetectorToFinder{ }; +enum class ConfirmSlot { + YES, + NO +}; + +class ConfirmSlotDetector : public SelectionSlotDetector { +public: + ConfirmSlotDetector(Color color, VideoOverlay* overlay, ConfirmSlot slot); +}; +class ConfirmSlotWatcher : public DetectorToFinder { +public: + ConfirmSlotWatcher(Color color, VideoOverlay* overlay, ConfirmSlot slot, Milliseconds hold_duration = Milliseconds(250)) + : DetectorToFinder("ConfirmSlotWatcher", hold_duration , color, overlay, slot) + {} +}; + + + } } } diff --git a/SerialPrograms/Source/PokemonRSE/Inference/PokemonRSE_SelectionDetector.cpp b/SerialPrograms/Source/PokemonRSE/Inference/PokemonRSE_SelectionDetector.cpp new file mode 100644 index 0000000000..6f5cf05a14 --- /dev/null +++ b/SerialPrograms/Source/PokemonRSE/Inference/PokemonRSE_SelectionDetector.cpp @@ -0,0 +1,178 @@ +/* Selection Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonTools/ImageMatch/WaterfillTemplateMatcher.h" +#include "CommonTools/Images/SolidColorTest.h" +#include "CommonTools/Images/ImageFilter.h" +#include "CommonTools/Images/WaterfillUtilities.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" +#include "CommonFramework/ImageTools/ImageStats.h" +#include "CommonFramework/ImageTypes/ImageViewRGB32.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "Kernels/Waterfill/Kernels_Waterfill_Types.h" +#include "PokemonRSE/PokemonRSE_Settings.h" +#include "PokemonRSE_SelectionDetector.h" + +#include + +//using std::cout; +//using std::endl; + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonRSE{ + + +class SelectionArrowMatcher : public ImageMatch::WaterfillTemplateMatcher { +public: + SelectionArrowMatcher(const char* path) + : WaterfillTemplateMatcher( + path, + Color(50, 50, 50), Color(115, 115, 115), 24 + ) + { + m_aspect_ratio_lower = 0.8; + m_aspect_ratio_upper = 1.2; + m_area_ratio_lower = 0.8; + m_area_ratio_upper = 1.2; + } + + + static const SelectionArrowMatcher& emerald_matcher() { + static SelectionArrowMatcher matcher("PokemonRSE/SelectionArrowEmerald.png"); + return matcher; + } + static const SelectionArrowMatcher& ruby_sapphire_jpn_matcher() { + static SelectionArrowMatcher matcher("PokemonRSE/SelectionArrowRubySapphireJpn.png"); + return matcher; + } + static const SelectionArrowMatcher& emerald_jpn_matcher() { + static SelectionArrowMatcher matcher("PokemonRSE/SelectionArrowEmeraldJpn.png"); + return matcher; + } +}; + +SelectionArrowDetector::SelectionArrowDetector(Color color, VideoOverlay* overlay, const ImageFloatBox& box) + : m_color(color) + , m_overlay(overlay) + , m_box(box) +{} +void SelectionArrowDetector::make_overlays(VideoOverlaySet& items) const { + const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX; + items.add(m_color, GAME_BOX.inner_to_outer(m_box)); +} +bool SelectionArrowDetector::detect(const ImageViewRGB32& screen) { + const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX; + ImageViewRGB32 game_screen = extract_box_reference(screen, GAME_BOX); + ImageViewRGB32 image = extract_box_reference(game_screen, m_box); + + double screen_rel_height = game_screen.height() / 160.0; + double screen_rel_width = game_screen.width() / 240.0; + double screen_rel_size = screen_rel_height * screen_rel_width; + + const double MIN_AREA_240 = 10; + const double RMSD_THRESHOLD = 80; + size_t min_area = size_t(screen_rel_size * MIN_AREA_240); + + const std::vector> FILTERS = { + {0xff4f4f4f, 0xff777777}, // Emerald + {0xff2f2f2f, 0xff5c5c5c}, // Emerald + {0xff363636, 0xff5e5e5e} // Ruby/Sapphire + }; + + auto try_match = [&](const SelectionArrowMatcher& matcher) -> bool { + return match_template_by_waterfill( + game_screen.size(), + image, + matcher, + FILTERS, + { min_area, SIZE_MAX }, + RMSD_THRESHOLD, + [&](Kernels::Waterfill::WaterfillObject& object) -> bool { + //std::cout << "area: " << object.area << std::endl; + return true; + } + ); + }; + + return try_match(SelectionArrowMatcher::emerald_matcher()) + || try_match(SelectionArrowMatcher::emerald_jpn_matcher()) + || try_match(SelectionArrowMatcher::ruby_sapphire_jpn_matcher()); +} + + +SelectionBorderDetector::SelectionBorderDetector(Color color, VideoOverlay* overlay, const ImageFloatBox& box) + : m_color(color) + , m_overlay(overlay) + , m_box(box) +{} +void SelectionBorderDetector::make_overlays(VideoOverlaySet& items) const { + const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX; + items.add(m_color, GAME_BOX.inner_to_outer(m_box)); +} +bool SelectionBorderDetector::detect(const ImageViewRGB32& screen) { + const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX; + ImageViewRGB32 game_screen = extract_box_reference(screen, GAME_BOX); + ImageViewRGB32 image = extract_box_reference(game_screen, m_box); + + double screen_rel_height = game_screen.height() / 160.0; + double screen_rel_width = game_screen.width() / 240.0; + double screen_rel_size = screen_rel_height * screen_rel_width; + + const double MIN_AREA_240 = 5; // the left/right side of the border is 12-13 pixels high + size_t min_area = size_t(screen_rel_size * MIN_AREA_240); + + size_t pixels_in_range; + filter_rgb32_range( + pixels_in_range, + image, + 0xffc82c23, + 0xffff9a91, + Color(0), + false + ); + + return pixels_in_range > min_area; +} + + +SelectionSlotDetector::SelectionSlotDetector(Color color, VideoOverlay* overlay, const std::vector& candidates) + : m_color(color) + , m_overlay(overlay) +{ + for (const auto& candidate : candidates) { + double slot_y = candidate.y; + double width = (candidate.indicator == SelectionIndicator::ARROW) ? SELECTION_ARROW_BOX_WIDTH : SELECTION_BORDER_BOX_WIDTH; + double height = (candidate.indicator == SelectionIndicator::BORDER) ? SELECTION_BORDER_BOX_HEIGHT : SELECTION_ARROW_BOX_HEIGHT; + ImageFloatBox box(candidate.x, slot_y, width, height); + + if (candidate.indicator == SelectionIndicator::ARROW) { + m_detectors.emplace_back(std::make_unique(color, overlay, box)); + } else if (candidate.indicator == SelectionIndicator::BORDER) { + m_detectors.emplace_back(std::make_unique(color, overlay, box)); + } + } +} +void SelectionSlotDetector::make_overlays(VideoOverlaySet& items) const { + for (const auto& detector : m_detectors) { + detector->make_overlays(items); + } +} +bool SelectionSlotDetector::detect(const ImageViewRGB32& screen) { + for (auto& detector : m_detectors) { + if (detector->detect(screen)) { + return true; + } + } + return false; +} + + + +} +} +} diff --git a/SerialPrograms/Source/PokemonRSE/Inference/PokemonRSE_SelectionDetector.h b/SerialPrograms/Source/PokemonRSE/Inference/PokemonRSE_SelectionDetector.h new file mode 100644 index 0000000000..0345637272 --- /dev/null +++ b/SerialPrograms/Source/PokemonRSE/Inference/PokemonRSE_SelectionDetector.h @@ -0,0 +1,142 @@ +/* Selection Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonRSE_SelectionDetector_H +#define PokemonAutomation_PokemonRSE_SelectionDetector_H + +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonTools/VisualDetector.h" + +#include + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonRSE{ + + +constexpr double SELECTION_ARROW_BOX_WIDTH = 0.039; +constexpr double SELECTION_ARROW_BOX_HEIGHT = 0.10; +constexpr double SELECTION_BORDER_BOX_WIDTH = 0.02; +constexpr double SELECTION_BORDER_BOX_HEIGHT = 0.084; + +constexpr double SELECTION_SLOT_OFFSETS[] = { + 0.0, // slot 0 + 0.10, // slot 1 + 0.20, // slot 2 + 0.30, // slot 3 + 0.40, // slot 4 + 0.50, // slot 5 + 0.60, // slot 6 + 0.70, // slot 7 +}; + +enum class SelectionIndicator { + ARROW, + BORDER, +}; + +struct SlotCandidate { + double x; + double y; + SelectionIndicator indicator; +}; + + +// international Emerald, japanese Ruby/Sapphire/Emerald +class SelectionArrowDetector : public StaticScreenDetector{ +public: + SelectionArrowDetector( + Color color, + VideoOverlay* overlay, + const ImageFloatBox& box + ); + + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool detect(const ImageViewRGB32& screen) override; + +private: + const Color m_color; + VideoOverlay* m_overlay; + const ImageFloatBox m_box; +}; +class SelectionArrowWatcher : public DetectorToFinder{ +public: + SelectionArrowWatcher( + Color color, + VideoOverlay* overlay, + const ImageFloatBox& box, + std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250) + ) + : DetectorToFinder("SelectionArrowWatcher", hold_duration, color, overlay, box) + {} +}; + + +// international Ruby/Sapphire +class SelectionBorderDetector : public StaticScreenDetector { +public: + SelectionBorderDetector( + Color color, + VideoOverlay* overlay, + const ImageFloatBox& box + ); + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool detect(const ImageViewRGB32& screen) override; + +private: + Color m_color; + VideoOverlay* m_overlay; + const ImageFloatBox m_box; +}; +class SelectionBorderWatcher : public DetectorToFinder { +public: + SelectionBorderWatcher( + Color color, + VideoOverlay* overlay, + const ImageFloatBox& box, + std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250) + ) + : DetectorToFinder("SelectionBorderWatcher", hold_duration, color, overlay, box) + {} +}; + + +// checks for one selection slot +// In most cases uses at least one arrow and one border detector +class SelectionSlotDetector : public StaticScreenDetector { +public: + SelectionSlotDetector( + Color color, + VideoOverlay* overlay, + const std::vector& candidates + ); + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool detect(const ImageViewRGB32& screen) override; + +private: + Color m_color; + VideoOverlay* m_overlay; + std::vector> m_detectors; +}; +class SelectionWatcher : public DetectorToFinder { +public: + SelectionWatcher( + Color color, + VideoOverlay* overlay, + const std::vector& candidates, + std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250) + ) + : DetectorToFinder("SelectionWatcher", hold_duration, color, overlay, candidates) + { + } +}; + + +} +} +} + +#endif \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonRSE/PokemonRSE_Settings.cpp b/SerialPrograms/Source/PokemonRSE/PokemonRSE_Settings.cpp index ded02f10c3..74ad32216d 100644 --- a/SerialPrograms/Source/PokemonRSE/PokemonRSE_Settings.cpp +++ b/SerialPrograms/Source/PokemonRSE/PokemonRSE_Settings.cpp @@ -38,7 +38,8 @@ GameSettings::GameSettings() {Device::switch_1_2, "switch_1_2", "Nintendo Switch 1 and 2"}, {Device::rg35xx, "rg35xx", "RG35XX"}, {Device::tablet, "tablet", "tablet"}, - //{Device::custom, "custom", "Custom"}, + {Device::gameboyplayer, "gameboyplayer", "GameBoyPlayer"}, + {Device::custom, "custom", "Custom"}, }, LockMode::LOCK_WHILE_RUNNING, Device::switch_1_2 @@ -115,6 +116,13 @@ void GameSettings::on_config_value_changed(void* object){ GAME_BOX.HEIGHT.set(0.643026); GAME_BOX.set_visibility(ConfigOptionState::DISABLED); break; + case Device::gameboyplayer: + GAME_BOX.X.set(0.064217); + GAME_BOX.Y.set(0.107667); + GAME_BOX.WIDTH.set(0.86413); + GAME_BOX.HEIGHT.set(0.787928); + GAME_BOX.set_visibility(ConfigOptionState::DISABLED); + break; case Device::custom: GAME_BOX.set_visibility(ConfigOptionState::ENABLED); break; diff --git a/SerialPrograms/Source/PokemonRSE/PokemonRSE_Settings.h b/SerialPrograms/Source/PokemonRSE/PokemonRSE_Settings.h index 54704ba1a1..fc6086e0e7 100644 --- a/SerialPrograms/Source/PokemonRSE/PokemonRSE_Settings.h +++ b/SerialPrograms/Source/PokemonRSE/PokemonRSE_Settings.h @@ -29,6 +29,7 @@ class GameSettings : public BatchOption, private ConfigOption::Listener{ switch_1_2, rg35xx, tablet, + gameboyplayer, custom, }; diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 00d52f1fc0..9f574b10e8 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -2156,6 +2156,8 @@ file(GLOB LIBRARY_SOURCES Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.h Source/PokemonRSE/Inference/Menus/PokemonRSE_SummaryDetector.cpp Source/PokemonRSE/Inference/Menus/PokemonRSE_SummaryDetector.h + Source/PokemonRSE/Inference/PokemonRSE_SelectionDetector.cpp + Source/PokemonRSE/Inference/PokemonRSE_SelectionDetector.h Source/PokemonRSE/Inference/PokemonRSE_ShinyNumberDetector.cpp Source/PokemonRSE/Inference/PokemonRSE_ShinyNumberDetector.h Source/PokemonRSE/Inference/Sounds/PokemonRSE_ShinySoundDetector.cpp From b4865bdcf5b692ffe8249c6e8505d1e97445d2ee Mon Sep 17 00:00:00 2001 From: fyex <33452346+fyex@users.noreply.github.com> Date: Tue, 25 Aug 2026 22:26:18 +0200 Subject: [PATCH 2/7] RSE: add ConfirmSlotDetector tests --- .../ComputerPrograms/UnitTestRunner.cpp | 2 + .../Dialogs/PokemonRSE_DialogDetector.cpp | 93 ++++++++++++++++++- .../Dialogs/PokemonRSE_DialogDetector.h | 12 ++- .../PokemonRSE_SelectionDetector.cpp | 45 +++++---- .../Inference/PokemonRSE_SelectionDetector.h | 13 +-- .../Source/PokemonRSE/PokemonRSE_Tests.cpp | 24 +++++ .../Source/PokemonRSE/PokemonRSE_Tests.h | 25 +++++ SerialPrograms/cmake/SourceFiles.cmake | 2 + 8 files changed, 176 insertions(+), 40 deletions(-) create mode 100644 SerialPrograms/Source/PokemonRSE/PokemonRSE_Tests.cpp create mode 100644 SerialPrograms/Source/PokemonRSE/PokemonRSE_Tests.h diff --git a/SerialPrograms/Source/ComputerPrograms/UnitTestRunner.cpp b/SerialPrograms/Source/ComputerPrograms/UnitTestRunner.cpp index 500b366a95..4f3b0ca5dd 100644 --- a/SerialPrograms/Source/ComputerPrograms/UnitTestRunner.cpp +++ b/SerialPrograms/Source/ComputerPrograms/UnitTestRunner.cpp @@ -19,6 +19,7 @@ #include "CommonTools/OCR/OCR_Tests.h" #include "Kernels/Kernels_Tests.h" #include "PokemonFRLG/PokemonFRLG_Tests.h" +#include "PokemonRSE/PokemonRSE_Tests.h" #include "PokemonHome/PokemonHome_Tests.h" #include "PokemonSwSh/PokemonSwSh_Tests.h" #include "PokemonLA/PokemonLA_Tests.h" @@ -43,6 +44,7 @@ UnitTestDatabase make_UNIT_TESTS_ALL(){ NintendoSwitch::add_tests_FailedToConnectDetector(ret); NintendoSwitch::add_tests_UpdatePopupDetector(ret); NintendoSwitch::PokemonFRLG::add_tests(ret); + NintendoSwitch::PokemonRSE::add_tests(ret); NintendoSwitch::PokemonHome::add_tests(ret); NintendoSwitch::PokemonSwSh::add_tests(ret); NintendoSwitch::PokemonLA::add_tests(ret); diff --git a/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp b/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp index 885d0327bf..fea6c584b6 100644 --- a/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp +++ b/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp @@ -6,6 +6,7 @@ #include "CommonTools/Images/SolidColorTest.h" #include "CommonTools/Images/ImageFilter.h" +#include "CommonFramework/GlobalAutoPaths.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/ImageTypes/ImageRGB32.h" #include "CommonFramework/ImageTools/ImageStats.h" @@ -201,8 +202,8 @@ bool SelectionDialogDetector::detect(const ImageViewRGB32& screen){ } -ConfirmSlotDetector::ConfirmSlotDetector(Color color, VideoOverlay* overlay, ConfirmSlot slot) - : SelectionSlotDetector(color, overlay +ConfirmSlotDetector::ConfirmSlotDetector(Color color, ConfirmSlot slot) + : SelectionSlotDetector(color , { { 0.69, 0.45 + SELECTION_SLOT_OFFSETS[(int)slot], SelectionIndicator::ARROW }, { 0.685, 0.458 + SELECTION_SLOT_OFFSETS[(int)slot], SelectionIndicator::BORDER } @@ -211,6 +212,94 @@ ConfirmSlotDetector::ConfirmSlotDetector(Color color, VideoOverlay* overlay, Con +class Test_SelectionDialogDetector : public UnitTest { +public: + + Test_SelectionDialogDetector( + const std::string& image, + bool expected + ) + : UnitTest("PokemonRSE::SelectionDialogDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected(expected) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override { + SelectionDialogDetector detector(COLOR_RED); + ImageRGB32 image(m_image); + return detector.detect(image) == m_expected; + }; + +private: + std::string m_image; + bool m_expected; +}; + +void add_tests_SelectionDialogDetector(UnitTestDatabase& database) { + // Switch 1/2 + + // Original GBA resolution; {0.0, 0.0, 1.0, 1.0} + database.add("PokemonRSE/SelectionDialogDetector/GBA-Eng-Ruby_true.png", true); + database.add("PokemonRSE/SelectionDialogDetector/GBA-Ger-Emerald_true.png", true); + + // Game Boy Player; {0.064217, 0.107667, 0.86413, 0.787928} + database.add("PokemonRSE/SelectionDialogDetector/GBP-Ger-Emerald_true.png", true); + database.add("PokemonRSE/SelectionDialogDetector/GBP-Ger-Ruby_true.png", true); +} + + +class Test_ConfirmSlotDetector : public UnitTest { +public: + + Test_ConfirmSlotDetector( + const std::string& image, + ConfirmSlot slot, + bool expected + ) + : UnitTest("PokemonRSE::ConfirmSlotDetector - " + std::to_string((int)slot) + " - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_slot(slot) + , m_expected(expected) + { + } + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override { + ConfirmSlotDetector detector(COLOR_RED, m_slot); + ImageRGB32 image(m_image); + return detector.detect(image) == m_expected; + }; + +private: + std::string m_image; + ConfirmSlot m_slot; + bool m_expected; +}; + +void add_tests_ConfirmSlotDetector(UnitTestDatabase& database) { + // Switch 1/2 + + // Original GBA resolution {0.0, 0.0, 1.0, 1.0} + database.add("PokemonRSE/ConfirmSlotDetector/GBA-Eng-Ruby_yes.png", ConfirmSlot::YES, true); + database.add("PokemonRSE/ConfirmSlotDetector/GBA-Eng-Ruby_yes.png", ConfirmSlot::NO, false); + database.add("PokemonRSE/ConfirmSlotDetector/GBA-Ger-Emerald_yes.png", ConfirmSlot::YES, true); + database.add("PokemonRSE/ConfirmSlotDetector/GBA-Ger-Emerald_yes.png", ConfirmSlot::NO, false); + database.add("PokemonRSE/ConfirmSlotDetector/GBA-Ger-Ruby_yes.png", ConfirmSlot::YES, true); + database.add("PokemonRSE/ConfirmSlotDetector/GBA-Ger-Ruby_yes.png", ConfirmSlot::NO, false); + + // Game Boy Player {0.064217, 0.107667, 0.86413, 0.787928} + database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Emerald_no.png", ConfirmSlot::NO, true); + database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Emerald_no.png", ConfirmSlot::YES, false); + database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Emerald_yes.png", ConfirmSlot::YES, true); + database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Emerald_yes.png", ConfirmSlot::NO, false); + database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Ruby_no.png", ConfirmSlot::NO, true); + database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Ruby_no.png", ConfirmSlot::YES, false); + database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Ruby_yes.png", ConfirmSlot::YES, true); + database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Ruby_yes.png", ConfirmSlot::NO, false); +} + + + + } } } diff --git a/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h b/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h index 0a0413346c..6bf51f21ee 100644 --- a/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h +++ b/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h @@ -9,9 +9,10 @@ #include #include -#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "Common/Cpp/Color.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/VisualDetector.h" #include "CommonTools/VisualDetectors/BlackScreenDetector.h" #include "CommonTools/InferenceCallbacks/VisualInferenceCallback.h" @@ -169,17 +170,20 @@ enum class ConfirmSlot { class ConfirmSlotDetector : public SelectionSlotDetector { public: - ConfirmSlotDetector(Color color, VideoOverlay* overlay, ConfirmSlot slot); + ConfirmSlotDetector(Color color, ConfirmSlot slot); }; class ConfirmSlotWatcher : public DetectorToFinder { public: - ConfirmSlotWatcher(Color color, VideoOverlay* overlay, ConfirmSlot slot, Milliseconds hold_duration = Milliseconds(250)) - : DetectorToFinder("ConfirmSlotWatcher", hold_duration , color, overlay, slot) + ConfirmSlotWatcher(Color color, ConfirmSlot slot, Milliseconds hold_duration = Milliseconds(250)) + : DetectorToFinder("ConfirmSlotWatcher", hold_duration , color, slot) {} }; +void add_tests_SelectionDialogDetector(UnitTestDatabase& database); +void add_tests_ConfirmSlotDetector(UnitTestDatabase& database); + } } } diff --git a/SerialPrograms/Source/PokemonRSE/Inference/PokemonRSE_SelectionDetector.cpp b/SerialPrograms/Source/PokemonRSE/Inference/PokemonRSE_SelectionDetector.cpp index 6f5cf05a14..947871df5b 100644 --- a/SerialPrograms/Source/PokemonRSE/Inference/PokemonRSE_SelectionDetector.cpp +++ b/SerialPrograms/Source/PokemonRSE/Inference/PokemonRSE_SelectionDetector.cpp @@ -56,14 +56,13 @@ class SelectionArrowMatcher : public ImageMatch::WaterfillTemplateMatcher { } }; -SelectionArrowDetector::SelectionArrowDetector(Color color, VideoOverlay* overlay, const ImageFloatBox& box) +SelectionArrowDetector::SelectionArrowDetector(Color color, const ImageFloatBox& box) : m_color(color) - , m_overlay(overlay) , m_box(box) {} void SelectionArrowDetector::make_overlays(VideoOverlaySet& items) const { const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX; - items.add(m_color, GAME_BOX.inner_to_outer(m_box)); + items.add(m_color, GAME_BOX.inner_to_outer(m_box)); } bool SelectionArrowDetector::detect(const ImageViewRGB32& screen) { const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX; @@ -105,9 +104,8 @@ bool SelectionArrowDetector::detect(const ImageViewRGB32& screen) { } -SelectionBorderDetector::SelectionBorderDetector(Color color, VideoOverlay* overlay, const ImageFloatBox& box) +SelectionBorderDetector::SelectionBorderDetector(Color color, const ImageFloatBox& box) : m_color(color) - , m_overlay(overlay) , m_box(box) {} void SelectionBorderDetector::make_overlays(VideoOverlaySet& items) const { @@ -140,34 +138,33 @@ bool SelectionBorderDetector::detect(const ImageViewRGB32& screen) { } -SelectionSlotDetector::SelectionSlotDetector(Color color, VideoOverlay* overlay, const std::vector& candidates) +SelectionSlotDetector::SelectionSlotDetector(Color color, const std::vector& candidates) : m_color(color) - , m_overlay(overlay) { - for (const auto& candidate : candidates) { + for (const auto& candidate : candidates) { double slot_y = candidate.y; - double width = (candidate.indicator == SelectionIndicator::ARROW) ? SELECTION_ARROW_BOX_WIDTH : SELECTION_BORDER_BOX_WIDTH; - double height = (candidate.indicator == SelectionIndicator::BORDER) ? SELECTION_BORDER_BOX_HEIGHT : SELECTION_ARROW_BOX_HEIGHT; - ImageFloatBox box(candidate.x, slot_y, width, height); + double width = (candidate.indicator == SelectionIndicator::ARROW) ? SELECTION_ARROW_BOX_WIDTH : SELECTION_BORDER_BOX_WIDTH; + double height = (candidate.indicator == SelectionIndicator::BORDER) ? SELECTION_BORDER_BOX_HEIGHT : SELECTION_ARROW_BOX_HEIGHT; + ImageFloatBox box(candidate.x, slot_y, width, height); if (candidate.indicator == SelectionIndicator::ARROW) { - m_detectors.emplace_back(std::make_unique(color, overlay, box)); - } else if (candidate.indicator == SelectionIndicator::BORDER) { - m_detectors.emplace_back(std::make_unique(color, overlay, box)); - } - } + m_detectors.emplace_back(std::make_unique(color, box)); + } else if (candidate.indicator == SelectionIndicator::BORDER) { + m_detectors.emplace_back(std::make_unique(color, box)); + } + } } void SelectionSlotDetector::make_overlays(VideoOverlaySet& items) const { - for (const auto& detector : m_detectors) { - detector->make_overlays(items); - } + for (const auto& detector : m_detectors) { + detector->make_overlays(items); + } } bool SelectionSlotDetector::detect(const ImageViewRGB32& screen) { - for (auto& detector : m_detectors) { - if (detector->detect(screen)) { - return true; - } - } + for (auto& detector : m_detectors) { + if (detector->detect(screen)) { + return true; + } + } return false; } diff --git a/SerialPrograms/Source/PokemonRSE/Inference/PokemonRSE_SelectionDetector.h b/SerialPrograms/Source/PokemonRSE/Inference/PokemonRSE_SelectionDetector.h index 0345637272..73f9d822bc 100644 --- a/SerialPrograms/Source/PokemonRSE/Inference/PokemonRSE_SelectionDetector.h +++ b/SerialPrograms/Source/PokemonRSE/Inference/PokemonRSE_SelectionDetector.h @@ -50,7 +50,6 @@ class SelectionArrowDetector : public StaticScreenDetector{ public: SelectionArrowDetector( Color color, - VideoOverlay* overlay, const ImageFloatBox& box ); @@ -59,7 +58,6 @@ class SelectionArrowDetector : public StaticScreenDetector{ private: const Color m_color; - VideoOverlay* m_overlay; const ImageFloatBox m_box; }; class SelectionArrowWatcher : public DetectorToFinder{ @@ -70,7 +68,7 @@ class SelectionArrowWatcher : public DetectorToFinder{ const ImageFloatBox& box, std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250) ) - : DetectorToFinder("SelectionArrowWatcher", hold_duration, color, overlay, box) + : DetectorToFinder("SelectionArrowWatcher", hold_duration, color, box) {} }; @@ -80,7 +78,6 @@ class SelectionBorderDetector : public StaticScreenDetector { public: SelectionBorderDetector( Color color, - VideoOverlay* overlay, const ImageFloatBox& box ); virtual void make_overlays(VideoOverlaySet& items) const override; @@ -88,18 +85,16 @@ class SelectionBorderDetector : public StaticScreenDetector { private: Color m_color; - VideoOverlay* m_overlay; const ImageFloatBox m_box; }; class SelectionBorderWatcher : public DetectorToFinder { public: SelectionBorderWatcher( Color color, - VideoOverlay* overlay, const ImageFloatBox& box, std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250) ) - : DetectorToFinder("SelectionBorderWatcher", hold_duration, color, overlay, box) + : DetectorToFinder("SelectionBorderWatcher", hold_duration, color, box) {} }; @@ -110,7 +105,6 @@ class SelectionSlotDetector : public StaticScreenDetector { public: SelectionSlotDetector( Color color, - VideoOverlay* overlay, const std::vector& candidates ); virtual void make_overlays(VideoOverlaySet& items) const override; @@ -118,7 +112,6 @@ class SelectionSlotDetector : public StaticScreenDetector { private: Color m_color; - VideoOverlay* m_overlay; std::vector> m_detectors; }; class SelectionWatcher : public DetectorToFinder { @@ -129,7 +122,7 @@ class SelectionWatcher : public DetectorToFinder { const std::vector& candidates, std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250) ) - : DetectorToFinder("SelectionWatcher", hold_duration, color, overlay, candidates) + : DetectorToFinder("SelectionWatcher", hold_duration, color, candidates) { } }; diff --git a/SerialPrograms/Source/PokemonRSE/PokemonRSE_Tests.cpp b/SerialPrograms/Source/PokemonRSE/PokemonRSE_Tests.cpp new file mode 100644 index 0000000000..9780174b40 --- /dev/null +++ b/SerialPrograms/Source/PokemonRSE/PokemonRSE_Tests.cpp @@ -0,0 +1,24 @@ +/* Pokemon RSE Tests + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "PokemonRSE/inference/Dialogs/PokemonRSE_DialogDetector.h" +#include "PokemonRSE_Tests.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonRSE{ + + +void add_tests(UnitTestDatabase& database){ + add_tests_SelectionDialogDetector(database); + add_tests_ConfirmSlotDetector(database); +} + + +} +} +} \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonRSE/PokemonRSE_Tests.h b/SerialPrograms/Source/PokemonRSE/PokemonRSE_Tests.h new file mode 100644 index 0000000000..8873ff84d7 --- /dev/null +++ b/SerialPrograms/Source/PokemonRSE/PokemonRSE_Tests.h @@ -0,0 +1,25 @@ +/* Pokemon RSE Tests + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonRSE_Tests_H +#define PokemonAutomation_PokemonRSE_Tests_H + +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonRSE{ + + + +void add_tests(UnitTestDatabase& database); + + + +} +} +} +#endif \ No newline at end of file diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 9f574b10e8..ea4541a76d 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -2168,6 +2168,8 @@ file(GLOB LIBRARY_SOURCES Source/PokemonRSE/PokemonRSE_Panels.h Source/PokemonRSE/PokemonRSE_Settings.cpp Source/PokemonRSE/PokemonRSE_Settings.h + Source/PokemonRSE/PokemonRSE_Tests.cpp + Source/PokemonRSE/PokemonRSE_Tests.h Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_AudioStarterReset.cpp Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_AudioStarterReset.h Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_GiftReset.cpp From 5d0a291b89cd6106f905b4a99870ca816c50261b Mon Sep 17 00:00:00 2001 From: fyex <33452346+fyex@users.noreply.github.com> Date: Sat, 29 Aug 2026 08:23:38 +0200 Subject: [PATCH 3/7] RSE: add positions for ConfirmSlotDetector --- .../Dialogs/PokemonRSE_DialogDetector.cpp | 109 ++++++++++++++---- .../Dialogs/PokemonRSE_DialogDetector.h | 14 ++- 2 files changed, 100 insertions(+), 23 deletions(-) diff --git a/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp b/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp index fea6c584b6..f4079010f2 100644 --- a/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp +++ b/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp @@ -202,12 +202,49 @@ bool SelectionDialogDetector::detect(const ImageViewRGB32& screen){ } -ConfirmSlotDetector::ConfirmSlotDetector(Color color, ConfirmSlot slot) +static std::vector confirm_candidates(ConfirmSlot slot, ConfirmPosition position) { + std::vector candidates; + double offset = SELECTION_SLOT_OFFSETS[(int)slot]; + + switch (position) { + case ConfirmPosition::STANDARD: + return { + { 0.69, 0.45 + offset, SelectionIndicator::ARROW }, + { 0.685, 0.458 + offset, SelectionIndicator::BORDER } + }; + case ConfirmPosition::SPECIAL: + return { + { 0.79, 0.45 + offset, SelectionIndicator::ARROW }, + { 0.785, 0.458 + offset, SelectionIndicator::BORDER } + }; + case ConfirmPosition::STARTER: + return { + { 0.79, 0.45 + offset, SelectionIndicator::ARROW }, + { 0.725, 0.397 + offset, SelectionIndicator::ARROW }, // jpn rs + { 0.72, 0.405 + offset, SelectionIndicator::BORDER } + }; + case ConfirmPosition::MART: + return { + { 0.69, 0.45 + offset, SelectionIndicator::ARROW }, + { 0.255, 0.45 + offset, SelectionIndicator::ARROW }, // jpn rs + { 0.25, 0.458 + offset, SelectionIndicator::BORDER } + }; + case ConfirmPosition::TOP_LEFT: + return { + { 0.094, 0.115 + offset, SelectionIndicator::ARROW }, + { 0.089, 0.123 + offset, SelectionIndicator::BORDER } + }; + default: + return { + { 0.69, 0.45 + offset, SelectionIndicator::ARROW }, + { 0.685, 0.458 + offset, SelectionIndicator::BORDER } + }; + } +} + +ConfirmSlotDetector::ConfirmSlotDetector(Color color, ConfirmSlot slot, ConfirmPosition position) : SelectionSlotDetector(color - , { - { 0.69, 0.45 + SELECTION_SLOT_OFFSETS[(int)slot], SelectionIndicator::ARROW }, - { 0.685, 0.458 + SELECTION_SLOT_OFFSETS[(int)slot], SelectionIndicator::BORDER } - }) + , confirm_candidates(slot, position)) {} @@ -254,17 +291,19 @@ class Test_ConfirmSlotDetector : public UnitTest { Test_ConfirmSlotDetector( const std::string& image, ConfirmSlot slot, + ConfirmPosition position, bool expected ) : UnitTest("PokemonRSE::ConfirmSlotDetector - " + std::to_string((int)slot) + " - " + image) , m_image(UNIT_TEST_RESOURCE_PATH() + image) , m_slot(slot) + , m_position(position) , m_expected(expected) { } virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override { - ConfirmSlotDetector detector(COLOR_RED, m_slot); + ConfirmSlotDetector detector(COLOR_RED, m_slot, m_position); ImageRGB32 image(m_image); return detector.detect(image) == m_expected; }; @@ -272,6 +311,7 @@ class Test_ConfirmSlotDetector : public UnitTest { private: std::string m_image; ConfirmSlot m_slot; + ConfirmPosition m_position; bool m_expected; }; @@ -279,22 +319,51 @@ void add_tests_ConfirmSlotDetector(UnitTestDatabase& database) { // Switch 1/2 // Original GBA resolution {0.0, 0.0, 1.0, 1.0} - database.add("PokemonRSE/ConfirmSlotDetector/GBA-Eng-Ruby_yes.png", ConfirmSlot::YES, true); - database.add("PokemonRSE/ConfirmSlotDetector/GBA-Eng-Ruby_yes.png", ConfirmSlot::NO, false); - database.add("PokemonRSE/ConfirmSlotDetector/GBA-Ger-Emerald_yes.png", ConfirmSlot::YES, true); - database.add("PokemonRSE/ConfirmSlotDetector/GBA-Ger-Emerald_yes.png", ConfirmSlot::NO, false); - database.add("PokemonRSE/ConfirmSlotDetector/GBA-Ger-Ruby_yes.png", ConfirmSlot::YES, true); - database.add("PokemonRSE/ConfirmSlotDetector/GBA-Ger-Ruby_yes.png", ConfirmSlot::NO, false); + database.add("PokemonRSE/ConfirmSlotDetector/GBA-Eng-Ruby_yes.png", ConfirmSlot::YES, ConfirmPosition::STANDARD, true); + //database.add("PokemonRSE/ConfirmSlotDetector/GBA-Eng-Ruby_yes.png", ConfirmSlot::NO, ConfirmPosition::STANDARD, false); + database.add("PokemonRSE/ConfirmSlotDetector/GBA-Ger-Emerald_yes.png", ConfirmSlot::YES, ConfirmPosition::STANDARD, true); + //database.add("PokemonRSE/ConfirmSlotDetector/GBA-Ger-Emerald_yes.png", ConfirmSlot::NO, ConfirmPosition::STANDARD, false); + database.add("PokemonRSE/ConfirmSlotDetector/GBA-Ger-Ruby_yes.png", ConfirmSlot::YES, ConfirmPosition::STANDARD, true); + //database.add("PokemonRSE/ConfirmSlotDetector/GBA-Ger-Ruby_yes.png", ConfirmSlot::NO, ConfirmPosition::STANDARD, false); + + // GBA SPECIAL + database.add("PokemonRSE/ConfirmSlotDetector/GBA-Eng-Emerald-Special_no.png", ConfirmSlot::NO, ConfirmPosition::SPECIAL, true); + database.add("PokemonRSE/ConfirmSlotDetector/GBA-Eng-Ruby-Special_yes.png", ConfirmSlot::YES, ConfirmPosition::SPECIAL, true); + + // GBA TOP_LEFT + database.add("PokemonRSE/ConfirmSlotDetector/GBA-Eng-Emerald-Top_no.png", ConfirmSlot::NO, ConfirmPosition::TOP_LEFT, true); + database.add("PokemonRSE/ConfirmSlotDetector/GBA-Eng-Ruby-Top_no.png", ConfirmSlot::NO, ConfirmPosition::TOP_LEFT, true); + + // GBA MART + database.add("PokemonRSE/ConfirmSlotDetector/GBA-Ger-Emerald-Mart_yes.png", ConfirmSlot::YES, ConfirmPosition::MART, true); + database.add("PokemonRSE/ConfirmSlotDetector/GBA-Ger-Ruby-Mart_no.png", ConfirmSlot::NO, ConfirmPosition::MART, true); + database.add("PokemonRSE/ConfirmSlotDetector/GBA-Jpn-Ruby-Mart_yes.png", ConfirmSlot::YES, ConfirmPosition::MART, true); + + // GBA STARTER + database.add("PokemonRSE/ConfirmSlotDetector/GBA-Ger-Emerald-Starter_yes.png", ConfirmSlot::YES, ConfirmPosition::STARTER, true); + database.add("PokemonRSE/ConfirmSlotDetector/GBA-Ger-Ruby-Starter_no.png", ConfirmSlot::NO, ConfirmPosition::STARTER, true); + database.add("PokemonRSE/ConfirmSlotDetector/GBA-Jpn-Ruby-Starter_yes.png", ConfirmSlot::YES, ConfirmPosition::STARTER, true); // Game Boy Player {0.064217, 0.107667, 0.86413, 0.787928} - database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Emerald_no.png", ConfirmSlot::NO, true); - database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Emerald_no.png", ConfirmSlot::YES, false); - database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Emerald_yes.png", ConfirmSlot::YES, true); - database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Emerald_yes.png", ConfirmSlot::NO, false); - database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Ruby_no.png", ConfirmSlot::NO, true); - database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Ruby_no.png", ConfirmSlot::YES, false); - database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Ruby_yes.png", ConfirmSlot::YES, true); - database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Ruby_yes.png", ConfirmSlot::NO, false); + database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Emerald_no.png", ConfirmSlot::NO, ConfirmPosition::STANDARD, true); + //database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Emerald_no.png", ConfirmSlot::YES, ConfirmPosition::STANDARD, false); + database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Emerald_yes.png", ConfirmSlot::YES, ConfirmPosition::STANDARD, true); + //database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Emerald_yes.png", ConfirmSlot::NO, ConfirmPosition::STANDARD, false); + database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Ruby_no.png", ConfirmSlot::NO, ConfirmPosition::STANDARD, true); + //database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Ruby_no.png", ConfirmSlot::YES, ConfirmPosition::STANDARD, false); + database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Ruby_yes.png", ConfirmSlot::YES, ConfirmPosition::STANDARD, true); + //database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Ruby_yes.png", ConfirmSlot::NO, ConfirmPosition::STANDARD, false); + + // GBP SPECIAL + database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Emerald-Special_yes.png", ConfirmSlot::YES, ConfirmPosition::SPECIAL, true); + database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Ruby-Special_no.png", ConfirmSlot::NO, ConfirmPosition::SPECIAL, true); + + // GBP MART + database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Emerald-Mart_yes.png", ConfirmSlot::YES, ConfirmPosition::MART, true); + database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Ruby-Mart_yes.png", ConfirmSlot::YES, ConfirmPosition::MART, true); + + // GBP STARTER + database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Emerald-Starter_yes.png", ConfirmSlot::YES, ConfirmPosition::STARTER, true); } diff --git a/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h b/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h index 6bf51f21ee..8553ec14d5 100644 --- a/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h +++ b/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h @@ -168,14 +168,22 @@ enum class ConfirmSlot { NO }; +enum class ConfirmPosition { + STANDARD, // e.g. save menu + SPECIAL, // e.g. clock in your room, replace move; not starter selection: ruby/sapphire is different + STARTER, // starter selection + MART, // confirm purchase + TOP_LEFT // e.g. player name +}; + class ConfirmSlotDetector : public SelectionSlotDetector { public: - ConfirmSlotDetector(Color color, ConfirmSlot slot); + ConfirmSlotDetector(Color color, ConfirmSlot slot, ConfirmPosition position = ConfirmPosition::STANDARD); }; class ConfirmSlotWatcher : public DetectorToFinder { public: - ConfirmSlotWatcher(Color color, ConfirmSlot slot, Milliseconds hold_duration = Milliseconds(250)) - : DetectorToFinder("ConfirmSlotWatcher", hold_duration , color, slot) + ConfirmSlotWatcher(Color color, ConfirmSlot slot, ConfirmPosition position = ConfirmPosition::STANDARD, Milliseconds hold_duration = Milliseconds(250)) + : DetectorToFinder("ConfirmSlotWatcher", hold_duration , color, slot, position) {} }; From 55a35a795fdb5f709fa4de0f35e1e1c0329082bd Mon Sep 17 00:00:00 2001 From: fyex <33452346+fyex@users.noreply.github.com> Date: Sun, 30 Aug 2026 10:39:29 +0200 Subject: [PATCH 4/7] RSE: add StartMenuSlotDetector --- .../Dialogs/PokemonRSE_DialogDetector.cpp | 1 - .../Menus/PokemonRSE_StartMenuDetector.cpp | 156 +++++++++++++++++- .../Menus/PokemonRSE_StartMenuDetector.h | 45 ++++- .../Source/PokemonRSE/PokemonRSE_Tests.cpp | 3 + 4 files changed, 198 insertions(+), 7 deletions(-) diff --git a/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp b/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp index f4079010f2..a3f2edf7ac 100644 --- a/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp +++ b/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp @@ -203,7 +203,6 @@ bool SelectionDialogDetector::detect(const ImageViewRGB32& screen){ static std::vector confirm_candidates(ConfirmSlot slot, ConfirmPosition position) { - std::vector candidates; double offset = SELECTION_SLOT_OFFSETS[(int)slot]; switch (position) { diff --git a/SerialPrograms/Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.cpp b/SerialPrograms/Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.cpp index b78281e285..55ffc2afcc 100644 --- a/SerialPrograms/Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.cpp +++ b/SerialPrograms/Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.cpp @@ -5,7 +5,10 @@ */ #include "Common/Cpp/Color.h" +#include "Common/Cpp/Exceptions.h" #include "CommonTools/Images/SolidColorTest.h" +#include "CommonFramework/GlobalAutoPaths.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" #include "CommonFramework/ImageTypes/ImageViewRGB32.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "PokemonRSE/PokemonRSE_Settings.h" @@ -15,9 +18,26 @@ namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonRSE{ -StartMenuDetector::StartMenuDetector(Color color) + +static double menu_bottom_box_y(StartMenuState state){ + switch (state) { + case StartMenuState::FULL: + return 0.94; + case StartMenuState::PRE_POKENAV: + case StartMenuState::SAFARI: + return 0.84; + case StartMenuState::PRE_POKEDEX: + return 0.74; + case StartMenuState::PRE_POKEMON: + return 0.64; + default: + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid StartMenuState"); + } +} + +StartMenuDetector::StartMenuDetector(Color color, StartMenuState state) : m_menu_top_box(0.766756, 0.046978, 0.197701, 0.01334) - , m_menu_bottom_box(0.766756, 0.94, 0.197701, 0.01334) + , m_menu_bottom_box(0.766756, menu_bottom_box_y(state), 0.197701, 0.01334) {} void StartMenuDetector::make_overlays(VideoOverlaySet& items) const{ const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX; @@ -36,6 +56,138 @@ bool StartMenuDetector::detect(const ImageViewRGB32& screen){ } +// Row of each option from the top of the menu, per state; -1 = not shown +// column = (int)StartMenuSlot, row = (int)StartMenuState. +// DEX MON BAG NAV TRN SAV OPT EXT RET +constexpr int8_t START_MENU_ROWS[5][9] = { + /* PRE_POKEMON */ { -1, -1, 0, -1, 1, 2, 3, 4, -1 }, + /* PRE_POKEDEX */ { -1, 0, 1, -1, 2, 3, 4, 5, -1 }, + /* PRE_POKENAV */ { 0, 1, 2, -1, 3, 4, 5, 6, -1 }, + /* FULL */ { 0, 1, 2, 3, 4, 5, 6, 7, -1 }, + /* SAFARI */ { 1, 2, 3, -1, 4, -1, 5, 6, 0 }, +}; + +static std::vector start_menu_slot_candidates(StartMenuSlot slot, StartMenuState state) { + int8_t row = START_MENU_ROWS[(int)state][(int)slot]; + if (row == -1) { + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid StartMenuSlot for StartMenuState"); + } + double offset = SELECTION_SLOT_OFFSETS[row]; + return { + { 0.725, 0.098 + offset, SelectionIndicator::ARROW }, + { 0.759, 0.098 + offset, SelectionIndicator::ARROW }, // jpn rse + { 0.754, 0.106 + offset, SelectionIndicator::BORDER } + }; +} + + +StartMenuSlotDetector::StartMenuSlotDetector(Color color, StartMenuSlot slot, StartMenuState state) + : SelectionSlotDetector(color, start_menu_slot_candidates(slot, state)) +{} + + + +class Test_StartMenuDetector : public UnitTest { +public: + + Test_StartMenuDetector( + const std::string& image, + StartMenuState state, + bool expected + ) + : UnitTest("PokemonRSE::StartMenuDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_state(state) + , m_expected(expected) + { + } + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override { + StartMenuDetector detector(COLOR_RED, m_state); + ImageRGB32 image(m_image); + return detector.detect(image) == m_expected; + }; + +private: + std::string m_image; + StartMenuState m_state; + bool m_expected; +}; + +void add_tests_StartMenuDetector(UnitTestDatabase & database) { + // Switch 1/2 + + // Original GBA resolution; {0.0, 0.0, 1.0, 1.0} + database.add("PokemonRSE/StartMenuDetector/GBA-Ger-Emerald-Full-Pokedex_true.png", StartMenuState::FULL, true); + database.add("PokemonRSE/StartMenuDetector/GBA-Ger-Emerald-PrePokenav-Exit_true.png", StartMenuState::PRE_POKENAV, true); + database.add("PokemonRSE/StartMenuDetector/GBA-Ger-Emerald-PrePokedex-Option_true.png", StartMenuState::PRE_POKEDEX, true); + database.add("PokemonRSE/StartMenuDetector/GBA-Ger-Emerald-PrePokemon-Bag_true.png", StartMenuState::PRE_POKEMON, true); + + database.add("PokemonRSE/StartMenuDetector/GBA-Ger-Ruby-Full-Exit_true.png", StartMenuState::FULL, true); + database.add("PokemonRSE/StartMenuDetector/GBA-Ger-Ruby-PrePokenav-Bag_true.png", StartMenuState::PRE_POKENAV, true); + database.add("PokemonRSE/StartMenuDetector/GBA-Ger-Ruby-PrePokedex-Exit_true.png", StartMenuState::PRE_POKEDEX, true); + database.add("PokemonRSE/StartMenuDetector/GBA-Ger-Ruby-PrePokemon-Bag_true.png", StartMenuState::PRE_POKEMON, true); + + // Game Boy Player; {0.064217, 0.107667, 0.86413, 0.787928} + database.add("PokemonRSE/StartMenuDetector/GBP-Ger-Emerald-Full-Pokemon_true.png", StartMenuState::FULL, true); + database.add("PokemonRSE/StartMenuDetector/GBP-Ger-Ruby-Full-Pokemon_true.png", StartMenuState::FULL, true); +} + + +class Test_StartMenuSlotDetector : public UnitTest { +public: + + Test_StartMenuSlotDetector( + const std::string& image, + StartMenuSlot slot, + StartMenuState state, + bool expected + ) + : UnitTest("PokemonRSE::StartMenuSlotDetector - " + std::to_string((int)slot) + " - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_slot(slot) + , m_state(state) + , m_expected(expected) + { + } + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override { + StartMenuSlotDetector detector(COLOR_RED, m_slot, m_state); + ImageRGB32 image(m_image); + return detector.detect(image) == m_expected; + }; + +private: + std::string m_image; + StartMenuSlot m_slot; + StartMenuState m_state; + bool m_expected; +}; + +void add_tests_StartMenuSlotDetector(UnitTestDatabase & database) { + // Switch 1/2 + + // Original GBA resolution; {0.0, 0.0, 1.0, 1.0} + database.add("PokemonRSE/StartMenuDetector/GBA-Ger-Emerald-Full-Pokedex_true.png", StartMenuSlot::POKEDEX, StartMenuState::FULL, true); + database.add("PokemonRSE/StartMenuDetector/GBA-Ger-Emerald-PrePokenav-Exit_true.png", StartMenuSlot::EXIT, StartMenuState::PRE_POKENAV, true); + database.add("PokemonRSE/StartMenuDetector/GBA-Ger-Emerald-PrePokedex-Option_true.png", StartMenuSlot::OPTION, StartMenuState::PRE_POKEDEX, true); + database.add("PokemonRSE/StartMenuDetector/GBA-Ger-Emerald-PrePokemon-Bag_true.png", StartMenuSlot::BAG, StartMenuState::PRE_POKEMON, true); + + database.add("PokemonRSE/StartMenuDetector/GBA-Ger-Ruby-Full-Exit_true.png", StartMenuSlot::EXIT, StartMenuState::FULL, true); + database.add("PokemonRSE/StartMenuDetector/GBA-Ger-Ruby-PrePokenav-Bag_true.png", StartMenuSlot::BAG, StartMenuState::PRE_POKENAV, true); + database.add("PokemonRSE/StartMenuDetector/GBA-Ger-Ruby-PrePokedex-Exit_true.png", StartMenuSlot::EXIT, StartMenuState::PRE_POKEDEX, true); + database.add("PokemonRSE/StartMenuDetector/GBA-Ger-Ruby-PrePokemon-Bag_true.png", StartMenuSlot::BAG, StartMenuState::PRE_POKEMON, true); + + // Game Boy Player; {0.064217, 0.107667, 0.86413, 0.787928} + database.add("PokemonRSE/StartMenuDetector/GBP-Ger-Emerald-Full-Pokemon_true.png", StartMenuSlot::POKEMON, StartMenuState::FULL, true); + database.add("PokemonRSE/StartMenuDetector/GBP-Ger-Ruby-Full-Pokemon_true.png", StartMenuSlot::POKEMON, StartMenuState::FULL, true); + +} + + + + + } } } diff --git a/SerialPrograms/Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.h b/SerialPrograms/Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.h index 4dee431bc4..9da3c093bf 100644 --- a/SerialPrograms/Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.h +++ b/SerialPrograms/Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.h @@ -9,8 +9,10 @@ #include #include "Common/Cpp/Color.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/VisualDetector.h" +#include "PokemonRSE/Inference/PokemonRSE_SelectionDetector.h" namespace PokemonAutomation{ class CancellableScope; @@ -18,13 +20,33 @@ namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonRSE{ +enum class StartMenuState { + PRE_POKEMON = 0, + PRE_POKEDEX = 1, + PRE_POKENAV = 2, + FULL = 3, + SAFARI = 4, +}; + +enum class StartMenuSlot { + POKEDEX = 0, + POKEMON = 1, + BAG = 2, + POKENAV = 3, + TRAINER = 4, + SAVE = 5, + OPTION = 6, + EXIT = 7, + RETIRE = 8, +}; + // Detect the full start menu by looking for empty white sections on the top and bottom -// Works for all RSE, all languages. Will not work for early game (before pokedex or party). +// Works for all RSE, all languages. // No arrow detection, RS japan and all emerald languages have an arrow, // but RS for all non-japan languages use a red box for selection. class StartMenuDetector : public StaticScreenDetector{ public: - StartMenuDetector(Color color); + StartMenuDetector(Color color, StartMenuState state = StartMenuState::FULL); virtual void make_overlays(VideoOverlaySet& items) const override; virtual bool detect(const ImageViewRGB32& screen) override; @@ -35,11 +57,26 @@ class StartMenuDetector : public StaticScreenDetector{ }; class StartMenuWatcher : public DetectorToFinder{ public: - StartMenuWatcher(Color color = COLOR_RED) - : DetectorToFinder("StartMenuWatcher", std::chrono::milliseconds(250), color) + StartMenuWatcher(Color color = COLOR_RED, StartMenuState state = StartMenuState::FULL) + : DetectorToFinder("StartMenuWatcher", std::chrono::milliseconds(250), color, state) {} }; +// Detect whether a specified slot is selected +class StartMenuSlotDetector : public SelectionSlotDetector { +public: + StartMenuSlotDetector(Color color, StartMenuSlot slot, StartMenuState state = StartMenuState::FULL); +}; +class StartMenuSlotWatcher : public DetectorToFinder { +public: + StartMenuSlotWatcher(Color color, StartMenuSlot slot, StartMenuState state = StartMenuState::FULL) + : DetectorToFinder("StartMenuSlotWatcher", std::chrono::milliseconds(250), color, slot, state) + {} +}; + + +void add_tests_StartMenuDetector(UnitTestDatabase& database); +void add_tests_StartMenuSlotDetector(UnitTestDatabase& database); } } diff --git a/SerialPrograms/Source/PokemonRSE/PokemonRSE_Tests.cpp b/SerialPrograms/Source/PokemonRSE/PokemonRSE_Tests.cpp index 9780174b40..7dec521d1f 100644 --- a/SerialPrograms/Source/PokemonRSE/PokemonRSE_Tests.cpp +++ b/SerialPrograms/Source/PokemonRSE/PokemonRSE_Tests.cpp @@ -6,6 +6,7 @@ #include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "PokemonRSE/inference/Dialogs/PokemonRSE_DialogDetector.h" +#include "PokemonRSE/inference/Menus/PokemonRSE_StartMenuDetector.h" #include "PokemonRSE_Tests.h" namespace PokemonAutomation{ @@ -16,6 +17,8 @@ namespace PokemonRSE{ void add_tests(UnitTestDatabase& database){ add_tests_SelectionDialogDetector(database); add_tests_ConfirmSlotDetector(database); + add_tests_StartMenuDetector(database); + add_tests_StartMenuSlotDetector(database); } From 4d0c31d9fc59a3fdac41d3aabdccfef1369e60c2 Mon Sep 17 00:00:00 2001 From: fyex <33452346+fyex@users.noreply.github.com> Date: Sun, 30 Aug 2026 11:19:52 +0200 Subject: [PATCH 5/7] fix tabs --- .../Dialogs/PokemonRSE_DialogDetector.cpp | 34 +++++++++---------- .../Dialogs/PokemonRSE_DialogDetector.h | 8 ++--- .../Menus/PokemonRSE_StartMenuDetector.cpp | 12 +++---- .../Menus/PokemonRSE_StartMenuDetector.h | 30 ++++++++-------- .../Inference/PokemonRSE_SelectionDetector.h | 8 ++--- .../Source/PokemonRSE/PokemonRSE_Tests.cpp | 8 ++--- 6 files changed, 50 insertions(+), 50 deletions(-) diff --git a/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp b/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp index a3f2edf7ac..f19131d8df 100644 --- a/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp +++ b/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp @@ -203,42 +203,42 @@ bool SelectionDialogDetector::detect(const ImageViewRGB32& screen){ static std::vector confirm_candidates(ConfirmSlot slot, ConfirmPosition position) { - double offset = SELECTION_SLOT_OFFSETS[(int)slot]; + double offset = SELECTION_SLOT_OFFSETS[(int)slot]; - switch (position) { - case ConfirmPosition::STANDARD: + switch (position) { + case ConfirmPosition::STANDARD: return { { 0.69, 0.45 + offset, SelectionIndicator::ARROW }, { 0.685, 0.458 + offset, SelectionIndicator::BORDER } }; - case ConfirmPosition::SPECIAL: - return { - { 0.79, 0.45 + offset, SelectionIndicator::ARROW }, - { 0.785, 0.458 + offset, SelectionIndicator::BORDER } + case ConfirmPosition::SPECIAL: + return { + { 0.79, 0.45 + offset, SelectionIndicator::ARROW }, + { 0.785, 0.458 + offset, SelectionIndicator::BORDER } }; - case ConfirmPosition::STARTER: + case ConfirmPosition::STARTER: return { { 0.79, 0.45 + offset, SelectionIndicator::ARROW }, { 0.725, 0.397 + offset, SelectionIndicator::ARROW }, // jpn rs { 0.72, 0.405 + offset, SelectionIndicator::BORDER } }; - case ConfirmPosition::MART: + case ConfirmPosition::MART: return { { 0.69, 0.45 + offset, SelectionIndicator::ARROW }, { 0.255, 0.45 + offset, SelectionIndicator::ARROW }, // jpn rs { 0.25, 0.458 + offset, SelectionIndicator::BORDER } }; - case ConfirmPosition::TOP_LEFT: + case ConfirmPosition::TOP_LEFT: return { { 0.094, 0.115 + offset, SelectionIndicator::ARROW }, { 0.089, 0.123 + offset, SelectionIndicator::BORDER } }; - default: + default: return { { 0.69, 0.45 + offset, SelectionIndicator::ARROW }, { 0.685, 0.458 + offset, SelectionIndicator::BORDER } }; - } + } } ConfirmSlotDetector::ConfirmSlotDetector(Color color, ConfirmSlot slot, ConfirmPosition position) @@ -289,14 +289,14 @@ class Test_ConfirmSlotDetector : public UnitTest { Test_ConfirmSlotDetector( const std::string& image, - ConfirmSlot slot, - ConfirmPosition position, + ConfirmSlot slot, + ConfirmPosition position, bool expected ) : UnitTest("PokemonRSE::ConfirmSlotDetector - " + std::to_string((int)slot) + " - " + image) , m_image(UNIT_TEST_RESOURCE_PATH() + image) - , m_slot(slot) - , m_position(position) + , m_slot(slot) + , m_position(position) , m_expected(expected) { } @@ -309,7 +309,7 @@ class Test_ConfirmSlotDetector : public UnitTest { private: std::string m_image; - ConfirmSlot m_slot; + ConfirmSlot m_slot; ConfirmPosition m_position; bool m_expected; }; diff --git a/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h b/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h index 8553ec14d5..7d29576fe5 100644 --- a/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h +++ b/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h @@ -169,16 +169,16 @@ enum class ConfirmSlot { }; enum class ConfirmPosition { - STANDARD, // e.g. save menu - SPECIAL, // e.g. clock in your room, replace move; not starter selection: ruby/sapphire is different - STARTER, // starter selection + STANDARD, // e.g. save menu + SPECIAL, // e.g. clock in your room, replace move; not starter selection: ruby/sapphire is different + STARTER, // starter selection MART, // confirm purchase TOP_LEFT // e.g. player name }; class ConfirmSlotDetector : public SelectionSlotDetector { public: - ConfirmSlotDetector(Color color, ConfirmSlot slot, ConfirmPosition position = ConfirmPosition::STANDARD); + ConfirmSlotDetector(Color color, ConfirmSlot slot, ConfirmPosition position = ConfirmPosition::STANDARD); }; class ConfirmSlotWatcher : public DetectorToFinder { public: diff --git a/SerialPrograms/Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.cpp b/SerialPrograms/Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.cpp index 55ffc2afcc..fbbb53b519 100644 --- a/SerialPrograms/Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.cpp +++ b/SerialPrograms/Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.cpp @@ -68,10 +68,10 @@ constexpr int8_t START_MENU_ROWS[5][9] = { }; static std::vector start_menu_slot_candidates(StartMenuSlot slot, StartMenuState state) { - int8_t row = START_MENU_ROWS[(int)state][(int)slot]; - if (row == -1) { - throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid StartMenuSlot for StartMenuState"); - } + int8_t row = START_MENU_ROWS[(int)state][(int)slot]; + if (row == -1) { + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid StartMenuSlot for StartMenuState"); + } double offset = SELECTION_SLOT_OFFSETS[row]; return { { 0.725, 0.098 + offset, SelectionIndicator::ARROW }, @@ -92,7 +92,7 @@ class Test_StartMenuDetector : public UnitTest { Test_StartMenuDetector( const std::string& image, - StartMenuState state, + StartMenuState state, bool expected ) : UnitTest("PokemonRSE::StartMenuDetector - " + image) @@ -110,7 +110,7 @@ class Test_StartMenuDetector : public UnitTest { private: std::string m_image; - StartMenuState m_state; + StartMenuState m_state; bool m_expected; }; diff --git a/SerialPrograms/Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.h b/SerialPrograms/Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.h index 9da3c093bf..0a17dc6858 100644 --- a/SerialPrograms/Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.h +++ b/SerialPrograms/Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.h @@ -21,23 +21,23 @@ namespace NintendoSwitch{ namespace PokemonRSE{ enum class StartMenuState { - PRE_POKEMON = 0, - PRE_POKEDEX = 1, - PRE_POKENAV = 2, + PRE_POKEMON = 0, + PRE_POKEDEX = 1, + PRE_POKENAV = 2, FULL = 3, SAFARI = 4, }; enum class StartMenuSlot { - POKEDEX = 0, - POKEMON = 1, - BAG = 2, - POKENAV = 3, - TRAINER = 4, - SAVE = 5, - OPTION = 6, - EXIT = 7, - RETIRE = 8, + POKEDEX = 0, + POKEMON = 1, + BAG = 2, + POKENAV = 3, + TRAINER = 4, + SAVE = 5, + OPTION = 6, + EXIT = 7, + RETIRE = 8, }; // Detect the full start menu by looking for empty white sections on the top and bottom @@ -65,13 +65,13 @@ class StartMenuWatcher : public DetectorToFinder{ // Detect whether a specified slot is selected class StartMenuSlotDetector : public SelectionSlotDetector { public: - StartMenuSlotDetector(Color color, StartMenuSlot slot, StartMenuState state = StartMenuState::FULL); + StartMenuSlotDetector(Color color, StartMenuSlot slot, StartMenuState state = StartMenuState::FULL); }; class StartMenuSlotWatcher : public DetectorToFinder { public: - StartMenuSlotWatcher(Color color, StartMenuSlot slot, StartMenuState state = StartMenuState::FULL) + StartMenuSlotWatcher(Color color, StartMenuSlot slot, StartMenuState state = StartMenuState::FULL) : DetectorToFinder("StartMenuSlotWatcher", std::chrono::milliseconds(250), color, slot, state) - {} + {} }; diff --git a/SerialPrograms/Source/PokemonRSE/Inference/PokemonRSE_SelectionDetector.h b/SerialPrograms/Source/PokemonRSE/Inference/PokemonRSE_SelectionDetector.h index 73f9d822bc..f1f794585d 100644 --- a/SerialPrograms/Source/PokemonRSE/Inference/PokemonRSE_SelectionDetector.h +++ b/SerialPrograms/Source/PokemonRSE/Inference/PokemonRSE_SelectionDetector.h @@ -34,8 +34,8 @@ constexpr double SELECTION_SLOT_OFFSETS[] = { }; enum class SelectionIndicator { - ARROW, - BORDER, + ARROW, + BORDER, }; struct SlotCandidate { @@ -112,14 +112,14 @@ class SelectionSlotDetector : public StaticScreenDetector { private: Color m_color; - std::vector> m_detectors; + std::vector> m_detectors; }; class SelectionWatcher : public DetectorToFinder { public: SelectionWatcher( Color color, VideoOverlay* overlay, - const std::vector& candidates, + const std::vector& candidates, std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250) ) : DetectorToFinder("SelectionWatcher", hold_duration, color, candidates) diff --git a/SerialPrograms/Source/PokemonRSE/PokemonRSE_Tests.cpp b/SerialPrograms/Source/PokemonRSE/PokemonRSE_Tests.cpp index 7dec521d1f..a84ca6f7cd 100644 --- a/SerialPrograms/Source/PokemonRSE/PokemonRSE_Tests.cpp +++ b/SerialPrograms/Source/PokemonRSE/PokemonRSE_Tests.cpp @@ -15,10 +15,10 @@ namespace PokemonRSE{ void add_tests(UnitTestDatabase& database){ - add_tests_SelectionDialogDetector(database); - add_tests_ConfirmSlotDetector(database); - add_tests_StartMenuDetector(database); - add_tests_StartMenuSlotDetector(database); + add_tests_SelectionDialogDetector(database); + add_tests_ConfirmSlotDetector(database); + add_tests_StartMenuDetector(database); + add_tests_StartMenuSlotDetector(database); } From b78ca787d81302d45211525318a2ca5b34527a0a Mon Sep 17 00:00:00 2001 From: fyex <33452346+fyex@users.noreply.github.com> Date: Sun, 30 Aug 2026 11:19:52 +0200 Subject: [PATCH 6/7] fix tabs and import --- .../Dialogs/PokemonRSE_DialogDetector.cpp | 34 +++++++++---------- .../Dialogs/PokemonRSE_DialogDetector.h | 8 ++--- .../Menus/PokemonRSE_StartMenuDetector.cpp | 12 +++---- .../Menus/PokemonRSE_StartMenuDetector.h | 30 ++++++++-------- .../Inference/PokemonRSE_SelectionDetector.h | 8 ++--- .../Source/PokemonRSE/PokemonRSE_Tests.cpp | 12 +++---- 6 files changed, 52 insertions(+), 52 deletions(-) diff --git a/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp b/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp index a3f2edf7ac..f19131d8df 100644 --- a/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp +++ b/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp @@ -203,42 +203,42 @@ bool SelectionDialogDetector::detect(const ImageViewRGB32& screen){ static std::vector confirm_candidates(ConfirmSlot slot, ConfirmPosition position) { - double offset = SELECTION_SLOT_OFFSETS[(int)slot]; + double offset = SELECTION_SLOT_OFFSETS[(int)slot]; - switch (position) { - case ConfirmPosition::STANDARD: + switch (position) { + case ConfirmPosition::STANDARD: return { { 0.69, 0.45 + offset, SelectionIndicator::ARROW }, { 0.685, 0.458 + offset, SelectionIndicator::BORDER } }; - case ConfirmPosition::SPECIAL: - return { - { 0.79, 0.45 + offset, SelectionIndicator::ARROW }, - { 0.785, 0.458 + offset, SelectionIndicator::BORDER } + case ConfirmPosition::SPECIAL: + return { + { 0.79, 0.45 + offset, SelectionIndicator::ARROW }, + { 0.785, 0.458 + offset, SelectionIndicator::BORDER } }; - case ConfirmPosition::STARTER: + case ConfirmPosition::STARTER: return { { 0.79, 0.45 + offset, SelectionIndicator::ARROW }, { 0.725, 0.397 + offset, SelectionIndicator::ARROW }, // jpn rs { 0.72, 0.405 + offset, SelectionIndicator::BORDER } }; - case ConfirmPosition::MART: + case ConfirmPosition::MART: return { { 0.69, 0.45 + offset, SelectionIndicator::ARROW }, { 0.255, 0.45 + offset, SelectionIndicator::ARROW }, // jpn rs { 0.25, 0.458 + offset, SelectionIndicator::BORDER } }; - case ConfirmPosition::TOP_LEFT: + case ConfirmPosition::TOP_LEFT: return { { 0.094, 0.115 + offset, SelectionIndicator::ARROW }, { 0.089, 0.123 + offset, SelectionIndicator::BORDER } }; - default: + default: return { { 0.69, 0.45 + offset, SelectionIndicator::ARROW }, { 0.685, 0.458 + offset, SelectionIndicator::BORDER } }; - } + } } ConfirmSlotDetector::ConfirmSlotDetector(Color color, ConfirmSlot slot, ConfirmPosition position) @@ -289,14 +289,14 @@ class Test_ConfirmSlotDetector : public UnitTest { Test_ConfirmSlotDetector( const std::string& image, - ConfirmSlot slot, - ConfirmPosition position, + ConfirmSlot slot, + ConfirmPosition position, bool expected ) : UnitTest("PokemonRSE::ConfirmSlotDetector - " + std::to_string((int)slot) + " - " + image) , m_image(UNIT_TEST_RESOURCE_PATH() + image) - , m_slot(slot) - , m_position(position) + , m_slot(slot) + , m_position(position) , m_expected(expected) { } @@ -309,7 +309,7 @@ class Test_ConfirmSlotDetector : public UnitTest { private: std::string m_image; - ConfirmSlot m_slot; + ConfirmSlot m_slot; ConfirmPosition m_position; bool m_expected; }; diff --git a/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h b/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h index 8553ec14d5..7d29576fe5 100644 --- a/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h +++ b/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h @@ -169,16 +169,16 @@ enum class ConfirmSlot { }; enum class ConfirmPosition { - STANDARD, // e.g. save menu - SPECIAL, // e.g. clock in your room, replace move; not starter selection: ruby/sapphire is different - STARTER, // starter selection + STANDARD, // e.g. save menu + SPECIAL, // e.g. clock in your room, replace move; not starter selection: ruby/sapphire is different + STARTER, // starter selection MART, // confirm purchase TOP_LEFT // e.g. player name }; class ConfirmSlotDetector : public SelectionSlotDetector { public: - ConfirmSlotDetector(Color color, ConfirmSlot slot, ConfirmPosition position = ConfirmPosition::STANDARD); + ConfirmSlotDetector(Color color, ConfirmSlot slot, ConfirmPosition position = ConfirmPosition::STANDARD); }; class ConfirmSlotWatcher : public DetectorToFinder { public: diff --git a/SerialPrograms/Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.cpp b/SerialPrograms/Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.cpp index 55ffc2afcc..fbbb53b519 100644 --- a/SerialPrograms/Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.cpp +++ b/SerialPrograms/Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.cpp @@ -68,10 +68,10 @@ constexpr int8_t START_MENU_ROWS[5][9] = { }; static std::vector start_menu_slot_candidates(StartMenuSlot slot, StartMenuState state) { - int8_t row = START_MENU_ROWS[(int)state][(int)slot]; - if (row == -1) { - throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid StartMenuSlot for StartMenuState"); - } + int8_t row = START_MENU_ROWS[(int)state][(int)slot]; + if (row == -1) { + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid StartMenuSlot for StartMenuState"); + } double offset = SELECTION_SLOT_OFFSETS[row]; return { { 0.725, 0.098 + offset, SelectionIndicator::ARROW }, @@ -92,7 +92,7 @@ class Test_StartMenuDetector : public UnitTest { Test_StartMenuDetector( const std::string& image, - StartMenuState state, + StartMenuState state, bool expected ) : UnitTest("PokemonRSE::StartMenuDetector - " + image) @@ -110,7 +110,7 @@ class Test_StartMenuDetector : public UnitTest { private: std::string m_image; - StartMenuState m_state; + StartMenuState m_state; bool m_expected; }; diff --git a/SerialPrograms/Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.h b/SerialPrograms/Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.h index 9da3c093bf..0a17dc6858 100644 --- a/SerialPrograms/Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.h +++ b/SerialPrograms/Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.h @@ -21,23 +21,23 @@ namespace NintendoSwitch{ namespace PokemonRSE{ enum class StartMenuState { - PRE_POKEMON = 0, - PRE_POKEDEX = 1, - PRE_POKENAV = 2, + PRE_POKEMON = 0, + PRE_POKEDEX = 1, + PRE_POKENAV = 2, FULL = 3, SAFARI = 4, }; enum class StartMenuSlot { - POKEDEX = 0, - POKEMON = 1, - BAG = 2, - POKENAV = 3, - TRAINER = 4, - SAVE = 5, - OPTION = 6, - EXIT = 7, - RETIRE = 8, + POKEDEX = 0, + POKEMON = 1, + BAG = 2, + POKENAV = 3, + TRAINER = 4, + SAVE = 5, + OPTION = 6, + EXIT = 7, + RETIRE = 8, }; // Detect the full start menu by looking for empty white sections on the top and bottom @@ -65,13 +65,13 @@ class StartMenuWatcher : public DetectorToFinder{ // Detect whether a specified slot is selected class StartMenuSlotDetector : public SelectionSlotDetector { public: - StartMenuSlotDetector(Color color, StartMenuSlot slot, StartMenuState state = StartMenuState::FULL); + StartMenuSlotDetector(Color color, StartMenuSlot slot, StartMenuState state = StartMenuState::FULL); }; class StartMenuSlotWatcher : public DetectorToFinder { public: - StartMenuSlotWatcher(Color color, StartMenuSlot slot, StartMenuState state = StartMenuState::FULL) + StartMenuSlotWatcher(Color color, StartMenuSlot slot, StartMenuState state = StartMenuState::FULL) : DetectorToFinder("StartMenuSlotWatcher", std::chrono::milliseconds(250), color, slot, state) - {} + {} }; diff --git a/SerialPrograms/Source/PokemonRSE/Inference/PokemonRSE_SelectionDetector.h b/SerialPrograms/Source/PokemonRSE/Inference/PokemonRSE_SelectionDetector.h index 73f9d822bc..f1f794585d 100644 --- a/SerialPrograms/Source/PokemonRSE/Inference/PokemonRSE_SelectionDetector.h +++ b/SerialPrograms/Source/PokemonRSE/Inference/PokemonRSE_SelectionDetector.h @@ -34,8 +34,8 @@ constexpr double SELECTION_SLOT_OFFSETS[] = { }; enum class SelectionIndicator { - ARROW, - BORDER, + ARROW, + BORDER, }; struct SlotCandidate { @@ -112,14 +112,14 @@ class SelectionSlotDetector : public StaticScreenDetector { private: Color m_color; - std::vector> m_detectors; + std::vector> m_detectors; }; class SelectionWatcher : public DetectorToFinder { public: SelectionWatcher( Color color, VideoOverlay* overlay, - const std::vector& candidates, + const std::vector& candidates, std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250) ) : DetectorToFinder("SelectionWatcher", hold_duration, color, candidates) diff --git a/SerialPrograms/Source/PokemonRSE/PokemonRSE_Tests.cpp b/SerialPrograms/Source/PokemonRSE/PokemonRSE_Tests.cpp index 7dec521d1f..e34b1af8de 100644 --- a/SerialPrograms/Source/PokemonRSE/PokemonRSE_Tests.cpp +++ b/SerialPrograms/Source/PokemonRSE/PokemonRSE_Tests.cpp @@ -5,8 +5,8 @@ */ #include "Common/Cpp/TestRunners/UnitTestDatabase.h" -#include "PokemonRSE/inference/Dialogs/PokemonRSE_DialogDetector.h" -#include "PokemonRSE/inference/Menus/PokemonRSE_StartMenuDetector.h" +#include "PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h" +#include "PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.h" #include "PokemonRSE_Tests.h" namespace PokemonAutomation{ @@ -15,10 +15,10 @@ namespace PokemonRSE{ void add_tests(UnitTestDatabase& database){ - add_tests_SelectionDialogDetector(database); - add_tests_ConfirmSlotDetector(database); - add_tests_StartMenuDetector(database); - add_tests_StartMenuSlotDetector(database); + add_tests_SelectionDialogDetector(database); + add_tests_ConfirmSlotDetector(database); + add_tests_StartMenuDetector(database); + add_tests_StartMenuSlotDetector(database); } From ccb1c48f8f739524a15503983f1ae8d658d40514 Mon Sep 17 00:00:00 2001 From: fyex <33452346+fyex@users.noreply.github.com> Date: Sun, 30 Aug 2026 12:46:20 +0200 Subject: [PATCH 7/7] comment out non-default game box tests --- .../Inference/Dialogs/PokemonRSE_DialogDetector.cpp | 4 ++++ .../Inference/Menus/PokemonRSE_StartMenuDetector.cpp | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp b/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp index f19131d8df..c0ab925c84 100644 --- a/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp +++ b/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp @@ -274,6 +274,7 @@ class Test_SelectionDialogDetector : public UnitTest { void add_tests_SelectionDialogDetector(UnitTestDatabase& database) { // Switch 1/2 + /* // Original GBA resolution; {0.0, 0.0, 1.0, 1.0} database.add("PokemonRSE/SelectionDialogDetector/GBA-Eng-Ruby_true.png", true); database.add("PokemonRSE/SelectionDialogDetector/GBA-Ger-Emerald_true.png", true); @@ -281,6 +282,7 @@ void add_tests_SelectionDialogDetector(UnitTestDatabase& database) { // Game Boy Player; {0.064217, 0.107667, 0.86413, 0.787928} database.add("PokemonRSE/SelectionDialogDetector/GBP-Ger-Emerald_true.png", true); database.add("PokemonRSE/SelectionDialogDetector/GBP-Ger-Ruby_true.png", true); + */ } @@ -317,6 +319,7 @@ class Test_ConfirmSlotDetector : public UnitTest { void add_tests_ConfirmSlotDetector(UnitTestDatabase& database) { // Switch 1/2 + /* // Original GBA resolution {0.0, 0.0, 1.0, 1.0} database.add("PokemonRSE/ConfirmSlotDetector/GBA-Eng-Ruby_yes.png", ConfirmSlot::YES, ConfirmPosition::STANDARD, true); //database.add("PokemonRSE/ConfirmSlotDetector/GBA-Eng-Ruby_yes.png", ConfirmSlot::NO, ConfirmPosition::STANDARD, false); @@ -363,6 +366,7 @@ void add_tests_ConfirmSlotDetector(UnitTestDatabase& database) { // GBP STARTER database.add("PokemonRSE/ConfirmSlotDetector/GBP-Ger-Emerald-Starter_yes.png", ConfirmSlot::YES, ConfirmPosition::STARTER, true); + */ } diff --git a/SerialPrograms/Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.cpp b/SerialPrograms/Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.cpp index fbbb53b519..23ef5d6c92 100644 --- a/SerialPrograms/Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.cpp +++ b/SerialPrograms/Source/PokemonRSE/Inference/Menus/PokemonRSE_StartMenuDetector.cpp @@ -117,6 +117,7 @@ class Test_StartMenuDetector : public UnitTest { void add_tests_StartMenuDetector(UnitTestDatabase & database) { // Switch 1/2 + /* // Original GBA resolution; {0.0, 0.0, 1.0, 1.0} database.add("PokemonRSE/StartMenuDetector/GBA-Ger-Emerald-Full-Pokedex_true.png", StartMenuState::FULL, true); database.add("PokemonRSE/StartMenuDetector/GBA-Ger-Emerald-PrePokenav-Exit_true.png", StartMenuState::PRE_POKENAV, true); @@ -131,6 +132,7 @@ void add_tests_StartMenuDetector(UnitTestDatabase & database) { // Game Boy Player; {0.064217, 0.107667, 0.86413, 0.787928} database.add("PokemonRSE/StartMenuDetector/GBP-Ger-Emerald-Full-Pokemon_true.png", StartMenuState::FULL, true); database.add("PokemonRSE/StartMenuDetector/GBP-Ger-Ruby-Full-Pokemon_true.png", StartMenuState::FULL, true); + */ } @@ -167,6 +169,7 @@ class Test_StartMenuSlotDetector : public UnitTest { void add_tests_StartMenuSlotDetector(UnitTestDatabase & database) { // Switch 1/2 + /* // Original GBA resolution; {0.0, 0.0, 1.0, 1.0} database.add("PokemonRSE/StartMenuDetector/GBA-Ger-Emerald-Full-Pokedex_true.png", StartMenuSlot::POKEDEX, StartMenuState::FULL, true); database.add("PokemonRSE/StartMenuDetector/GBA-Ger-Emerald-PrePokenav-Exit_true.png", StartMenuSlot::EXIT, StartMenuState::PRE_POKENAV, true); @@ -181,7 +184,7 @@ void add_tests_StartMenuSlotDetector(UnitTestDatabase & database) { // Game Boy Player; {0.064217, 0.107667, 0.86413, 0.787928} database.add("PokemonRSE/StartMenuDetector/GBP-Ger-Emerald-Full-Pokemon_true.png", StartMenuSlot::POKEMON, StartMenuState::FULL, true); database.add("PokemonRSE/StartMenuDetector/GBP-Ger-Ruby-Full-Pokemon_true.png", StartMenuSlot::POKEMON, StartMenuState::FULL, true); - + */ }