R-Type
Distributed multiplayer game engine in C++
Loading...
Searching...
No Matches
MatchmakingService.cpp
Go to the documentation of this file.
1/*
2** EPITECH PROJECT, 2025
3** Created by hugo on 06/12/2025
4** File description:
5** MatchmakingService.cpp
6*/
7
9#include <algorithm>
10#include <sstream>
12#include "server/Rooms/Room.hpp"
13
14namespace server {
15
16 MatchmakingService::MatchmakingService(size_t minPlayers, size_t maxPlayers,
17 std::shared_ptr<EventBus> eventBus)
18 : _minPlayers(minPlayers), _maxPlayers(maxPlayers), _eventBus(eventBus) {
19 if (_minPlayers < 1) {
20 _minPlayers = 1;
21 }
24 }
25 LOG_INFO("MatchmakingService created (min: ", _minPlayers, ", max: ", _maxPlayers, " players)");
26 }
27
28 void MatchmakingService::addPlayer(uint32_t playerId) {
29 std::lock_guard<std::mutex> lock(_mutex);
30
31 // Check if player is already in queue
32 auto it = std::find_if(_waitingPlayers.begin(), _waitingPlayers.end(),
33 [playerId](const PlayerQueueInfo &info) { return info.playerId == playerId; });
34
35 if (it != _waitingPlayers.end()) {
36 LOG_WARNING("Player ", playerId, " is already in matchmaking queue");
37 return;
38 }
39
40 PlayerQueueInfo info;
41 info.playerId = playerId;
42 info.joinTime = std::chrono::steady_clock::now();
43
44 _waitingPlayers.push_back(info);
45 LOG_INFO("✓ Player ", playerId, " added to matchmaking queue (", _waitingPlayers.size(),
46 " players waiting)");
47 }
48
49 void MatchmakingService::removePlayer(uint32_t playerId) {
50 std::lock_guard<std::mutex> lock(_mutex);
51
52 auto it = std::find_if(_waitingPlayers.begin(), _waitingPlayers.end(),
53 [playerId](const PlayerQueueInfo &info) { return info.playerId == playerId; });
54
55 if (it != _waitingPlayers.end()) {
56 _waitingPlayers.erase(it);
57 LOG_INFO("✓ Player ", playerId, " removed from matchmaking queue (", _waitingPlayers.size(),
58 " players remaining)");
59 } else {
60 LOG_WARNING("Player ", playerId, " not found in matchmaking queue");
61 }
62 }
63
65 std::lock_guard<std::mutex> lock(_mutex);
66
67 // Try to create matches while we have enough players
68 while (_waitingPlayers.size() >= _minPlayers) {
69 if (!_tryCreateMatch()) {
70 break; // Could not create match, stop trying
71 }
72 }
73 }
74
76 if (_waitingPlayers.size() < _minPlayers) {
77 return false;
78 }
79
80 // Determine match size (up to maxPlayers)
81 size_t matchSize = std::min(_waitingPlayers.size(), _maxPlayers);
82
83 // Extract players for this match
84 std::vector<uint32_t> matchedPlayers;
85 matchedPlayers.reserve(matchSize);
86
87 for (size_t i = 0; i < matchSize; ++i) {
88 matchedPlayers.push_back(_waitingPlayers[i].playerId);
89 }
90
91 // Remove matched players from queue
92 _waitingPlayers.erase(_waitingPlayers.begin(), _waitingPlayers.begin() + matchSize);
93
94 // Create room for the match
95 std::string roomId = _generateRoomId();
96 std::shared_ptr<Room> room;
97
98 try {
99 room = std::make_shared<Room>(roomId, "Match #" + std::to_string(_totalMatchesCreated + 1),
100 _maxPlayers, false, 1.0f, _eventBus);
101 } catch (const std::exception &e) {
102 LOG_ERROR("Failed to create match room: ", e.what());
103 // Re-add players to waiting queue
104 for (uint32_t playerId : matchedPlayers) {
105 _waitingPlayers.push_back({playerId, std::chrono::steady_clock::now()});
106 }
107 return false;
108 }
109
110 // Add all matched players to the room
111 for (uint32_t playerId : matchedPlayers) {
112 room->join(playerId);
113 }
114
115 // Update statistics
117 _totalPlayersMatched += matchedPlayers.size();
118
119 LOG_INFO("✓ Match created: Room '", roomId, "' with ", matchedPlayers.size(), " players");
120
121 // Log player list
122 std::ostringstream playerList;
123 for (size_t i = 0; i < matchedPlayers.size(); ++i) {
124 if (i > 0)
125 playerList << ", ";
126 playerList << matchedPlayers[i];
127 }
128 LOG_INFO(" Players: ", playerList.str());
129
130 // Notify via callback if set
133 }
134
135 return true;
136 }
137
139 return "match_" + std::to_string(_totalMatchesCreated);
140 }
141
143 std::lock_guard<std::mutex> lock(_mutex);
144 return _waitingPlayers.size();
145 }
146
150
151 std::pair<std::shared_ptr<Room>, bool> MatchmakingService::findOrCreateMatch(
152 uint32_t playerId, const std::vector<std::shared_ptr<Room>> &availableRooms, bool allowSpectator) {
153
154 std::lock_guard<std::mutex> lock(_mutex);
155
156 LOG_INFO("[MatchmakingService] Finding match for player ", playerId);
157
158 // STRATEGY 1: Try to find a waiting room (instant join - best UX)
159 for (const auto &room : availableRooms) {
160 if (room->getState() == RoomState::WAITING && !room->isFull()) {
161 LOG_INFO("[MatchmakingService] Found waiting room '", room->getId(), "' for player ",
162 playerId);
163 return {room, false}; // Join as player
164 }
165 }
166
167 // STRATEGY 2: If no waiting room and spectator allowed, try to spectate an in-progress game
168 if (allowSpectator) {
169 for (const auto &room : availableRooms) {
170 if (room->getState() == RoomState::IN_PROGRESS) {
171 LOG_INFO("[MatchmakingService] No waiting rooms, player ", playerId, " will spectate '",
172 room->getId(), "'");
173 return {room, true}; // Join as spectator
174 }
175 }
176 }
177
178 // STRATEGY 3: No immediate match available, add to queue
179 LOG_INFO("[MatchmakingService] No immediate match, adding player ", playerId, " to queue");
180
181 // Check if player is already in queue
182 auto it = std::find_if(_waitingPlayers.begin(), _waitingPlayers.end(),
183 [playerId](const PlayerQueueInfo &info) { return info.playerId == playerId; });
184
185 if (it == _waitingPlayers.end()) {
186 PlayerQueueInfo info;
187 info.playerId = playerId;
188 info.joinTime = std::chrono::steady_clock::now();
189 _waitingPlayers.push_back(info);
190 LOG_INFO("✓ Player ", playerId, " added to matchmaking queue (", _waitingPlayers.size(),
191 " players waiting)");
192 }
193
194 return {nullptr, false}; // No immediate match, player is in queue
195 }
196
197 std::vector<PlayerQueueInfo> MatchmakingService::getWaitingPlayers() const {
198 std::lock_guard<std::mutex> lock(_mutex);
199 return _waitingPlayers;
200 }
201
203 std::lock_guard<std::mutex> lock(_mutex);
204 if (min >= 1 && min <= _maxPlayers) {
205 _minPlayers = min;
206 LOG_INFO("Matchmaking min players set to ", min);
207 }
208 }
209
211 std::lock_guard<std::mutex> lock(_mutex);
212 if (max >= _minPlayers) {
213 _maxPlayers = max;
214 LOG_INFO("Matchmaking max players set to ", max);
215 }
216 }
217
219 std::lock_guard<std::mutex> lock(_mutex);
220 std::ostringstream oss;
221 oss << "Matchmaking Statistics:\n";
222 oss << " Players in queue: " << _waitingPlayers.size() << "\n";
223 oss << " Total matches created: " << _totalMatchesCreated << "\n";
224 oss << " Total players matched: " << _totalPlayersMatched << "\n";
225 oss << " Min/Max players per match: " << _minPlayers << "/" << _maxPlayers;
226 return oss.str();
227 }
228
229} // namespace server
#define LOG_INFO(...)
Definition Logger.hpp:181
#define LOG_ERROR(...)
Definition Logger.hpp:183
#define LOG_WARNING(...)
Definition Logger.hpp:182
std::vector< PlayerQueueInfo > _waitingPlayers
std::string getStatistics() const
Get statistics about matchmaking.
std::pair< std::shared_ptr< Room >, bool > findOrCreateMatch(uint32_t playerId, const std::vector< std::shared_ptr< Room > > &availableRooms, bool allowSpectator=true) override
Find an available room or add player to matchmaking queue.
void removePlayer(uint32_t playerId) override
Remove a player from the matchmaking queue.
MatchCreatedCallback _matchCreatedCallback
void tick() override
Process matchmaking queue and create matches Called periodically by the server.
std::shared_ptr< server::EventBus > _eventBus
std::string _generateRoomId()
Generate unique room ID for a new match.
size_t getQueueSize() const override
Get the number of players waiting in queue.
bool _tryCreateMatch()
Try to create a match from waiting players.
MatchmakingService(size_t minPlayers=2, size_t maxPlayers=4, std::shared_ptr< server::EventBus > eventBus=nullptr)
Construct matchmaking service.
void setMatchCreatedCallback(MatchCreatedCallback callback) override
Set callback for when a match is created.
std::vector< PlayerQueueInfo > getWaitingPlayers() const
Get list of waiting players.
void addPlayer(uint32_t playerId) override
Add a player to the matchmaking queue.
void setMaxPlayers(size_t max)
Set maximum players per match.
void setMinPlayers(size_t min)
Set minimum players required to start a match.
std::function< void(std::shared_ptr< Room >)> MatchCreatedCallback
Callback invoked when a match is created.
Information about a player in matchmaking queue.
std::chrono::steady_clock::time_point joinTime