diff --git a/game/src/ludo/LudoGameMode.cpp b/game/src/ludo/LudoGameMode.cpp index 1fd92f8..c11c181 100644 --- a/game/src/ludo/LudoGameMode.cpp +++ b/game/src/ludo/LudoGameMode.cpp @@ -92,7 +92,9 @@ void ludo::LudoGameMode::autoReleasePiece(int pieceIndex) { m_pendingEvents.emplace_back(PieceMovedEvent::create(pieceIndex, result.fromPosition, result.toPosition, result.captured, result.startState, result.targetState)); if (result.captured) { //Todo: Determine real toPosition Index dependent on home belegung - m_pendingEvents.emplace_back(PieceMovedEvent::create(result.capturedPieceIndex, result.fromPosition, -1, result.captured, result.startState, result.targetState)); + std::vector freeHomePositions = m_gameState.getFreeHomePositions(m_gameState.getColorOfPieceIndex(result.capturedPieceIndex)); + const int smallestFreeHomePosition = *std::ranges::min_element(freeHomePositions); + m_pendingEvents.emplace_back(PieceMovedEvent::create(result.capturedPieceIndex, result.fromPosition, smallestFreeHomePosition, result.captured, result.startState, result.targetState)); } } @@ -238,6 +240,15 @@ std::vector ludo::LudoGameMode::computePath(PlayerColor color, P return {}; } + // --- Sonderfall: Figur wird geschlagen und springt direkt zurück --- + if (targetState == PieceState::AtHome) { + if (startingState != PieceState::OnTrack) { + throw std::invalid_argument( + "computePath: targetState==AtHome ist nur von OnTrack aus erlaubt (Capture)"); + } + return { PathNode{startingState, startPosition}, PathNode{targetState, targetPosition} }; + } + // --- Nur Vorwärtsbewegungen sind erlaubt --- if (stateOrder(targetState) < stateOrder(startingState)) { throw std::invalid_argument( diff --git a/game/src/ludo/LudoGameState.cpp b/game/src/ludo/LudoGameState.cpp index f16a13c..5dc7db6 100644 --- a/game/src/ludo/LudoGameState.cpp +++ b/game/src/ludo/LudoGameState.cpp @@ -8,6 +8,21 @@ const std::vector & ludo::LudoGameState::getPieces() const { return m_pieces; } +std::vector ludo::LudoGameState::getFreeHomePositions(PlayerColor color) const { + std::vector freeHomePositions; + for (int i = 0; i < kHomePiecesPerPlayer; ++i) { + bool occupied = false; + for (const auto& piece : m_pieces) { + if (piece.color == color && piece.state == PieceState::AtHome && piece.position == i) { + occupied = true; + } + } + + if (!occupied) freeHomePositions.push_back(i); + } + return freeHomePositions; +} + int ludo::LudoGameState::getDiceValue() const { return m_diceValue; } @@ -15,3 +30,7 @@ int ludo::LudoGameState::getDiceValue() const { ludo::TurnPhase ludo::LudoGameState::getPhase() const { return m_phase; } + +ludo::PlayerColor ludo::LudoGameState::getColorOfPieceIndex(int pieceIndex) const { + return m_pieces[pieceIndex].color; +} diff --git a/game/src/ludo/LudoGameState.h b/game/src/ludo/LudoGameState.h index 37663ec..b3762a3 100644 --- a/game/src/ludo/LudoGameState.h +++ b/game/src/ludo/LudoGameState.h @@ -23,8 +23,10 @@ namespace ludo { } }; [[nodiscard]] const std::vector &getPieces() const; + [[nodiscard]] std::vector getFreeHomePositions(PlayerColor color) const; [[nodiscard]] int getDiceValue() const; [[nodiscard]] TurnPhase getPhase() const; + [[nodiscard]] PlayerColor getColorOfPieceIndex(int pieceIndex) const; private: friend class LudoGameMode; std::vector m_pieces; diff --git a/game/src/ludo/LudoTypes.h b/game/src/ludo/LudoTypes.h index 14c1c18..5176727 100644 --- a/game/src/ludo/LudoTypes.h +++ b/game/src/ludo/LudoTypes.h @@ -17,6 +17,7 @@ namespace ludo { inline constexpr int kTrackLength = 40; inline constexpr int kHomeStretchLength = 4; + inline constexpr int kHomePiecesPerPlayer = 4; inline constexpr float kPieceRadius = 0.7f; enum class PlayerColor : uint8_t { Red, Blue, Yellow, Green, Count}; diff --git a/game/src/ludo/PiecePresenter.cpp b/game/src/ludo/PiecePresenter.cpp index a3e5ff9..27a495d 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 "spdlog/spdlog.h" void ludo::PiecePresenter::spawnPieces(const ludo::LudoGameState &state) { @@ -41,9 +42,29 @@ void ludo::PiecePresenter::onPieceMoved(const ludo::PieceMovedEvent &event, cons std::vector> keyframes; float time = 0; - for (const auto& position : pathPositions) { - keyframes.emplace_back(time, position); - time += 0.5f; // Todo: Check if this is in ms or s + constexpr int kHopSubdivisions = 8; // Punkte zwischen den beiden Endpunkten + constexpr float kHopHeight = 0.3f; // Sprunghöhe in Weltkoordinaten + + if (pathPositions.back() != target) { + spdlog::warn("PiecePresenter: computePath-Ziel weicht von autoritativem Board-State ab (pieceIndex={})", event.pieceIndex); + } + + for (int i=0; i + 1 (s) / (kHopSubdivisions + 1); + glm::vec3 pos = glm::mix(from, to, t); + pos.y += kHopHeight * (1.0f - (2.0f * t - 1.0f) * (2.0f * t - 1.0f)); // -x² Parabel, Scheitel bei t=0.5 + keyframes.emplace_back(time, pos); + time += 0.5f / (kHopSubdivisions + 1); + } + keyframes.emplace_back(time, to); + time += 0.5f / (kHopSubdivisions + 1); } engine::animation::AnimationTrack positionTrack; diff --git a/tests/LoduGameMode.cpp b/tests/LoduGameMode.cpp index 5fb338c..2c08b3d 100644 --- a/tests/LoduGameMode.cpp +++ b/tests/LoduGameMode.cpp @@ -139,4 +139,32 @@ TEST(LudoGameMode, PathLengthMatchesIndependentFormula) { assertPathIsWellFormed(path); } } +} + +TEST(ComputePath, CaptureFromOnTrackReturnsAtHome) { + auto path = ludo::LudoGameMode::computePath( + ludo::PlayerColor::Red, ludo::PieceState::OnTrack, 17, + ludo::PieceState::AtHome, 2); + + ASSERT_EQ(path.size(), 2u); + EXPECT_EQ(path[0].state, ludo::PieceState::OnTrack); + EXPECT_EQ(path[0].position, 17); + EXPECT_EQ(path[1].state, ludo::PieceState::AtHome); + EXPECT_EQ(path[1].position, 2); +} + +TEST(ComputePath, ThrowsOnCaptureFromInHomeStretch) { + EXPECT_THROW( + ludo::LudoGameMode::computePath( + ludo::PlayerColor::Red, ludo::PieceState::InHomeStretch, 1, + ludo::PieceState::AtHome, 0), + std::invalid_argument); +} + +TEST(ComputePath, ThrowsOnCaptureFromAtHome) { + EXPECT_THROW( + ludo::LudoGameMode::computePath( + ludo::PlayerColor::Red, ludo::PieceState::AtHome, 0, + ludo::PieceState::AtHome, 1), // targetPosition != startPosition, also kein Early-Return + std::invalid_argument); } \ No newline at end of file