Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "Inference/PokemonFRLG_TrainerIdReader.h"
#include "Inference/PokemonFRLG_WildEncounterReader.h"
#include "PokemonFRLG_Navigation.h"
#include "Programs/RngManipulation/PokemonFRLG_RngPlanInputs_Tests.h"
#include "Tests/TestUtils.h"

#include "PokemonFRLG_Tests.h"
Expand Down Expand Up @@ -288,6 +289,7 @@ void add_tests(UnitTestDatabase& database){
add_tests_BattleMoveSelectionArrow(database);
add_tests_BagDetector(database);
add_tests_ExitWildBattle(database);
add_tests_RngPlanInputs(database);
}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/* RNG Plan Inputs
*
* From: https://github.com/PokemonAutomation/
*
*/

#include <algorithm>
#include <format>
#include "PokemonFRLG_RngPlanInputs.h"

namespace PokemonAutomation{
namespace NintendoSwitch{
namespace PokemonFRLG{

using namespace Pokemon;


namespace{

std::string trim(const std::string& text){
const size_t first = text.find_first_not_of(' ');
if (first == std::string::npos){
return "";
}
const size_t last = text.find_last_not_of(' ');
return text.substr(first, last - first + 1);
}

std::vector<std::string> split(const std::string& text, char separator){
std::vector<std::string> parts;
size_t start = 0;
while (true){
const size_t next = text.find(separator, start);
parts.push_back(trim(text.substr(start, next == std::string::npos ? std::string::npos : next - start)));
if (next == std::string::npos){
return parts;
}
start = next + 1;
}
}

}


std::optional<uint16_t> parse_trainer_id(const std::string& text){
const std::string digits = trim(text);
if (digits.empty() || digits.size() > 5 || !std::all_of(digits.begin(), digits.end(), [](char ch){ return ch >= '0' && ch <= '9'; })){
return std::nullopt;
}
const int value = std::stoi(digits);
if (value > 0xffff){
return std::nullopt;
}
return uint16_t(value);
}

std::optional<AdvIVs> parse_ivs(const std::string& text){
const std::vector<std::string> parts = split(text, '/');
if (parts.size() != 6){
return std::nullopt;
}
uint8_t values[6];
for (size_t c = 0; c < 6; c++){
const std::string& part = parts[c];
if (part.empty() || part.size() > 2 || !std::all_of(part.begin(), part.end(), [](char ch){ return ch >= '0' && ch <= '9'; })){
return std::nullopt;
}
const int value = std::stoi(part);
if (value > 31){
return std::nullopt;
}
values[c] = uint8_t(value);
}
AdvIVs ivs;
ivs.hp = values[0];
ivs.attack = values[1];
ivs.defense = values[2];
ivs.spatk = values[3];
ivs.spdef = values[4];
ivs.speed = values[5];
return ivs;
}

std::string ivs_to_string(const AdvIVs& ivs){
return std::format("{}/{}/{}/{}/{}/{}", ivs.hp, ivs.attack, ivs.defense, ivs.spatk, ivs.spdef, ivs.speed);
}

std::optional<std::vector<AdvNature>> parse_natures(const std::string& text){
std::vector<AdvNature> natures;
if (trim(text).empty()){
return natures;
}
for (const std::string& name : split(text, ',')){
const AdvNature nature = string_to_nature(name);
if (nature == AdvNature::Any){
return std::nullopt;
}
natures.push_back(nature);
}
return natures;
}


}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* RNG Plan Inputs
*
* From: https://github.com/PokemonAutomation/
*
* The TID, IVs and natures an RNG plan is typed in with, as text.
*
*/

#ifndef PokemonAutomation_PokemonFRLG_RngPlanInputs_H
#define PokemonAutomation_PokemonFRLG_RngPlanInputs_H

#include <cstdint>
#include <optional>
#include <string>
#include <vector>
#include "Pokemon/Pokemon_AdvRng.h"

namespace PokemonAutomation{
namespace NintendoSwitch{
namespace PokemonFRLG{


// A TID or SID typed as a number from 0 to 65535. Nothing for any other text.
std::optional<uint16_t> parse_trainer_id(const std::string& text);

// "HP/Atk/Def/SpA/SpD/Spe", as Ten Lines and the RNG programs list IVs.
// Nothing for text that isn't six numbers from 0 to 31.
std::optional<Pokemon::AdvIVs> parse_ivs(const std::string& text);
std::string ivs_to_string(const Pokemon::AdvIVs& ivs);

// Natures separated by commas ("Modest, Timid"). Empty text is any nature
// (no entries). Nothing for a name that isn't a nature.
std::optional<std::vector<Pokemon::AdvNature>> parse_natures(const std::string& text);


}
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/* RNG Plan Inputs Tests
*
* From: https://github.com/PokemonAutomation/
*
*/

#include <algorithm>
#include <format>
#include <functional>
#include "Common/Cpp/Exceptions.h"
#include "Common/Cpp/Logging/AbstractLogger.h"
#include "Common/Cpp/TestRunners/UnitTest.h"
#include "PokemonFRLG_RngPlanInputs.h"
#include "PokemonFRLG_RngPlanInputs_Tests.h"

namespace PokemonAutomation{
namespace NintendoSwitch{
namespace PokemonFRLG{

using namespace Pokemon;


namespace{

class Test_RngPlanInputs : public UnitTest{
public:
Test_RngPlanInputs(std::string name, std::function<UnitTestResult(Logger&)> body)
: UnitTest("PokemonFRLG::RngPlanInputs - " + std::move(name))
, m_body(std::move(body))
{}
virtual UnitTestResult run(Logger& logger, CancellableScope&) const override{
try{
return m_body(logger);
}catch (Exception& e){
return std::string(e.name()) + ": " + e.message();
}
}

private:
std::function<UnitTestResult(Logger&)> m_body;
};


UnitTestResult test_parse_trainer_id(Logger&){
struct Case{
std::string text;
std::string expected;
};
const Case cases[] = {
{.text = "64694", .expected = "64694"},
{.text = " 0 ", .expected = "0"},
{.text = "65535", .expected = "65535"},
{.text = "65536", .expected = "none"},
{.text = "", .expected = "none"},
{.text = "-1", .expected = "none"},
{.text = "12a", .expected = "none"},
};
for (const Case& item : cases){
const std::optional<uint16_t> sid = parse_trainer_id(item.text);
const std::string got = sid ? std::to_string(*sid) : "none";
if (got != item.expected){
return std::format("SID \"{}\": expected {}, got {}.", item.text, item.expected, got);
}
}
return true;
}

UnitTestResult test_parse_ivs(Logger&){
struct Case{
std::string text;
std::string expected;
};
const Case cases[] = {
{.text = "31/14/7/29/1/27", .expected = "31/14/7/29/1/27"},
{.text = " 0 / 0/0/0/0/31 ", .expected = "0/0/0/0/0/31"},
{.text = "31/0/0", .expected = "none"},
{.text = "32/0/0/0/0/0", .expected = "none"},
{.text = "a/0/0/0/0/0", .expected = "none"},
{.text = "-1/0/0/0/0/0", .expected = "none"},
{.text = "", .expected = "none"},
};
for (const Case& item : cases){
const std::optional<AdvIVs> ivs = parse_ivs(item.text);
const std::string got = ivs ? ivs_to_string(*ivs) : "none";
if (got != item.expected){
return std::format("IVs \"{}\": expected {}, got {}.", item.text, item.expected, got);
}
}
return true;
}

UnitTestResult test_parse_natures(Logger&){
struct Case{
std::string text;
std::string expected;
};
const Case cases[] = {
{.text = "", .expected = "any"},
{.text = " ", .expected = "any"},
{.text = "Modest", .expected = "Modest"},
{.text = "Modest, Timid", .expected = "Modest Timid"},
{.text = "Modset", .expected = "none"},
{.text = "Modest,", .expected = "none"},
};
for (const Case& item : cases){
const std::optional<std::vector<AdvNature>> natures = parse_natures(item.text);
std::string got;
if (!natures){
got = "none";
}else if (natures->empty()){
got = "any";
}else{
for (AdvNature nature : *natures){
got += (got.empty() ? "" : " ") + nature_to_string(nature);
}
}
if (got != item.expected){
return std::format("Natures \"{}\": expected {}, got {}.", item.text, item.expected, got);
}
}
return true;
}

}


void add_tests_RngPlanInputs(UnitTestDatabase& database){
database.add<Test_RngPlanInputs>("TID and SID text", test_parse_trainer_id);
database.add<Test_RngPlanInputs>("IVs text", test_parse_ivs);
database.add<Test_RngPlanInputs>("natures text", test_parse_natures);
}



}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* RNG Plan Inputs Tests
*
* From: https://github.com/PokemonAutomation/
*
*/

#ifndef PokemonAutomation_PokemonFRLG_RngPlanInputs_Tests_H
#define PokemonAutomation_PokemonFRLG_RngPlanInputs_Tests_H

#include "Common/Cpp/TestRunners/UnitTestDatabase.h"

namespace PokemonAutomation{
namespace NintendoSwitch{
namespace PokemonFRLG{


void add_tests_RngPlanInputs(UnitTestDatabase& database);


}
}
}
#endif
4 changes: 4 additions & 0 deletions SerialPrograms/cmake/SourceFiles.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -1809,6 +1809,10 @@ file(GLOB LIBRARY_SOURCES
Source/VideoGames/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngLoopRoutines.h
Source/VideoGames/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp
Source/VideoGames/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.h
Source/VideoGames/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngPlanInputs.cpp
Source/VideoGames/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngPlanInputs.h
Source/VideoGames/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngPlanInputs_Tests.cpp
Source/VideoGames/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngPlanInputs_Tests.h
Source/VideoGames/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngStatsDatabase.cpp
Source/VideoGames/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngStatsDatabase.h
Source/VideoGames/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RoamingLegendaryRng.cpp
Expand Down
Loading