UPD: Move computePath to PiecePathBuilder

This commit is contained in:
sebastian 2026-08-05 18:34:02 +02:00
parent bb75a3400a
commit a3bb54f041
9 changed files with 310 additions and 216 deletions

View File

@ -287,6 +287,11 @@ add_library(GameLib STATIC
game/src/gameLayer/GameInput.cpp
game/src/gameLayer/GameInput.h
game/src/gameLayer/gameController/LudoGameControllerContext.h
game/src/ludo/LudoRules.cpp
game/src/ludo/LudoRules.h
game/src/ludo/gameMode/PiecePathBuilder.cpp
game/src/ludo/gameMode/PiecePathBuilder.h
game/src/ludo/gameMode/PathNode.h
)
target_include_directories(GameLib PUBLIC game/src)
target_link_libraries(GameLib PUBLIC Engine)

View File

@ -236,216 +236,3 @@ void ludo::LudoGameMode::publishEvent(LudoEvent event) {
m_pendingEvents.push_back(event);
m_eventBus.publish(event);
}
namespace {
int stateOrder(ludo::PieceState state) {
switch (state) {
case ludo::PieceState::AtHome: return 0;
case ludo::PieceState::OnTrack: return 1;
case ludo::PieceState::InHomeStretch: return 2;
case ludo::PieceState::Finished: return 3;
}
throw std::invalid_argument("computePath: unbekannter PieceState");
}
}
std::vector<ludo::PathNode> ludo::LudoGameMode::computePath(PlayerColor color, PieceState startingState, int startPosition, PieceState targetState, int targetPosition) {
auto errorContext = [&]() {
return std::format(
"color={}, startState={}, startPosition={}, targetState={}, targetPosition={}, "
"kTrackLength={}, kHomeStretchLength={}",
toString(color),
toString(startingState),
startPosition,
toString(targetState),
targetPosition,
kTrackLength,
ludo::kHomeStretchLength
);
};
if (startingState == targetState && startPosition == targetPosition) {
return {};
}
// --- Sonderfall: Figur wird geschlagen und springt direkt zurück ---
if (targetState == PieceState::AtHome) {
if (startingState != PieceState::OnTrack) {
throw std::invalid_argument(
std::format(
"computePath: targetState==AtHome ist nur von OnTrack aus erlaubt (Capture). {}",
errorContext()
)
);
}
return {
PathNode{startingState, startPosition},
PathNode{targetState, targetPosition}
};
}
// --- Nur Vorwärtsbewegungen sind erlaubt ---
if (stateOrder(targetState) < stateOrder(startingState)) {
throw std::invalid_argument(
std::format(
"computePath: targetState liegt vor startingState (Rueckwaerts nicht unterstuetzt). "
"stateOrder(start)={}, stateOrder(target)={}. {}",
stateOrder(startingState),
stateOrder(targetState),
errorContext()
)
);
}
if (startingState == PieceState::Finished) {
throw std::invalid_argument(
std::format(
"computePath: startingState ist bereits Finished, aber Ziel weicht ab. {}",
errorContext()
)
);
}
if (startingState == PieceState::InHomeStretch &&
targetState == PieceState::InHomeStretch &&
targetPosition < startPosition) {
throw std::invalid_argument(
std::format(
"computePath: targetPosition liegt im HomeStretch vor startPosition "
"(Rueckwaertsbewegung). {}",
errorContext()
)
);
}
// --- Positionsbereiche ---
if (targetState == PieceState::OnTrack &&
(targetPosition < 0 || targetPosition >= kTrackLength)) {
throw std::invalid_argument(
std::format(
"computePath: targetPosition ausserhalb OnTrack-Bereich. "
"Erwartet [0,{}), erhalten {}. {}",
kTrackLength,
targetPosition,
errorContext()
)
);
}
if (targetState == PieceState::InHomeStretch &&
(targetPosition < 0 || targetPosition >= kHomeStretchLength)) {
throw std::invalid_argument(
std::format(
"computePath: targetPosition ausserhalb InHomeStretch-Bereich. "
"Erwartet [0,{}), erhalten {}. {}",
kHomeStretchLength,
targetPosition,
errorContext()
)
);
}
if (targetState == PieceState::Finished &&
targetPosition != kHomeStretchLength - 1) {
throw std::invalid_argument(
std::format(
"computePath: targetPosition fuer Finished ungueltig. "
"Erwartet {}, erhalten {}. {}",
kHomeStretchLength - 1,
targetPosition,
errorContext()
)
);
}
if (startingState == PieceState::OnTrack &&
(startPosition < 0 || startPosition >= kTrackLength)) {
throw std::invalid_argument(
std::format(
"computePath: startPosition ausserhalb OnTrack-Bereich. "
"Erwartet [0,{}), erhalten {}. {}",
kTrackLength,
startPosition,
errorContext()
)
);
}
if (startingState == PieceState::InHomeStretch &&
(startPosition < 0 || startPosition >= kHomeStretchLength)) {
throw std::invalid_argument(
std::format(
"computePath: startPosition ausserhalb InHomeStretch-Bereich. "
"Erwartet [0,{}), erhalten {}. {}",
kHomeStretchLength,
startPosition,
errorContext()
)
);
}
std::vector<PathNode> path;
if (startPosition == targetPosition && startingState == targetState) return path;
PieceState state = startingState;
int position = startPosition;
path.push_back({state, position});
// Defense in depth: falls die Validierung oben eine Kombination übersieht
// (z.B. nach zukünftigen Erweiterungen), lieber laut abstürzen als RAM fressen.
const int maxSteps = kTrackLength + kHomeStretchLength + 4;
int steps = 0;
while (state != targetState || position != targetPosition) {
if (++steps > maxSteps) {
throw std::logic_error(
"computePath: Iterationslimit ueberschritten - unreachable Ziel trotz Validierung");
}
switch (state) {
case PieceState::AtHome:
state = PieceState::OnTrack;
position = getEntryOffset(color);
break;
case PieceState::OnTrack:
if (position == getEntryOffset(color) && (targetState == PieceState::InHomeStretch || targetState == PieceState::Finished)) {
state = PieceState::InHomeStretch;
position = 0;
} else {
position = (position + 1) % kTrackLength;
}
break;
case PieceState::InHomeStretch:
if (targetState == PieceState::Finished && position == kHomeStretchLength - 1) {
state = PieceState::Finished;
} else {
++position;
}
break;
case PieceState::Finished:
break;
}
path.push_back({state, position});
}
return path;
}

