// // Created by sebastian on 02.08.26. // #include "LudoGameMode.h" #include #include #include "gameMode/LudoRules.h" #include "spdlog/spdlog.h" void ludo::LudoGameMode::sendCommand(const std::variant &command) { std::visit([this](const auto& cmd) { handle(cmd);}, command); } std::vector ludo::LudoGameMode::pollEvents() { auto events = std::move(m_pendingEvents); m_pendingEvents.clear(); return events; } std::optional ludo::LudoGameMode::findLowestHomeSlotPiece(PlayerColor color) const { for (int i = 0; i < static_cast(m_gameState.m_pieces.size()); ++i) { const auto& piece = m_gameState.m_pieces[i]; if (piece.color == color && piece.state == PieceState::AtHome) { return i; // erste Übereinstimmung = kleinster Home-Slot, dank Erzeugungsreihenfolge } } return std::nullopt; } ludo::LudoGameMode::MoveResult ludo::LudoGameMode::movePieceInternal(int pieceIndex, int diceValue) { Piece& piece = m_gameState.m_pieces[pieceIndex]; int fromPosition = piece.position; Destination destination = LudoRules::computeDestination(m_gameState, piece, diceValue).value(); PieceState startState = piece.state; PieceState targetState = destination.state; piece.state = destination.state; piece.position = destination.position; MoveResult result{fromPosition, piece.position, false, -1, targetState, startState}; if (piece.state == PieceState::OnTrack) { for (int i = 0; i < static_cast(m_gameState.m_pieces.size()); ++i) { if (i == pieceIndex) continue; Piece& other = m_gameState.m_pieces[i]; if (other.color != piece.color && other.state == PieceState::OnTrack && other.position == piece.position) { other.state = PieceState::AtHome; other.position = -1; result.captured = true; result.capturedPieceIndex = i; break; // dank Blockade-Regel kann max. eine gegnerische Figur auf einem Feld stehen } } } return result; } void ludo::LudoGameMode::autoReleasePiece(int pieceIndex) { auto result = movePieceInternal(pieceIndex, m_gameState.getDiceValue()); publishEvent(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 std::vector freeHomePositions = m_gameState.getFreeHomePositions(m_gameState.getColorOfPieceIndex(result.capturedPieceIndex)); const int smallestFreeHomePosition = *std::ranges::min_element(freeHomePositions); publishEvent(PieceMovedEvent::create(result.capturedPieceIndex, result.fromPosition, smallestFreeHomePosition, result.captured, result.startState, result.targetState)); } } void ludo::LudoGameMode::handle(const RollDiceCommand &command) { if (!isCurrentPlayer(command.player) || m_gameState.m_phase != TurnPhase::AwaitingRoll) return; m_gameState.m_diceValue = rollDice(); publishEvent(DiceRolledEvent(command.player, m_gameState.m_diceValue)); if (m_gameState.m_diceValue == 6) { if (auto homeIndex = findLowestHomeSlotPiece(getColorOf(command.player))) { if (!LudoRules::isBlockedByOwnPiece(m_gameState, getColorOf(command.player), PieceState::OnTrack, getEntryOffset(getColorOf(command.player)))) { autoReleasePiece(*homeIndex); finishTurn(); return; } } } if (getMoveablePieceIndices(getColorOf(command.player), m_gameState.m_diceValue).empty()) { finishTurn(); // kein gültiger zug, sofort weitergeben } else { m_gameState.m_phase = TurnPhase::AwaitingPieceSelection; } } ludo::PlayerColor ludo::LudoGameMode::getColorOf(engine::PlayerID player) const { const auto& order = getPlayerOrder(); auto it = std::find(order.begin(), order.end(), player); assert(it != order.end() && "getColorOf mit unregistrierter PlayerID aufgerufen"); return static_cast(std::distance(order.begin(), it)); } void ludo::LudoGameMode::handle(const MovePieceCommand &command) { if (!isCurrentPlayer(command.player)) { spdlog::warn("MovePieceCommand abgewiesen: {} ist nicht am Zug (aktuell: {})", command.player, getCurrentPlayer()); return; } if (m_gameState.m_phase != TurnPhase::AwaitingPieceSelection) { spdlog::warn("MovePieceCommand abgewiesen: falsche Phase ({})", static_cast(m_gameState.m_phase)); return; } auto movable = getMoveablePieceIndices(getColorOf(command.player), m_gameState.m_diceValue); if (std::find(movable.begin(), movable.end(), command.pieceIndex) == movable.end()) { spdlog::warn("MovePieceCommand abgewiesen: pieceIndex={} nicht in movable-Liste", command.pieceIndex); return; } // ungültiger Zug, abweisen spdlog::info("MovePieceCommand akzeptiert: pieceIndex={}", command.pieceIndex); auto result = movePieceInternal(command.pieceIndex, m_gameState.m_diceValue); 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); 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(); } void ludo::LudoGameMode::finishTurn() { bool extraTurn = (m_gameState.m_diceValue == 6 && (++m_gameState.m_consecutiveSixes < 2)); if (m_gameState.m_diceValue != 6) { m_gameState.m_consecutiveSixes = 0; } endTurn(extraTurn); m_gameState.m_phase = TurnPhase::AwaitingRoll; publishEvent(TurnChangedEvent{getCurrentPlayer()}); } std::vector ludo::LudoGameMode::getMoveablePieceIndices(PlayerColor color, int diceValue) const { std::vector moveablePieceIndices; for (int i = 0; i < m_gameState.getPieces().size(); ++i) { const Piece& piece = m_gameState.getPieces()[i]; if (piece.color != color) continue; Destination dest = LudoRules::computeDestination(m_gameState, piece, diceValue).value(); // Überschuss in der Zielgeraden abfangen: computeDestination liefert bei Überschuss // aktuell denselben Zustand zurück, wie unten in computeDestination definiert if (piece.state == PieceState::InHomeStretch && dest.position > kHomeStretchLength - 1) continue; if (piece.state == PieceState::AtHome && diceValue != 6) { continue; } if (!LudoRules::isBlockedByOwnPiece(m_gameState, color, dest.state, dest.position)) { moveablePieceIndices.push_back(i); } } return moveablePieceIndices; } std::optional ludo::LudoGameMode::wouldCapture(int pieceIndex, int diceValue) const { const auto& piece = m_gameState.m_pieces[pieceIndex]; auto dest = LudoRules::computeDestination(m_gameState, piece, diceValue); if (dest.value().state != PieceState::OnTrack) return std::nullopt; for (int i = 0; i < static_cast(m_gameState.m_pieces.size()); ++i) { if (i == pieceIndex) continue; const auto& other = m_gameState.m_pieces[i]; if (other.color != piece.color && other.state == PieceState::OnTrack && other.position == dest.value().position) { return i; } } return std::nullopt; } int ludo::LudoGameMode::rollDice() { return m_diceSource->roll(); } int ludo::LudoGameMode::getEntryOffset(PlayerColor color) { return static_cast(color) * 10; } void ludo::LudoGameMode::publishEvent(LudoEvent event) { m_pendingEvents.push_back(event); m_eventBus.publish(event); }