From 596273012089d245204a5e652c4a0a15f02a41ca Mon Sep 17 00:00:00 2001 From: bumpsoo Date: Tue, 17 Feb 2026 14:44:40 +0000 Subject: [PATCH] init --- .gitignore | 2 ++ CMakeLists.txt | 24 ++++++++++++++ README.md | 58 +++++++++++++++++++++++++++++++++ compile_commands.json | 1 + include/network.h | 26 +++++++++++++++ include/riot.h | 42 ++++++++++++++++++++++++ schema.sql | 14 ++++++++ setup.sh | 9 +++++ src/main.cpp | 34 +++++++++++++++++++ src/network.cpp | 68 ++++++++++++++++++++++++++++++++++++++ src/riot.cpp | 76 +++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 354 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 120000 compile_commands.json create mode 100644 include/network.h create mode 100644 include/riot.h create mode 100644 schema.sql create mode 100644 setup.sh create mode 100644 src/main.cpp create mode 100644 src/network.cpp create mode 100644 src/riot.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7194ea7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.cache +build diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..78a95d9 --- /dev/null +++ b/CMakeLists.txt @@ -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 +) \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f73d455 --- /dev/null +++ b/README.md @@ -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 +``` + diff --git a/compile_commands.json b/compile_commands.json new file mode 120000 index 0000000..25eb4b2 --- /dev/null +++ b/compile_commands.json @@ -0,0 +1 @@ +build/compile_commands.json \ No newline at end of file diff --git a/include/network.h b/include/network.h new file mode 100644 index 0000000..3d8e910 --- /dev/null +++ b/include/network.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace asio = boost::asio; + +class NetworkService : public std::enable_shared_from_this { +public: + NetworkService(asio::io_context &, asio::ip::tcp::endpoint, int); + + void start(); + +private: + asio::io_context &ctx_; + asio::ip::tcp::endpoint endpoint_; + std::vector threads_; + asio::executor_work_guard work_guard_; + + asio::awaitable listen(); + + asio::awaitable session(asio::ip::tcp::socket); +}; \ No newline at end of file diff --git a/include/riot.h b/include/riot.h new file mode 100644 index 0000000..0bfc089 --- /dev/null +++ b/include/riot.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include +#include +#include + +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> 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 diff --git a/schema.sql b/schema.sql new file mode 100644 index 0000000..37536c1 --- /dev/null +++ b/schema.sql @@ -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) +); \ No newline at end of file diff --git a/setup.sh b/setup.sh new file mode 100644 index 0000000..93b38d5 --- /dev/null +++ b/setup.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -e + +cmake -B build -S . + +ln -sf build/compile_commands.json compile_commands.json + +echo "Setup completed" \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..5a07fdc --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,34 @@ +#include "network.h" +#include "riot.h" +#include +#include +#include +#include +#include + +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( + 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; +} \ No newline at end of file diff --git a/src/network.cpp b/src/network.cpp new file mode 100644 index 0000000..063010a --- /dev/null +++ b/src/network.cpp @@ -0,0 +1,68 @@ +#include "network.h" +#include +#include +#include +#include +#include +#include + +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 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 { + co_await self->session(std::move(socket)); + }, + asio::detached); + } +} + +asio::awaitable NetworkService::session(asio::ip::tcp::socket socket) { + try { + beast::flat_buffer buffer; + + beast::http::request req; + co_await beast::http::async_read(socket, buffer, req, asio::use_awaitable); + + beast::http::response 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; +} \ No newline at end of file diff --git a/src/riot.cpp b/src/riot.cpp new file mode 100644 index 0000000..7d9d039 --- /dev/null +++ b/src/riot.cpp @@ -0,0 +1,76 @@ +#include "riot.h" +#include +#include +#include +#include +#include +#include +#include + +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> +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{}; +} + +} // namespace riot