init
This commit is contained in:
commit
5962730120
11 changed files with 354 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
.cache
|
||||
build
|
||||
24
CMakeLists.txt
Normal file
24
CMakeLists.txt
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(LolAnalytics VERSION 1.0)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||
|
||||
find_package(Boost REQUIRED COMPONENTS system)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
add_executable(lol_analytics
|
||||
src/main.cpp
|
||||
src/riot.cpp
|
||||
src/network.cpp
|
||||
)
|
||||
|
||||
target_include_directories(lol_analytics PRIVATE include)
|
||||
target_link_libraries(lol_analytics PRIVATE
|
||||
Boost::system
|
||||
OpenSSL::SSL
|
||||
OpenSSL::Crypto
|
||||
)
|
||||
58
README.md
Normal file
58
README.md
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
# 📊 LoL Bio-Rhythm Analytics
|
||||
|
||||
## 1. Overview
|
||||
A high-performance data analysis engine built with C++ to identify Champion Bio-Rhythms—specific hours of the day when a League of Legends champion's win rate peaks.
|
||||
|
||||
This project analyzes whether timing (e.g., 3:00 PM vs. 3:00 AM) significantly impacts win rates for specific champions by processing large-scale match data from the Riot Games API.
|
||||
|
||||
## 2. Architecture
|
||||
|
||||
- C++ + Boost(Asio, Beast)
|
||||
- MySQL (might be changed to another one)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Admin as User/Admin
|
||||
participant Sys as lol_analytics
|
||||
participant Riot as Riot API
|
||||
participant DB as DB
|
||||
|
||||
%% ==========================================
|
||||
%% API 1: Sync All Users by League
|
||||
%% ==========================================
|
||||
rect rgb(240, 248, 255)
|
||||
Note over Admin, DB: [API 1] Fetch all users by league
|
||||
Admin->>Sys: Request: Sync League Users (League ID)
|
||||
|
||||
loop Page 1 to End (Until empty)
|
||||
Sys->>Riot: GET /lol/league/v4/entries/... (page=N)
|
||||
Riot-->>Sys: User List Data
|
||||
Sys->>DB: Save/Update Users (Summoner ID, PUUID, etc.)
|
||||
end
|
||||
Sys-->>Admin: User Sync Completed
|
||||
end
|
||||
|
||||
%% ==========================================
|
||||
%% API 2: Sync Matches within Time X ~ Y
|
||||
%% ==========================================
|
||||
rect rgb(255, 245, 238)
|
||||
Note over Admin, DB: [API 2] Fetch matches within time X ~ Y
|
||||
Admin->>Sys: Request: Fetch Matches (Time X ~ Y)
|
||||
|
||||
Sys->>DB: Query User IDs (PUUIDs)
|
||||
DB-->>Sys: PUUID List
|
||||
|
||||
loop For each User
|
||||
Sys->>Riot: GET /lol/match/v5/matches/by-puuid/{puuid}?startTime=X&endTime=Y
|
||||
Riot-->>Sys: Match ID List
|
||||
|
||||
loop For each Match ID
|
||||
Sys->>Riot: GET /lol/match/v5/matches/{matchId}
|
||||
Riot-->>Sys: Match Detail Data
|
||||
Sys->>DB: Save Match Result (win/loss, timestamp, champion)
|
||||
end
|
||||
end
|
||||
Sys-->>Admin: Match Sync Completed
|
||||
end
|
||||
```
|
||||
|
||||
1
compile_commands.json
Symbolic link
1
compile_commands.json
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
build/compile_commands.json
|
||||
26
include/network.h
Normal file
26
include/network.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/executor_work_guard.hpp>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
class NetworkService : public std::enable_shared_from_this<NetworkService> {
|
||||
public:
|
||||
NetworkService(asio::io_context &, asio::ip::tcp::endpoint, int);
|
||||
|
||||
void start();
|
||||
|
||||
private:
|
||||
asio::io_context &ctx_;
|
||||
asio::ip::tcp::endpoint endpoint_;
|
||||
std::vector<std::jthread> threads_;
|
||||
asio::executor_work_guard<asio::io_context::executor_type> work_guard_;
|
||||
|
||||
asio::awaitable<void> listen();
|
||||
|
||||
asio::awaitable<void> session(asio::ip::tcp::socket);
|
||||
};
|
||||
42
include/riot.h
Normal file
42
include/riot.h
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#pragma once
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
namespace riot {
|
||||
|
||||
struct LeagueEntry {
|
||||
std::string puuid;
|
||||
};
|
||||
|
||||
enum class Division { I, II, III, IV };
|
||||
|
||||
enum class Tier { BRONZE, SILVER, GOLD, PLATINUM, DIAMOND };
|
||||
|
||||
enum class Region { KR };
|
||||
|
||||
class Client {
|
||||
public:
|
||||
Client(asio::io_context &, std::string, Region = Region::KR);
|
||||
|
||||
asio::awaitable<std::vector<LeagueEntry>> GetIdsInTier(Division, Tier,
|
||||
uint8_t) const;
|
||||
|
||||
private:
|
||||
asio::io_context &ctx_;
|
||||
std::string api_key_;
|
||||
Region region_;
|
||||
|
||||
static constexpr std::string_view BASE_URL = "api.riotgames.com";
|
||||
|
||||
std::string divisionToString(Division) const;
|
||||
std::string tierToString(Tier) const;
|
||||
std::string regionToString(Region) const;
|
||||
};
|
||||
|
||||
} // namespace riot
|
||||
14
schema.sql
Normal file
14
schema.sql
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
CREATE DATABASE IF NOT EXISTS lol_analytics;
|
||||
USE lol_analytics;
|
||||
-- TODO: adjust size
|
||||
CREATE TABLE IF NOT EXISTS user_ids (
|
||||
puuid VARCHAR(256) PRIMARY KEY,
|
||||
tier VARCHAR(256) NOT NULL,
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS matches (
|
||||
match_id VARCHAR(256) PRIMARY KEY,
|
||||
puuid VARCHAR(256) NOT NULL,
|
||||
tier VARCHAR(256) NOT NULL,
|
||||
FOREIGN KEY (puuid) REFERENCES user_ids(puuid)
|
||||
);
|
||||
9
setup.sh
Normal file
9
setup.sh
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
cmake -B build -S .
|
||||
|
||||
ln -sf build/compile_commands.json compile_commands.json
|
||||
|
||||
echo "Setup completed"
|
||||
34
src/main.cpp
Normal file
34
src/main.cpp
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#include "network.h"
|
||||
#include "riot.h"
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/awaitable.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
int main() {
|
||||
uint32_t thread_cnt = std::thread::hardware_concurrency();
|
||||
if (thread_cnt == 0) {
|
||||
thread_cnt = 1;
|
||||
}
|
||||
|
||||
boost::asio::io_context ctx;
|
||||
|
||||
const std::string address = "0.0.0.0";
|
||||
const uint16_t port = 8080;
|
||||
|
||||
try {
|
||||
|
||||
auto service = std::make_shared<NetworkService>(
|
||||
ctx, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port),
|
||||
thread_cnt);
|
||||
service->start();
|
||||
|
||||
ctx.run();
|
||||
|
||||
} catch (const std::exception &e) {
|
||||
std::cerr << "Error: " << e.what() << "\n";
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
68
src/network.cpp
Normal file
68
src/network.cpp
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
#include "network.h"
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/beast.hpp>
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/http.hpp>
|
||||
#include <iostream>
|
||||
|
||||
namespace beast = boost::beast;
|
||||
namespace asio = boost::asio;
|
||||
|
||||
NetworkService::NetworkService(asio::io_context &ctx,
|
||||
asio::ip::tcp::endpoint endpoint, int thread_cnt)
|
||||
: ctx_(ctx), endpoint_(endpoint), work_guard_(ctx.get_executor()) {
|
||||
threads_.reserve(thread_cnt);
|
||||
for (int i = 0; i < thread_cnt; ++i) {
|
||||
threads_.emplace_back([ctx = &ctx_]() { ctx->run(); });
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkService::start() {
|
||||
asio::co_spawn(
|
||||
asio::make_strand(ctx_),
|
||||
[self = shared_from_this()]() { return self->listen(); }, asio::detached);
|
||||
}
|
||||
|
||||
asio::awaitable<void> NetworkService::listen() {
|
||||
auto executor = co_await asio::this_coro::executor;
|
||||
asio::ip::tcp::acceptor acceptor(executor, endpoint_);
|
||||
|
||||
std::cout << "Server listening on port: " << endpoint_.port() << std::endl;
|
||||
|
||||
while (true) {
|
||||
asio::ip::tcp::socket socket =
|
||||
co_await acceptor.async_accept(asio::use_awaitable);
|
||||
|
||||
asio::co_spawn(
|
||||
asio::make_strand(ctx_),
|
||||
[self = shared_from_this(),
|
||||
socket = std::move(socket)]() mutable -> asio::awaitable<void> {
|
||||
co_await self->session(std::move(socket));
|
||||
},
|
||||
asio::detached);
|
||||
}
|
||||
}
|
||||
|
||||
asio::awaitable<void> NetworkService::session(asio::ip::tcp::socket socket) {
|
||||
try {
|
||||
beast::flat_buffer buffer;
|
||||
|
||||
beast::http::request<beast::http::string_body> req;
|
||||
co_await beast::http::async_read(socket, buffer, req, asio::use_awaitable);
|
||||
|
||||
beast::http::response<beast::http::string_body> res{beast::http::status::ok,
|
||||
req.version()};
|
||||
res.set(beast::http::field::content_type, "text/plain");
|
||||
res.keep_alive(req.keep_alive());
|
||||
res.body() = "Bye, World!";
|
||||
res.prepare_payload();
|
||||
|
||||
co_await beast::http::async_write(socket, res, asio::use_awaitable);
|
||||
|
||||
} catch (std::exception &e) {
|
||||
std::cerr << "Session error: " << e.what() << "\n";
|
||||
}
|
||||
|
||||
co_return;
|
||||
}
|
||||
76
src/riot.cpp
Normal file
76
src/riot.cpp
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
#include "riot.h"
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/http.hpp>
|
||||
#include <boost/beast/ssl.hpp>
|
||||
#include <boost/beast/version.hpp>
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace beast = boost::beast;
|
||||
namespace http = beast::http;
|
||||
|
||||
namespace riot {
|
||||
|
||||
std::string Client::divisionToString(Division division) const {
|
||||
switch (division) {
|
||||
case Division::I:
|
||||
return "I";
|
||||
case Division::II:
|
||||
return "II";
|
||||
case Division::III:
|
||||
return "III";
|
||||
case Division::IV:
|
||||
return "IV";
|
||||
default:
|
||||
throw std::invalid_argument("invalid division");
|
||||
}
|
||||
}
|
||||
|
||||
std::string Client::tierToString(Tier tier) const {
|
||||
switch (tier) {
|
||||
case Tier::BRONZE:
|
||||
return "BRONZE";
|
||||
case Tier::SILVER:
|
||||
return "SILVER";
|
||||
case Tier::GOLD:
|
||||
return "GOLD";
|
||||
case Tier::PLATINUM:
|
||||
return "PLATINUM";
|
||||
case Tier::DIAMOND:
|
||||
return "DIAMOND";
|
||||
default:
|
||||
throw std::invalid_argument("invalid tier");
|
||||
}
|
||||
}
|
||||
|
||||
std::string Client::regionToString(Region region) const {
|
||||
switch (region) {
|
||||
case Region::KR:
|
||||
return "kr";
|
||||
default:
|
||||
throw std::invalid_argument("invalid region");
|
||||
}
|
||||
}
|
||||
|
||||
Client::Client(boost::asio::io_context &ctx, std::string key, Region region)
|
||||
: ctx_(ctx), api_key_(std::move(key)), region_(region) {}
|
||||
|
||||
asio::awaitable<std::vector<LeagueEntry>>
|
||||
Client::GetIdsInTier(Division division, Tier tier, uint8_t page) const {
|
||||
auto div_str = divisionToString(division);
|
||||
auto tier_str = tierToString(tier);
|
||||
auto region_str = regionToString(region_);
|
||||
|
||||
auto request_url =
|
||||
std::format("https://{}.{}/lol/league/v4/entries/RANKED_SOLO_5x5/{}/"
|
||||
"{}?api_key={}&page={}",
|
||||
region_str, BASE_URL, tier_str, div_str, api_key_, page);
|
||||
|
||||
std::cout << "[DEBUG] Fetching from: " << request_url << std::endl;
|
||||
|
||||
// TODO: Implement JSON parsing
|
||||
co_return std::vector<LeagueEntry>{};
|
||||
}
|
||||
|
||||
} // namespace riot
|
||||
Loading…
Add table
Add a link
Reference in a new issue