View File

@ -43,7 +43,7 @@ namespace ludo {
std::vector<int> getMoveablePieceIndices(PlayerColor color, int diceValue) const;
std::optional<int> wouldCapture(int pieceIndex, int diceValue) const;
[[nodiscard]] static int getEntryOffset(PlayerColor color);
static std::vector<PathNode> computePath(PlayerColor color, PieceState startingState, int startPosition, PieceState targetState, int targetPosition);
void setDiceSource(std::unique_ptr<IDiceSource> diceSource) { m_diceSource = std::move(diceSource); }
private:

View File

@ -0,0 +1,9 @@
//
// Created by sebastian on 05.08.26.
//
#include "LudoRules.h"
int ludo::LudoRules::getEntryOffset(PlayerColor color) {
return static_cast<int>(color) * 10;
}

31
game/src/ludo/LudoRules.h Normal file
View File

@ -0,0 +1,31 @@
//
// Created by sebastian on 05.08.26.
//
#ifndef COLORRACE_LUDORULES_H
#define COLORRACE_LUDORULES_H
#include "LudoTypes.h"
// LudoRules.h
namespace ludo {
class LudoRules {
public:
// Alles hier: reine Funktionen, keine Member, kein Zustand.
// Nimmt GameState als Parameter, verändert nichts, wirft keine Events.
static int getEntryOffset(PlayerColor color);
};
struct Destination {
PieceState state;
int position;
};
} // namespace ludo
#endif //COLORRACE_LUDORULES_H

View File

@ -11,6 +11,7 @@
#include "ecs/standardComponents/MeshComponent.h"
#include "ecs/standardComponents/PickableComponent.h"
#include "ecs/standardComponents/TransformComponent.h"
#include "gameMode/PiecePathBuilder.h"
#include "spdlog/spdlog.h"
@ -41,7 +42,7 @@ void ludo::PiecePresenter::onPieceMoved(const ludo::PieceMovedEvent &event, cons
auto entity = m_pieceEntities[event.pieceIndex];
engine::animation::TransformAnimationComponent animationComponent;
std::vector<PathNode> path = LudoGameMode::computePath(piece.color, event.startState, event.fromPosition, event.targetState, event.toPosition);
std::vector<ludo::PathNode> path = PiecePathBuilder::computePath(piece.color, event.startState, event.fromPosition, event.targetState, event.toPosition);
std::vector<glm::vec3> pathPositions;
for (const auto& node : path) {
pathPositions.push_back(m_layout.getWorldPosition(piece.color, node.state, node.position));

View File

@ -0,0 +1,17 @@
//
// Created by sebastian on 05.08.26.
//
#ifndef COLORRACE_PATHNODE_H
#define COLORRACE_PATHNODE_H
#include "ludo/LudoTypes.h"
struct PathNode {
ludo::PieceState state;
int position;
friend bool operator==(const PathNode& lhs, const PathNode& rhs) {
return lhs.state == rhs.state && lhs.position == rhs.position;
}
};
#endif //COLORRACE_PATHNODE_H

View File

@ -0,0 +1,221 @@
//
// Created by sebastian on 05.08.26.
//
#include "PiecePathBuilder.h"
#include <format>
#include "ludo/LudoGameMode.h"
#include "ludo/LudoRules.h"
namespace {
int stateOrder(ludo::PieceState state) {
switch (state) {
case ludo::PieceState::AtHome: return 0;
case ludo::PieceState::OnTrack: return 1;
case ludo::PieceState::InHomeStretch: return 2;
case ludo::PieceState::Finished: return 3;
}
throw std::invalid_argument("computePath: unbekannter PieceState");
}
}
std::vector<ludo::PathNode> ludo::PiecePathBuilder::computePath(PlayerColor color, PieceState startingState,
int startPosition, PieceState targetState, int targetPosition) {
auto errorContext = [&]() {
return std::format(
"color={}, startState={}, startPosition={}, targetState={}, targetPosition={}, "
"kTrackLength={}, kHomeStretchLength={}",
toString(color),
toString(startingState),
startPosition,
toString(targetState),
targetPosition,
kTrackLength,
ludo::kHomeStretchLength
);
};
if (startingState == targetState && startPosition == targetPosition) {
return {};
}
// --- Sonderfall: Figur wird geschlagen und springt direkt zurück ---
if (targetState == PieceState::AtHome) {
if (startingState != PieceState::OnTrack) {
throw std::invalid_argument(
std::format(
"computePath: targetState==AtHome ist nur von OnTrack aus erlaubt (Capture). {}",
errorContext()
)
);
}
return {
PathNode{startingState, startPosition},
PathNode{targetState, targetPosition}
};
}
// --- Nur Vorwärtsbewegungen sind erlaubt ---
if (stateOrder(targetState) < stateOrder(startingState)) {
throw std::invalid_argument(
std::format(
"computePath: targetState liegt vor startingState (Rueckwaerts nicht unterstuetzt). "
"stateOrder(start)={}, stateOrder(target)={}. {}",
stateOrder(startingState),
stateOrder(targetState),
errorContext()
)
);
}
if (startingState == PieceState::Finished) {
throw std::invalid_argument(
std::format(
"computePath: startingState ist bereits Finished, aber Ziel weicht ab. {}",
errorContext()
)
);
}
if (startingState == PieceState::InHomeStretch &&
targetState == PieceState::InHomeStretch &&
targetPosition < startPosition) {
throw std::invalid_argument(
std::format(
"computePath: targetPosition liegt im HomeStretch vor startPosition "
"(Rueckwaertsbewegung). {}",
errorContext()
)
);
}
// --- Positionsbereiche ---
if (targetState == PieceState::OnTrack &&
(targetPosition < 0 || targetPosition >= kTrackLength)) {
throw std::invalid_argument(
std::format(
"computePath: targetPosition ausserhalb OnTrack-Bereich. "
"Erwartet [0,{}), erhalten {}. {}",
kTrackLength,
targetPosition,
errorContext()
)
);
}
if (targetState == PieceState::InHomeStretch &&
(targetPosition < 0 || targetPosition >= kHomeStretchLength)) {
throw std::invalid_argument(
std::format(
"computePath: targetPosition ausserhalb InHomeStretch-Bereich. "
"Erwartet [0,{}), erhalten {}. {}",
kHomeStretchLength,
targetPosition,
errorContext()
)
);
}
if (targetState == PieceState::Finished &&
targetPosition != kHomeStretchLength - 1) {
throw std::invalid_argument(
std::format(
"computePath: targetPosition fuer Finished ungueltig. "
"Erwartet {}, erhalten {}. {}",
kHomeStretchLength - 1,
targetPosition,
errorContext()
)
);
}
if (startingState == PieceState::OnTrack &&
(startPosition < 0 || startPosition >= kTrackLength)) {
throw std::invalid_argument(
std::format(
"computePath: startPosition ausserhalb OnTrack-Bereich. "
"Erwartet [0,{}), erhalten {}. {}",
kTrackLength,
startPosition,
errorContext()
)
);
}
if (startingState == PieceState::InHomeStretch &&
(startPosition < 0 || startPosition >= kHomeStretchLength)) {
throw std::invalid_argument(
std::format(
"computePath: startPosition ausserhalb InHomeStretch-Bereich. "
"Erwartet [0,{}), erhalten {}. {}",
kHomeStretchLength,
startPosition,
errorContext()
)
);
}
std::vector<PathNode> path;
if (startPosition == targetPosition && startingState == targetState) return path;
PieceState state = startingState;
int position = startPosition;
path.push_back({state, position});
// Defense in depth: falls die Validierung oben eine Kombination übersieht
// (z.B. nach zukünftigen Erweiterungen), lieber laut abstürzen als RAM fressen.
const int maxSteps = kTrackLength + kHomeStretchLength + 4;
int steps = 0;
while (state != targetState || position != targetPosition) {
if (++steps > maxSteps) {
throw std::logic_error(
"computePath: Iterationslimit ueberschritten - unreachable Ziel trotz Validierung");
}
switch (state) {
case PieceState::AtHome:
state = PieceState::OnTrack;
position = LudoRules::getEntryOffset(color);
break;
case PieceState::OnTrack:
if (position == LudoRules::getEntryOffset(color) && (targetState == PieceState::InHomeStretch || targetState == PieceState::Finished)) {
state = PieceState::InHomeStretch;
position = 0;
} else {
position = (position + 1) % kTrackLength;
}
break;
case PieceState::InHomeStretch:
if (targetState == PieceState::Finished && position == kHomeStretchLength - 1) {
state = PieceState::Finished;
} else {
++position;
}
break;
case PieceState::Finished:
break;
}
path.push_back({state, position});
}
return path;
}

View File

@ -0,0 +1,23 @@
//
// Created by sebastian on 05.08.26.
//
#ifndef COLORRACE_PIECEPATHBUILDER_H
#define COLORRACE_PIECEPATHBUILDER_H
#include <vector>
#include "PathNode.h"
#include "ludo/LudoGameMode.h"
#include "ludo/LudoTypes.h"
namespace ludo {
class PiecePathBuilder {
public:
static std::vector<ludo::PathNode> computePath(PlayerColor color, PieceState startingState, int startPosition, PieceState targetState, int targetPosition);
};
}
#endif //COLORRACE_PIECEPATHBUILDER_H