A rank-based multiplayer matchmaking system built as the Data Structures semester project. MatchKernel simulates the exact logic behind real game matchmaking — rank queues, sub-rank proximity, wait-time expansion, and cross-rank matching — all powered by queue-based data structures, a C++17 HTTP backend, and a live browser UI.
Not a simulation of matchmaking. An actual matchmaking engine.
Phase 1 → Same-rank players matched first
Sub-rank proximity is the tiebreaker (I beats III)
↓
Phase 2 → Unmatched players accumulate wait cycles
↓
Phase 3 → Every 3 wait cycles, expansion radius grows by +1
Players can now match one rank tier further away
↓
Phase 4 → Remaining unmatched players reported
Cross-rank matches flagged with a purple badge in UI
This mirrors how real matchmaking handles long queue times — the longer you wait, the wider the net casts.
| Layer | Technology | Role |
|---|---|---|
| Backend | C++17 + cpp-httplib | HTTP server, all endpoints, matchmaking engine |
| Frontend | HTML5, CSS3, Vanilla JS | Live UI, queue visualization, match display |
| Build | g++ MinGW, build.bat | One-command compile and launch |
| Data | In-memory + CSV input | Queue state, match history, bulk player loading |
MatchKernel/
│
├── backend/
│ ├── main.cpp ← HTTP server and all endpoints
│ ├── httplib.h ← single-header server library
│ ├── Player.h ← Player struct and rank enums
│ ├── Match.h ← Match struct
│ ├── MatchmakingSystem.h ← core matchmaking engine
│ ├── JsonHelper.h ← JSON serialization
│ └── Utils.h ← string to enum parsers
│
├── frontend/
│ ├── index.html ← UI layout
│ ├── style.css ← dark theme styling
│ └── script.js ← API calls and DOM rendering
│
├── sample_data.csv ← demo player data
├── build.bat ← one command build and launch
├── TECHNICAL_DOCS.md
├── API_REFERENCE.md
└── README.md
// Phase 1 — Same-rank matching with sub-rank proximity priority
for(int i = 0; i < totalRanks; i++) {
if(rankQueues[i].size() < 2) continue;
// Convert queue to vector for flexible access
std::vector<Player> bucket;
while(!rankQueues[i].empty()) {
bucket.push_back(rankQueues[i].front());
rankQueues[i].pop();
}
std::vector<bool> matched(bucket.size(), false);
for(int j = 0; j < (int)bucket.size(); j++) {
if(matched[j]) continue;
int bestIndex = -1;
int bestDistance = INT_MAX;
// Search all unmatched players after j
for(int k = j + 1; k < (int)bucket.size(); k++) {
if(matched[k]) continue;
int distance = abs(static_cast<int>(bucket[j].subRank) -
static_cast<int>(bucket[k].subRank));
if(distance < bestDistance) {
bestDistance = distance;
bestIndex = k;
}
}
// If a valid match was found
if(bestIndex != -1) {
matched[j] = true;
matched[bestIndex] = true;
Match m(++matchIdCounter, bucket[j], bucket[bestIndex]);
formedMatches.push_back(m);
matchesThisRound++;
std::cout << "MATCHED (same-rank, sub-priority): ";
m.display();
}
}
// Push unmatched players back into queue
for(int j = 0; j < (int)bucket.size(); j++) {
if(!matched[j]) {
rankQueues[i].push(bucket[j]);
}
}
}
// Phase 3 — Expansion: wait cycles unlock cross-rank matching
// For each rank bucket, check if front player can expand
for(int i = 0; i < totalRanks; i++){
if(rankQueues[i].empty()) continue;
Player p1 = rankQueues[i].front();
int radius = p1.expansionRadius();
if(radius == 0) continue;
// Search adjacent buckets within radius
bool matched = false;
for(int offset = 1; offset <= radius && !matched; offset++){
// Check bucket above
int above = i + offset;
if(above < totalRanks && !rankQueues[above].empty()){
rankQueues[i].pop();
Player p2 = rankQueues[above].front();
rankQueues[above].pop();
Match m(++matchIdCounter, p1, p2);
formedMatches.push_back(m);
matchesThisRound++;
matched = true;
std::cout << "Matched (EXPANDED):";
m.display();
}
// Check bucket below
int below = i - offset;
if(!matched && below >= 0 && !rankQueues[below].empty()){
rankQueues[i].pop();
Player p2 = rankQueues[below].front();
rankQueues[below].pop();
Match m(++matchIdCounter, p1, p2);
formedMatches.push_back(m);
matchesThisRound++;
matched = true;
std::cout << "Matched (EXPANDED):";
m.display();
}
}
}
| Method | Endpoint | What It Does |
|---|---|---|
GET |
/queue-state |
Returns current queue as JSON |
GET |
/matches |
Returns all formed matches |
GET |
/stats |
Total players and match count |
POST |
/add-player |
Adds one player to the queue |
POST |
/matchmake |
Triggers one full matchmaking pass |
POST |
/load-file |
Bulk loads players from CSV |
# Add a single player
curl -X POST http://localhost:8080/add-player \
-d "username=Ace&rank=Gold&subrank=II"
# Bulk load from CSV
curl -X POST http://localhost:8080/load-file \
--data-binary @sample_data.csvname,rank,subrank
Ace,Gold,II
Zoro,Gold,I
Levi,Platinum,III
Mikasa,Silver,I| Field | Valid Values |
|---|---|
| rank | Bronze · Silver · Gold · Platinum · Diamond |
| subrank | I · II · III |
Requirements: Windows 10+ · MinGW g++ with C++17 support
# Clone
git clone https://github.com/MuhammadAhmadHamim/MatchKernel.git
# Build and launch — one command does everything
.\build.batServer compiles, starts on http://localhost:8080, and the browser opens automatically.
1. UI loads → rank queues display automatically
2. Add players manually OR upload sample_data.csv
3. Watch rank queues populate in real time
4. Hit Run Matchmaking → matches appear instantly
5. Cross-rank matches flagged with a purple badge
6. Run multiple rounds → watch expansion logic activate
| Structure | Where It Appears |
|---|---|
| Queue | Per-rank player queues — core of the matchmaking engine |
| Vector | Match history, unmatched player tracking |
| Vector of Queues | Rank-to-queue mapping for O(1) queue access |
| Enum | Rank and subrank type safety across the entire system |
| Struct | Player and Match as clean, typed data containers |
Every matchmaking system in every game you've ever played runs on some version of this logic — ranked queues, wait time expansion, cross-tier fallback. MatchKernel is that logic stripped to its core, built from scratch in C++, and wired to a live UI that shows it working in real time.
Data structures aren't theory here. They're the engine.