diff --git a/SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramSwitch.cpp b/SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramSwitch.cpp index 7589a47b52..3a2ca8d8a8 100644 --- a/SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramSwitch.cpp +++ b/SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramSwitch.cpp @@ -124,6 +124,7 @@ #include "NintendoSwitch/Programs/DateSpam/NintendoSwitch_HomeToDateTime.h" #include "NintendoSwitch/Inference/NintendoSwitch_ConsoleTypeDetector.h" #include "NintendoSwitch/Inference/NintendoSwitch_HomeMenuDetector.h" +#include "NintendoSwitch/Inference/NintendoSwitch_CloseGameDetector.h" #include "NintendoSwitch/Inference/NintendoSwitch_StartGameUserSelectDetector.h" #include "NintendoSwitch/Inference/NintendoSwitch_UpdatePopupDetector.h" #include "NintendoSwitch/Programs/DateSpam/NintendoSwitch_RollDateForward1.h" @@ -289,9 +290,28 @@ void TestProgram::program(MultiSwitchProgramEnvironment& env, CancellableScope& [[maybe_unused]] VideoFeed& feed = env.consoles[0]; [[maybe_unused]] VideoOverlay& overlay = env.consoles[0]; ProControllerContext context(scope, console.controller()); + // JoyconContext context(scope, console.controller()); VideoOverlaySet overlays(overlay); +#if 0 +close_game_from_home(console, context); +// ssf_issue_scroll(context, DPAD_DOWN, 24ms); +#endif + +#if 0 + // auto snapshot = feed.snapshot(); + // CloseGameDetector detector(console); + // cout << detector.detect(snapshot) << endl; + CloseGameWatcher watcher(console); + + int ret = wait_until(console, context, Seconds(10), {watcher}); + + if (ret == 0){ + console.log("CloseGameWatcher detected."); + } + +#endif #if 0 ImageRGB32 image1("itemprinter.png"); diff --git a/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_CloseGameDetector.cpp b/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_CloseGameDetector.cpp new file mode 100644 index 0000000000..f4475f6caa --- /dev/null +++ b/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_CloseGameDetector.cpp @@ -0,0 +1,97 @@ +/* Close Game Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonTools/Images/SolidColorTest.h" +#include "NintendoSwitch/NintendoSwitch_ConsoleHandle.h" +#include "NintendoSwitch_CloseGameDetector.h" + +#include +using std::cout; +using std::endl; + +namespace PokemonAutomation{ +namespace NintendoSwitch{ + + + +CloseGameDetector::CloseGameDetector(ConsoleHandle& console, Color color) + : m_color(color) + , m_top_box(0.226358, 0.272648, 0.407445, 0.033989) + , m_left_box(0.225, 0.275, 0.01995, 0.350) + , m_close_game_text_row(0.330986, 0.649052, 0.342052, 0.051878) +{} +void CloseGameDetector::make_overlays(VideoOverlaySet& items) const{ + items.add(m_color, m_top_box); + items.add(m_color, m_left_box); + items.add(m_color, m_close_game_text_row); +} + + +bool CloseGameDetector::detect(const ImageViewRGB32& screen){ + + ImageStats stats_top = image_stats(extract_box_reference(screen, m_top_box)); // the top portion of the Close game popup + ImageStats stats_left = image_stats(extract_box_reference(screen, m_left_box)); // the left portion of the Close game popup + ImageStats stats_close_game_text = image_stats(extract_box_reference(screen, m_close_game_text_row)); // the bottom portion of the Close game popup. overlapping with the close game text. + +// cout << "stats_close_game_text.stddev.sum()" << stats_close_game_text.stddev.sum() << endl; + bool white; +// cout << "stats_top.average.sum() = " << stats_top.average.sum() << endl; + if (stats_top.average.sum() < 300){ + white = false; + }else if (stats_top.average.sum() > 500){ + white = true; + }else{ + return false; + } + +// cout << "stats_top.stddev.sum() = " << stats_top.stddev.sum() << endl; + + // ensure top is uniform in color + if (stats_top.stddev.sum() > 20){ + return false; + } + +// cout << "stats_left.stddev.sum() = " << stats_left.stddev.sum() << endl; + // ensure left is uniform in color + if (stats_left.stddev.sum() > 20){ + return false; + } + + // ensure bottom is NOT uniform in color + if (stats_close_game_text.stddev.sum() < 50){ + return false; + } + + if (white){ // if top is white, ensure left is also white + if (!is_white(stats_top) || !is_white(stats_left)){ +// cout << "asdf" << endl; + return false; + } + }else{ + if (!is_grey(stats_top, 0, 300) || !is_grey(stats_left, 0, 300)){ +// cout << "qwer" << endl; + return false; + } + } + + // ensure top and left are the same color. + if (euclidean_distance(stats_top.average, stats_left.average) > 20){ +// cout << "qwer = " << euclidean_distance(stats_bottom_row.average, stats_bottom_row.average) << endl; + return false; + } + + + return true; +} + + + + + + +} +} diff --git a/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_CloseGameDetector.h b/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_CloseGameDetector.h new file mode 100644 index 0000000000..c02b643589 --- /dev/null +++ b/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_CloseGameDetector.h @@ -0,0 +1,48 @@ +/* Close Game Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_NintendoSwitch_CloseGameDetector_H +#define PokemonAutomation_NintendoSwitch_CloseGameDetector_H + +#include "Common/Cpp/Color.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonTools/VisualDetector.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ + + + +class CloseGameDetector : public StaticScreenDetector{ +public: + CloseGameDetector(ConsoleHandle& console, Color color = COLOR_RED); + + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool detect(const ImageViewRGB32& screen) override; + +private: + Color m_color; + ImageFloatBox m_top_box; + ImageFloatBox m_left_box; + ImageFloatBox m_close_game_text_row; + +}; +class CloseGameWatcher : public DetectorToFinder{ +public: + CloseGameWatcher( + ConsoleHandle& console, + std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250) + ) + : DetectorToFinder("CloseGameWatcher", hold_duration, console) + {} +}; + + + + +} +} +#endif diff --git a/SerialPrograms/Source/NintendoSwitch/Programs/NintendoSwitch_GameEntry.cpp b/SerialPrograms/Source/NintendoSwitch/Programs/NintendoSwitch_GameEntry.cpp index 8d4f2768ea..c987e7beb6 100644 --- a/SerialPrograms/Source/NintendoSwitch/Programs/NintendoSwitch_GameEntry.cpp +++ b/SerialPrograms/Source/NintendoSwitch/Programs/NintendoSwitch_GameEntry.cpp @@ -18,6 +18,7 @@ #include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" #include "NintendoSwitch/Inference/NintendoSwitch_DetectHome.h" #include "NintendoSwitch/Inference/NintendoSwitch_HomeMenuDetector.h" +#include "NintendoSwitch/Inference/NintendoSwitch_CloseGameDetector.h" #include "NintendoSwitch/Inference/NintendoSwitch_StartGameUserSelectDetector.h" #include "NintendoSwitch/Inference/NintendoSwitch_UpdatePopupDetector.h" #include "NintendoSwitch_GameEntry.h" @@ -99,13 +100,12 @@ void ensure_at_home(ConsoleHandle& console, JoyconContext& context){ } - // // close_game_from_home() // -void close_game_from_home(ConsoleHandle& console, ProControllerContext& context){ - console.log("close_game_from_home"); +void close_game_from_home_blind(ConsoleHandle& console, ProControllerContext& context){ + console.log("close_game_from_home_blind"); ensure_at_home(console, context); // Use mashing to ensure that the X press succeeds. If it fails, the SR @@ -125,8 +125,9 @@ void close_game_from_home(ConsoleHandle& console, ProControllerContext& context) pbf_mash_button(context, BUTTON_X, 50); pbf_mash_button(context, BUTTON_B, 350); } -void close_game_from_home(ConsoleHandle& console, JoyconContext& context){ - console.log("close_game_from_home"); + +void close_game_from_home_blind(ConsoleHandle& console, JoyconContext& context){ + console.log("close_game_from_home_blind"); ensure_at_home(console, context); // Use mashing to ensure that the X press succeeds. If it fails, the SR // will fail and can kill a den for the autohosts. @@ -146,6 +147,87 @@ void close_game_from_home(ConsoleHandle& console, JoyconContext& context){ pbf_mash_button(context, BUTTON_B, 2000ms); } +template +void close_game_from_home(ConsoleHandle& console, ControllerContext& context){ + if (!console.video().snapshot()){ // no visual feedback available + close_game_from_home_blind(console, context); + return; + } + + console.log("close_game_from_home"); + ensure_at_home(console, context); + + // this sequence will close the game from the home screen, + // regardless of whether the game is initially open or closed. + bool seen_close_game = false; + size_t times_seen_home_before = 0; + while (true){ + context.wait_for_all_requests(); + + CloseGameWatcher close_game(console); + HomeMenuWatcher home(console); + int ret = wait_until( + console, context, + Seconds(10), + {close_game, home} + ); + + switch(ret){ + case 0: // close_game + console.log("Detected close game menu."); + pbf_mash_button(context, BUTTON_A, 400ms); + seen_close_game = true; + continue; + case 1: // home + if (seen_close_game){ // successfully closed the game + return; + } + + if (times_seen_home_before == 0){ // Try closing the game + pbf_mash_button(context, BUTTON_X, 800ms); + times_seen_home_before++; + continue; + } + + if (times_seen_home_before == 1){ // The game not being selected can happen in Switch 2, when you touch the touchscreen on empty space on the Home screen. + console.log("Failed to close game once. Either game is already closed, or the game is not selected."); + ssf_issue_scroll(context, DPAD_DOWN, 24ms); // moving the DPAD/joystick will allow the selection to come back. + ssf_issue_scroll(context, DPAD_DOWN, 24ms); + ssf_issue_scroll(context, DPAD_DOWN, 24ms); + console.log("Click the Home button to ensure that current game is selected."); + pbf_press_button(context, BUTTON_HOME, 160ms, 500ms); // clicking home ensures that the cursor is selected on the current game. + go_home(console, context); + console.log("Try again to close the game."); + pbf_mash_button(context, BUTTON_X, 800ms); // try again to close the game. + times_seen_home_before++; + continue; + } + + if (times_seen_home_before == 2){ + console.log("Game was already closed."); + return; + } + + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "close_game_from_home: Unexpected state."); + + default: + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "close_game_from_home(): Failed to detect either the Home screen or the Close game menu after 10 seconds.", + console + ); + } + } + +} + +void close_game_from_home(ConsoleHandle& console, ProControllerContext& context){ + close_game_from_home(console, context); +} + +void close_game_from_home(ConsoleHandle& console, JoyconContext& context){ + close_game_from_home(console, context); +} // diff --git a/SerialPrograms/SourceFiles.cmake b/SerialPrograms/SourceFiles.cmake index 56a90fc197..bbc50d8183 100644 --- a/SerialPrograms/SourceFiles.cmake +++ b/SerialPrograms/SourceFiles.cmake @@ -1000,6 +1000,8 @@ file(GLOB LIBRARY_SOURCES Source/NintendoSwitch/Framework/UI/NintendoSwitch_SwitchSystemWidget.h Source/NintendoSwitch/Inference/NintendoSwitch2_BinarySliderDetector.cpp Source/NintendoSwitch/Inference/NintendoSwitch2_BinarySliderDetector.h + Source/NintendoSwitch/Inference/NintendoSwitch_CloseGameDetector.cpp + Source/NintendoSwitch/Inference/NintendoSwitch_CloseGameDetector.h Source/NintendoSwitch/Inference/NintendoSwitch_ConsoleTypeDetector.cpp Source/NintendoSwitch/Inference/NintendoSwitch_ConsoleTypeDetector.h Source/NintendoSwitch/Inference/NintendoSwitch_DateChangeDetector.cpp