202 lines
8.4 KiB
C++
202 lines
8.4 KiB
C++
//
|
|
// Created by sebastian on 02.08.26.
|
|
//
|
|
|
|
#include "LudoGameMode.h"
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
|
|
#include "gameMode/LudoRules.h"
|
|
#include "spdlog/spdlog.h"
|
|
|
|
void ludo::LudoGameMode::sendCommand(const std::variant<MovePieceCommand, RollDiceCommand> &command) {
|
|
std::visit([this](const auto& cmd) { handle(cmd);}, command);
|
|
}
|
|
|
|
std::vector<ludo::LudoEvent> ludo::LudoGameMode::pollEvents() {
|
|
auto events = std::move(m_pendingEvents);
|
|
m_pendingEvents.clear();
|
|
return events;
|
|
}
|
|
|
|
std::optional<int> ludo::LudoGameMode::findLowestHomeSlotPiece(PlayerColor color) const {
|
|
for (int i = 0; i < static_cast<int>(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<int>(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<int> 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<PlayerColor>(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<int>(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<int> ludo::LudoGameMode::getMoveablePieceIndices(PlayerColor color, int diceValue) const {
|
|
std::vector<int> 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<int> 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<int>(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<int>(color) * 10;
|
|
}
|
|
|
|
void ludo::LudoGameMode::publishEvent(LudoEvent event) {
|
|
m_pendingEvents.push_back(event);
|
|
m_eventBus.publish(event);
|
|
} |