FIX: Animation for Capturing

This commit is contained in:
sebastian 2026-08-05 09:37:09 +02:00
parent 97f8e71e18
commit 89435a66a4
6 changed files with 86 additions and 4 deletions

View File

@ -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<int> 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::PathNode> 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(

View File

@ -8,6 +8,21 @@ const std::vector<ludo::Piece> & ludo::LudoGameState::getPieces() const {
return m_pieces;
}
std::vector<int> ludo::LudoGameState::getFreeHomePositions(PlayerColor color) const {
std::vector<int> 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;
}

View File

@ -23,8 +23,10 @@ namespace ludo {
}
};
[[nodiscard]] const std::vector<Piece> &getPieces() const;
[[nodiscard]] std::vector<int> getFreeHomePositions(PlayerColor color) const;
[[nodiscard]] int getDiceValue() const;
[[nodiscard]] TurnPhase getPhase() const;
[[nodiscard]] PlayerColor getColorOfPieceIndex(int pieceIndex) const;
private:
friend class LudoGameMode;
std::vector<Piece> m_pieces;

View File

@ -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};

View File

@ -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<engine::animation::Keyframe<glm::vec3>> 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 <pathPositions.size(); ++i) {
const auto& from = pathPositions[i];
const auto& to = pathPositions[i + 1];
keyframes.emplace_back(time, from);
time += 0.5f / (kHopSubdivisions + 1);
for (int s = 1; s < kHopSubdivisions; ++s) {
float t = static_cast<float>(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<glm::vec3> positionTrack;

View File

@ -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);
}