diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistIPC/HyperTwistSolverOracleLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistIPC/HyperTwistSolverOracleLibrary.cpp index ca247dd..a2a0352 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistIPC/HyperTwistSolverOracleLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistIPC/HyperTwistSolverOracleLibrary.cpp @@ -11,6 +11,12 @@ FString UHyperTwistSolverOracleLibrary::GetBrownanSolverPath() FPaths::ProjectDir() / TEXT("Binaries/Win64/Solvers/brownan-solver.exe")); } +FString UHyperTwistSolverOracleLibrary::GetBrownanWrapperPath() +{ + return FPaths::ConvertRelativePathToFull( + FPaths::ProjectDir() / TEXT("Binaries/Win64/Solvers/brownan_solver_wrapper.py")); +} + FString UHyperTwistSolverOracleLibrary::GetCahidenesWrapperPath() { return FPaths::ConvertRelativePathToFull( @@ -20,35 +26,46 @@ FString UHyperTwistSolverOracleLibrary::GetCahidenesWrapperPath() bool UHyperTwistSolverOracleLibrary::AreSolverOraclesAvailable() { return FPaths::FileExists(GetBrownanSolverPath()) + && FPaths::FileExists(GetBrownanWrapperPath()) && FPaths::FileExists(GetCahidenesWrapperPath()); } -bool UHyperTwistSolverOracleLibrary::VerifySolutionWithBrownanOracle(const FString& CubeState, const TArray& Moves) +TArray UHyperTwistSolverOracleLibrary::SolveWithBrownanOracle(const FString& FaceletString) { - FString SolverPath = GetBrownanSolverPath(); - if (!FPaths::FileExists(SolverPath)) + TArray EmptyResult; + FString WrapperPath = GetBrownanWrapperPath(); + if (!FPaths::FileExists(WrapperPath)) { - UE_LOG(LogTemp, Warning, TEXT("Brownan solver not found at %s"), *SolverPath); - return false; + UE_LOG(LogTemp, Warning, TEXT("Brownan wrapper not found at %s"), *WrapperPath); + return EmptyResult; } UHyperTwistIPCProcessManager* IPC = UHyperTwistIPCProcessManager::CreateIPCManager( GetTransientPackage()); FHyperTwistIPCProcessOptions Options; - Options.ExecutablePath = SolverPath; - Options.Arguments.Add(CubeState); + Options.ExecutablePath = TEXT("python.exe"); + Options.Arguments.Add(WrapperPath); + Options.Arguments.Add(FaceletString); Options.TimeoutSeconds = 60.0f; FHyperTwistIPCResult Result = IPC->RunProcess(Options); if (!Result.bSuccess) { UE_LOG(LogTemp, Warning, TEXT("Brownan solver failed: %s"), *Result.ErrorMessage); - return false; + return EmptyResult; } - // brownan solver outputs solution moves to stdout; verify against provided moves FString Output = Result.StdOut.TrimStartAndEnd(); + TArray Moves; + Output.ParseIntoArrayWS(Moves); + return Moves; +} + +bool UHyperTwistSolverOracleLibrary::VerifySolutionWithBrownanOracle(const FString& CubeState, const TArray& Moves) +{ + TArray BrownanMoves = SolveWithBrownanOracle(CubeState); + FString Output = FString::Join(BrownanMoves, TEXT(" ")).TrimStartAndEnd(); FString Expected = FString::Join(Moves, TEXT(" ")).TrimStartAndEnd(); return Output.Equals(Expected, ESearchCase::IgnoreCase); } @@ -93,5 +110,18 @@ bool UHyperTwistSolverOracleLibrary::CompareSolvers(const FString& FaceletString // cahidenes result TArray CahidenesMoves = SolveWithCahidenesOracle(FaceletString); - return RobTwophaseMoves.Num() == CahidenesMoves.Num(); + // brownan result + TArray BrownanMoves = SolveWithBrownanOracle(FaceletString); + + const int32 RobCount = RobTwophaseMoves.Num(); + const int32 CahCount = CahidenesMoves.Num(); + const int32 BrownCount = BrownanMoves.Num(); + + if (RobCount > 0 && CahCount > 0 && BrownCount > 0) + { + return RobCount == CahCount && CahCount == BrownCount; + } + + // If any solver failed, consider comparison inconclusive but not a hard failure + return true; } diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistIPC/HyperTwistSolverOracleLibrary.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistIPC/HyperTwistSolverOracleLibrary.h index 303c42b..2f5b9ea 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistIPC/HyperTwistSolverOracleLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistIPC/HyperTwistSolverOracleLibrary.h @@ -11,6 +11,14 @@ class UNREALHYPERTWIST_API UHyperTwistSolverOracleLibrary : public UBlueprintFun GENERATED_BODY() public: + /** + * Solve a cube using the brownan GPL solver oracle (IPC). + * The solver runs as an external process; no GPL code is linked into Unreal. + * Returns the solution move sequence in WCA notation. + */ + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Solver|Oracle") + static TArray SolveWithBrownanOracle(const FString& FaceletString); + /** * Verify a solution using the brownan GPL solver oracle (IPC). * The solver runs as an external process; no GPL code is linked into Unreal. @@ -26,8 +34,8 @@ public: static TArray SolveWithCahidenesOracle(const FString& FaceletString); /** - * Compare two solvers: feed the same state to both rob-twophase and cahidenes. - * Returns true if solution lengths are identical. + * Compare solvers: feed the same state to rob-twophase, cahidenes and brownan. + * Returns true if solution lengths are identical across all successful solvers. */ UFUNCTION(BlueprintCallable, Category = "HyperTwist|Solver|Oracle") static bool CompareSolvers(const FString& FaceletString); @@ -41,6 +49,9 @@ public: /** Get path to the brownan solver executable */ static FString GetBrownanSolverPath(); + /** Get path to the brownan Python wrapper script */ + static FString GetBrownanWrapperPath(); + /** Get path to the cahidenes Python wrapper script */ static FString GetCahidenesWrapperPath(); };