diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f5bc89..14cd9d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -198,6 +198,11 @@ add_executable(ColorRace game/src/GameScene.h engine/src/core/inputsOutputs/context/InputContext.cpp engine/src/core/inputsOutputs/context/InputContext.h + game/src/ludo/LudoGameState.cpp + game/src/ludo/LudoGameState.h + game/src/ludo/LudoTypes.h + game/src/ludo/LudoGameMode.cpp + game/src/ludo/LudoGameMode.h ) diff --git a/engine/src/game/TurnBasedGameMode.h b/engine/src/game/TurnBasedGameMode.h index 443fbb5..3720f6b 100644 --- a/engine/src/game/TurnBasedGameMode.h +++ b/engine/src/game/TurnBasedGameMode.h @@ -22,6 +22,7 @@ namespace engine { void endTurn(bool extraTurn = false) { if (!extraTurn) m_currentPlayerIndex = (m_currentPlayerIndex + 1) % m_playerOrder.size(); } + std::vector getPlayerOrder() const { return m_playerOrder; } private: std::vector m_playerOrder; size_t m_currentPlayerIndex = 0; diff --git a/game/src/ludo/LudoGameMode.cpp b/game/src/ludo/LudoGameMode.cpp new file mode 100644 index 0000000..7b56807 --- /dev/null +++ b/game/src/ludo/LudoGameMode.cpp @@ -0,0 +1,180 @@ +// +// Created by sebastian on 02.08.26. +// + +#include "LudoGameMode.h" + +#include +#include + +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; +} + +ludo::LudoGameMode::MoveResult ludo::LudoGameMode::movePieceInternal(int pieceIndex, int diceValue) { + Piece& piece = m_gameState.m_pieces[pieceIndex]; + int fromPosition = piece.position; + + switch (piece.state) { + case PieceState::AtHome: { + piece.state = PieceState::OnTrack; + piece.position = getEntryOffset(piece.color); + break; + } + case PieceState::OnTrack: { + int distanceTraveled = (piece.position - getEntryOffset(piece.color) + kTrackLength) % kTrackLength; + int newDistance = distanceTraveled + diceValue; + if (newDistance <= kTrackLength - 1) { + piece.position = (piece.position + diceValue) % kTrackLength; + } else { + int homeIndex = newDistance - kTrackLength; + piece.state = PieceState::InHomeStretch; + piece.position = homeIndex; + } + break; + } + case PieceState::InHomeStretch: { + piece.position += diceValue; + if (piece.position == kHomeStretchLength - 1) { + piece.state = PieceState::Finished; + } + } + case PieceState::Finished: + break; + } + + MoveResult result{fromPosition, piece.position, false, -1}; + + 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::handle(const RollDiceCommand &command) { + if (!isCurrentPlayer(command.player) || m_gameState.m_phase != TurnPhase::AwaitingRoll) return; + + m_gameState.m_diceValue = rollDice(); + m_pendingEvents.emplace_back(DiceRolledEvent(command.player, m_gameState.m_diceValue)); + + 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 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) || m_gameState.m_phase != TurnPhase::AwaitingPieceSelection) return; + + auto movable = getMoveablePieceIndices(getColorOf(command.player), m_gameState.m_diceValue); + if (std::find(movable.begin(), movable.end(), command.pieceIndex) == movable.end()) return; // ungültiger Zug, abweisen + + auto result = movePieceInternal(command.pieceIndex, m_gameState.m_diceValue); + m_pendingEvents.emplace_back(PieceMovedEvent{command.pieceIndex, result.fromPosition, result.toPosition, result.captured}); + if (result.captured) { + m_pendingEvents.emplace_back(PieceMovedEvent{result.capturedPieceIndex, result.toPosition, -1, false}); + } + 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; + m_pendingEvents.emplace_back(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; + + switch (piece.state) { + case PieceState::AtHome: { + if (diceValue == 6 && !isBlockedByOwnPiece(color, PieceState::OnTrack, getEntryOffset(color))) { + moveablePieceIndices.push_back(i); + } + break; + } + case PieceState::OnTrack: { + int distanceTraveled = (piece.position - getEntryOffset(color)) + kTrackLength; + int newDistance = distanceTraveled + diceValue; + + if (newDistance <= kTrackLength - 1) { + int newPos = (piece.position + diceValue) % kTrackLength; + if (!isBlockedByOwnPiece(color, PieceState::OnTrack, newPos)) { + moveablePieceIndices.push_back(i); + } + } else if (newDistance <= kTrackLength - 1 + kHomeStretchLength){ + int homeIndex = newDistance - kTrackLength; + if (!isBlockedByOwnPiece(color, PieceState::InHomeStretch, homeIndex)) { + moveablePieceIndices.push_back(i); + } + } + // sonst: Wurf zu hoch, Figur kann sich nicht "totlaufen" -> nicht bewegbar + break; + } + case PieceState::InHomeStretch: { + int newIndex = piece.position + diceValue; + if (newIndex <= kHomeStretchLength - 1 && !isBlockedByOwnPiece(color, PieceState::Finished, newIndex)) { + moveablePieceIndices.push_back(i); + } + break; + } + case PieceState::Finished: + break; + + } + } + return moveablePieceIndices; +} + + +int ludo::LudoGameMode::rollDice() { + std::uniform_int_distribution distribution(1, 6); + return distribution(m_rng); +} + +int ludo::LudoGameMode::getEntryOffset(PlayerColor color) const { + return static_cast(color) * 10; +} + +bool ludo::LudoGameMode::isBlockedByOwnPiece(PlayerColor owner, PieceState state, int position) const { + for (const auto& piece : m_gameState.getPieces()) { + if (piece.color == owner && piece.state == state && piece.position == position) { + return true; + } + } + return false; +} diff --git a/game/src/ludo/LudoGameMode.h b/game/src/ludo/LudoGameMode.h new file mode 100644 index 0000000..0626387 --- /dev/null +++ b/game/src/ludo/LudoGameMode.h @@ -0,0 +1,55 @@ +// +// Created by sebastian on 02.08.26. +// + +#ifndef COLORRACE_LUDOGAMEMODE_H +#define COLORRACE_LUDOGAMEMODE_H +#include +#include + +#include "LudoGameState.h" +#include "LudoTypes.h" +#include "game/TurnBasedGameMode.h" + +namespace ludo { + class LudoGameMode: public engine::TurnBasedGameMode { + public: + explicit LudoGameMode(std::vector players, std::optional seed = std::nullopt) : TurnBasedGameMode(std::move(players)), m_rng(seed.value_or(std::random_device{}())) {} + void sendCommand(const std::variant &command) override; + std::vector pollEvents() override; + + + [[nodiscard]] const LudoGameState& getState() const override { return m_gameState; } + private: + struct MoveResult { + int fromPosition; + int toPosition; + bool captured = false; + int capturedPieceIndex = -1; + }; + + MoveResult movePieceInternal(int pieceIndex, int diceValue); + + void handle(const RollDiceCommand &command); + + PlayerColor getColorOf(engine::PlayerID playerID); + + void handle(const MovePieceCommand &command); + void finishTurn(); + + std::vector getMoveablePieceIndices(PlayerColor color, int diceValue) const; + + LudoGameState m_gameState; + std::vector m_pendingEvents; + + int rollDice(); + std::mt19937 m_rng; + + [[nodiscard]] int getEntryOffset(PlayerColor color) const; + [[nodiscard]] bool isBlockedByOwnPiece(PlayerColor owner, PieceState state, int position) const; + + }; +} + + +#endif //COLORRACE_LUDOGAMEMODE_H diff --git a/game/src/ludo/LudoGameState.cpp b/game/src/ludo/LudoGameState.cpp new file mode 100644 index 0000000..f16a13c --- /dev/null +++ b/game/src/ludo/LudoGameState.cpp @@ -0,0 +1,17 @@ +// +// Created by sebastian on 02.08.26. +// + +#include "LudoGameState.h" + +const std::vector & ludo::LudoGameState::getPieces() const { + return m_pieces; +} + +int ludo::LudoGameState::getDiceValue() const { + return m_diceValue; +} + +ludo::TurnPhase ludo::LudoGameState::getPhase() const { + return m_phase; +} diff --git a/game/src/ludo/LudoGameState.h b/game/src/ludo/LudoGameState.h new file mode 100644 index 0000000..feeff86 --- /dev/null +++ b/game/src/ludo/LudoGameState.h @@ -0,0 +1,29 @@ +// +// Created by sebastian on 02.08.26. +// + +#ifndef COLORRACE_LUDOGAMESTATE_H +#define COLORRACE_LUDOGAMESTATE_H +#include + +#include "LudoTypes.h" +#include "game/GameState.h" + + +namespace ludo { + class LudoGameState: public engine::GameState { + public: + [[nodiscard]] const std::vector &getPieces() const; + [[nodiscard]] int getDiceValue() const; + [[nodiscard]] TurnPhase getPhase() const; + private: + friend class LudoGameMode; + std::vector m_pieces; + int m_diceValue = 0; + TurnPhase m_phase = TurnPhase::AwaitingRoll; + int m_consecutiveSixes = 0; + }; + +} + +#endif //COLORRACE_LUDOGAMESTATE_H diff --git a/game/src/ludo/LudoTypes.h b/game/src/ludo/LudoTypes.h new file mode 100644 index 0000000..0ebc3e0 --- /dev/null +++ b/game/src/ludo/LudoTypes.h @@ -0,0 +1,38 @@ +// +// Created by sebastian on 02.08.26. +// + +#ifndef COLORRACE_LUDOTYPES_H +#define COLORRACE_LUDOTYPES_H +#include +#include + +#include "game/TurnBasedGameMode.h" + +namespace ludo { + inline constexpr int kTrackLength = 40; + inline constexpr int kHomeStretchLength = 4; + + enum class PlayerColor : uint8_t { Red, Blue, Yellow, Green, Count}; + enum class PieceState : uint8_t { AtHome, OnTrack, InHomeStretch, Finished }; + enum class TurnPhase : uint8_t { AwaitingRoll, AwaitingPieceSelection }; + + struct Piece { + PlayerColor color; + PieceState state = PieceState::AtHome; + int position = -1; + }; + + // Commands: Client -> Autorität, adressiert über PlayerID (Engine-Identität, netzwerkfähig) + struct MovePieceCommand { engine::PlayerID player; int pieceIndex;}; + struct RollDiceCommand { engine::PlayerID player; }; + using LudoCommand = std::variant; + + struct PieceMovedEvent { int pieceIndex; int fromPosition; int toPosition; bool captured; }; + struct DiceRolledEvent { engine::PlayerID player; int diceValue; }; + struct TurnChangedEvent {engine::PlayerID newPlayer; }; + using LudoEvent = std::variant; + + +} +#endif //COLORRACE_LUDOTYPES_H