@@ -93,17 +93,17 @@ std::unique_ptr<StatsTracker> BoxSorting_Descriptor::make_stats() const{
9393
9494BoxSorting::BoxSorting ()
9595 : BOX_NUMBER(
96- " <b>Number of boxes to order :</b>" ,
96+ " <b>Number of Boxes to Sort :</b>" ,
9797 LockMode::LOCK_WHILE_RUNNING,
9898 1 , 1 , MAX_BOXES
9999 )
100100 , VIDEO_DELAY(
101- " <b>Delay of your capture card :</b>" ,
101+ " <b>Capture Card Delay :</b>" ,
102102 LockMode::LOCK_WHILE_RUNNING,
103103 50
104104 )
105105 , GAME_DELAY(
106- " <b>Delay of your Pokemon Home app :</b>" ,
106+ " <b>" + STRING_POKEMON + " Home App Delay :</b>" ,
107107 LockMode::LOCK_WHILE_RUNNING,
108108 30
109109 )
@@ -112,13 +112,13 @@ BoxSorting::BoxSorting()
112112 )
113113 , OUTPUT_FILE(
114114 false ,
115- " <b>Output File:</b><br>JSON file for output of storage boxes ." ,
115+ " <b>Output File:</b><br>JSON file basename to catalogue box info found by the program ." ,
116116 LockMode::LOCK_WHILE_RUNNING,
117117 " box_order" ,
118118 " box_order"
119119 )
120120 , DRY_RUN(
121- " <b>Dry Run:</b><br>Catalogue and make sort plan without executing. (Will output to OUTPUT_FILE and OUTPUT_FILE.sortplan )" ,
121+ " <b>Dry Run:</b><br>Catalogue and make sorting plan without execution. Check output at <output_file>.json and <output_file>-sorted.json )" ,
122122 LockMode::LOCK_WHILE_RUNNING,
123123 false
124124 )
@@ -174,75 +174,84 @@ struct Pokemon{
174174 uint16_t national_dex_number = 0 ;
175175 bool shiny = false ;
176176 bool gmax = false ;
177+ bool alpha = false ;
177178 std::string ball_slug = " " ;
178179 StatsHuntGenderFilter gender = StatsHuntGenderFilter::Genderless;
179- uint32_t ot_id = 0 ;
180+ uint32_t ot_id = 0 ; // original trainer ID
180181};
181182
182183bool operator ==(const Pokemon& lhs, const Pokemon& rhs){
183184 // NOTE edit when adding new struct members
184185 return lhs.national_dex_number == rhs.national_dex_number &&
185186 lhs.shiny == rhs.shiny &&
186187 lhs.gmax == rhs.gmax &&
188+ lhs.alpha == rhs.alpha &&
187189 lhs.ball_slug == rhs.ball_slug &&
188190 lhs.gender == rhs.gender &&
189191 lhs.ot_id == rhs.ot_id ;
190192}
191193
194+ // Sort two pokemon slot. "Smaller" pokemon is placed closer to front.
195+ // If a slot is empty, it is always larger than non-empty slot so all the empty slots are at end after sorting.
196+ // For two pokemon, check each preference rule. If we cannot determine the order based on the first rule, go
197+ // to the second rule, and so on.
198+ // Available rules:
199+ // - National dex number: smaller ID should be at front.
200+ // - Shiny: shiny pokemon should be at front.
201+ // - Gigantamax: Gigantamax pokemon should be at front.
202+ // - Alpha: Alpha pokemon should be at front.
203+ // - Ball slug: pokemon with a ball slug that's smaller in the alphabetical order should be at front.
204+ // - Gender: Male should be at front, then comes female, and genderless is last
205+ // Each rule type also has a "reverse" which if true, reverses above ordering for that rule type.
206+ // If user does not give a preference ruleset, sort by national dex number.
192207bool operator <(const std::optional<Pokemon>& lhs, const std::optional<Pokemon>& rhs){
193- if (!lhs.has_value ()){
208+ if (!lhs.has_value ()){ // lhs is empty
194209 return false ;
195210 }
196- if (!rhs.has_value ()){
211+ if (!rhs.has_value ()){ // lhs not empty but rhs is empty
197212 return true ;
198213 }
199-
214+ // both sides are not empty:
200215 for (const BoxSortingSelection preference : *lhs->preferences ){
201- std::optional<bool > ret{};
202216 switch (preference.sort_type ){
203217 // NOTE edit when adding new struct members
204218 case BoxSortingSortType::NationalDexNo:
205219 if (lhs->national_dex_number != rhs->national_dex_number ){
206- ret = lhs->national_dex_number < rhs->national_dex_number ;
220+ // we use (boolean != reverse) to apply the reverse effect
221+ return (lhs->national_dex_number < rhs->national_dex_number ) != preference.reverse ;
207222 }
208223 break ;
209224 case BoxSortingSortType::Shiny:
210225 if (lhs->shiny != rhs->shiny ){
211- ret = lhs->shiny ;
226+ return lhs->shiny != preference. reverse ;
212227 }
213228 break ;
214229 case BoxSortingSortType::Gigantamax:
215230 if (lhs->gmax != rhs->gmax ){
216- ret = lhs->gmax ;
231+ return lhs->gmax != preference. reverse ;
217232 }
218233 break ;
219- case BoxSortingSortType::Ball_Slug :
220- if (lhs->ball_slug < rhs->ball_slug ){
221- ret = true ;
234+ case BoxSortingSortType::Alpha :
235+ if (lhs->alpha != rhs->alpha ){
236+ return lhs-> gmax != preference. reverse ;
222237 }
223- if (lhs->ball_slug > rhs->ball_slug ){
224- ret = false ;
238+ break ;
239+ case BoxSortingSortType::Ball_Slug:
240+ if (lhs->ball_slug != rhs->ball_slug ){
241+ return (lhs->ball_slug < rhs->ball_slug ) != preference.reverse ;
225242 }
226243 break ;
227244 case BoxSortingSortType::Gender:
228- if (lhs->gender < rhs->gender ){
229- ret = true ;
230- }
231- if (lhs->gender > rhs->gender ){
232- ret = false ;
245+ if (lhs->gender != rhs->gender ){
246+ return (lhs->gender < rhs->gender ) != preference.reverse ;
233247 }
234248 break ;
235- }
236- if (ret.has_value ()){
237- bool value = *ret;
238- if (preference.reverse ){
239- return !value;
240- }else {
241- return value;
242- }
243- }
244- }
249+ default :
250+ throw InternalProgramError (nullptr , PA_CURRENT_FUNCTION, " unknown BoxSortingSortType" );
251+ } // end switch
252+ } // end for preference
245253
254+ // Default to sort by national dex number
246255 return lhs->national_dex_number < rhs->national_dex_number ;
247256}
248257
@@ -254,6 +263,7 @@ std::ostream& operator<<(std::ostream& os, const std::optional<Pokemon>& pokemon
254263 os << " national_dex_number:" << pokemon->national_dex_number << " " ;
255264 os << " shiny:" << (pokemon->shiny ? " true" : " false" ) << " " ;
256265 os << " gmax:" << (pokemon->gmax ? " true" : " false" ) << " " ;
266+ os << " alpha:" << (pokemon->alpha ? " true" : " false" ) << " " ;
257267 os << " ball_slug:" << pokemon->ball_slug << " " ;
258268 os << " gender:" << gender_to_string (pokemon->gender ) << " " ;
259269 os << " ot_id:" << pokemon->ot_id << " " ;
@@ -382,7 +392,7 @@ void print_boxes_data(const std::vector<std::optional<Pokemon>>& boxes_data, Sin
382392 env.console .log (ss.str ());
383393}
384394
385- void output_boxes_data_json (const std::vector<std::optional<Pokemon>>& boxes_data, const std::string& json_path ){
395+ void output_boxes_data_json (const std::vector<std::optional<Pokemon>>& boxes_data, const std::string& json_path_basename ){
386396 JsonArray pokemon_data;
387397 for (size_t poke_nb = 0 ; poke_nb < boxes_data.size (); poke_nb++){
388398 Cursor cursor = get_cursor (poke_nb);
@@ -396,13 +406,14 @@ void output_boxes_data_json(const std::vector<std::optional<Pokemon>>& boxes_dat
396406 pokemon[" national_dex_number" ] = current_pokemon->national_dex_number ;
397407 pokemon[" shiny" ] = current_pokemon->shiny ;
398408 pokemon[" gmax" ] = current_pokemon->gmax ;
409+ pokemon[" alpha" ] = current_pokemon->alpha ;
399410 pokemon[" ball_slug" ] = current_pokemon->ball_slug ;
400411 pokemon[" gender" ] = gender_to_string (current_pokemon->gender );
401412 pokemon[" ot_id" ] = current_pokemon->ot_id ;
402413 }
403414 pokemon_data.push_back (std::move (pokemon));
404415 }
405- pokemon_data.dump (json_path + " .json" );
416+ pokemon_data.dump (json_path_basename + " .json" );
406417}
407418
408419void do_sort (
@@ -485,7 +496,7 @@ void BoxSorting::program(SingleSwitchProgramEnvironment& env, ProControllerConte
485496 ImageFloatBox ot_box (0.492 , 0.719 , 0.165 , 0.049 ); // OT
486497 ImageFloatBox nature_box (0.157 , 0.783 , 0.212 , 0.042 ); // Nature
487498 ImageFloatBox ability_box (0.158 , 0.838 , 0.213 , 0.042 ); // Ability
488- // TODO: add alpha detection
499+ ImageFloatBox alpha_box ( 0.787 , 0.095 , 0.024 , 0.046 ); // Alpha symbol
489500
490501
491502 // vector that will store data for each slot
@@ -631,41 +642,47 @@ void BoxSorting::program(SingleSwitchProgramEnvironment& env, ProControllerConte
631642 // cycle through each summary of the current box and fill pokemon information
632643 for (size_t row = 0 ; row < MAX_ROWS; row++){
633644 for (size_t column = 0 ; column < MAX_COLUMNS; column++){
634- if (!boxes_data[get_index (box_idx, row, column)].has_value ()){
645+ const size_t global_idx = get_index (box_idx, row, column);
646+ if (!boxes_data[global_idx].has_value ()){
635647 continue ;
636648 }
637649
638650 screen = env.console .video ().snapshot ();
639651
640- int national_dex_number = OCR::read_number_waterfill (env.console , extract_box_reference (screen, national_dex_number_box), 0xff808080 , 0xffffffff );
652+ const int national_dex_number = OCR::read_number_waterfill (env.console , extract_box_reference (screen, national_dex_number_box), 0xff808080 , 0xffffffff );
641653 if (national_dex_number <= 0 || national_dex_number > 1025 ) { // Current last pokemon is pecharunt
642654 dump_image (env.console , ProgramInfo (), " ReadSummary_national_dex_number" , screen);
643655 }
644- boxes_data[get_index (box_idx, row, column) ]->national_dex_number = (uint16_t )national_dex_number;
656+ boxes_data[global_idx ]->national_dex_number = (uint16_t )national_dex_number;
645657
646- int shiny_stddev_value = (int )image_stddev (extract_box_reference (screen, shiny_symbol_box)).sum ();
647- bool is_shiny = shiny_stddev_value > 30 ;
648- boxes_data[get_index (box_idx, row, column) ]->shiny = is_shiny;
658+ const int shiny_stddev_value = (int )image_stddev (extract_box_reference (screen, shiny_symbol_box)).sum ();
659+ const bool is_shiny = shiny_stddev_value > 30 ;
660+ boxes_data[global_idx ]->shiny = is_shiny;
649661 env.console .log (" Shiny detection stddev:" + std::to_string (shiny_stddev_value) + " is shiny:" + std::to_string (is_shiny));
650662
651- int gmax_stddev_value = (int )image_stddev (extract_box_reference (screen, gmax_symbol_box)).sum ();
652- bool is_gmax = gmax_stddev_value > 30 ;
653- boxes_data[get_index (box_idx, row, column) ]->gmax = is_gmax;
663+ const int gmax_stddev_value = (int )image_stddev (extract_box_reference (screen, gmax_symbol_box)).sum ();
664+ const bool is_gmax = gmax_stddev_value > 30 ;
665+ boxes_data[global_idx ]->gmax = is_gmax;
654666 env.console .log (" Gmax detection stddev:" + std::to_string (gmax_stddev_value) + " is gmax:" + std::to_string (is_gmax));
655667
668+ const int alpha_stddev_value = (int )image_stddev (extract_box_reference (screen, alpha_box)).sum ();
669+ const bool is_alpha = alpha_stddev_value > 40 ;
670+ boxes_data[global_idx]->alpha = is_alpha;
671+ env.console .log (" Alpha detection stddev:" + std::to_string (alpha_stddev_value) + " is alpha:" + std::to_string (is_alpha));
672+
656673 BallReader ball_reader (env.console );
657- boxes_data[get_index (box_idx, row, column) ]->ball_slug = ball_reader.read_ball (screen);
674+ boxes_data[global_idx ]->ball_slug = ball_reader.read_ball (screen);
658675
659676 BoxGenderDetector::make_overlays (box_render);
660- StatsHuntGenderFilter gender = BoxGenderDetector::detect (screen);
677+ const StatsHuntGenderFilter gender = BoxGenderDetector::detect (screen);
661678 env.console .log (" Gender: " + gender_to_string (gender), COLOR_GREEN);
662- boxes_data[get_index (box_idx, row, column) ]->gender = gender;
679+ boxes_data[global_idx ]->gender = gender;
663680
664- int ot_id = OCR::read_number_waterfill (env.console , extract_box_reference (screen, ot_id_box), 0xff808080 , 0xffffffff );
681+ const int ot_id = OCR::read_number_waterfill (env.console , extract_box_reference (screen, ot_id_box), 0xff808080 , 0xffffffff );
665682 if (ot_id < 0 || ot_id > 999'999 ) {
666683 dump_image (env.console , ProgramInfo (), " ReadSummary_OT" , screen);
667684 }
668- boxes_data[get_index (box_idx, row, column) ]->ot_id = ot_id;
685+ boxes_data[global_idx ]->ot_id = ot_id;
669686
670687 const std::string& species_slug = NATIONAL_DEX_SLUGS ()[national_dex_number-1 ];
671688 const std::string& display_name = get_pokemon_name (species_slug).display_name ();
@@ -719,12 +736,12 @@ void BoxSorting::program(SingleSwitchProgramEnvironment& env, ProControllerConte
719736
720737 env.console .log (" Current boxes data :" );
721738 print_boxes_data (boxes_data, env);
722- const std::string json_path = OUTPUT_FILE;
723- output_boxes_data_json (boxes_data, json_path );
739+ const std::string json_path_basename = OUTPUT_FILE;
740+ output_boxes_data_json (boxes_data, json_path_basename );
724741
725742 env.console .log (" Sorted boxes data :" );
726743 print_boxes_data (boxes_sorted, env);
727- const std::string sorted_path = json_path + " -sorted" ;
744+ const std::string sorted_path = json_path_basename + " -sorted" ;
728745 output_boxes_data_json (boxes_sorted, sorted_path);
729746
730747 if (!DRY_RUN){
0 commit comments