FIX: Correct Handling of Captured Case

This commit is contained in:
sebastian 2026-08-05 14:21:32 +02:00
parent cdc6a1f92c
commit c755d39677
2 changed files with 165 additions and 31 deletions

View File

@ -147,10 +147,19 @@ void ludo::LudoGameMode::handle(const MovePieceCommand &command) {
spdlog::info("MovePieceCommand akzeptiert: pieceIndex={}", command.pieceIndex);
auto result = movePieceInternal(command.pieceIndex, m_gameState.m_diceValue);
publishEvent(PieceMovedEvent{command.pieceIndex, result.fromPosition, result.toPosition, result.captured, result.targetState, result.startState});
publishEvent(PieceMovedEvent::create(command.pieceIndex, result.fromPosition, result.toPosition, result.captured, result.startState, result.targetState));
if (result.captured) {
spdlog::info("MovePieceCommand: gegnerische Figur auf {} geworfen", result.toPosition);
publishEvent(PieceMovedEvent{result.capturedPieceIndex, result.toPosition, -1, false});
auto freeHomePositions = m_gameState.getFreeHomePositions(m_gameState.getColorOfPieceIndex(result.capturedPieceIndex));
if (!freeHomePositions.empty()) {
Piece capturedPiece = m_gameState.m_pieces[result.capturedPieceIndex];
PieceMovedEvent event = PieceMovedEvent::create(result.capturedPieceIndex, capturedPiece.position, freeHomePositions.front(), true, capturedPiece.state, PieceState::AtHome);
publishEvent(event);
} else {
throw std::runtime_error(
std::format("No free home positions for captured piece {}",std::string(ludo::toString(m_gameState.getColorOfPieceIndex(result.capturedPieceIndex))))
);
}
}
finishTurn();
}
@ -238,9 +247,26 @@ namespace {
}
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 {};
}
@ -249,55 +275,131 @@ std::vector<ludo::PathNode> ludo::LudoGameMode::computePath(PlayerColor color, P
if (targetState == PieceState::AtHome) {
if (startingState != PieceState::OnTrack) {
throw std::invalid_argument(
"computePath: targetState==AtHome ist nur von OnTrack aus erlaubt (Capture)");
std::format(
"computePath: targetState==AtHome ist nur von OnTrack aus erlaubt (Capture). {}",
errorContext()
)
);
}
return { PathNode{startingState, startPosition}, PathNode{targetState, targetPosition} };
return {
PathNode{startingState, startPosition},
PathNode{targetState, targetPosition}
};
}
// --- Nur Vorwärtsbewegungen sind erlaubt ---
if (stateOrder(targetState) < stateOrder(startingState)) {
throw std::invalid_argument(
"computePath: targetState liegt vor startingState (Rueckwaerts nicht unterstuetzt)");
std::format(
"computePath: targetState liegt vor startingState (Rueckwaerts nicht unterstuetzt). "
"stateOrder(start)={}, stateOrder(target)={}. {}",
stateOrder(startingState),
stateOrder(targetState),
errorContext()
)
);
}
if (targetState == PieceState::AtHome) {
throw std::invalid_argument(
"computePath: AtHome kann kein Zielzustand sein (ausser Start==Ziel)");
}
if (startingState == PieceState::Finished) {
// hier nur erreichbar, wenn targetState/targetPosition abweicht (early return greift sonst)
throw std::invalid_argument(
"computePath: startingState ist bereits Finished, aber Ziel weicht ab");
std::format(
"computePath: startingState ist bereits Finished, aber Ziel weicht ab. {}",
errorContext()
)
);
}
if (startingState == PieceState::InHomeStretch && targetState == PieceState::InHomeStretch
&& targetPosition < startPosition) {
if (startingState == PieceState::InHomeStretch &&
targetState == PieceState::InHomeStretch &&
targetPosition < startPosition) {
throw std::invalid_argument(
"computePath: targetPosition liegt in InHomeStretch vor startPosition");
std::format(
"computePath: targetPosition liegt im HomeStretch vor startPosition "
"(Rueckwaertsbewegung). {}",
errorContext()
)
);
}
// --- Positionsbereiche ---
if (targetState == PieceState::OnTrack
&& (targetPosition < 0 || targetPosition >= kTrackLength)) {
throw std::invalid_argument("computePath: targetPosition ausserhalb OnTrack-Bereich");
}
if (targetState == PieceState::InHomeStretch
&& (targetPosition < 0 || targetPosition >= kHomeStretchLength)) {
throw std::invalid_argument("computePath: targetPosition ausserhalb InHomeStretch-Bereich");
}
if (targetState == PieceState::Finished && targetPosition != kHomeStretchLength - 1) {
// <-- vermutlich dein aktueller Bug
if (targetState == PieceState::OnTrack &&
(targetPosition < 0 || targetPosition >= kTrackLength)) {
throw std::invalid_argument(
"computePath: targetPosition fuer Finished muss kHomeStretchLength - 1 sein");
std::format(
"computePath: targetPosition ausserhalb OnTrack-Bereich. "
"Erwartet [0,{}), erhalten {}. {}",
kTrackLength,
targetPosition,
errorContext()
)
);
}
if (startingState == PieceState::OnTrack
&& (startPosition < 0 || startPosition >= kTrackLength)) {
throw std::invalid_argument("computePath: startPosition ausserhalb OnTrack-Bereich");
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 (startingState == PieceState::InHomeStretch
&& (startPosition < 0 || startPosition >= kHomeStretchLength)) {
throw std::invalid_argument("computePath: startPosition ausserhalb InHomeStretch-Bereich");
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;

View File

@ -42,6 +42,7 @@ namespace ludo {
return "Unknown";
}
constexpr std::array<PlayerColor, static_cast<size_t>(PlayerColor::Count)> PlayerColors = {
PlayerColor::Red,
PlayerColor::Blue,
@ -55,6 +56,22 @@ namespace ludo {
}
enum class PieceState : uint8_t { AtHome, OnTrack, InHomeStretch, Finished };
constexpr std::string_view toString(PieceState state)
{
switch(state)
{
case PieceState::AtHome:
return "AtHome";
case PieceState::OnTrack:
return "OnTrack";
case PieceState::InHomeStretch:
return "InHomeStretch";
case PieceState::Finished:
return "Finished";
}
return "Unknown";
}
enum class TurnPhase : uint8_t { AwaitingRoll, AwaitingPieceSelection };
struct Piece {
@ -83,6 +100,21 @@ namespace ludo {
}
private:
PieceMovedEvent(
int pieceIndex,
int fromPosition,
int toPosition,
bool captured,
PieceState targetState,
PieceState startState)
:
pieceIndex(pieceIndex),
fromPosition(fromPosition),
toPosition(toPosition),
captured(captured),
targetState(targetState),
startState(startState)
{}
static void validate(PieceState startState, int fromPosition,
PieceState targetState, int toPosition, bool captured) {
if (captured && targetState != PieceState::AtHome) {