diff --git a/AdventureMap.cpp b/AdventureMap.cpp index f59eead..7a4cc32 100644 --- a/AdventureMap.cpp +++ b/AdventureMap.cpp @@ -7,6 +7,7 @@ #include "AdventurePlayerController.h" #include "Kismet/GameplayStatics.h" #include "Algo/Reverse.h" +#include "Util/IndexPriorityQueue.h" // Sets default values AAdventureMap::AAdventureMap() @@ -85,12 +86,12 @@ AHexTile* AAdventureMap::RandomHex() TArray AAdventureMap::Neighbors(AHexTile* OfHex, bool bFreeOnly = false) { TArray Results; - for (auto& V : UnitVectors) { - int32 I = GridIndex(OfHex->Q + V.Q, OfHex->R + V.R); - if (Grid.IsValidIndex(I)) { - AHexTile* H = Grid[I]; - if (bFreeOnly && !H->bFree) { continue; } - if (H->Distance(OfHex) == 1) { Results.Add(H); } + for (FHexVector NeighborVector : UnitVectors) { + int32 Index = GridIndex(OfHex->Q + NeighborVector.Q, OfHex->R + NeighborVector.R); + if (Grid.IsValidIndex(Index)) { + AHexTile* Hex = Grid[Index]; + if (bFreeOnly && !Hex->bFree) { continue; } + if (Hex->Distance(OfHex) == 1) { Results.Add(Hex); } } } return Results; @@ -120,66 +121,66 @@ TArray AAdventureMap::FreeDiagonals(AHexTile* OfHex) TSet AAdventureMap::BreadthFirstSearch(AHexTile* Start, int32 Radius) { TSet Results; - TArray ToExamine; + TArray Frontier; TSet Processed; Results.Add(Start); - ToExamine.Add(Start); + Frontier.Add(Start); - while (!ToExamine.IsEmpty()) { - AHexTile* Candidate = ToExamine[0]; - Processed.Add(Candidate); - ToExamine.Remove(Candidate); + while (!Frontier.IsEmpty()) { + AHexTile* Current = Frontier[0]; + Processed.Add(Current); + Frontier.Remove(Current); - for (AHexTile* Neighbor : Neighbors(Candidate)) { - if (Neighbor->Distance(Candidate) > 1) { continue; } + for (AHexTile* Neighbor : Neighbors(Current)) { + if (Neighbor->Distance(Current) > 1) { continue; } if (Processed.Contains(Neighbor)) { continue; } if (Neighbor->Distance(Start) > Radius) { continue; } - ToExamine.Add(Neighbor); + Frontier.Add(Neighbor); Results.Add(Neighbor); } } return Results; } +/* // Faulty implementation which uses an actual PriorityQueue TArray AAdventureMap::FindPathAStar(AHexTile* Start, AHexTile* Goal, bool bDiags) { - TArray ToExamine; + TSet Processed; - ToExamine.Add(Start); + TSet ToSearch; + TPriorityQueue Frontier; - while (!ToExamine.IsEmpty()) { - AHexTile* Candidate = ToExamine[0]; - ToExamine.Remove(Candidate); - // find Hex with lower estimatet F-cost - for (AHexTile* t : ToExamine) { - t->FCost = t->GCost + t->HCost; - if (t->FCost < Candidate->FCost || t->FCost == Candidate->FCost && t->HCost < Candidate->HCost) { Candidate = t; } - } + Start->GCost = 0; + Frontier.Push(Start, .0f); + ToSearch.Add(Start); - Processed.Add(Candidate); - // exit - if (Candidate == Goal) { break; } + while (!Frontier.IsEmpty()) { + AHexTile* Current = Frontier.Pop(); + ToSearch.Remove(Current); - // expand frontier & adjust path data - for (AHexTile* Neighbor : Neighbors(Candidate, true)) { - if (!Neighbor->bFree) { continue; } + if (Current == Goal) { break; } + Processed.Add(Current); + + for (AHexTile* Neighbor : Neighbors(Current, true)) { if (Processed.Contains(Neighbor)) { continue; } + bool bInToSearch = ToSearch.Contains(Start); - bool bInToExamine = ToExamine.Contains(Neighbor); - float NewGCost = Candidate->GCost + Neighbor->MoveCost; + int32 NewGCost = Current->GCost + Neighbor->MoveCost; - if (NewGCost < Neighbor->GCost || !bInToExamine) { + if (!bInToSearch || NewGCost < Neighbor->GCost) { Neighbor->GCost = NewGCost; - Neighbor->CameFrom = Candidate; // chain - Neighbor->bDiagMove = false; + Neighbor->CameFrom = Current; - if (!bInToExamine) { + if (!bInToSearch) { Neighbor->HCost = Neighbor->Distance(Goal); - ToExamine.Add(Neighbor); - } } + Frontier.Push(Neighbor, NewGCost + Neighbor->HCost); + ToSearch.Add(Neighbor); + } + } } } + TArray Path; if (!IsValid(Goal->CameFrom)) { return Path; } AHexTile* iPathNode = Goal; @@ -188,10 +189,62 @@ TArray AAdventureMap::FindPathAStar(AHexTile* Start, AHexTile* Goal, iPathNode = iPathNode->CameFrom; } Algo::Reverse(Path); + return Path; +} +*/ - if (bDiags) { // DO NOT USE - Path = ShortcutAStar(Path); +TArray AAdventureMap::FindPathAStar(AHexTile * Start, AHexTile * Goal, bool bDiags) +{ + TArray Frontier; + TSet Processed; + Frontier.Add(Start); + while (!Frontier.IsEmpty()) { + // Pop + AHexTile* Candidate = Frontier[0]; + + // Exit + if (Candidate == Goal) { break; } + + // Find contender with an even lower F-cost + for (AHexTile* Other : Frontier) { + if (Other->FCost < Candidate->FCost + || Other->FCost == Candidate->FCost && Other->HCost < Candidate->HCost) + { Candidate = Other; } + } + + Frontier.Remove(Candidate); + Processed.Add(Candidate); + // Expand frontier, make connections when appropriate + for (AHexTile* Neighbor : Neighbors(Candidate, true)) { + if (Processed.Contains(Neighbor)) { continue; } + + bool bInFrontier = Frontier.Contains(Neighbor); + int32 NewGCost = Candidate->GCost + Neighbor->MoveCost; + + if (NewGCost < Neighbor->GCost || !bInFrontier) { + Neighbor->GCost = NewGCost; + Neighbor->HCost = Neighbor->Distance(Goal); + Neighbor->FCost = Neighbor->GCost + Neighbor->HCost; + Neighbor->CameFrom = Candidate; // chain + if (!bInFrontier) { + Frontier.Add(Neighbor); + } + } + } } + + // Build and return path + TArray Path; + AHexTile* iPathNode = Goal; + while (iPathNode != Start) { + if (!IsValid(iPathNode->CameFrom) || !iPathNode->bFree) { + Path.Empty(); + return Path; + } + Path.Emplace(iPathNode); + iPathNode = iPathNode->CameFrom; + } + Algo::Reverse(Path); return Path; } @@ -246,11 +299,11 @@ TArray AAdventureMap::ShortcutAStar(TArray Path) if (DiagIsReachable(CurrentHex, UnitDiag)) { int32 CanIndex = GridIndex(CurrentHex->Q + UnitDiag.Q, CurrentHex->R + UnitDiag.R); if (Grid.IsValidIndex(CanIndex)) { - AHexTile* Candidate = Grid[CanIndex]; - if (Candidate->bFree) { - Shortcut.Add(Candidate); + AHexTile* Current = Grid[CanIndex]; + if (Current->bFree) { + Shortcut.Add(Current); bDiagAdded = true; - CurrentHex = Candidate; + CurrentHex = Current; NumDiags--; continue; } } } @@ -298,4 +351,5 @@ bool AAdventureMap::DiagIsReachable(AHexTile* InStart, FHexVector InDiagUnitVec) AHexTile* HexA = Grid[IndexA]; AHexTile* HexB = Grid[IndexB]; return (HexA->bFree && HexB->bFree); -} \ No newline at end of file +} + diff --git a/AdventureMap.h b/AdventureMap.h index cb21f85..b7c2685 100644 --- a/AdventureMap.h +++ b/AdventureMap.h @@ -106,3 +106,63 @@ protected: // Called when the game starts or when spawned virtual void BeginPlay() override; }; + + +// only used for an experimental implementation of A* +template +struct TPriorityQueueNode { + InElementType Element; + float Priority; + + TPriorityQueueNode() + { + } + + TPriorityQueueNode(InElementType InElement, float InPriority) + { + Element = InElement; + Priority = InPriority; + } + + bool operator<(const TPriorityQueueNode Other) const + { + return Priority < Other.Priority; + } +}; +template +class TPriorityQueue { +public: + TPriorityQueue() + { + Array.Heapify(); + } + +public: + // Always check if IsEmpty() before Pop-ing! + InElementType Pop() + { + TPriorityQueueNode Node; + Array.HeapPop(Node); + return Node.Element; + } + + TPriorityQueueNode PopNode() + { + TPriorityQueueNode Node; + Array.HeapPop(Node); + return Node; + } + + void Push(InElementType Element, float Priority) + { + Array.HeapPush(TPriorityQueueNode(Element, Priority)); + } + + bool IsEmpty() const + { + return Array.Num() == 0; + } + +public: // make private later on + TArray> Array; +}; \ No newline at end of file diff --git a/AdventurePlayerController.cpp b/AdventurePlayerController.cpp index 338ed0c..07de042 100644 --- a/AdventurePlayerController.cpp +++ b/AdventurePlayerController.cpp @@ -59,7 +59,6 @@ TArray AAdventurePlayerController::Vision(int32 Radius) { TArray Results; TSet Visible; - if (CurrentHex->bDiagMove) { Radius = FMath::FloorToInt(float(Radius) * (2.f / 3.f)); } Visible = MapRef->BreadthFirstSearch(CurrentHex, Radius); for (auto& Hex : Visible) { if (ExploredHexes.Contains(Hex)) { continue; } @@ -71,6 +70,10 @@ TArray AAdventurePlayerController::Vision(int32 Radius) void AAdventurePlayerController::MarkPath(TArray Path) { + if (Path.IsEmpty()) { return; } + else if (!IsValid(Path[0])) { return; } + else if (CurrentHex->Distance(Path[0])>1) { return; } + FHexVector DirA = FHexVector(Path[0]->Q - CurrentHex->Q, Path[0]->R - CurrentHex->R); FHexVector DirB; for (int32 i = 0; i < Path.Num() - 1; i++) diff --git a/HexTile.h b/HexTile.h index de87a83..8679d78 100644 --- a/HexTile.h +++ b/HexTile.h @@ -48,19 +48,15 @@ public: // Pathfinding UPROPERTY(BlueprintReadWrite) - float MoveCost = 10; + int32 MoveCost = 1; + UPROPERTY() + int32 FCost; + UPROPERTY() + int32 GCost = 9999; + UPROPERTY() + int32 HCost; UPROPERTY(BlueprintReadWrite, VisibleInstanceOnly, Category = "Runtime") AHexTile* CameFrom; - UPROPERTY(BlueprintReadWrite, VisibleInstanceOnly, Category = "Runtime") - AHexTile* LeadsTo; - UPROPERTY(BlueprintReadWrite, VisibleInstanceOnly, Category = "Runtime") - bool bDiagMove = false; - UPROPERTY() - float FCost; - UPROPERTY() - float GCost; - UPROPERTY() - float HCost = 9999; // MapObject Placement UPROPERTY(BlueprintReadWrite, VisibleAnywhere) diff --git a/MovementArrow.cpp b/MovementArrow.cpp index 3fd5f45..338ab71 100644 --- a/MovementArrow.cpp +++ b/MovementArrow.cpp @@ -26,60 +26,51 @@ AMovementArrow::AMovementArrow() } void AMovementArrow::SetVariant(FHexVector InVector, FHexVector OutVector) { - int32 InVectorIndex = MapRef->UnitVectors.Find(InVector); int32 OutVectorIndex = MapRef->UnitVectors.Find(OutVector); int32 VariantIndex; - - if (InVectorIndex == 0) - { + if (InVectorIndex == 0) { if (OutVectorIndex == 0) { VariantIndex = 0; } if (OutVectorIndex == 1) { VariantIndex = 1; } if (OutVectorIndex == 2) { VariantIndex = 2; } if (OutVectorIndex == 4) { VariantIndex = 3; } if (OutVectorIndex == 5) { VariantIndex = 4; } } - if (InVectorIndex == 1) - { + if (InVectorIndex == 1) { if (OutVectorIndex == 0) { VariantIndex = 4; } if (OutVectorIndex == 1) { VariantIndex = 0; } if (OutVectorIndex == 2) { VariantIndex = 1; } if (OutVectorIndex == 3) { VariantIndex = 2; } if (OutVectorIndex == 5) { VariantIndex = 3; } } - if (InVectorIndex == 2) - { + if (InVectorIndex == 2) { if (OutVectorIndex == 0) { VariantIndex = 3; } if (OutVectorIndex == 1) { VariantIndex = 4; } if (OutVectorIndex == 2) { VariantIndex = 0; } if (OutVectorIndex == 3) { VariantIndex = 1; } if (OutVectorIndex == 4) { VariantIndex = 2; } } - if (InVectorIndex == 3) - { + if (InVectorIndex == 3) { if (OutVectorIndex == 1) { VariantIndex = 3; } if (OutVectorIndex == 2) { VariantIndex = 4; } if (OutVectorIndex == 3) { VariantIndex = 0; } if (OutVectorIndex == 4) { VariantIndex = 1; } if (OutVectorIndex == 5) { VariantIndex = 2; } } - if (InVectorIndex == 4) - { + if (InVectorIndex == 4) { if (OutVectorIndex == 0) { VariantIndex = 2; } if (OutVectorIndex == 2) { VariantIndex = 3; } if (OutVectorIndex == 3) { VariantIndex = 4; } if (OutVectorIndex == 4) { VariantIndex = 0; } if (OutVectorIndex == 5) { VariantIndex = 1; } } - if (InVectorIndex == 5) - { + if (InVectorIndex == 5) { if (OutVectorIndex == 0) { VariantIndex = 1; } if (OutVectorIndex == 1) { VariantIndex = 2; } if (OutVectorIndex == 3) { VariantIndex = 3; } if (OutVectorIndex == 4) { VariantIndex = 4; } if (OutVectorIndex == 5) { VariantIndex = 0; } } - SceneComponent->SetRelativeRotation(FRotator(0, InVectorIndex * 60.f, 0)); MeshVariants[VariantIndex]->ToggleVisibility(); }