diff --git a/CMakeLists.txt b/CMakeLists.txt index 34427ed..39b711f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/game/src/ludo/LudoGameMode.cpp b/game/src/ludo/LudoGameMode.cpp index 43d34c5..32baaea 100644 --- a/game/src/ludo/LudoGameMode.cpp +++ b/game/src/ludo/LudoGameMode.cpp @@ -235,217 +235,4 @@ bool ludo::LudoGameMode::isBlockedByOwnPiece(PlayerColor owner, PieceState state 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::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 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; -} +} \ No newline at end of file diff --git a/game/src/ludo/LudoGameMode.h b/game/src/ludo/LudoGameMode.h index 20efadb..184fdaf 100644 --- a/game/src/ludo/LudoGameMode.h +++ b/game/src/ludo/LudoGameMode.h @@ -43,7 +43,7 @@ namespace ludo { std::vector getMoveablePieceIndices(PlayerColor color, int diceValue) const; std::optional wouldCapture(int pieceIndex, int diceValue) const; [[nodiscard]] static int getEntryOffset(PlayerColor color); - static std::vector computePath(PlayerColor color, PieceState startingState, int startPosition, PieceState targetState, int targetPosition); + void setDiceSource(std::unique_ptr diceSource) { m_diceSource = std::move(diceSource); } private: diff --git a/game/src/ludo/LudoRules.cpp b/game/src/ludo/LudoRules.cpp new file mode 100644 index 0000000..8e2bae8 --- /dev/null +++ b/game/src/ludo/LudoRules.cpp @@ -0,0 +1,9 @@ +// +// Created by sebastian on 05.08.26. +// + +#include "LudoRules.h" + +int ludo::LudoRules::getEntryOffset(PlayerColor color) { + return static_cast(color) * 10; +} diff --git a/game/src/ludo/LudoRules.h b/game/src/ludo/LudoRules.h new file mode 100644 index 0000000..4e2c8ee --- /dev/null +++ b/game/src/ludo/LudoRules.h @@ -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 diff --git a/game/src/ludo/PiecePresenter.cpp b/game/src/ludo/PiecePresenter.cpp index 4372c4f..209a843 100644 --- a/game/src/ludo/PiecePresenter.cpp +++ b/game/src/ludo/PiecePresenter.cpp @@ -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 path = LudoGameMode::computePath(piece.color, event.startState, event.fromPosition, event.targetState, event.toPosition); + std::vector path = PiecePathBuilder::computePath(piece.color, event.startState, event.fromPosition, event.targetState, event.toPosition); std::vector pathPositions; for (const auto& node : path) { pathPositions.push_back(m_layout.getWorldPosition(piece.color, node.state, node.position)); diff --git a/game/src/ludo/gameMode/PathNode.h b/game/src/ludo/gameMode/PathNode.h new file mode 100644 index 0000000..e7ad75c --- /dev/null +++ b/game/src/ludo/gameMode/PathNode.h @@ -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 diff --git a/game/src/ludo/gameMode/PiecePathBuilder.cpp b/game/src/ludo/gameMode/PiecePathBuilder.cpp new file mode 100644 index 0000000..b2ba145 --- /dev/null +++ b/game/src/ludo/gameMode/PiecePathBuilder.cpp @@ -0,0 +1,221 @@ +// +// Created by sebastian on 05.08.26. +// + +#include "PiecePathBuilder.h" + +#include + +#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::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 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; +} diff --git a/game/src/ludo/gameMode/PiecePathBuilder.h b/game/src/ludo/gameMode/PiecePathBuilder.h new file mode 100644 index 0000000..2ecd55d --- /dev/null +++ b/game/src/ludo/gameMode/PiecePathBuilder.h @@ -0,0 +1,23 @@ +// +// Created by sebastian on 05.08.26. +// + +#ifndef COLORRACE_PIECEPATHBUILDER_H +#define COLORRACE_PIECEPATHBUILDER_H +#include + +#include "PathNode.h" +#include "ludo/LudoGameMode.h" +#include "ludo/LudoTypes.h" + + +namespace ludo { + + class PiecePathBuilder { + public: + static std::vector computePath(PlayerColor color, PieceState startingState, int startPosition, PieceState targetState, int targetPosition); + }; +} + + +#endif //COLORRACE_PIECEPATHBUILDER_H