Implement cubing alg bound 2 traversal helpers
This commit is contained in:
parent
fe75513126
commit
d76b0cf9b7
3 changed files with 538 additions and 190 deletions
|
|
@ -5,8 +5,76 @@
|
|||
#include "HyperTwistAlgorithm/HyperTwistAlgorithmLibrary.h"
|
||||
#include "HyperTwistAlgorithm/HyperTwistAlgorithmParser.h"
|
||||
#include "HyperTwistAlgorithm/HyperTwistAlgorithmSerializer.h"
|
||||
#include "HyperTwistAlgorithm/HyperTwistAlgorithmTraversal.h"
|
||||
#include "HyperTwistCore/HyperTwistCoreLibrary.h"
|
||||
|
||||
namespace HyperTwistAlgorithmAutomationTestInternal
|
||||
{
|
||||
bool TryParseSequence(
|
||||
FAutomationTestBase& Test,
|
||||
const FString& Input,
|
||||
FHyperTwistAlgorithmSequence& OutSequence)
|
||||
{
|
||||
const FHyperTwistAlgorithmParseResult ParseResult =
|
||||
UHyperTwistAlgorithmParser::ParseAlgorithm(Input);
|
||||
|
||||
Test.TestTrue(FString::Printf(TEXT("Parse succeeded for '%s'"), *Input), ParseResult.bSuccess);
|
||||
if (!ParseResult.bSuccess)
|
||||
{
|
||||
Test.AddError(FString::Printf(
|
||||
TEXT("Parse failed for '%s' at %d: %s"),
|
||||
*Input,
|
||||
ParseResult.ErrorPosition,
|
||||
*ParseResult.ErrorMessage));
|
||||
OutSequence = FHyperTwistAlgorithmSequence();
|
||||
return false;
|
||||
}
|
||||
|
||||
OutSequence = ParseResult.Sequence;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryRoundTripJson(
|
||||
FAutomationTestBase& Test,
|
||||
const FHyperTwistAlgorithmSequence& InputSequence,
|
||||
FHyperTwistAlgorithmSequence& OutSequence,
|
||||
const FString& ContextLabel)
|
||||
{
|
||||
FString JsonText;
|
||||
const bool bSerializeSucceeded =
|
||||
UHyperTwistAlgorithmSerializer::TrySerializeToJson(InputSequence, JsonText);
|
||||
Test.TestTrue(
|
||||
FString::Printf(TEXT("Structured JSON serialization succeeded for %s"), *ContextLabel),
|
||||
bSerializeSucceeded);
|
||||
if (!bSerializeSucceeded)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FString ParseError;
|
||||
const bool bParseSucceeded =
|
||||
UHyperTwistAlgorithmParser::TryParseFromJson(JsonText, OutSequence, ParseError);
|
||||
Test.TestTrue(
|
||||
FString::Printf(TEXT("Structured JSON parse succeeded for %s"), *ContextLabel),
|
||||
bParseSucceeded);
|
||||
if (!bParseSucceeded)
|
||||
{
|
||||
Test.AddError(FString::Printf(
|
||||
TEXT("Structured JSON parse failed for %s: %s"),
|
||||
*ContextLabel,
|
||||
*ParseError));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FString SerializeSequence(const FHyperTwistAlgorithmSequence& Sequence)
|
||||
{
|
||||
return UHyperTwistAlgorithmSerializer::SerializeAlgorithm(Sequence);
|
||||
}
|
||||
}
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FHyperTwistAlgorithmCanonicalizationAutomationTest,
|
||||
"HyperTwist.Algorithm.Bound1.Canonicalization",
|
||||
|
|
@ -22,6 +90,26 @@ IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
|||
"HyperTwist.Algorithm.Bound1.StructuredJson",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FHyperTwistAlgorithmTraversalInvertAutomationTest,
|
||||
"HyperTwist.Algorithm.Bound2.Invert",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FHyperTwistAlgorithmTraversalExpandAutomationTest,
|
||||
"HyperTwist.Algorithm.Bound2.Expand",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FHyperTwistAlgorithmTraversalCoalesceAutomationTest,
|
||||
"HyperTwist.Algorithm.Bound2.Coalesce",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FHyperTwistAlgorithmTraversalStructuredJsonAutomationTest,
|
||||
"HyperTwist.Algorithm.Bound2.StructuredJsonRebaseline",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
|
||||
|
||||
bool FHyperTwistAlgorithmCanonicalizationAutomationTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
(void)Parameters;
|
||||
|
|
@ -177,4 +265,228 @@ bool FHyperTwistAlgorithmStructuredJsonAutomationTest::RunTest(const FString& Pa
|
|||
return true;
|
||||
}
|
||||
|
||||
bool FHyperTwistAlgorithmTraversalInvertAutomationTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
(void)Parameters;
|
||||
|
||||
using namespace HyperTwistAlgorithmAutomationTestInternal;
|
||||
|
||||
struct FInvertCase
|
||||
{
|
||||
const TCHAR* Input;
|
||||
const TCHAR* Expected;
|
||||
};
|
||||
|
||||
const TArray<FInvertCase> Cases = {
|
||||
{ TEXT("R U R' U'"), TEXT("U R U' R'") },
|
||||
{ TEXT("[R, U]2"), TEXT("[U, R]2") },
|
||||
{ TEXT("[R: U]"), TEXT("[R: U']") },
|
||||
{ TEXT("R . U"), TEXT("U' . R'") },
|
||||
{ TEXT("R\nU"), TEXT("U'\nR'") }
|
||||
};
|
||||
|
||||
for (const FInvertCase& Case : Cases)
|
||||
{
|
||||
FHyperTwistAlgorithmSequence InputSequence;
|
||||
if (!TryParseSequence(*this, FString(Case.Input), InputSequence))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const FHyperTwistAlgorithmSequence InvertedSequence =
|
||||
UHyperTwistAlgorithmTraversal::InvertSequence(InputSequence);
|
||||
const FString ActualText = SerializeSequence(InvertedSequence);
|
||||
TestEqual(
|
||||
FString::Printf(TEXT("Inverted canonical text for '%s'"), Case.Input),
|
||||
ActualText,
|
||||
FString(Case.Expected));
|
||||
|
||||
FHyperTwistAlgorithmSequence ExpectedSequence;
|
||||
if (TryParseSequence(*this, FString(Case.Expected), ExpectedSequence))
|
||||
{
|
||||
TestTrue(
|
||||
FString::Printf(TEXT("Inverted structure matches expected for '%s'"), Case.Input),
|
||||
UHyperTwistAlgorithmLibrary::AreAlgorithmsStructurallyEqual(InvertedSequence, ExpectedSequence));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FHyperTwistAlgorithmTraversalExpandAutomationTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
(void)Parameters;
|
||||
|
||||
using namespace HyperTwistAlgorithmAutomationTestInternal;
|
||||
|
||||
struct FExpandCase
|
||||
{
|
||||
const TCHAR* Input;
|
||||
const TCHAR* Expected;
|
||||
};
|
||||
|
||||
const TArray<FExpandCase> Cases = {
|
||||
{ TEXT("(R U)'"), TEXT("U' R'") },
|
||||
{ TEXT("[R, U]2"), TEXT("R U R' U' R U R' U'") },
|
||||
{ TEXT("[R: U]"), TEXT("R U R'") },
|
||||
{ TEXT("R . [U, F]"), TEXT("R . U F U' F'") }
|
||||
};
|
||||
|
||||
for (const FExpandCase& Case : Cases)
|
||||
{
|
||||
FHyperTwistAlgorithmSequence InputSequence;
|
||||
if (!TryParseSequence(*this, FString(Case.Input), InputSequence))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const FHyperTwistAlgorithmSequence ExpandedSequence =
|
||||
UHyperTwistAlgorithmTraversal::ExpandToFlatSequence(InputSequence);
|
||||
const FString ActualText = SerializeSequence(ExpandedSequence);
|
||||
TestEqual(
|
||||
FString::Printf(TEXT("Expanded canonical text for '%s'"), Case.Input),
|
||||
ActualText,
|
||||
FString(Case.Expected));
|
||||
|
||||
FHyperTwistAlgorithmSequence ExpectedSequence;
|
||||
if (TryParseSequence(*this, FString(Case.Expected), ExpectedSequence))
|
||||
{
|
||||
TestTrue(
|
||||
FString::Printf(TEXT("Expanded structure matches expected for '%s'"), Case.Input),
|
||||
UHyperTwistAlgorithmLibrary::AreAlgorithmsStructurallyEqual(ExpandedSequence, ExpectedSequence));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FHyperTwistAlgorithmTraversalCoalesceAutomationTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
(void)Parameters;
|
||||
|
||||
using namespace HyperTwistAlgorithmAutomationTestInternal;
|
||||
|
||||
struct FCoalesceCase
|
||||
{
|
||||
const TCHAR* Input;
|
||||
const TCHAR* Expected;
|
||||
};
|
||||
|
||||
const TArray<FCoalesceCase> Cases = {
|
||||
{ TEXT("R R"), TEXT("R2") },
|
||||
{ TEXT("R2 R2"), TEXT("R4") },
|
||||
{ TEXT("R R'"), TEXT("") },
|
||||
{ TEXT("R . R"), TEXT("R . R") },
|
||||
{ TEXT("R (R R') U"), TEXT("R U") }
|
||||
};
|
||||
|
||||
for (const FCoalesceCase& Case : Cases)
|
||||
{
|
||||
FHyperTwistAlgorithmSequence InputSequence;
|
||||
if (!TryParseSequence(*this, FString(Case.Input), InputSequence))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const FHyperTwistAlgorithmSequence CoalescedSequence =
|
||||
UHyperTwistAlgorithmTraversal::ExpandAndCoalesceSequence(InputSequence);
|
||||
const FString ActualText = SerializeSequence(CoalescedSequence);
|
||||
TestEqual(
|
||||
FString::Printf(TEXT("Expanded/coalesced canonical text for '%s'"), Case.Input),
|
||||
ActualText,
|
||||
FString(Case.Expected));
|
||||
|
||||
FHyperTwistAlgorithmSequence ExpectedSequence;
|
||||
if (TryParseSequence(*this, FString(Case.Expected), ExpectedSequence))
|
||||
{
|
||||
TestTrue(
|
||||
FString::Printf(TEXT("Expanded/coalesced structure matches expected for '%s'"), Case.Input),
|
||||
UHyperTwistAlgorithmLibrary::AreAlgorithmsStructurallyEqual(CoalescedSequence, ExpectedSequence));
|
||||
}
|
||||
}
|
||||
|
||||
FHyperTwistAlgorithmSequence IntegerSumSequence;
|
||||
if (TryParseSequence(*this, TEXT("R2 R2"), IntegerSumSequence))
|
||||
{
|
||||
const TArray<FHyperTwistAlgorithmBlockMove> Moves =
|
||||
UHyperTwistAlgorithmTraversal::ExpandAndSimplify(IntegerSumSequence);
|
||||
TestEqual(TEXT("Integer-sum coalescing retains one move"), Moves.Num(), 1);
|
||||
if (Moves.Num() == 1)
|
||||
{
|
||||
TestEqual(TEXT("Integer-sum coalescing preserves amount 4"), Moves[0].Amount, 4);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FHyperTwistAlgorithmTraversalStructuredJsonAutomationTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
(void)Parameters;
|
||||
|
||||
using namespace HyperTwistAlgorithmAutomationTestInternal;
|
||||
|
||||
FHyperTwistAlgorithmSequence TextSequence;
|
||||
if (!TryParseSequence(*this, TEXT("R R . [U, F]"), TextSequence))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FHyperTwistAlgorithmSequence JsonSequence;
|
||||
if (!TryRoundTripJson(*this, TextSequence, JsonSequence, TEXT("input sequence")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FHyperTwistAlgorithmSequence ExpandedFromText =
|
||||
UHyperTwistAlgorithmTraversal::ExpandToFlatSequence(TextSequence);
|
||||
const FHyperTwistAlgorithmSequence ExpandedFromJson =
|
||||
UHyperTwistAlgorithmTraversal::ExpandToFlatSequence(JsonSequence);
|
||||
TestTrue(
|
||||
TEXT("Expanded text-ingested and JSON-ingested sequences match structurally"),
|
||||
UHyperTwistAlgorithmLibrary::AreAlgorithmsStructurallyEqual(ExpandedFromText, ExpandedFromJson));
|
||||
TestEqual(
|
||||
TEXT("Expanded text-ingested and JSON-ingested sequences share canonical text"),
|
||||
SerializeSequence(ExpandedFromText),
|
||||
SerializeSequence(ExpandedFromJson));
|
||||
|
||||
const FHyperTwistAlgorithmSequence CoalescedFromText =
|
||||
UHyperTwistAlgorithmTraversal::ExpandAndCoalesceSequence(TextSequence);
|
||||
const FHyperTwistAlgorithmSequence CoalescedFromJson =
|
||||
UHyperTwistAlgorithmTraversal::ExpandAndCoalesceSequence(JsonSequence);
|
||||
TestTrue(
|
||||
TEXT("Expanded/coalesced text-ingested and JSON-ingested sequences match structurally"),
|
||||
UHyperTwistAlgorithmLibrary::AreAlgorithmsStructurallyEqual(CoalescedFromText, CoalescedFromJson));
|
||||
TestEqual(
|
||||
TEXT("Expanded/coalesced text-ingested and JSON-ingested sequences share canonical text"),
|
||||
SerializeSequence(CoalescedFromText),
|
||||
SerializeSequence(CoalescedFromJson));
|
||||
TestEqual(
|
||||
TEXT("Expanded/coalesced canonical text matches expected output"),
|
||||
SerializeSequence(CoalescedFromText),
|
||||
FString(TEXT("R2 . U F U' F'")));
|
||||
|
||||
FHyperTwistAlgorithmSequence JsonRoundTrippedTransformedSequence;
|
||||
if (!TryRoundTripJson(
|
||||
*this,
|
||||
CoalescedFromText,
|
||||
JsonRoundTrippedTransformedSequence,
|
||||
TEXT("expanded/coalesced sequence")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
TestTrue(
|
||||
TEXT("Structured JSON preserves transformed sequence structure"),
|
||||
UHyperTwistAlgorithmLibrary::AreAlgorithmsStructurallyEqual(
|
||||
CoalescedFromText,
|
||||
JsonRoundTrippedTransformedSequence));
|
||||
TestEqual(
|
||||
TEXT("Structured JSON preserves transformed sequence canonical text"),
|
||||
SerializeSequence(CoalescedFromText),
|
||||
SerializeSequence(JsonRoundTrippedTransformedSequence));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -3,42 +3,30 @@
|
|||
// Source: Behavioral contracts from cubing/alg.js Model A handoff (GPL-3.0-or-later lane)
|
||||
// No source code inspection. Clean-room workflow only.
|
||||
// Date: 2026-05-15
|
||||
// Bound 2 — Traversal and transformation helpers
|
||||
// Bound 2 - Traversal and transformation helpers
|
||||
|
||||
#include "HyperTwistAlgorithm/HyperTwistAlgorithmTraversal.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Internal implementation helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
namespace HyperTwistAlgorithmTraversalInternal
|
||||
{
|
||||
// -----------------------------------------------------------------------
|
||||
// Block-move inversion
|
||||
// Negates Amount: 1→-1, -1→1, 2→-2, -2→2, 3→-3, etc.
|
||||
// All other fields (Family, MoveType, InnerLayer, OuterLayer) are unchanged.
|
||||
// -----------------------------------------------------------------------
|
||||
static FHyperTwistAlgorithmBlockMove InvertBlockMove(
|
||||
const FHyperTwistAlgorithmBlockMove& Move)
|
||||
static FHyperTwistAlgorithmBlockMove InvertBlockMove(const FHyperTwistAlgorithmBlockMove& Move)
|
||||
{
|
||||
FHyperTwistAlgorithmBlockMove Result = Move;
|
||||
Result.Amount = -Move.Amount;
|
||||
return Result;
|
||||
}
|
||||
|
||||
// Forward-declare so InvertNode can call InvertNodeList.
|
||||
static TArray<FHyperTwistAlgorithmNode> InvertNodeList(
|
||||
const TArray<FHyperTwistAlgorithmNode>& Nodes);
|
||||
static FHyperTwistAlgorithmNode MakeBlockMoveNode(const FHyperTwistAlgorithmBlockMove& Move)
|
||||
{
|
||||
FHyperTwistAlgorithmNode Result;
|
||||
Result.NodeType = EHyperTwistAlgorithmNodeType::BlockMove;
|
||||
Result.BlockMove = Move;
|
||||
return Result;
|
||||
}
|
||||
|
||||
static TArray<FHyperTwistAlgorithmNode> InvertNodeList(const TArray<FHyperTwistAlgorithmNode>& Nodes);
|
||||
static TArray<FHyperTwistAlgorithmNode> CoalesceNodeList(const TArray<FHyperTwistAlgorithmNode>& Nodes);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Node inversion
|
||||
//
|
||||
// BlockMove : negate Amount
|
||||
// Group : InvertNodeList(Inner), Amount unchanged
|
||||
// Commutator : [A, B]' = [B, A] — swap operands
|
||||
// Conjugate : [A: B]' = [A: B'] — invert B; A unchanged
|
||||
// Pause / Newline / Comment : pass through
|
||||
// -----------------------------------------------------------------------
|
||||
static FHyperTwistAlgorithmNode InvertNode(const FHyperTwistAlgorithmNode& Node)
|
||||
{
|
||||
FHyperTwistAlgorithmNode Result = Node;
|
||||
|
|
@ -51,18 +39,14 @@ namespace HyperTwistAlgorithmTraversalInternal
|
|||
|
||||
case EHyperTwistAlgorithmNodeType::Group:
|
||||
Result.Group.Inner = InvertNodeList(Node.Group.Inner);
|
||||
// Amount stays the same — the inverse repeats the inverted content
|
||||
// the same number of times, not in a different direction.
|
||||
break;
|
||||
|
||||
case EHyperTwistAlgorithmNodeType::Commutator:
|
||||
// [A, B]' = [B, A]
|
||||
Result.Commutator.A = Node.Commutator.B;
|
||||
Result.Commutator.B = Node.Commutator.A;
|
||||
break;
|
||||
|
||||
case EHyperTwistAlgorithmNodeType::Conjugate:
|
||||
// [A: B]' = [A: B'] — setup A is unchanged; only B is inverted
|
||||
Result.Conjugate.A = Node.Conjugate.A;
|
||||
Result.Conjugate.B = InvertNodeList(Node.Conjugate.B);
|
||||
break;
|
||||
|
|
@ -72,21 +56,13 @@ namespace HyperTwistAlgorithmTraversalInternal
|
|||
case EHyperTwistAlgorithmNodeType::Comment:
|
||||
case EHyperTwistAlgorithmNodeType::Sequence:
|
||||
default:
|
||||
// These node types pass through unchanged.
|
||||
break;
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// InvertNodeList
|
||||
// Reverses the node ordering and inverts each node individually.
|
||||
// This implements the standard group inversion rule:
|
||||
// (A B C)' = C' B' A'
|
||||
// -----------------------------------------------------------------------
|
||||
static TArray<FHyperTwistAlgorithmNode> InvertNodeList(
|
||||
const TArray<FHyperTwistAlgorithmNode>& Nodes)
|
||||
static TArray<FHyperTwistAlgorithmNode> InvertNodeList(const TArray<FHyperTwistAlgorithmNode>& Nodes)
|
||||
{
|
||||
TArray<FHyperTwistAlgorithmNode> Result;
|
||||
Result.Reserve(Nodes.Num());
|
||||
|
|
@ -99,167 +75,105 @@ namespace HyperTwistAlgorithmTraversalInternal
|
|||
return Result;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Flat-move inversion helper used during expansion.
|
||||
// Appends the inverse of each move (in reverse order) to OutMoves.
|
||||
// Does not return a new array — appends directly for efficiency.
|
||||
// -----------------------------------------------------------------------
|
||||
static void AppendInvertedBlockMoves(
|
||||
const TArray<FHyperTwistAlgorithmBlockMove>& Moves,
|
||||
TArray<FHyperTwistAlgorithmBlockMove>& OutMoves)
|
||||
{
|
||||
for (int32 Index = Moves.Num() - 1; Index >= 0; --Index)
|
||||
{
|
||||
OutMoves.Add(InvertBlockMove(Moves[Index]));
|
||||
}
|
||||
}
|
||||
|
||||
static void AppendRepeatedMoves(
|
||||
const TArray<FHyperTwistAlgorithmBlockMove>& Moves,
|
||||
static void AppendRepeatedNodes(
|
||||
const TArray<FHyperTwistAlgorithmNode>& Nodes,
|
||||
const int32 Amount,
|
||||
TArray<FHyperTwistAlgorithmBlockMove>& OutMoves)
|
||||
TArray<FHyperTwistAlgorithmNode>& OutNodes)
|
||||
{
|
||||
const int32 RepeatCount = FMath::Abs(Amount);
|
||||
const bool bShouldInvert = Amount < 0;
|
||||
if (RepeatCount == 0 || Nodes.Num() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Amount < 0)
|
||||
{
|
||||
const TArray<FHyperTwistAlgorithmNode> InvertedNodes = InvertNodeList(Nodes);
|
||||
for (int32 RepeatIndex = 0; RepeatIndex < RepeatCount; ++RepeatIndex)
|
||||
{
|
||||
OutNodes.Append(InvertedNodes);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (int32 RepeatIndex = 0; RepeatIndex < RepeatCount; ++RepeatIndex)
|
||||
{
|
||||
if (bShouldInvert)
|
||||
{
|
||||
AppendInvertedBlockMoves(Moves, OutMoves);
|
||||
}
|
||||
else
|
||||
{
|
||||
OutMoves.Append(Moves);
|
||||
}
|
||||
OutNodes.Append(Nodes);
|
||||
}
|
||||
}
|
||||
|
||||
// Forward declaration for ExpandNodeListToMoves.
|
||||
static void ExpandNodeListToMoves(
|
||||
static void ExpandNodeListToSequence(
|
||||
const TArray<FHyperTwistAlgorithmNode>& Nodes,
|
||||
TArray<FHyperTwistAlgorithmBlockMove>& OutMoves);
|
||||
TArray<FHyperTwistAlgorithmNode>& OutNodes);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// ExpandNodeToMoves
|
||||
//
|
||||
// Recursively expands a single node into a flat BlockMove list.
|
||||
//
|
||||
// BlockMove → itself
|
||||
// Group n → inner expanded then repeated n times
|
||||
// (negative n → inverted inner repeated |n| times)
|
||||
// Commutator → A + B + A⁻¹ + B⁻¹
|
||||
// Conjugate → A + B + A⁻¹
|
||||
// Other → skipped (Pause, Newline, Comment not emitted)
|
||||
// -----------------------------------------------------------------------
|
||||
static void ExpandNodeToMoves(
|
||||
static void ExpandNodeToSequence(
|
||||
const FHyperTwistAlgorithmNode& Node,
|
||||
TArray<FHyperTwistAlgorithmBlockMove>& OutMoves)
|
||||
TArray<FHyperTwistAlgorithmNode>& OutNodes)
|
||||
{
|
||||
switch (Node.NodeType)
|
||||
{
|
||||
case EHyperTwistAlgorithmNodeType::BlockMove:
|
||||
OutMoves.Add(Node.BlockMove);
|
||||
case EHyperTwistAlgorithmNodeType::Pause:
|
||||
case EHyperTwistAlgorithmNodeType::Newline:
|
||||
case EHyperTwistAlgorithmNodeType::Comment:
|
||||
OutNodes.Add(Node);
|
||||
break;
|
||||
|
||||
case EHyperTwistAlgorithmNodeType::Group:
|
||||
{
|
||||
TArray<FHyperTwistAlgorithmBlockMove> InnerMoves;
|
||||
ExpandNodeListToMoves(Node.Group.Inner, InnerMoves);
|
||||
AppendRepeatedMoves(InnerMoves, Node.Group.Amount, OutMoves);
|
||||
TArray<FHyperTwistAlgorithmNode> ExpandedInner;
|
||||
ExpandNodeListToSequence(Node.Group.Inner, ExpandedInner);
|
||||
AppendRepeatedNodes(ExpandedInner, Node.Group.Amount, OutNodes);
|
||||
}
|
||||
break;
|
||||
|
||||
case EHyperTwistAlgorithmNodeType::Commutator:
|
||||
{
|
||||
// [A, B] = A B A⁻¹ B⁻¹
|
||||
TArray<FHyperTwistAlgorithmBlockMove> AMoves;
|
||||
TArray<FHyperTwistAlgorithmBlockMove> BMoves;
|
||||
ExpandNodeListToMoves(Node.Commutator.A, AMoves);
|
||||
ExpandNodeListToMoves(Node.Commutator.B, BMoves);
|
||||
TArray<FHyperTwistAlgorithmNode> ExpandedLeft;
|
||||
TArray<FHyperTwistAlgorithmNode> ExpandedRight;
|
||||
TArray<FHyperTwistAlgorithmNode> ExpandedNodeRun;
|
||||
ExpandNodeListToSequence(Node.Commutator.A, ExpandedLeft);
|
||||
ExpandNodeListToSequence(Node.Commutator.B, ExpandedRight);
|
||||
|
||||
TArray<FHyperTwistAlgorithmBlockMove> ExpandedMoves;
|
||||
ExpandedMoves.Append(AMoves);
|
||||
ExpandedMoves.Append(BMoves);
|
||||
AppendInvertedBlockMoves(AMoves, ExpandedMoves);
|
||||
AppendInvertedBlockMoves(BMoves, ExpandedMoves);
|
||||
AppendRepeatedMoves(ExpandedMoves, Node.Commutator.Amount, OutMoves);
|
||||
ExpandedNodeRun.Append(ExpandedLeft);
|
||||
ExpandedNodeRun.Append(ExpandedRight);
|
||||
ExpandedNodeRun.Append(InvertNodeList(ExpandedLeft));
|
||||
ExpandedNodeRun.Append(InvertNodeList(ExpandedRight));
|
||||
AppendRepeatedNodes(ExpandedNodeRun, Node.Commutator.Amount, OutNodes);
|
||||
}
|
||||
break;
|
||||
|
||||
case EHyperTwistAlgorithmNodeType::Conjugate:
|
||||
{
|
||||
// [A: B] = A B A⁻¹
|
||||
TArray<FHyperTwistAlgorithmBlockMove> AMoves;
|
||||
TArray<FHyperTwistAlgorithmBlockMove> BMoves;
|
||||
ExpandNodeListToMoves(Node.Conjugate.A, AMoves);
|
||||
ExpandNodeListToMoves(Node.Conjugate.B, BMoves);
|
||||
TArray<FHyperTwistAlgorithmNode> ExpandedLeft;
|
||||
TArray<FHyperTwistAlgorithmNode> ExpandedRight;
|
||||
TArray<FHyperTwistAlgorithmNode> ExpandedNodeRun;
|
||||
ExpandNodeListToSequence(Node.Conjugate.A, ExpandedLeft);
|
||||
ExpandNodeListToSequence(Node.Conjugate.B, ExpandedRight);
|
||||
|
||||
TArray<FHyperTwistAlgorithmBlockMove> ExpandedMoves;
|
||||
ExpandedMoves.Append(AMoves);
|
||||
ExpandedMoves.Append(BMoves);
|
||||
AppendInvertedBlockMoves(AMoves, ExpandedMoves);
|
||||
AppendRepeatedMoves(ExpandedMoves, Node.Conjugate.Amount, OutMoves);
|
||||
ExpandedNodeRun.Append(ExpandedLeft);
|
||||
ExpandedNodeRun.Append(ExpandedRight);
|
||||
ExpandedNodeRun.Append(InvertNodeList(ExpandedLeft));
|
||||
AppendRepeatedNodes(ExpandedNodeRun, Node.Conjugate.Amount, OutNodes);
|
||||
}
|
||||
break;
|
||||
|
||||
case EHyperTwistAlgorithmNodeType::Pause:
|
||||
case EHyperTwistAlgorithmNodeType::Newline:
|
||||
case EHyperTwistAlgorithmNodeType::Comment:
|
||||
case EHyperTwistAlgorithmNodeType::Sequence:
|
||||
default:
|
||||
// Structural/annotation nodes are not emitted in the flat output.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// ExpandNodeListToMoves — expand an array of nodes in order
|
||||
// -----------------------------------------------------------------------
|
||||
static void ExpandNodeListToMoves(
|
||||
static void ExpandNodeListToSequence(
|
||||
const TArray<FHyperTwistAlgorithmNode>& Nodes,
|
||||
TArray<FHyperTwistAlgorithmBlockMove>& OutMoves)
|
||||
TArray<FHyperTwistAlgorithmNode>& OutNodes)
|
||||
{
|
||||
for (const FHyperTwistAlgorithmNode& Node : Nodes)
|
||||
{
|
||||
ExpandNodeToMoves(Node, OutMoves);
|
||||
ExpandNodeToSequence(Node, OutNodes);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// NormalizeAmount
|
||||
//
|
||||
// Reduce an arbitrary integer turn amount to the canonical range.
|
||||
// Modulo 4 (since four quarter turns = identity for standard face turns),
|
||||
// then bias toward the shortest representation in [-2, 2]:
|
||||
// 0 → 0 (cancelled)
|
||||
// 1 or -3 → 1
|
||||
// -1 or 3 → -1
|
||||
// 2 or -2 → 2
|
||||
// -----------------------------------------------------------------------
|
||||
static int32 NormalizeAmount(int32 Amount)
|
||||
{
|
||||
// Reduce modulo 4 to range (-3 .. 3).
|
||||
Amount = Amount % 4;
|
||||
|
||||
// Bias to [-2, 2] for canonical shortest-path representation.
|
||||
if (Amount > 2)
|
||||
{
|
||||
Amount -= 4; // 3 → -1
|
||||
}
|
||||
else if (Amount < -2)
|
||||
{
|
||||
Amount += 4; // -3 → 1
|
||||
}
|
||||
|
||||
return Amount;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// MovesAreCoalesceable
|
||||
//
|
||||
// Two moves coalesce if and only if they address the same physical slice:
|
||||
// same Family, same MoveType, same InnerLayer, same OuterLayer.
|
||||
// -----------------------------------------------------------------------
|
||||
static bool MovesAreCoalesceable(
|
||||
const FHyperTwistAlgorithmBlockMove& A,
|
||||
const FHyperTwistAlgorithmBlockMove& B)
|
||||
|
|
@ -271,75 +185,157 @@ namespace HyperTwistAlgorithmTraversalInternal
|
|||
&& A.GetCanonicalOuterLayer() == B.GetCanonicalOuterLayer();
|
||||
}
|
||||
|
||||
} // namespace HyperTwistAlgorithmTraversalInternal
|
||||
static void AppendCoalescedBlockMove(
|
||||
TArray<FHyperTwistAlgorithmNode>& OutNodes,
|
||||
const FHyperTwistAlgorithmBlockMove& Move)
|
||||
{
|
||||
if (Move.Amount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// UHyperTwistAlgorithmTraversal — public API
|
||||
// ---------------------------------------------------------------------------
|
||||
if (OutNodes.Num() > 0
|
||||
&& OutNodes.Last().NodeType == EHyperTwistAlgorithmNodeType::BlockMove
|
||||
&& MovesAreCoalesceable(OutNodes.Last().BlockMove, Move))
|
||||
{
|
||||
const int32 CombinedAmount = OutNodes.Last().BlockMove.Amount + Move.Amount;
|
||||
if (CombinedAmount == 0)
|
||||
{
|
||||
OutNodes.RemoveAt(OutNodes.Num() - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
OutNodes.Last().BlockMove.Amount = CombinedAmount;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
OutNodes.Add(MakeBlockMoveNode(Move));
|
||||
}
|
||||
|
||||
static FHyperTwistAlgorithmNode CoalesceNode(const FHyperTwistAlgorithmNode& Node)
|
||||
{
|
||||
FHyperTwistAlgorithmNode Result = Node;
|
||||
|
||||
switch (Node.NodeType)
|
||||
{
|
||||
case EHyperTwistAlgorithmNodeType::Group:
|
||||
Result.Group.Inner = CoalesceNodeList(Node.Group.Inner);
|
||||
break;
|
||||
|
||||
case EHyperTwistAlgorithmNodeType::Commutator:
|
||||
Result.Commutator.A = CoalesceNodeList(Node.Commutator.A);
|
||||
Result.Commutator.B = CoalesceNodeList(Node.Commutator.B);
|
||||
break;
|
||||
|
||||
case EHyperTwistAlgorithmNodeType::Conjugate:
|
||||
Result.Conjugate.A = CoalesceNodeList(Node.Conjugate.A);
|
||||
Result.Conjugate.B = CoalesceNodeList(Node.Conjugate.B);
|
||||
break;
|
||||
|
||||
case EHyperTwistAlgorithmNodeType::BlockMove:
|
||||
case EHyperTwistAlgorithmNodeType::Pause:
|
||||
case EHyperTwistAlgorithmNodeType::Newline:
|
||||
case EHyperTwistAlgorithmNodeType::Comment:
|
||||
case EHyperTwistAlgorithmNodeType::Sequence:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
static TArray<FHyperTwistAlgorithmNode> CoalesceNodeList(const TArray<FHyperTwistAlgorithmNode>& Nodes)
|
||||
{
|
||||
TArray<FHyperTwistAlgorithmNode> Result;
|
||||
Result.Reserve(Nodes.Num());
|
||||
|
||||
for (const FHyperTwistAlgorithmNode& Node : Nodes)
|
||||
{
|
||||
if (Node.NodeType == EHyperTwistAlgorithmNodeType::BlockMove)
|
||||
{
|
||||
AppendCoalescedBlockMove(Result, Node.BlockMove);
|
||||
continue;
|
||||
}
|
||||
|
||||
Result.Add(CoalesceNode(Node));
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
}
|
||||
|
||||
FHyperTwistAlgorithmSequence UHyperTwistAlgorithmTraversal::InvertSequence(
|
||||
const FHyperTwistAlgorithmSequence& Sequence)
|
||||
{
|
||||
using namespace HyperTwistAlgorithmTraversalInternal;
|
||||
|
||||
FHyperTwistAlgorithmSequence Result;
|
||||
Result.Nodes = InvertNodeList(Sequence.Nodes);
|
||||
Result.Nodes = HyperTwistAlgorithmTraversalInternal::InvertNodeList(Sequence.Nodes);
|
||||
return Result;
|
||||
}
|
||||
|
||||
FHyperTwistAlgorithmSequence UHyperTwistAlgorithmTraversal::ExpandToFlatSequence(
|
||||
const FHyperTwistAlgorithmSequence& Sequence)
|
||||
{
|
||||
FHyperTwistAlgorithmSequence Result;
|
||||
HyperTwistAlgorithmTraversalInternal::ExpandNodeListToSequence(Sequence.Nodes, Result.Nodes);
|
||||
return Result;
|
||||
}
|
||||
|
||||
TArray<FHyperTwistAlgorithmBlockMove> UHyperTwistAlgorithmTraversal::ExpandToFlatMoves(
|
||||
const FHyperTwistAlgorithmSequence& Sequence)
|
||||
{
|
||||
using namespace HyperTwistAlgorithmTraversalInternal;
|
||||
const FHyperTwistAlgorithmSequence ExpandedSequence = ExpandToFlatSequence(Sequence);
|
||||
|
||||
TArray<FHyperTwistAlgorithmBlockMove> Result;
|
||||
ExpandNodeListToMoves(Sequence.Nodes, Result);
|
||||
for (const FHyperTwistAlgorithmNode& Node : ExpandedSequence.Nodes)
|
||||
{
|
||||
if (Node.NodeType == EHyperTwistAlgorithmNodeType::BlockMove)
|
||||
{
|
||||
Result.Add(Node.BlockMove);
|
||||
}
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
TArray<FHyperTwistAlgorithmBlockMove> UHyperTwistAlgorithmTraversal::CoalesceMoves(
|
||||
const TArray<FHyperTwistAlgorithmBlockMove>& Moves)
|
||||
{
|
||||
using namespace HyperTwistAlgorithmTraversalInternal;
|
||||
|
||||
TArray<FHyperTwistAlgorithmBlockMove> Result;
|
||||
Result.Reserve(Moves.Num());
|
||||
TArray<FHyperTwistAlgorithmNode> CoalescedNodes;
|
||||
CoalescedNodes.Reserve(Moves.Num());
|
||||
|
||||
for (const FHyperTwistAlgorithmBlockMove& Move : Moves)
|
||||
{
|
||||
const int32 NormalizedAmount = NormalizeAmount(Move.Amount);
|
||||
if (NormalizedAmount == 0)
|
||||
{
|
||||
// A move with a zero net amount is a no-op; skip it entirely.
|
||||
continue;
|
||||
}
|
||||
HyperTwistAlgorithmTraversalInternal::AppendCoalescedBlockMove(CoalescedNodes, Move);
|
||||
}
|
||||
|
||||
if (Result.Num() > 0 && MovesAreCoalesceable(Result.Last(), Move))
|
||||
TArray<FHyperTwistAlgorithmBlockMove> Result;
|
||||
Result.Reserve(CoalescedNodes.Num());
|
||||
for (const FHyperTwistAlgorithmNode& Node : CoalescedNodes)
|
||||
{
|
||||
if (Node.NodeType == EHyperTwistAlgorithmNodeType::BlockMove)
|
||||
{
|
||||
// Combine with the previous move.
|
||||
const int32 Combined = NormalizeAmount(Result.Last().Amount + Move.Amount);
|
||||
if (Combined == 0)
|
||||
{
|
||||
// They cancel — remove the previous move.
|
||||
Result.RemoveAt(Result.Num() - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Result.Last().Amount = Combined;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Start a new coalescing window with this move (normalized).
|
||||
FHyperTwistAlgorithmBlockMove NormalizedMove = Move;
|
||||
NormalizedMove.Amount = NormalizedAmount;
|
||||
Result.Add(NormalizedMove);
|
||||
Result.Add(Node.BlockMove);
|
||||
}
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
FHyperTwistAlgorithmSequence UHyperTwistAlgorithmTraversal::CoalesceSequence(
|
||||
const FHyperTwistAlgorithmSequence& Sequence)
|
||||
{
|
||||
FHyperTwistAlgorithmSequence Result;
|
||||
Result.Nodes = HyperTwistAlgorithmTraversalInternal::CoalesceNodeList(Sequence.Nodes);
|
||||
return Result;
|
||||
}
|
||||
|
||||
FHyperTwistAlgorithmSequence UHyperTwistAlgorithmTraversal::ExpandAndCoalesceSequence(
|
||||
const FHyperTwistAlgorithmSequence& Sequence)
|
||||
{
|
||||
return CoalesceSequence(ExpandToFlatSequence(Sequence));
|
||||
}
|
||||
|
||||
TArray<FHyperTwistAlgorithmBlockMove> UHyperTwistAlgorithmTraversal::ExpandAndSimplify(
|
||||
const FHyperTwistAlgorithmSequence& Sequence)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -56,6 +56,20 @@ public:
|
|||
const FHyperTwistAlgorithmSequence& Sequence
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// ExpandToFlatSequence
|
||||
//
|
||||
// Recursively expand an algorithm sequence into a flat owned sequence.
|
||||
// Container nodes (Group, Commutator, Conjugate) are replaced by their
|
||||
// expanded unit sequences. Non-container units such as Pause, Newline,
|
||||
// and Comment are preserved in-order so the result can still flow through
|
||||
// the canonical serializer and structured JSON surfaces.
|
||||
// -----------------------------------------------------------------------
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Algorithm|Traversal")
|
||||
static FHyperTwistAlgorithmSequence ExpandToFlatSequence(
|
||||
const FHyperTwistAlgorithmSequence& Sequence
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// ExpandToFlatMoves
|
||||
//
|
||||
|
|
@ -83,7 +97,8 @@ public:
|
|||
// Accept a flat move list and return a simplified list where:
|
||||
// - Adjacent moves with the same Family, MoveType, InnerLayer, and
|
||||
// OuterLayer are combined by summing their Amount values.
|
||||
// - Amounts are normalized modulo 4 to the canonical range [-2, -1, 1, 2].
|
||||
// - Amounts are preserved as integer sums; no modulo-4 normalization
|
||||
// is applied in this owner layer.
|
||||
// - Moves with a resulting amount of 0 are removed (cancelled).
|
||||
//
|
||||
// The function makes a single left-to-right pass; it does not repeat
|
||||
|
|
@ -96,6 +111,31 @@ public:
|
|||
const TArray<FHyperTwistAlgorithmBlockMove>& Moves
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// CoalesceSequence
|
||||
//
|
||||
// Coalesce adjacent block moves across the owned AST while preserving the
|
||||
// surrounding unit stream. Non-move nodes remain in place and break
|
||||
// adjacency. Composite containers are preserved structurally, with their
|
||||
// child unit lists coalesced recursively.
|
||||
// -----------------------------------------------------------------------
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Algorithm|Traversal")
|
||||
static FHyperTwistAlgorithmSequence CoalesceSequence(
|
||||
const FHyperTwistAlgorithmSequence& Sequence
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// ExpandAndCoalesceSequence
|
||||
//
|
||||
// Convenience: ExpandToFlatSequence followed by CoalesceSequence.
|
||||
// Equivalent to:
|
||||
// CoalesceSequence(ExpandToFlatSequence(Sequence))
|
||||
// -----------------------------------------------------------------------
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Algorithm|Traversal")
|
||||
static FHyperTwistAlgorithmSequence ExpandAndCoalesceSequence(
|
||||
const FHyperTwistAlgorithmSequence& Sequence
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// ExpandAndSimplify
|
||||
//
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue