From e47f1bb6f93e90b088fc5d8e470320d3fa810725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=B5=E5=91=B5=E3=81=A7=E3=81=99?= <51957264+shoucandanghehe@users.noreply.github.com> Date: Wed, 8 May 2024 18:26:08 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=E6=B8=B2=E6=9F=93=20=E5=8E=86?= =?UTF-8?q?=E5=8F=B2tr=20=E6=9B=B2=E7=BA=BF=E5=9B=BE=20(#312)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io_data_processor/constant.py | 2 + .../io_data_processor/processor.py | 136 ++++++++++++++++-- .../templates/data.j2.html | 16 ++- nonebot_plugin_tetris_stats/utils/render.py | 4 +- 4 files changed, 142 insertions(+), 16 deletions(-) diff --git a/nonebot_plugin_tetris_stats/game_data_processor/io_data_processor/constant.py b/nonebot_plugin_tetris_stats/game_data_processor/io_data_processor/constant.py index 1aab01d..94a9c8f 100644 --- a/nonebot_plugin_tetris_stats/game_data_processor/io_data_processor/constant.py +++ b/nonebot_plugin_tetris_stats/game_data_processor/io_data_processor/constant.py @@ -23,3 +23,5 @@ RANK_PERCENTILE: dict[Rank, float] = { 'd+': 97.5, 'd': 100, } +TR_MIN = 0 +TR_MAX = 25000 diff --git a/nonebot_plugin_tetris_stats/game_data_processor/io_data_processor/processor.py b/nonebot_plugin_tetris_stats/game_data_processor/io_data_processor/processor.py index 32b87d7..6443a36 100644 --- a/nonebot_plugin_tetris_stats/game_data_processor/io_data_processor/processor.py +++ b/nonebot_plugin_tetris_stats/game_data_processor/io_data_processor/processor.py @@ -3,11 +3,12 @@ from collections import defaultdict from collections.abc import Callable from datetime import datetime, timedelta, timezone from hashlib import md5, sha512 -from math import floor +from math import ceil, floor from re import match from statistics import mean -from typing import Literal +from typing import Literal, NamedTuple from urllib.parse import urlunparse +from zoneinfo import ZoneInfo from aiofiles import open from nonebot import get_driver @@ -23,6 +24,7 @@ from typing_extensions import override from zstandard import ZstdCompressor from ...db import BindStatus, create_or_update_bind +from ...db.models import HistoricalData from ...utils.avatar import get_avatar from ...utils.exception import MessageFormatError, RequestError, WhatTheFuckError from ...utils.host import HostPage, get_self_netloc @@ -32,7 +34,7 @@ from ...utils.retry import retry from ...utils.screenshot import screenshot from .. import Processor as ProcessorMeta from .cache import Cache -from .constant import BASE_URL, GAME_TYPE, RANK_PERCENTILE +from .constant import BASE_URL, GAME_TYPE, RANK_PERCENTILE, TR_MAX, TR_MIN from .model import IORank from .schemas.base import FailedModel from .schemas.league_all import LeagueAll @@ -124,6 +126,122 @@ class Processor(ProcessorMeta): sprint = user_records.data.records.sprint blitz = user_records.data.records.blitz if isinstance(league, RatedLeague) and league.vs is not None: + today = datetime.now(ZoneInfo('Asia/Shanghai')).replace(hour=0, minute=0, second=0, microsecond=0) + forward = timedelta(days=9) + start_time = (today - forward).astimezone(UTC) + async with get_session() as session: + historical_data = ( + await session.scalars( + select(HistoricalData) + .where(HistoricalData.trigger_time >= start_time) + .where(HistoricalData.game_platform == GAME_TYPE) + .where(HistoricalData.user_unique_identifier == self.user.unique_identifier) + ) + ).all() + extra = ( + await session.scalars( + select(HistoricalData) + .where(HistoricalData.game_platform == GAME_TYPE) + .where(HistoricalData.user_unique_identifier == self.user.unique_identifier) + .order_by(HistoricalData.id.desc()) + .where(HistoricalData.id < min([i.id for i in historical_data])) + .limit(1) + ) + ).one_or_none() + if extra is not None: + historical_data = list(historical_data) + historical_data.append(extra) + + class HistoricalTr(NamedTuple): + time: datetime + tr: float + + histories = [ + HistoricalTr( + time=i.processed_data.user_info.cache.cached_at.astimezone(ZoneInfo('Asia/Shanghai')), + tr=i.processed_data.user_info.data.user.league.rating, + ) + for i in historical_data + if isinstance(i.processed_data, ProcessedData) + and i.processed_data.user_info is not None + and isinstance(i.processed_data.user_info.data.user.league, RatedLeague) + ] + + def get_specified_point( + previous_point: HistoricalTr, behind_point: HistoricalTr, point_time: datetime + ) -> HistoricalTr: + """根据给出的 previous_point 和 behind_point, 推算 point_time 点处的数据 + + Args: + previous_point (HistoricalTr): 前面的数据点 + behind_point (HistoricalTr): 后面的数据点 + point_time (datetime): 要推算的点的位置 + + Returns: + HistoricalTr: 要推算的点的数据 + """ + # 求两个点的斜率 + slope = (behind_point.tr - previous_point.tr) / ( + datetime.timestamp(behind_point.time) - datetime.timestamp(previous_point.time) + ) + return HistoricalTr( + time=point_time, + tr=previous_point.tr + + slope * (datetime.timestamp(point_time) - datetime.timestamp(previous_point.time)), + ) + + # 按照时间排序 + histories = sorted(histories, key=lambda x: x.time) + for index, value in enumerate(histories): + # 在历史记录里找有没有今天0点后的数据 + if value.time > today: + histories = histories[:index] + [ + get_specified_point(histories[index - 1], histories[index], today.replace(microsecond=1000)) + ] + break + else: + histories.append( + get_specified_point( + histories[-1], + HistoricalTr(user_info.cache.cached_at, league.rating), + today.replace(microsecond=1000), + ) + ) + if histories[0].time < (today - forward): + histories[0] = get_specified_point( + histories[0], + histories[1], + today - forward, + ) + + else: + histories.insert(0, HistoricalTr((today - forward), histories[0].tr)) + + def get_value_bounds(values: list[int | float]) -> tuple[int, int]: + value_max = 10 * ceil(max(values) / 10) + value_min = 10 * floor(min(values) / 10) + return value_max, value_min + + def get_split(value_max: int, value_min: int) -> tuple[int, int]: + offset = 0 + overflow = 0 + + while True: + if (new_max_value := value_max + offset + overflow) > TR_MAX: + overflow -= 1 + continue + if (new_min_value := value_min - offset + overflow) < TR_MIN: + overflow += 1 + continue + if ((new_max_value - new_min_value) / 40).is_integer(): + split_value = int((value_max + offset - (value_min - offset)) / 4) + break + offset += 1 + return split_value, offset + overflow + + value_max, value_min = get_value_bounds([i.tr for i in histories]) + split_value, offset = get_split(value_max, value_min) + if sprint.record is None: sprint_value = 'N/A' else: @@ -160,11 +278,11 @@ class Processor(ProcessorMeta): vs=league.vs, sprint=sprint_value, blitz=blitz_value, - data=[[0, 0]], - split_value=0, - value_max=0, - value_min=0, - offset=0, + data=[[int(datetime.timestamp(time) * 1000), tr] for time, tr in histories], + split_value=split_value, + offset=offset, + value_max=value_max, + value_min=value_min, app=(app := (league.apm / (60 * league.pps))), dsps=(dsps := ((league.vs / 100) - (league.apm / 60))), dspp=(dspp := (dsps / league.pps)), @@ -177,7 +295,7 @@ class Processor(ProcessorMeta): urlunparse(('http', get_self_netloc(), f'/host/page/{page_hash}.html', '', '', '')) ) ) - # call back + # fallback ret_message = '' if isinstance(league, NeverPlayedLeague): ret_message += f'用户 {user_name} 没有排位统计数据' diff --git a/nonebot_plugin_tetris_stats/templates/data.j2.html b/nonebot_plugin_tetris_stats/templates/data.j2.html index 755b35e..4810b90 100644 --- a/nonebot_plugin_tetris_stats/templates/data.j2.html +++ b/nonebot_plugin_tetris_stats/templates/data.j2.html @@ -38,14 +38,14 @@
- {#
+
Tetra Rating (TR) {{TR}} 

(#{{global_rank}})

-
#} +
Multiplayer Stats
@@ -111,7 +111,8 @@ xAxis: { type: 'time', - minInterval: 3600 * 48 * 1000, + minInterval: 3600 * 24 * 1000, + maxInterval: 3600 * 24 * 1000, axisTick: { show: false, }, @@ -133,12 +134,17 @@ } switch (index) { case 0: - case 6: + case 1: + case 3: + case 5: + case 7: + case 9: + case 11: ret = ''; break; default: lst = format_date(); - if (index === 5) { + if (index === 10) { ret = '{last_month|' + lst[0] + '}\n{last_day|' + lst[1] + '}'; break; } diff --git a/nonebot_plugin_tetris_stats/utils/render.py b/nonebot_plugin_tetris_stats/utils/render.py index c9b4fe7..b9be555 100644 --- a/nonebot_plugin_tetris_stats/utils/render.py +++ b/nonebot_plugin_tetris_stats/utils/render.py @@ -50,11 +50,11 @@ async def render( vs: str | float, sprint: str, blitz: str, - data: list[list[int]], + data: list[list[int | float]], split_value: int, + offset: int, value_max: int, value_min: int, - offset: int, app: str | float, dsps: str | float, dspp: str | float,