Skip to content

Commit 345567c

Browse files
author
Gin
committed
improve home sorter using visual inference
1 parent 5556462 commit 345567c

File tree

3 files changed

+127
-103
lines changed

3 files changed

+127
-103
lines changed

SerialPrograms/Source/Pokemon/Pokemon_CollectedPokemonInfo.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ std::ostream& operator<<(std::ostream& os, const std::optional<CollectedPokemonI
8787
if (pokemon.has_value()){
8888
// NOTE edit when adding new struct members
8989
os << "(";
90-
os << "dex_id: " << pokemon->dex_number << " " << pokemon->name_slug << " ";
90+
os << "dex: " << pokemon->dex_number << " " << pokemon->name_slug << " ";
9191
os << "shiny:" << (pokemon->shiny ? "true" : "false") << " ";
9292
os << "gmax:" << (pokemon->gmax ? "true" : "false") << " ";
9393
os << "alpha:" << (pokemon->alpha ? "true" : "false") << " ";

SerialPrograms/Source/Pokemon/Pokemon_CollectedPokemonInfo.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ bool operator==(const CollectedPokemonInfo& lhs, const CollectedPokemonInfo& rhs
5050
// If user does not give a preference ruleset, sort by dex number.
5151
bool operator<(const std::optional<CollectedPokemonInfo>& lhs, const std::optional<CollectedPokemonInfo>& rhs);
5252

53-
// Print pokemon info into ostream
53+
// Print pokemon info into ostream.
54+
// If empty, return "(empty)". Otherwise, "(dex: 1: bulbasaur shiny:...)"
5455
std::ostream& operator<<(std::ostream& os, const std::optional<CollectedPokemonInfo>& pokemon);
5556

5657
// Create short info of a pokemon for overlay log display

SerialPrograms/Source/PokemonHome/Programs/PokemonHome_BoxSorter.cpp

Lines changed: 124 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ language
4646
#include "Pokemon/Resources/Pokemon_PokemonSlugs.h"
4747
#include "Pokemon/Pokemon_BoxCursor.h"
4848
#include "Pokemon/Pokemon_CollectedPokemonInfo.h"
49+
#include "PokemonHome/Inference/PokemonHome_ButtonDetector.h"
4950
#include "PokemonHome/Inference/PokemonHome_BoxGenderDetector.h"
5051
#include "PokemonHome/Inference/PokemonHome_BallReader.h"
5152
#include "PokemonHome_BoxSorter.h"
@@ -371,17 +372,13 @@ std::array<size_t, 2> find_occupied_slots_in_box(
371372
return first_pokemon_slot;
372373
}
373374

374-
void BoxSorter::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){
375-
StartProgramChecks::check_performance_class_wired_or_wireless(context);
376-
377-
const std::vector<SortingRule> sort_preferences = SORT_TABLE.preferences();
378-
if (sort_preferences.empty()){
379-
throw UserSetupError(env.console, "At least one sorting method selection needs to be made!");
380-
}
381-
382-
BoxSorter_Descriptor::Stats& stats = env.current_stats< BoxSorter_Descriptor::Stats>();
375+
// Read the current summary screen and assign various pokemon info into `cur_pokemon-info`
376+
void read_summary_screen(
377+
SingleSwitchProgramEnvironment& env, ProControllerContext& context,
378+
CollectedPokemonInfo& cur_pokemon_info
379+
){
380+
VideoOverlaySet video_overlay_set(env.console);
383381

384-
ImageFloatBox select_check(0.495, 0.0045, 0.01, 0.005); // square color to check which mode is active
385382
ImageFloatBox national_dex_number_box(0.448, 0.245, 0.049, 0.04); //pokemon national dex number pos
386383
ImageFloatBox shiny_symbol_box(0.702, 0.09, 0.04, 0.06); // shiny symbol pos
387384
// TODO: gmax symbol is at the same location as Tera type symbol! Need better detection to tell apart
@@ -396,6 +393,93 @@ void BoxSorter::program(SingleSwitchProgramEnvironment& env, ProControllerContex
396393
ImageFloatBox ability_box(0.158, 0.838, 0.213, 0.042); // Ability
397394
ImageFloatBox alpha_box(0.787, 0.095, 0.024, 0.046); // Alpha symbol
398395

396+
video_overlay_set.add(COLOR_WHITE, national_dex_number_box);
397+
video_overlay_set.add(COLOR_BLUE, shiny_symbol_box);
398+
video_overlay_set.add(COLOR_RED, gmax_symbol_box);
399+
video_overlay_set.add(COLOR_RED, alpha_box);
400+
video_overlay_set.add(COLOR_DARKGREEN, origin_symbol_box);
401+
video_overlay_set.add(COLOR_DARK_BLUE, pokemon_box);
402+
video_overlay_set.add(COLOR_RED, level_box);
403+
video_overlay_set.add(COLOR_RED, ot_id_box);
404+
video_overlay_set.add(COLOR_RED, ot_box);
405+
video_overlay_set.add(COLOR_RED, nature_box);
406+
video_overlay_set.add(COLOR_RED, ability_box);
407+
BoxGenderDetector::make_overlays(video_overlay_set);
408+
409+
410+
// Wait for the summary screen transition to end
411+
FrozenImageDetector frozen_image_detector(COLOR_GREEN, {0.388, 0.238, 0.109, 0.062}, Milliseconds(80), 20);
412+
frozen_image_detector.make_overlays(video_overlay_set);
413+
wait_until(env.console, context, 5s, {frozen_image_detector});
414+
415+
VideoSnapshot screen = env.console.video().snapshot();
416+
417+
const int dex_number = OCR::read_number_waterfill(env.console, extract_box_reference(screen, national_dex_number_box), 0xff808080, 0xffffffff);
418+
if (dex_number <= 0 || dex_number > static_cast<int>(NATIONAL_DEX_SLUGS().size())) {
419+
OperationFailedException::fire(
420+
ErrorReport::SEND_ERROR_REPORT,
421+
"BoxSorter Check Summary: Unable to read a correct dex number, found: " + std::to_string(dex_number),
422+
env.console
423+
);
424+
}
425+
cur_pokemon_info.dex_number = (uint16_t)dex_number;
426+
cur_pokemon_info.name_slug = NATIONAL_DEX_SLUGS()[dex_number-1];
427+
428+
const int shiny_stddev_value = (int)image_stddev(extract_box_reference(screen, shiny_symbol_box)).sum();
429+
const bool is_shiny = shiny_stddev_value > 30;
430+
cur_pokemon_info.shiny = is_shiny;
431+
env.console.log("Shiny detection stddev:" + std::to_string(shiny_stddev_value) + " is shiny:" + std::to_string(is_shiny));
432+
433+
const int gmax_stddev_value = (int)image_stddev(extract_box_reference(screen, gmax_symbol_box)).sum();
434+
const bool is_gmax = gmax_stddev_value > 30;
435+
cur_pokemon_info.gmax = is_gmax;
436+
env.console.log("Gmax detection stddev:" + std::to_string(gmax_stddev_value) + " is gmax:" + std::to_string(is_gmax));
437+
438+
const int alpha_stddev_value = (int)image_stddev(extract_box_reference(screen, alpha_box)).sum();
439+
const bool is_alpha = alpha_stddev_value > 40;
440+
cur_pokemon_info.alpha = is_alpha;
441+
env.console.log("Alpha detection stddev:" + std::to_string(alpha_stddev_value) + " is alpha:" + std::to_string(is_alpha));
442+
443+
BallReader ball_reader(env.console);
444+
cur_pokemon_info.ball_slug = ball_reader.read_ball(screen);
445+
446+
const StatsHuntGenderFilter gender = BoxGenderDetector::detect(screen);
447+
env.console.log("Gender: " + gender_to_string(gender), COLOR_GREEN);
448+
cur_pokemon_info.gender = gender;
449+
450+
const int ot_id = OCR::read_number_waterfill(env.console, extract_box_reference(screen, ot_id_box), 0xff808080, 0xffffffff);
451+
if (ot_id < 0 || ot_id > 999'999) {
452+
dump_image(env.console, ProgramInfo(), "ReadSummary_OT", screen);
453+
}
454+
cur_pokemon_info.ot_id = ot_id;
455+
456+
env.add_overlay_log(create_overlay_info(cur_pokemon_info));
457+
video_overlay_set.clear();
458+
459+
// NOTE edit when adding new struct members (detections go here likely)
460+
461+
// level_box
462+
// ot_box
463+
// nature_box
464+
// ability_box
465+
466+
// Press button R to go to next summary screen
467+
pbf_press_button(context, BUTTON_R, 80ms, 300ms);
468+
context.wait_for_all_requests();
469+
}
470+
471+
void BoxSorter::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){
472+
StartProgramChecks::check_performance_class_wired_or_wireless(context);
473+
474+
const std::vector<SortingRule> sort_preferences = SORT_TABLE.preferences();
475+
if (sort_preferences.empty()){
476+
throw UserSetupError(env.console, "At least one sorting method selection needs to be made!");
477+
}
478+
479+
BoxSorter_Descriptor::Stats& stats = env.current_stats< BoxSorter_Descriptor::Stats>();
480+
481+
ImageFloatBox select_check(0.495, 0.0045, 0.01, 0.005); // square color to check which mode is active
482+
399483

400484
// vector that will store data for each slot
401485
std::vector<std::optional<CollectedPokemonInfo>> boxes_data;
@@ -404,16 +488,14 @@ void BoxSorter::program(SingleSwitchProgramEnvironment& env, ProControllerContex
404488

405489
VideoSnapshot screen = env.console.video().snapshot();
406490

407-
VideoOverlaySet box_render(env.console);
408-
409-
std::ostringstream ss;
491+
VideoOverlaySet video_overlay_set(env.console);
410492

411493
FloatPixel image_value = image_stats(extract_box_reference(screen, select_check)).average;
412494

413495
env.console.log("Color detected from the select square: " + image_value.to_string());
414496

415497
//if the correct color is not detected, getting out of every possible menu to make sure the program work no matter where you start it in your pokemon home
416-
box_render.add(COLOR_BLUE, select_check);
498+
video_overlay_set.add(COLOR_BLUE, select_check);
417499
if(image_value.r <= image_value.g + image_value.b){
418500
for (int var = 0; var < 5; ++var){
419501
pbf_press_button(context, BUTTON_B, 10, GAME_DELAY+10);
@@ -441,11 +523,15 @@ void BoxSorter::program(SingleSwitchProgramEnvironment& env, ProControllerContex
441523
}
442524
}
443525

444-
box_render.clear();
526+
video_overlay_set.clear();
445527

446528
BoxCursor dest_cursor;
447529
BoxCursor nav_cursor = {0, 0, 0};
448530

531+
532+
BoxViewWatcher box_view_watcher(&env.console.overlay());
533+
SummaryScreenWatcher summary_screen_watcher(&env.console.overlay());
534+
449535
//cycle through each box
450536
for (size_t box_idx = 0; box_idx < BOX_NUMBER; box_idx++){
451537
if(box_idx != 0){
@@ -469,7 +555,6 @@ void BoxSorter::program(SingleSwitchProgramEnvironment& env, ProControllerContex
469555

470556
// Check box grid color stddev to find occupied slots and fill boxes_data with placeholder pokemon info.
471557
const std::array<size_t, 2> first_pokemon_slot = find_occupied_slots_in_box(env, context, boxes_data, sort_preferences);
472-
ss.str("");
473558

474559
//enter the summary screen to read pokemon info
475560
if (first_pokemon_slot[0] != SIZE_MAX){
@@ -481,22 +566,17 @@ void BoxSorter::program(SingleSwitchProgramEnvironment& env, ProControllerContex
481566

482567
pbf_press_button(context, BUTTON_A, 10, GAME_DELAY);
483568
context.wait_for_all_requests();
484-
box_render.clear();
569+
video_overlay_set.clear();
485570
pbf_press_dpad(context, DPAD_DOWN, 10, GAME_DELAY);
486-
pbf_press_button(context, BUTTON_A, 10, VIDEO_DELAY+150);
571+
pbf_press_button(context, BUTTON_A, 80ms, 100ms);
487572
context.wait_for_all_requests();
573+
int ret = wait_until(env.console, context, Seconds(5), {summary_screen_watcher});
574+
if (ret != 0){
575+
OperationFailedException::fire(
576+
ErrorReport::SEND_ERROR_REPORT, "HomeBoxSorter(): does not find summary screen after 5 sec", env.console
577+
);
578+
}
488579

489-
box_render.add(COLOR_WHITE, national_dex_number_box);
490-
box_render.add(COLOR_BLUE, shiny_symbol_box);
491-
box_render.add(COLOR_RED, gmax_symbol_box);
492-
box_render.add(COLOR_RED, alpha_box);
493-
box_render.add(COLOR_DARKGREEN, origin_symbol_box);
494-
box_render.add(COLOR_DARK_BLUE, pokemon_box);
495-
box_render.add(COLOR_RED, level_box);
496-
box_render.add(COLOR_RED, ot_id_box);
497-
box_render.add(COLOR_RED, ot_box);
498-
box_render.add(COLOR_RED, nature_box);
499-
box_render.add(COLOR_RED, ability_box);
500580

501581
// cycle through each summary of the current box and fill pokemon information
502582
for (size_t row = 0; row < BOX_ROWS; row++){
@@ -506,91 +586,34 @@ void BoxSorter::program(SingleSwitchProgramEnvironment& env, ProControllerContex
506586
continue;
507587
}
508588

509-
// Wait for the summary screen transition to end
510-
FrozenImageDetector frozen_image_detector(COLOR_GREEN, {0.388, 0.238, 0.109, 0.062}, Milliseconds(80), 20);
511-
frozen_image_detector.make_overlays(box_render);
512-
wait_until(env.console, context, 5s, {frozen_image_detector});
513-
514-
auto& cur_pokemon_info = boxes_data[global_idx];
515-
screen = env.console.video().snapshot();
516-
517-
const int dex_number = OCR::read_number_waterfill(env.console, extract_box_reference(screen, national_dex_number_box), 0xff808080, 0xffffffff);
518-
if (dex_number <= 0 || dex_number > static_cast<int>(NATIONAL_DEX_SLUGS().size())) {
519-
OperationFailedException::fire(
520-
ErrorReport::SEND_ERROR_REPORT,
521-
"BoxSorter Check Summary: Unable to read a correct dex number, found: " + std::to_string(dex_number),
522-
env.console
523-
);
524-
}
525-
cur_pokemon_info->dex_number = (uint16_t)dex_number;
526-
cur_pokemon_info->name_slug = NATIONAL_DEX_SLUGS()[dex_number-1];
527-
528-
const int shiny_stddev_value = (int)image_stddev(extract_box_reference(screen, shiny_symbol_box)).sum();
529-
const bool is_shiny = shiny_stddev_value > 30;
530-
cur_pokemon_info->shiny = is_shiny;
531-
env.console.log("Shiny detection stddev:" + std::to_string(shiny_stddev_value) + " is shiny:" + std::to_string(is_shiny));
532-
533-
const int gmax_stddev_value = (int)image_stddev(extract_box_reference(screen, gmax_symbol_box)).sum();
534-
const bool is_gmax = gmax_stddev_value > 30;
535-
cur_pokemon_info->gmax = is_gmax;
536-
env.console.log("Gmax detection stddev:" + std::to_string(gmax_stddev_value) + " is gmax:" + std::to_string(is_gmax));
537-
538-
const int alpha_stddev_value = (int)image_stddev(extract_box_reference(screen, alpha_box)).sum();
539-
const bool is_alpha = alpha_stddev_value > 40;
540-
cur_pokemon_info->alpha = is_alpha;
541-
env.console.log("Alpha detection stddev:" + std::to_string(alpha_stddev_value) + " is alpha:" + std::to_string(is_alpha));
542-
543-
BallReader ball_reader(env.console);
544-
cur_pokemon_info->ball_slug = ball_reader.read_ball(screen);
545-
546-
BoxGenderDetector::make_overlays(box_render);
547-
const StatsHuntGenderFilter gender = BoxGenderDetector::detect(screen);
548-
env.console.log("Gender: " + gender_to_string(gender), COLOR_GREEN);
549-
cur_pokemon_info->gender = gender;
550-
551-
const int ot_id = OCR::read_number_waterfill(env.console, extract_box_reference(screen, ot_id_box), 0xff808080, 0xffffffff);
552-
if (ot_id < 0 || ot_id > 999'999) {
553-
dump_image(env.console, ProgramInfo(), "ReadSummary_OT", screen);
554-
}
555-
cur_pokemon_info->ot_id = ot_id;
556-
557-
env.add_overlay_log(create_overlay_info(*cur_pokemon_info));
558-
559-
// NOTE edit when adding new struct members (detections go here likely)
560-
561-
// level_box
562-
// ot_box
563-
// nature_box
564-
// ability_box
565-
566-
// Press button R to go to next summary screen
567-
pbf_press_button(context, BUTTON_R, 10, 40);
568-
context.wait_for_all_requests();
589+
// Read the summary screen and assign data to boxes_data[global_idx]
590+
read_summary_screen(env, context, boxes_data[global_idx].value());
569591
}
570592
}
571593

572-
box_render.clear();
573-
594+
// log detailed pokemon infomation of this box
595+
std::ostringstream ss;
574596
ss << std::endl;
575-
576-
// print box information
577597
for (size_t row = 0; row < BOX_ROWS; row++){
578598
for (size_t column = 0; column < BOX_COLS; column++){
579-
ss << boxes_data[to_global_index(box_idx, row, column)] << " ";
599+
ss << "[" << row << ", " << column << "]: " << boxes_data[to_global_index(box_idx, row, column)] << std::endl;
580600
}
581-
ss << std::endl;
582601
}
583-
584-
env.console.log(ss.str());
585-
ss.str("");
602+
env.log(ss.str());
586603

587604
//get out of summary with a lot of delay because it's slow for some reasons
588-
pbf_press_button(context, BUTTON_B, 10, VIDEO_DELAY+250);
589-
box_render.clear();
605+
pbf_press_button(context, BUTTON_B, 80ms, 100ms);
590606
context.wait_for_all_requests();
607+
ret = wait_until(env.console, context, Seconds(5), {box_view_watcher});
608+
if (ret != 0){
609+
OperationFailedException::fire(
610+
ErrorReport::SEND_ERROR_REPORT, "HomeBoxSorter(): does not find box view after 5 sec", env.console
611+
);
612+
}
613+
video_overlay_set.clear();
591614
}
592615

593-
box_render.clear();
616+
video_overlay_set.clear();
594617

595618
dest_cursor = {0, 0, 0};
596619
nav_cursor = move_cursor_to(env, context, nav_cursor, dest_cursor, GAME_DELAY);

0 commit comments

Comments
 (0)