ADD: Ludo Game
This commit is contained in:
parent
7d1774e222
commit
a1c6ffa0b7
@ -198,6 +198,11 @@ add_executable(ColorRace
|
|||||||
game/src/GameScene.h
|
game/src/GameScene.h
|
||||||
engine/src/core/inputsOutputs/context/InputContext.cpp
|
engine/src/core/inputsOutputs/context/InputContext.cpp
|
||||||
engine/src/core/inputsOutputs/context/InputContext.h
|
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) {
|
void endTurn(bool extraTurn = false) {
|
||||||
if (!extraTurn) m_currentPlayerIndex = (m_currentPlayerIndex + 1) % m_playerOrder.size();
|
if (!extraTurn) m_currentPlayerIndex = (m_currentPlayerIndex + 1) % m_playerOrder.size();
|
||||||
}
|
}
|
||||||
|
std::vector<PlayerID> getPlayerOrder() const { return m_playerOrder; }
|
||||||
private:
|
private:
|
||||||
std::vector<PlayerID> m_playerOrder;
|
std::vector<PlayerID> m_playerOrder;
|
||||||
size_t m_currentPlayerIndex = 0;
|
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