feat(playlist): support leaderboard

**UNTESTED**
This commit is contained in:
MingxuanGame
2025-08-07 14:52:02 +00:00
parent 18d16e2542
commit bc2961de10
6 changed files with 175 additions and 23 deletions

View File

@@ -15,7 +15,7 @@ from .lazer_user import (
User,
UserResp,
)
from .playlist_attempts import ItemAttemptsCount
from .playlist_attempts import ItemAttemptsCount, ItemAttemptsResp
from .playlist_best_score import PlaylistBestScore
from .playlists import Playlist, PlaylistResp
from .pp_best_score import PPBestScore
@@ -50,6 +50,7 @@ __all__ = [
"DailyChallengeStatsResp",
"FavouriteBeatmapset",
"ItemAttemptsCount",
"ItemAttemptsResp",
"MultiplayerScores",
"OAuthToken",
"PPBestScore",

View File

@@ -1,9 +1,116 @@
from sqlmodel import Field, SQLModel
from .lazer_user import User, UserResp
from .playlist_best_score import PlaylistBestScore
from sqlmodel import (
BigInteger,
Column,
Field,
ForeignKey,
Relationship,
SQLModel,
col,
func,
select,
)
from sqlmodel.ext.asyncio.session import AsyncSession
class ItemAttemptsCount(SQLModel, table=True):
__tablename__ = "item_attempts_count" # pyright: ignore[reportAssignmentType]
id: int = Field(foreign_key="room_playlists.db_id", primary_key=True, index=True)
class ItemAttemptsCountBase(SQLModel):
room_id: int = Field(foreign_key="rooms.id", index=True)
attempts: int = Field(default=0)
passed: int = Field(default=0)
completed: int = Field(default=0)
user_id: int = Field(
sa_column=Column(BigInteger, ForeignKey("lazer_users.id"), index=True)
)
accuracy: float = 0.0
pp: float = 0
total_score: int = 0
class ItemAttemptsCount(ItemAttemptsCountBase, table=True):
__tablename__ = "item_attempts_count" # pyright: ignore[reportAssignmentType]
id: int | None = Field(
default=None, foreign_key="room_playlists.db_id", primary_key=True
)
user: User = Relationship()
async def get_position(self, session: AsyncSession) -> int:
rownum = (
func.row_number()
.over(
partition_by=col(ItemAttemptsCountBase.room_id),
order_by=col(ItemAttemptsCountBase.total_score).desc(),
)
.label("rn")
)
subq = select(ItemAttemptsCountBase, rownum).subquery()
stmt = select(subq.c.rn).where(subq.c.user_id == self.user_id)
result = await session.exec(stmt)
return result.one()
async def update(self, session: AsyncSession):
playlist_scores = (
await session.exec(
select(PlaylistBestScore).where(
PlaylistBestScore.room_id == self.room_id,
PlaylistBestScore.user_id == self.user_id,
)
)
).all()
self.attempts = sum(score.attempts for score in playlist_scores)
self.total_score = sum(score.total_score for score in playlist_scores)
self.pp = sum(score.score.pp for score in playlist_scores)
self.completed = len(playlist_scores)
self.accuracy = (
sum(score.score.accuracy * score.attempts for score in playlist_scores)
/ self.completed
if self.completed > 0
else 0.0
)
await session.commit()
await session.refresh(self)
@classmethod
async def get_or_create(
cls,
room_id: int,
user_id: int,
session: AsyncSession,
) -> "ItemAttemptsCount":
item_attempts = await session.exec(
select(cls).where(
cls.room_id == room_id,
cls.user_id == user_id,
)
)
item_attempts = item_attempts.first()
if item_attempts is None:
item_attempts = cls(room_id=room_id, user_id=user_id)
session.add(item_attempts)
await session.commit()
await session.refresh(item_attempts)
await item_attempts.update(session)
return item_attempts
class ItemAttemptsResp(ItemAttemptsCountBase):
user: UserResp | None = None
position: int | None = None
@classmethod
async def from_db(
cls,
item_attempts: ItemAttemptsCount,
session: AsyncSession,
include: list[str] = [],
) -> "ItemAttemptsResp":
resp = cls.model_validate(item_attempts)
resp.user = await UserResp.from_db(
item_attempts.user,
session=session,
include=["statistics", "team", "daily_challenge_user_stats"],
)
if "position" in include:
resp.position = await item_attempts.get_position(session)
return resp

View File

@@ -32,6 +32,7 @@ class PlaylistBestScore(SQLModel, table=True):
room_id: int = Field(foreign_key="rooms.id", index=True)
playlist_id: int = Field(foreign_key="room_playlists.id", index=True)
total_score: int = Field(default=0, sa_column=Column(BigInteger))
attempts: int = Field(default=0) # playlist
user: User = Relationship()
score: "Score" = Relationship(
@@ -72,6 +73,7 @@ async def process_playlist_best_score(
else:
previous.score_id = score_id
previous.total_score = total_score
previous.attempts += 1
await session.commit()
await redis.decr(f"multiplayer:{room_id}:gameplay:players")

View File

@@ -11,7 +11,6 @@ from app.models.room import (
)
from .lazer_user import User, UserResp
from .playlist_attempts import ItemAttemptsCount
from .playlists import Playlist, PlaylistResp
from sqlmodel import (
@@ -67,13 +66,6 @@ class Room(RoomBase, table=True):
"overlaps": "room",
}
)
# playlist_item_attempts: list["ItemAttemptsCount"] = Relationship(
# sa_relationship_kwargs={
# "lazy": "joined",
# "cascade": "all, delete-orphan",
# "primaryjoin": "ItemAttemptsCount.room_id == Room.id",
# }
# )
class RoomResp(RoomBase):
@@ -84,7 +76,6 @@ class RoomResp(RoomBase):
playlist_item_stats: RoomPlaylistItemStats | None = None
difficulty_range: RoomDifficultyRange | None = None
current_playlist_item: PlaylistResp | None = None
playlist_item_attempts: list[ItemAttemptsCount] = []
@classmethod
async def from_db(cls, room: Room) -> "RoomResp":
@@ -112,7 +103,6 @@ class RoomResp(RoomBase):
resp.playlist_item_stats = stats
resp.difficulty_range = difficulty_range
resp.current_playlist_item = resp.playlist[-1] if resp.playlist else None
# resp.playlist_item_attempts = room.playlist_item_attempts
return resp