Add HyperTwist Melinda bound 3 core algebra
This commit is contained in:
parent
ba59e59a00
commit
096c1f52ea
4 changed files with 952 additions and 2 deletions
|
|
@ -169,6 +169,27 @@ namespace HyperTwistCoreLibraryInternal
|
|||
return FindMelindaOrientationIndex(ComposedPermutation);
|
||||
}
|
||||
|
||||
int32 InvertMelindaOrientationIndex(const int32 OrientationIndex)
|
||||
{
|
||||
if (!IsValidMelindaOrientationIndex(OrientationIndex))
|
||||
{
|
||||
return INDEX_NONE;
|
||||
}
|
||||
|
||||
const TArray<TArray<int32>>& KnownPermutations = GetMelindaOrientationPermutations();
|
||||
const TArray<int32>& OrientationPermutation = KnownPermutations[OrientationIndex];
|
||||
|
||||
TArray<int32> InversePermutation;
|
||||
InversePermutation.SetNum(4);
|
||||
|
||||
for (int32 AxisIndex = 0; AxisIndex < 4; ++AxisIndex)
|
||||
{
|
||||
InversePermutation[OrientationPermutation[AxisIndex]] = AxisIndex;
|
||||
}
|
||||
|
||||
return FindMelindaOrientationIndex(InversePermutation);
|
||||
}
|
||||
|
||||
int32 CountPermutationInversions(const TArray<int32>& Values)
|
||||
{
|
||||
int32 Inversions = 0;
|
||||
|
|
@ -211,6 +232,34 @@ namespace HyperTwistCoreLibraryInternal
|
|||
&& HasExpectedMelindaSizeVector(Definition.SizeVector);
|
||||
}
|
||||
|
||||
bool DoMelindaDefinitionsMatch(
|
||||
const FHyperTwistPuzzleDefinitionRef& LeftDefinition,
|
||||
const FHyperTwistPuzzleDefinitionRef& RightDefinition
|
||||
)
|
||||
{
|
||||
return LeftDefinition.PuzzleId == RightDefinition.PuzzleId
|
||||
&& LeftDefinition.PuzzleFamily == RightDefinition.PuzzleFamily
|
||||
&& LeftDefinition.Dimension == RightDefinition.Dimension
|
||||
&& LeftDefinition.DefinitionVersion == RightDefinition.DefinitionVersion
|
||||
&& LeftDefinition.NotationProfile == RightDefinition.NotationProfile
|
||||
&& LeftDefinition.SizeVector == RightDefinition.SizeVector
|
||||
&& LeftDefinition.Variant == RightDefinition.Variant;
|
||||
}
|
||||
|
||||
bool IsSupportedMelindaState(const FHyperTwistPuzzleState& State)
|
||||
{
|
||||
return State.IsStructurallyValid()
|
||||
&& State.StateEncoding.EncodingProfile == TEXT("melinda-2x2x2x2-state-v1")
|
||||
&& IsSupportedMelindaDefinition(State.Definition);
|
||||
}
|
||||
|
||||
bool IsSupportedMelindaTransformation(const FHyperTwistTransformation& Transformation)
|
||||
{
|
||||
return Transformation.IsStructurallyValid()
|
||||
&& Transformation.TransformEncoding.EncodingProfile == TEXT("melinda-2x2x2x2-transform-v1")
|
||||
&& IsSupportedMelindaDefinition(Transformation.Definition);
|
||||
}
|
||||
|
||||
int32 GetMelindaHandednessBit(const int32 SignatureId)
|
||||
{
|
||||
int32 WorkingValue = SignatureId & 0xF;
|
||||
|
|
@ -311,6 +360,19 @@ namespace HyperTwistCoreLibraryInternal
|
|||
&& IsZeroOrientation(MelindaState.PieceOrientation);
|
||||
}
|
||||
|
||||
FHyperTwistMelinda2x2x2x2StateEncoding MakeSolvedMelindaStateEncoding()
|
||||
{
|
||||
FHyperTwistMelinda2x2x2x2StateEncoding MelindaState;
|
||||
MelindaState.PositionToPiece.Reserve(16);
|
||||
for (int32 PieceId = 0; PieceId < 16; ++PieceId)
|
||||
{
|
||||
MelindaState.PositionToPiece.Add(PieceId);
|
||||
}
|
||||
|
||||
MelindaState.PieceOrientation.Init(0, 16);
|
||||
return MelindaState;
|
||||
}
|
||||
|
||||
FHyperTwistPuzzleState MakeMelindaPuzzleState(
|
||||
const FHyperTwistPuzzleDefinitionRef& Definition,
|
||||
const FHyperTwistMelinda2x2x2x2StateEncoding& MelindaState
|
||||
|
|
@ -327,6 +389,102 @@ namespace HyperTwistCoreLibraryInternal
|
|||
return State;
|
||||
}
|
||||
|
||||
FHyperTwistTransformation MakeMelindaTransformation(
|
||||
const FHyperTwistPuzzleDefinitionRef& Definition,
|
||||
const FHyperTwistMelinda2x2x2x2TransformEncoding& MelindaTransform,
|
||||
const EHyperTwistTransformKind TransformKind,
|
||||
const FString& Notation,
|
||||
const FString& OriginalNotation
|
||||
)
|
||||
{
|
||||
FHyperTwistTransformation Transformation;
|
||||
Transformation.Definition = Definition;
|
||||
Transformation.TransformKind = TransformKind;
|
||||
Transformation.Notation = Notation;
|
||||
Transformation.TransformEncoding.EncodingProfile = MelindaTransform.EncodingProfile;
|
||||
Transformation.TransformEncoding.PayloadJson = SerializePayload(MelindaTransform);
|
||||
Transformation.bInvertible = true;
|
||||
Transformation.OriginalNotation = OriginalNotation.IsEmpty() ? Notation : OriginalNotation;
|
||||
return Transformation;
|
||||
}
|
||||
|
||||
FHyperTwistTransformation MakeIdentityMelindaTransformation(const FHyperTwistPuzzleDefinitionRef& Definition)
|
||||
{
|
||||
FHyperTwistMelinda2x2x2x2TransformEncoding IdentityTransform;
|
||||
IdentityTransform.MoveId = TEXT("identity");
|
||||
IdentityTransform.PositionPullMap.Reserve(16);
|
||||
for (int32 PositionIndex = 0; PositionIndex < 16; ++PositionIndex)
|
||||
{
|
||||
IdentityTransform.PositionPullMap.Add(PositionIndex);
|
||||
}
|
||||
|
||||
IdentityTransform.OrientationDeltaPerNewPosition.Init(0, 16);
|
||||
return MakeMelindaTransformation(
|
||||
Definition,
|
||||
IdentityTransform,
|
||||
EHyperTwistTransformKind::SingleMove,
|
||||
TEXT("identity"),
|
||||
TEXT("identity")
|
||||
);
|
||||
}
|
||||
|
||||
bool TryDeserializeMelindaState(
|
||||
const FHyperTwistPuzzleState& State,
|
||||
FHyperTwistMelinda2x2x2x2StateEncoding& OutMelindaState,
|
||||
TArray<FString>& OutWarnings
|
||||
)
|
||||
{
|
||||
if (!IsSupportedMelindaState(State))
|
||||
{
|
||||
OutWarnings.Add(TEXT("unsupported-melinda-state"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!DeserializePayload(State.StateEncoding, OutMelindaState) || !OutMelindaState.IsStructurallyValid())
|
||||
{
|
||||
OutWarnings.Add(TEXT("invalid-melinda-state-payload"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryDeserializeMelindaTransform(
|
||||
const FHyperTwistTransformation& Transformation,
|
||||
FHyperTwistMelinda2x2x2x2TransformEncoding& OutMelindaTransform,
|
||||
TArray<FString>& OutWarnings
|
||||
)
|
||||
{
|
||||
if (!IsSupportedMelindaTransformation(Transformation))
|
||||
{
|
||||
OutWarnings.Add(TEXT("unsupported-melinda-transformation"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!DeserializePayload(Transformation.TransformEncoding, OutMelindaTransform)
|
||||
|| !OutMelindaTransform.IsStructurallyValid())
|
||||
{
|
||||
OutWarnings.Add(TEXT("invalid-melinda-transform-payload"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FString MakeMelindaInverseLabel(const FString& BaseLabel)
|
||||
{
|
||||
return FString::Printf(TEXT("inverse(%s)"), BaseLabel.IsEmpty() ? TEXT("transform") : *BaseLabel);
|
||||
}
|
||||
|
||||
FString MakeMelindaCompositionLabel(const FString& FirstLabel, const FString& SecondLabel)
|
||||
{
|
||||
return FString::Printf(
|
||||
TEXT("compose(%s,%s)"),
|
||||
FirstLabel.IsEmpty() ? TEXT("first") : *FirstLabel,
|
||||
SecondLabel.IsEmpty() ? TEXT("second") : *SecondLabel
|
||||
);
|
||||
}
|
||||
|
||||
bool TryApplyMelindaTransform(
|
||||
const FHyperTwistPuzzleState& State,
|
||||
const FHyperTwistTransformation& Transformation,
|
||||
|
|
@ -339,8 +497,7 @@ namespace HyperTwistCoreLibraryInternal
|
|||
return false;
|
||||
}
|
||||
|
||||
if (State.Definition.PuzzleId != TEXT("hypercube/2x2x2x2")
|
||||
|| Transformation.Definition.PuzzleId != State.Definition.PuzzleId)
|
||||
if (!DoMelindaDefinitionsMatch(State.Definition, Transformation.Definition))
|
||||
{
|
||||
Result.Warnings.Add(TEXT("melinda-definition-mismatch"));
|
||||
return true;
|
||||
|
|
@ -722,6 +879,291 @@ FHyperTwistRandomStateGenerationResult UHyperTwistCoreLibrary::GenerateRandomPuz
|
|||
return Result;
|
||||
}
|
||||
|
||||
FHyperTwistTransformationInversionResult UHyperTwistCoreLibrary::InvertTransformation(
|
||||
const FHyperTwistTransformation& Transformation
|
||||
)
|
||||
{
|
||||
FHyperTwistTransformationInversionResult Result;
|
||||
|
||||
if (!Transformation.IsStructurallyValid())
|
||||
{
|
||||
Result.Warnings.Add(TEXT("invalid-transformation"));
|
||||
return Result;
|
||||
}
|
||||
|
||||
FHyperTwistMelinda2x2x2x2TransformEncoding MelindaTransform;
|
||||
if (!HyperTwistCoreLibraryInternal::TryDeserializeMelindaTransform(
|
||||
Transformation,
|
||||
MelindaTransform,
|
||||
Result.Warnings
|
||||
))
|
||||
{
|
||||
return Result;
|
||||
}
|
||||
|
||||
FHyperTwistMelinda2x2x2x2TransformEncoding InverseTransform = MelindaTransform;
|
||||
InverseTransform.MoveId = HyperTwistCoreLibraryInternal::MakeMelindaInverseLabel(MelindaTransform.MoveId);
|
||||
InverseTransform.PositionPullMap.Init(0, 16);
|
||||
InverseTransform.OrientationDeltaPerNewPosition.Init(0, 16);
|
||||
|
||||
for (int32 NewPosition = 0; NewPosition < 16; ++NewPosition)
|
||||
{
|
||||
const int32 OldPosition = MelindaTransform.PositionPullMap[NewPosition];
|
||||
const int32 InverseOrientation =
|
||||
HyperTwistCoreLibraryInternal::InvertMelindaOrientationIndex(
|
||||
MelindaTransform.OrientationDeltaPerNewPosition[NewPosition]
|
||||
);
|
||||
|
||||
if (InverseOrientation == INDEX_NONE)
|
||||
{
|
||||
Result.Warnings.Add(TEXT("melinda-orientation-inverse-failed"));
|
||||
return Result;
|
||||
}
|
||||
|
||||
InverseTransform.PositionPullMap[OldPosition] = NewPosition;
|
||||
InverseTransform.OrientationDeltaPerNewPosition[OldPosition] = InverseOrientation;
|
||||
}
|
||||
|
||||
Result.Transformation = HyperTwistCoreLibraryInternal::MakeMelindaTransformation(
|
||||
Transformation.Definition,
|
||||
InverseTransform,
|
||||
Transformation.TransformKind,
|
||||
HyperTwistCoreLibraryInternal::MakeMelindaInverseLabel(
|
||||
MelindaTransform.MoveId.IsEmpty() ? Transformation.Notation : MelindaTransform.MoveId
|
||||
),
|
||||
HyperTwistCoreLibraryInternal::MakeMelindaInverseLabel(Transformation.Notation)
|
||||
);
|
||||
Result.bInverted = true;
|
||||
Result.bExactInverse = true;
|
||||
return Result;
|
||||
}
|
||||
|
||||
FHyperTwistTransformationCompositionResult UHyperTwistCoreLibrary::ComposeTransformations(
|
||||
const FHyperTwistTransformation& FirstTransformation,
|
||||
const FHyperTwistTransformation& SecondTransformation
|
||||
)
|
||||
{
|
||||
FHyperTwistTransformationCompositionResult Result;
|
||||
|
||||
if (!FirstTransformation.IsStructurallyValid() || !SecondTransformation.IsStructurallyValid())
|
||||
{
|
||||
Result.Warnings.Add(TEXT("invalid-transformation"));
|
||||
return Result;
|
||||
}
|
||||
|
||||
if (!HyperTwistCoreLibraryInternal::DoMelindaDefinitionsMatch(
|
||||
FirstTransformation.Definition,
|
||||
SecondTransformation.Definition
|
||||
))
|
||||
{
|
||||
Result.Warnings.Add(TEXT("melinda-definition-mismatch"));
|
||||
return Result;
|
||||
}
|
||||
|
||||
FHyperTwistMelinda2x2x2x2TransformEncoding FirstMelindaTransform;
|
||||
if (!HyperTwistCoreLibraryInternal::TryDeserializeMelindaTransform(
|
||||
FirstTransformation,
|
||||
FirstMelindaTransform,
|
||||
Result.Warnings
|
||||
))
|
||||
{
|
||||
return Result;
|
||||
}
|
||||
|
||||
FHyperTwistMelinda2x2x2x2TransformEncoding SecondMelindaTransform;
|
||||
if (!HyperTwistCoreLibraryInternal::TryDeserializeMelindaTransform(
|
||||
SecondTransformation,
|
||||
SecondMelindaTransform,
|
||||
Result.Warnings
|
||||
))
|
||||
{
|
||||
return Result;
|
||||
}
|
||||
|
||||
if (FirstMelindaTransform.FrameProfile != SecondMelindaTransform.FrameProfile
|
||||
|| FirstMelindaTransform.TopologyVersion != SecondMelindaTransform.TopologyVersion)
|
||||
{
|
||||
Result.Warnings.Add(TEXT("melinda-frame-or-topology-mismatch"));
|
||||
return Result;
|
||||
}
|
||||
|
||||
FHyperTwistMelinda2x2x2x2TransformEncoding ComposedTransform;
|
||||
ComposedTransform.MoveId = HyperTwistCoreLibraryInternal::MakeMelindaCompositionLabel(
|
||||
FirstMelindaTransform.MoveId,
|
||||
SecondMelindaTransform.MoveId
|
||||
);
|
||||
ComposedTransform.FrameProfile = FirstMelindaTransform.FrameProfile;
|
||||
ComposedTransform.TopologyVersion = FirstMelindaTransform.TopologyVersion;
|
||||
ComposedTransform.PositionPullMap.Init(0, 16);
|
||||
ComposedTransform.OrientationDeltaPerNewPosition.Init(0, 16);
|
||||
|
||||
for (int32 NewPosition = 0; NewPosition < 16; ++NewPosition)
|
||||
{
|
||||
const int32 IntermediatePosition = SecondMelindaTransform.PositionPullMap[NewPosition];
|
||||
ComposedTransform.PositionPullMap[NewPosition] = FirstMelindaTransform.PositionPullMap[IntermediatePosition];
|
||||
|
||||
const int32 ComposedOrientation = HyperTwistCoreLibraryInternal::ComposeMelindaOrientationIndices(
|
||||
SecondMelindaTransform.OrientationDeltaPerNewPosition[NewPosition],
|
||||
FirstMelindaTransform.OrientationDeltaPerNewPosition[IntermediatePosition]
|
||||
);
|
||||
|
||||
if (ComposedOrientation == INDEX_NONE)
|
||||
{
|
||||
Result.Warnings.Add(TEXT("melinda-orientation-composition-failed"));
|
||||
return Result;
|
||||
}
|
||||
|
||||
ComposedTransform.OrientationDeltaPerNewPosition[NewPosition] = ComposedOrientation;
|
||||
}
|
||||
|
||||
Result.Transformation = HyperTwistCoreLibraryInternal::MakeMelindaTransformation(
|
||||
FirstTransformation.Definition,
|
||||
ComposedTransform,
|
||||
EHyperTwistTransformKind::Composite,
|
||||
HyperTwistCoreLibraryInternal::MakeMelindaCompositionLabel(
|
||||
FirstMelindaTransform.MoveId.IsEmpty() ? FirstTransformation.Notation : FirstMelindaTransform.MoveId,
|
||||
SecondMelindaTransform.MoveId.IsEmpty() ? SecondTransformation.Notation : SecondMelindaTransform.MoveId
|
||||
),
|
||||
HyperTwistCoreLibraryInternal::MakeMelindaCompositionLabel(
|
||||
FirstTransformation.Notation,
|
||||
SecondTransformation.Notation
|
||||
)
|
||||
);
|
||||
Result.bComposed = true;
|
||||
Result.bExactComposition = true;
|
||||
return Result;
|
||||
}
|
||||
|
||||
FHyperTwistMelindaScramblePacketBuildResult UHyperTwistCoreLibrary::BuildMelindaScramblePacketFromGeneratedState(
|
||||
const FHyperTwistRandomStateGenerationResult& GeneratedStateResult
|
||||
)
|
||||
{
|
||||
FHyperTwistMelindaScramblePacketBuildResult Result;
|
||||
Result.Warnings.Append(GeneratedStateResult.Warnings);
|
||||
|
||||
if (GeneratedStateResult.State.IsStructurallyValid())
|
||||
{
|
||||
const bool bHasUsableValidation =
|
||||
GeneratedStateResult.Validation.bStateSupported
|
||||
|| GeneratedStateResult.Validation.bStructureValid
|
||||
|| GeneratedStateResult.Validation.Errors.Num() > 0
|
||||
|| GeneratedStateResult.Validation.Warnings.Num() > 0;
|
||||
Result.TargetStateValidation = bHasUsableValidation
|
||||
? GeneratedStateResult.Validation
|
||||
: ValidatePuzzleState(GeneratedStateResult.State);
|
||||
}
|
||||
|
||||
if (!GeneratedStateResult.bGenerated)
|
||||
{
|
||||
Result.Warnings.Add(TEXT("generated-state-result-missing-solvable-state"));
|
||||
return Result;
|
||||
}
|
||||
|
||||
FHyperTwistMelinda2x2x2x2StateEncoding GeneratedMelindaState;
|
||||
if (!HyperTwistCoreLibraryInternal::TryDeserializeMelindaState(
|
||||
GeneratedStateResult.State,
|
||||
GeneratedMelindaState,
|
||||
Result.Warnings
|
||||
))
|
||||
{
|
||||
return Result;
|
||||
}
|
||||
|
||||
if (!Result.TargetStateValidation.bIsSolvable)
|
||||
{
|
||||
Result.Warnings.Add(TEXT("generated-state-result-failed-validation"));
|
||||
return Result;
|
||||
}
|
||||
|
||||
Result.Packet.Definition = GeneratedStateResult.State.Definition;
|
||||
Result.Packet.PacketSource = EHyperTwistMelindaScramblePacketSource::GeneratedState;
|
||||
Result.Packet.TargetState = GeneratedStateResult.State;
|
||||
Result.Packet.RandomSeed = GeneratedStateResult.SeedUsed;
|
||||
Result.Packet.bCarriesTargetState = true;
|
||||
Result.Packet.bCarriesExactTransformSequence = false;
|
||||
Result.bBuilt = Result.Packet.IsStructurallyValid();
|
||||
|
||||
if (!Result.bBuilt)
|
||||
{
|
||||
Result.Warnings.Add(TEXT("invalid-melinda-scramble-packet"));
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
FHyperTwistMelindaScramblePacketBuildResult UHyperTwistCoreLibrary::BuildMelindaScramblePacketFromTransformSequence(
|
||||
const FHyperTwistPuzzleDefinitionRef& Definition,
|
||||
const TArray<FHyperTwistTransformation>& TransformSequence
|
||||
)
|
||||
{
|
||||
FHyperTwistMelindaScramblePacketBuildResult Result;
|
||||
|
||||
if (!HyperTwistCoreLibraryInternal::IsSupportedMelindaDefinition(Definition))
|
||||
{
|
||||
Result.Warnings.Add(TEXT("unsupported-scramble-packet-definition"));
|
||||
return Result;
|
||||
}
|
||||
|
||||
FHyperTwistPuzzleState CurrentState = HyperTwistCoreLibraryInternal::MakeMelindaPuzzleState(
|
||||
Definition,
|
||||
HyperTwistCoreLibraryInternal::MakeSolvedMelindaStateEncoding()
|
||||
);
|
||||
FHyperTwistTransformation NetTransform =
|
||||
HyperTwistCoreLibraryInternal::MakeIdentityMelindaTransformation(Definition);
|
||||
|
||||
for (const FHyperTwistTransformation& Transformation : TransformSequence)
|
||||
{
|
||||
if (!HyperTwistCoreLibraryInternal::DoMelindaDefinitionsMatch(Definition, Transformation.Definition))
|
||||
{
|
||||
Result.Warnings.Add(TEXT("melinda-definition-mismatch"));
|
||||
return Result;
|
||||
}
|
||||
|
||||
const FHyperTwistApplyTransformationResult ApplyResult = ApplyTransformation(CurrentState, Transformation);
|
||||
Result.Warnings.Append(ApplyResult.Warnings);
|
||||
if (!ApplyResult.bApplied || !ApplyResult.bExactStateUpdate)
|
||||
{
|
||||
Result.Warnings.Add(TEXT("melinda-transform-sequence-apply-failed"));
|
||||
return Result;
|
||||
}
|
||||
|
||||
const FHyperTwistTransformationCompositionResult CompositionResult =
|
||||
ComposeTransformations(NetTransform, Transformation);
|
||||
Result.Warnings.Append(CompositionResult.Warnings);
|
||||
if (!CompositionResult.bComposed || !CompositionResult.bExactComposition)
|
||||
{
|
||||
Result.Warnings.Add(TEXT("melinda-transform-sequence-compose-failed"));
|
||||
return Result;
|
||||
}
|
||||
|
||||
CurrentState = ApplyResult.State;
|
||||
NetTransform = CompositionResult.Transformation;
|
||||
}
|
||||
|
||||
Result.TargetStateValidation = ValidatePuzzleState(CurrentState);
|
||||
if (!Result.TargetStateValidation.bIsSolvable)
|
||||
{
|
||||
Result.Warnings.Add(TEXT("melinda-transform-sequence-target-state-invalid"));
|
||||
return Result;
|
||||
}
|
||||
|
||||
Result.Packet.Definition = Definition;
|
||||
Result.Packet.PacketSource = EHyperTwistMelindaScramblePacketSource::TransformSequence;
|
||||
Result.Packet.TargetState = CurrentState;
|
||||
Result.Packet.TransformSequence = TransformSequence;
|
||||
Result.Packet.NetTransform = NetTransform;
|
||||
Result.Packet.bCarriesTargetState = true;
|
||||
Result.Packet.bCarriesExactTransformSequence = true;
|
||||
Result.bBuilt = Result.Packet.IsStructurallyValid();
|
||||
|
||||
if (!Result.bBuilt)
|
||||
{
|
||||
Result.Warnings.Add(TEXT("invalid-melinda-scramble-packet"));
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
FHyperTwistApplyTransformationResult UHyperTwistCoreLibrary::ApplyTransformation(const FHyperTwistPuzzleState& State, const FHyperTwistTransformation& Transformation)
|
||||
{
|
||||
FHyperTwistApplyTransformationResult Result;
|
||||
|
|
|
|||
|
|
@ -107,6 +107,60 @@ struct FHyperTwistRandomStateGenerationResult
|
|||
bool bGenerated = false;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTransformationInversionResult
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistTransformation Transformation;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FString> Warnings;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bInverted = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bExactInverse = false;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTransformationCompositionResult
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistTransformation Transformation;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FString> Warnings;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bComposed = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bExactComposition = false;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistMelindaScramblePacketBuildResult
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistMelinda2x2x2x2ScramblePacket Packet;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistPuzzleStateValidationResult TargetStateValidation;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FString> Warnings;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bBuilt = false;
|
||||
};
|
||||
|
||||
UCLASS()
|
||||
class UNREALHYPERTWIST_API UHyperTwistCoreLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
|
|
@ -136,6 +190,26 @@ public:
|
|||
int32 RandomSeed
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Core")
|
||||
static FHyperTwistTransformationInversionResult InvertTransformation(const FHyperTwistTransformation& Transformation);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Core")
|
||||
static FHyperTwistTransformationCompositionResult ComposeTransformations(
|
||||
const FHyperTwistTransformation& FirstTransformation,
|
||||
const FHyperTwistTransformation& SecondTransformation
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Scramble")
|
||||
static FHyperTwistMelindaScramblePacketBuildResult BuildMelindaScramblePacketFromGeneratedState(
|
||||
const FHyperTwistRandomStateGenerationResult& GeneratedStateResult
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Scramble")
|
||||
static FHyperTwistMelindaScramblePacketBuildResult BuildMelindaScramblePacketFromTransformSequence(
|
||||
const FHyperTwistPuzzleDefinitionRef& Definition,
|
||||
const TArray<FHyperTwistTransformation>& TransformSequence
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Core")
|
||||
static FHyperTwistApplyTransformationResult ApplyTransformation(const FHyperTwistPuzzleState& State, const FHyperTwistTransformation& Transformation);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -41,6 +41,13 @@ enum class EHyperTwistTransformKind : uint8
|
|||
Composite UMETA(DisplayName = "Composite")
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EHyperTwistMelindaScramblePacketSource : uint8
|
||||
{
|
||||
GeneratedState UMETA(DisplayName = "Generated State"),
|
||||
TransformSequence UMETA(DisplayName = "Transform Sequence")
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistPuzzleDefinitionRef
|
||||
{
|
||||
|
|
@ -429,3 +436,88 @@ struct FHyperTwistMelinda2x2x2x2TransformEncoding
|
|||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistMelinda2x2x2x2ScramblePacket
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString PacketProfile = TEXT("melinda-2x2x2x2-scramble-packet-v1");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistPuzzleDefinitionRef Definition;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistMelindaScramblePacketSource PacketSource = EHyperTwistMelindaScramblePacketSource::GeneratedState;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistPuzzleState TargetState;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FHyperTwistTransformation> TransformSequence;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistTransformation NetTransform;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 RandomSeed = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bCarriesTargetState = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bCarriesExactTransformSequence = false;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
if (PacketProfile.IsEmpty() || !Definition.IsStructurallyValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bCarriesTargetState && !bCarriesExactTransformSequence)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bCarriesTargetState)
|
||||
{
|
||||
if (!TargetState.IsStructurallyValid()
|
||||
|| TargetState.Definition.PuzzleId != Definition.PuzzleId
|
||||
|| TargetState.Definition.PuzzleFamily != Definition.PuzzleFamily
|
||||
|| TargetState.Definition.Dimension != Definition.Dimension)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (bCarriesExactTransformSequence)
|
||||
{
|
||||
if (!NetTransform.IsStructurallyValid()
|
||||
|| NetTransform.Definition.PuzzleId != Definition.PuzzleId
|
||||
|| NetTransform.Definition.PuzzleFamily != Definition.PuzzleFamily
|
||||
|| NetTransform.Definition.Dimension != Definition.Dimension)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const FHyperTwistTransformation& Transformation : TransformSequence)
|
||||
{
|
||||
if (!Transformation.IsStructurallyValid()
|
||||
|| Transformation.Definition.PuzzleId != Definition.PuzzleId
|
||||
|| Transformation.Definition.PuzzleFamily != Definition.PuzzleFamily
|
||||
|| Transformation.Definition.Dimension != Definition.Dimension)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (TransformSequence.Num() > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,342 @@
|
|||
// Copyright HyperTwist, Inc. All Rights Reserved.
|
||||
|
||||
#include "Misc/AutomationTest.h"
|
||||
|
||||
#include "HyperTwistBootstrap/HyperTwistContractLibrary.h"
|
||||
#include "HyperTwistCore/HyperTwistCoreLibrary.h"
|
||||
#include "JsonObjectConverter.h"
|
||||
|
||||
#if WITH_AUTOMATION_TESTS
|
||||
|
||||
namespace HyperTwistMelindaBound3CoreTestInternal
|
||||
{
|
||||
template <typename TStruct>
|
||||
FString SerializeStructToJson(const TStruct& Value)
|
||||
{
|
||||
FString Json;
|
||||
FJsonObjectConverter::UStructToJsonObjectString(TStruct::StaticStruct(), &Value, Json, 0, 0);
|
||||
return Json;
|
||||
}
|
||||
|
||||
bool DeserializeTransformEncoding(
|
||||
const FHyperTwistTransformation& Transformation,
|
||||
FHyperTwistMelinda2x2x2x2TransformEncoding& OutEncoding
|
||||
)
|
||||
{
|
||||
return FJsonObjectConverter::JsonObjectStringToUStruct(
|
||||
Transformation.TransformEncoding.PayloadJson,
|
||||
&OutEncoding,
|
||||
0,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
bool HaveMatchingMelindaStatePayload(
|
||||
const FHyperTwistPuzzleState& LeftState,
|
||||
const FHyperTwistPuzzleState& RightState
|
||||
)
|
||||
{
|
||||
return LeftState.Definition.PuzzleId == RightState.Definition.PuzzleId
|
||||
&& LeftState.StateEncoding.EncodingProfile == RightState.StateEncoding.EncodingProfile
|
||||
&& LeftState.StateEncoding.PayloadJson == RightState.StateEncoding.PayloadJson
|
||||
&& LeftState.OrientationFrame.Reference == RightState.OrientationFrame.Reference
|
||||
&& LeftState.bIsSolved == RightState.bIsSolved;
|
||||
}
|
||||
|
||||
FHyperTwistTransformation MakeMelindaTransformation(
|
||||
const FHyperTwistPuzzleDefinitionRef& Definition,
|
||||
const FString& MoveId,
|
||||
const FString& Notation,
|
||||
const TArray<int32>& PositionPullMap,
|
||||
const TArray<int32>& OrientationDeltaPerNewPosition
|
||||
)
|
||||
{
|
||||
FHyperTwistMelinda2x2x2x2TransformEncoding Encoding;
|
||||
Encoding.MoveId = MoveId;
|
||||
Encoding.PositionPullMap = PositionPullMap;
|
||||
Encoding.OrientationDeltaPerNewPosition = OrientationDeltaPerNewPosition;
|
||||
|
||||
FHyperTwistTransformation Transformation;
|
||||
Transformation.Definition = Definition;
|
||||
Transformation.TransformKind = EHyperTwistTransformKind::SingleMove;
|
||||
Transformation.Notation = Notation;
|
||||
Transformation.TransformEncoding.EncodingProfile = Encoding.EncodingProfile;
|
||||
Transformation.TransformEncoding.PayloadJson = SerializeStructToJson(Encoding);
|
||||
Transformation.OriginalNotation = Notation;
|
||||
return Transformation;
|
||||
}
|
||||
|
||||
FHyperTwistTransformation MakeIdentityTransformation(const FHyperTwistPuzzleDefinitionRef& Definition)
|
||||
{
|
||||
TArray<int32> PositionPullMap;
|
||||
PositionPullMap.Reserve(16);
|
||||
for (int32 PositionIndex = 0; PositionIndex < 16; ++PositionIndex)
|
||||
{
|
||||
PositionPullMap.Add(PositionIndex);
|
||||
}
|
||||
|
||||
TArray<int32> OrientationDeltaPerNewPosition;
|
||||
OrientationDeltaPerNewPosition.Init(0, 16);
|
||||
return MakeMelindaTransformation(
|
||||
Definition,
|
||||
TEXT("identity"),
|
||||
TEXT("identity"),
|
||||
PositionPullMap,
|
||||
OrientationDeltaPerNewPosition
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FHyperTwistMelindaBound3MoveAlgebraTest,
|
||||
"HyperTwist.CleanRoom.HactarCE.Bound3.MoveAlgebra",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
|
||||
)
|
||||
|
||||
bool FHyperTwistMelindaBound3MoveAlgebraTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
const FHyperTwistPuzzleDefinitionRef Definition = UHyperTwistContractLibrary::MakeSampleHyperPuzzleDefinition();
|
||||
const FHyperTwistRandomStateGenerationResult GeneratedBaseState =
|
||||
UHyperTwistCoreLibrary::GenerateRandomPuzzleState(Definition, 2026);
|
||||
TestTrue(TEXT("Bound 3 move algebra should start from a solvable generated state."), GeneratedBaseState.bGenerated);
|
||||
|
||||
const FHyperTwistPuzzleState BaseState = GeneratedBaseState.State;
|
||||
const FString SerializedBaseState = UHyperTwistContractLibrary::SerializePuzzleStateToJson(BaseState);
|
||||
|
||||
const FHyperTwistTransformation TransformA =
|
||||
HyperTwistMelindaBound3CoreTestInternal::MakeMelindaTransformation(
|
||||
Definition,
|
||||
TEXT("bound3.a"),
|
||||
TEXT("A"),
|
||||
{3, 0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
|
||||
{3, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
);
|
||||
const FHyperTwistTransformation TransformB =
|
||||
HyperTwistMelindaBound3CoreTestInternal::MakeMelindaTransformation(
|
||||
Definition,
|
||||
TEXT("bound3.b"),
|
||||
TEXT("B"),
|
||||
{1, 2, 3, 0, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
|
||||
{4, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
);
|
||||
const FHyperTwistTransformation IdentityTransformation =
|
||||
HyperTwistMelindaBound3CoreTestInternal::MakeIdentityTransformation(Definition);
|
||||
|
||||
const FHyperTwistTransformationInversionResult InverseA =
|
||||
UHyperTwistCoreLibrary::InvertTransformation(TransformA);
|
||||
TestTrue(TEXT("Bound 3 should invert Melinda transforms exactly."), InverseA.bInverted);
|
||||
TestTrue(TEXT("Bound 3 inverse surface should remain exact."), InverseA.bExactInverse);
|
||||
|
||||
const FHyperTwistApplyTransformationResult AppliedA =
|
||||
UHyperTwistCoreLibrary::ApplyTransformation(BaseState, TransformA);
|
||||
TestTrue(TEXT("Transform A should apply exactly."), AppliedA.bApplied && AppliedA.bExactStateUpdate);
|
||||
|
||||
const FHyperTwistApplyTransformationResult RoundTripA =
|
||||
UHyperTwistCoreLibrary::ApplyTransformation(AppliedA.State, InverseA.Transformation);
|
||||
TestTrue(TEXT("Applying a transform then its inverse should apply exactly."), RoundTripA.bApplied && RoundTripA.bExactStateUpdate);
|
||||
TestEqual(
|
||||
TEXT("Applying a transform then its inverse should return the original state."),
|
||||
UHyperTwistContractLibrary::SerializePuzzleStateToJson(RoundTripA.State),
|
||||
SerializedBaseState
|
||||
);
|
||||
|
||||
const FHyperTwistApplyTransformationResult SequentialAB =
|
||||
UHyperTwistCoreLibrary::ApplyTransformation(AppliedA.State, TransformB);
|
||||
TestTrue(TEXT("Sequential Melinda application should remain exact."), SequentialAB.bApplied && SequentialAB.bExactStateUpdate);
|
||||
|
||||
const FHyperTwistTransformationCompositionResult ComposedAB =
|
||||
UHyperTwistCoreLibrary::ComposeTransformations(TransformA, TransformB);
|
||||
TestTrue(TEXT("Bound 3 should compose Melinda transforms exactly."), ComposedAB.bComposed);
|
||||
TestTrue(TEXT("Bound 3 composition surface should remain exact."), ComposedAB.bExactComposition);
|
||||
|
||||
const FHyperTwistApplyTransformationResult AppliedComposedAB =
|
||||
UHyperTwistCoreLibrary::ApplyTransformation(BaseState, ComposedAB.Transformation);
|
||||
TestTrue(TEXT("Applying a composed Melinda transform should remain exact."), AppliedComposedAB.bApplied && AppliedComposedAB.bExactStateUpdate);
|
||||
TestEqual(
|
||||
TEXT("Applying Compose(A, B) once should match sequential application of A then B."),
|
||||
UHyperTwistContractLibrary::SerializePuzzleStateToJson(AppliedComposedAB.State),
|
||||
UHyperTwistContractLibrary::SerializePuzzleStateToJson(SequentialAB.State)
|
||||
);
|
||||
|
||||
const FHyperTwistTransformationCompositionResult IdentityThenA =
|
||||
UHyperTwistCoreLibrary::ComposeTransformations(IdentityTransformation, TransformA);
|
||||
TestTrue(TEXT("Compose(identity, A) should succeed."), IdentityThenA.bComposed && IdentityThenA.bExactComposition);
|
||||
const FHyperTwistApplyTransformationResult AppliedIdentityThenA =
|
||||
UHyperTwistCoreLibrary::ApplyTransformation(BaseState, IdentityThenA.Transformation);
|
||||
TestEqual(
|
||||
TEXT("Compose(identity, A) should preserve exact application behavior."),
|
||||
UHyperTwistContractLibrary::SerializePuzzleStateToJson(AppliedIdentityThenA.State),
|
||||
UHyperTwistContractLibrary::SerializePuzzleStateToJson(AppliedA.State)
|
||||
);
|
||||
|
||||
const FHyperTwistTransformationCompositionResult AThenIdentity =
|
||||
UHyperTwistCoreLibrary::ComposeTransformations(TransformA, IdentityTransformation);
|
||||
TestTrue(TEXT("Compose(A, identity) should succeed."), AThenIdentity.bComposed && AThenIdentity.bExactComposition);
|
||||
const FHyperTwistApplyTransformationResult AppliedAThenIdentity =
|
||||
UHyperTwistCoreLibrary::ApplyTransformation(BaseState, AThenIdentity.Transformation);
|
||||
TestEqual(
|
||||
TEXT("Compose(A, identity) should preserve exact application behavior."),
|
||||
UHyperTwistContractLibrary::SerializePuzzleStateToJson(AppliedAThenIdentity.State),
|
||||
UHyperTwistContractLibrary::SerializePuzzleStateToJson(AppliedA.State)
|
||||
);
|
||||
|
||||
const FHyperTwistTransformationInversionResult InverseB =
|
||||
UHyperTwistCoreLibrary::InvertTransformation(TransformB);
|
||||
TestTrue(TEXT("Transform B should also invert exactly."), InverseB.bInverted && InverseB.bExactInverse);
|
||||
|
||||
const FHyperTwistTransformationInversionResult InverseComposedAB =
|
||||
UHyperTwistCoreLibrary::InvertTransformation(ComposedAB.Transformation);
|
||||
TestTrue(TEXT("Inverse(Compose(A, B)) should succeed."), InverseComposedAB.bInverted && InverseComposedAB.bExactInverse);
|
||||
|
||||
const FHyperTwistTransformationCompositionResult ComposeInverseBInverseA =
|
||||
UHyperTwistCoreLibrary::ComposeTransformations(InverseB.Transformation, InverseA.Transformation);
|
||||
TestTrue(
|
||||
TEXT("Compose(Inverse(B), Inverse(A)) should succeed."),
|
||||
ComposeInverseBInverseA.bComposed && ComposeInverseBInverseA.bExactComposition
|
||||
);
|
||||
|
||||
FHyperTwistMelinda2x2x2x2TransformEncoding InverseComposedEncoding;
|
||||
FHyperTwistMelinda2x2x2x2TransformEncoding ReverseOrderInverseEncoding;
|
||||
TestTrue(
|
||||
TEXT("Inverse(Compose(A, B)) payload should deserialize."),
|
||||
HyperTwistMelindaBound3CoreTestInternal::DeserializeTransformEncoding(
|
||||
InverseComposedAB.Transformation,
|
||||
InverseComposedEncoding
|
||||
)
|
||||
);
|
||||
TestTrue(
|
||||
TEXT("Compose(Inverse(B), Inverse(A)) payload should deserialize."),
|
||||
HyperTwistMelindaBound3CoreTestInternal::DeserializeTransformEncoding(
|
||||
ComposeInverseBInverseA.Transformation,
|
||||
ReverseOrderInverseEncoding
|
||||
)
|
||||
);
|
||||
TestTrue(
|
||||
TEXT("Inverse-of-compose should preserve the exact position pull map."),
|
||||
InverseComposedEncoding.PositionPullMap == ReverseOrderInverseEncoding.PositionPullMap
|
||||
);
|
||||
TestTrue(
|
||||
TEXT("Inverse-of-compose should preserve the exact orientation deltas."),
|
||||
InverseComposedEncoding.OrientationDeltaPerNewPosition
|
||||
== ReverseOrderInverseEncoding.OrientationDeltaPerNewPosition
|
||||
);
|
||||
|
||||
const FHyperTwistApplyTransformationResult InverseComposeRoundTrip =
|
||||
UHyperTwistCoreLibrary::ApplyTransformation(SequentialAB.State, InverseComposedAB.Transformation);
|
||||
const FHyperTwistApplyTransformationResult ReverseOrderRoundTrip =
|
||||
UHyperTwistCoreLibrary::ApplyTransformation(SequentialAB.State, ComposeInverseBInverseA.Transformation);
|
||||
TestEqual(
|
||||
TEXT("Inverse(Compose(A, B)) should return the original state."),
|
||||
UHyperTwistContractLibrary::SerializePuzzleStateToJson(InverseComposeRoundTrip.State),
|
||||
SerializedBaseState
|
||||
);
|
||||
TestEqual(
|
||||
TEXT("Compose(Inverse(B), Inverse(A)) should return the original state."),
|
||||
UHyperTwistContractLibrary::SerializePuzzleStateToJson(ReverseOrderRoundTrip.State),
|
||||
SerializedBaseState
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FHyperTwistMelindaBound3ScramblePacketTest,
|
||||
"HyperTwist.CleanRoom.HactarCE.Bound3.ScramblePacket",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
|
||||
)
|
||||
|
||||
bool FHyperTwistMelindaBound3ScramblePacketTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
const FHyperTwistPuzzleDefinitionRef Definition = UHyperTwistContractLibrary::MakeSampleHyperPuzzleDefinition();
|
||||
const FHyperTwistRandomStateGenerationResult GeneratedStateResult =
|
||||
UHyperTwistCoreLibrary::GenerateRandomPuzzleState(Definition, 77);
|
||||
TestTrue(TEXT("Generated-state scramble packets should start from a solvable state."), GeneratedStateResult.bGenerated);
|
||||
|
||||
const FHyperTwistMelindaScramblePacketBuildResult GeneratedPacketResult =
|
||||
UHyperTwistCoreLibrary::BuildMelindaScramblePacketFromGeneratedState(GeneratedStateResult);
|
||||
TestTrue(TEXT("Bound 3 should build a generated-state scramble packet."), GeneratedPacketResult.bBuilt);
|
||||
TestTrue(TEXT("Generated-state scramble packets should be structurally valid."), GeneratedPacketResult.Packet.IsStructurallyValid());
|
||||
TestTrue(
|
||||
TEXT("Generated-state scramble packets should preserve solvable target-state validation."),
|
||||
GeneratedPacketResult.TargetStateValidation.bIsSolvable
|
||||
);
|
||||
TestTrue(
|
||||
TEXT("Generated-state scramble packets should stay marked as generated-state packets."),
|
||||
GeneratedPacketResult.Packet.PacketSource == EHyperTwistMelindaScramblePacketSource::GeneratedState
|
||||
);
|
||||
TestTrue(TEXT("Generated-state scramble packets should carry a target state."), GeneratedPacketResult.Packet.bCarriesTargetState);
|
||||
TestFalse(
|
||||
TEXT("Generated-state scramble packets should not pretend to carry an exact transform sequence."),
|
||||
GeneratedPacketResult.Packet.bCarriesExactTransformSequence
|
||||
);
|
||||
TestEqual(
|
||||
TEXT("Generated-state scramble packets should preserve the generated target state exactly."),
|
||||
UHyperTwistContractLibrary::SerializePuzzleStateToJson(GeneratedPacketResult.Packet.TargetState),
|
||||
UHyperTwistContractLibrary::SerializePuzzleStateToJson(GeneratedStateResult.State)
|
||||
);
|
||||
TestEqual(
|
||||
TEXT("Generated-state scramble packets should not include a transform sequence."),
|
||||
GeneratedPacketResult.Packet.TransformSequence.Num(),
|
||||
0
|
||||
);
|
||||
|
||||
const FHyperTwistTransformation TransformA =
|
||||
HyperTwistMelindaBound3CoreTestInternal::MakeMelindaTransformation(
|
||||
Definition,
|
||||
TEXT("bound3.packet.a"),
|
||||
TEXT("PA"),
|
||||
{3, 0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
|
||||
{3, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
);
|
||||
const FHyperTwistTransformationInversionResult InverseA =
|
||||
UHyperTwistCoreLibrary::InvertTransformation(TransformA);
|
||||
TestTrue(TEXT("Sequence-backed scramble packets should be able to use exact inverses."), InverseA.bInverted && InverseA.bExactInverse);
|
||||
|
||||
TArray<FHyperTwistTransformation> TransformSequence;
|
||||
TransformSequence.Add(TransformA);
|
||||
TransformSequence.Add(InverseA.Transformation);
|
||||
const FHyperTwistMelindaScramblePacketBuildResult SequencePacketResult =
|
||||
UHyperTwistCoreLibrary::BuildMelindaScramblePacketFromTransformSequence(Definition, TransformSequence);
|
||||
TestTrue(TEXT("Bound 3 should build a transform-sequence scramble packet."), SequencePacketResult.bBuilt);
|
||||
TestTrue(TEXT("Transform-sequence scramble packets should be structurally valid."), SequencePacketResult.Packet.IsStructurallyValid());
|
||||
TestTrue(
|
||||
TEXT("Transform-sequence scramble packets should remain solvable at their target state."),
|
||||
SequencePacketResult.TargetStateValidation.bIsSolvable
|
||||
);
|
||||
TestTrue(
|
||||
TEXT("Transform-sequence scramble packets should stay marked as transform-sequence packets."),
|
||||
SequencePacketResult.Packet.PacketSource == EHyperTwistMelindaScramblePacketSource::TransformSequence
|
||||
);
|
||||
TestTrue(
|
||||
TEXT("Transform-sequence scramble packets should carry an exact transform sequence."),
|
||||
SequencePacketResult.Packet.bCarriesExactTransformSequence
|
||||
);
|
||||
TestEqual(
|
||||
TEXT("Transform-sequence scramble packets should preserve the exact sequence length."),
|
||||
SequencePacketResult.Packet.TransformSequence.Num(),
|
||||
2
|
||||
);
|
||||
|
||||
const FHyperTwistPuzzleState SolvedState = UHyperTwistContractLibrary::MakeSampleHyperPuzzleState();
|
||||
const FHyperTwistApplyTransformationResult AppliedNetTransform =
|
||||
UHyperTwistCoreLibrary::ApplyTransformation(SolvedState, SequencePacketResult.Packet.NetTransform);
|
||||
TestTrue(TEXT("Packet net transform should apply exactly from solved state."), AppliedNetTransform.bApplied && AppliedNetTransform.bExactStateUpdate);
|
||||
TestTrue(
|
||||
TEXT("Applying the packet net transform should reproduce the packet target state."),
|
||||
HyperTwistMelindaBound3CoreTestInternal::HaveMatchingMelindaStatePayload(
|
||||
AppliedNetTransform.State,
|
||||
SequencePacketResult.Packet.TargetState
|
||||
)
|
||||
);
|
||||
TestTrue(
|
||||
TEXT("A transform and its inverse should collapse the packet target state back to solved."),
|
||||
HyperTwistMelindaBound3CoreTestInternal::HaveMatchingMelindaStatePayload(
|
||||
SequencePacketResult.Packet.TargetState,
|
||||
SolvedState
|
||||
)
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // WITH_AUTOMATION_TESTS
|
||||
Loading…
Add table
Reference in a new issue