ADD: Ludo Game
This commit is contained in:
parent
7d1774e222
commit
a1c6ffa0b7
@ -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
|
||||
|
||||
)
|
||||
|
||||
|
||||
@ -22,6 +22,7 @@ namespace engine {
|
||||
void endTurn(bool extraTurn = false) {
|
||||
if (!extraTurn) m_currentPlayerIndex = (m_currentPlayerIndex + 1) % m_playerOrder.size();
|
||||
}
|
||||
std::vector<PlayerID> getPlayerOrder() const { return m_playerOrder; }
|
||||
private:
|
||||
std::vector<PlayerID> m_playerOrder;
|
||||
size_t m_currentPlayerIndex = 0;
|
||||
|
||||
180
game/src/ludo/LudoGameMode.cpp
Normal file
180
game/src/ludo/LudoGameMode.cpp
Normal file
@ -0,0 +1,180 @@
|
||||
//
|
||||
// Created by sebastian on 02.08.26.
|
||||
//
|
||||
|
||||
#include "LudoGameMode.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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<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::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<PlayerColor>(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<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;
|
||||
|
||||
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<int> distribution(1, 6);
|
||||
return distribution(m_rng);
|
||||
}
|
||||
|
||||
int ludo::LudoGameMode::getEntryOffset(PlayerColor color) const {
|
||||
return static_cast<int>(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;
|
||||
}
|
||||
55
game/src/ludo/LudoGameMode.h
Normal file
55
game/src/ludo/LudoGameMode.h
Normal file
@ -0,0 +1,55 @@
|
||||
//
|
||||
// Created by sebastian on 02.08.26.
|
||||
//
|
||||
|
||||
#ifndef COLORRACE_LUDOGAMEMODE_H
|
||||
#define COLORRACE_LUDOGAMEMODE_H
|
||||
#include <optional>
|
||||
#include <random>
|
||||
|
||||
#include "LudoGameState.h"
|
||||
#include "LudoTypes.h"
|
||||
#include "game/TurnBasedGameMode.h"
|
||||
|
||||
namespace ludo {
|
||||
class LudoGameMode: public engine::TurnBasedGameMode<LudoCommand, LudoEvent, LudoGameState> {
|
||||
public:
|
||||
explicit LudoGameMode(std::vector<engine::PlayerID> players, std::optional<unsigned int> seed = std::nullopt) : TurnBasedGameMode(std::move(players)), m_rng(seed.value_or(std::random_device{}())) {}
|
||||
void sendCommand(const std::variant<MovePieceCommand, RollDiceCommand> &command) override;
|
||||
std::vector<LudoEvent> 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<int> getMoveablePieceIndices(PlayerColor color, int diceValue) const;
|
||||
|
||||
LudoGameState m_gameState;
|
||||
std::vector<LudoEvent> 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
|
||||
17
game/src/ludo/LudoGameState.cpp
Normal file
17
game/src/ludo/LudoGameState.cpp
Normal file
@ -0,0 +1,17 @@
|
||||
//
|
||||
// Created by sebastian on 02.08.26.
|
||||
//
|
||||
|
||||
#include "LudoGameState.h"
|
||||
|
||||
const std::vector<ludo::Piece> & ludo::LudoGameState::getPieces() const {
|
||||
return m_pieces;
|
||||
}
|
||||
|
||||
int ludo::LudoGameState::getDiceValue() const {
|
||||
return m_diceValue;
|
||||
}
|
||||
|
||||
ludo::TurnPhase ludo::LudoGameState::getPhase() const {
|
||||
return m_phase;
|
||||
}
|
||||
29
game/src/ludo/LudoGameState.h
Normal file
29
game/src/ludo/LudoGameState.h
Normal file
@ -0,0 +1,29 @@
|
||||
//
|
||||
// Created by sebastian on 02.08.26.
|
||||
//
|
||||
|
||||
#ifndef COLORRACE_LUDOGAMESTATE_H
|
||||
#define COLORRACE_LUDOGAMESTATE_H
|
||||
#include <vector>
|
||||
|
||||
#include "LudoTypes.h"
|
||||
#include "game/GameState.h"
|
||||
|
||||
|
||||
namespace ludo {
|
||||
class LudoGameState: public engine::GameState {
|
||||
public:
|
||||
[[nodiscard]] const std::vector<Piece> &getPieces() const;
|
||||
[[nodiscard]] int getDiceValue() const;
|
||||
[[nodiscard]] TurnPhase getPhase() const;
|
||||
private:
|
||||
friend class LudoGameMode;
|
||||
std::vector<Piece> m_pieces;
|
||||
int m_diceValue = 0;
|
||||
TurnPhase m_phase = TurnPhase::AwaitingRoll;
|
||||
int m_consecutiveSixes = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //COLORRACE_LUDOGAMESTATE_H
|
||||
38
game/src/ludo/LudoTypes.h
Normal file
38
game/src/ludo/LudoTypes.h
Normal file
@ -0,0 +1,38 @@
|
||||
//
|
||||
// Created by sebastian on 02.08.26.
|
||||
//
|
||||
|
||||
#ifndef COLORRACE_LUDOTYPES_H
|
||||
#define COLORRACE_LUDOTYPES_H
|
||||
#include <cstdint>
|
||||
#include <variant>
|
||||
|
||||
#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<MovePieceCommand, RollDiceCommand>;
|
||||
|
||||
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<PieceMovedEvent, DiceRolledEvent, TurnChangedEvent>;
|
||||
|
||||
|
||||
}
|
||||
#endif //COLORRACE_LUDOTYPES_H
|
||||
Loading…
Reference in New Issue
Block a user