Add stats for cf

feature/currency-improvements
stijndcl 2024-03-03 17:57:07 +01:00
parent de7b5cd960
commit a051423203
8 changed files with 230 additions and 3 deletions

View File

@ -0,0 +1,76 @@
"""Currency updates
Revision ID: 8f7e8384cec3
Revises: 09128b6e34dd
Create Date: 2024-03-03 17:48:36.205106
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "8f7e8384cec3"
down_revision = "09128b6e34dd"
branch_labels = None
depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"cf_stats",
sa.Column("cf_stats_id", sa.Integer(), nullable=False),
sa.Column("games_won", sa.BigInteger(), server_default="0", nullable=False),
sa.Column("games_lost", sa.BigInteger(), server_default="0", nullable=False),
sa.Column("dinks_won", sa.BigInteger(), server_default="0", nullable=False),
sa.Column("dinks_lost", sa.BigInteger(), server_default="0", nullable=False),
sa.Column("user_id", sa.BigInteger(), nullable=False),
sa.ForeignKeyConstraint(
["user_id"],
["users.user_id"],
),
sa.PrimaryKeyConstraint("cf_stats_id"),
)
op.create_table(
"jail",
sa.Column("jail_entry_i", sa.Integer(), nullable=False),
sa.Column("user_id", sa.BigInteger(), nullable=False),
sa.Column("until", sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(
["user_id"],
["users.user_id"],
),
sa.PrimaryKeyConstraint("jail_entry_i"),
)
op.create_table(
"savings",
sa.Column("savings_id", sa.Integer(), nullable=False),
sa.Column("user_id", sa.BigInteger(), nullable=False),
sa.Column("saved", sa.BigInteger(), server_default="0", nullable=False),
sa.Column("daily_minimum", sa.BigInteger(), server_default="0", nullable=False),
sa.ForeignKeyConstraint(
["user_id"],
["users.user_id"],
),
sa.PrimaryKeyConstraint("savings_id"),
)
with op.batch_alter_table("bank", schema=None) as batch_op:
batch_op.drop_column("invested")
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("bank", schema=None) as batch_op:
batch_op.add_column(
sa.Column(
"invested", sa.BIGINT(), server_default=sa.text("'0'::bigint"), autoincrement=False, nullable=False
)
)
op.drop_table("savings")
op.drop_table("jail")
op.drop_table("cf_stats")
# ### end Alembic commands ###

View File

@ -0,0 +1,34 @@
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from database.schemas import CFStats
__all__ = ["get_cf_stats", "update_cf_stats"]
async def get_cf_stats(session: AsyncSession, user_id: int) -> CFStats:
"""Get a user's coinflip stats"""
statement = select(CFStats).where(CFStats.user_id == user_id)
result = (await session.execute(statement)).scalar_one_or_none()
if result is None:
result = CFStats(user_id=user_id)
session.add(result)
await session.commit()
await session.refresh(result)
return result
async def update_cf_stats(session: AsyncSession, user_id: int, outcome: int):
"""Update a user's coinflip stats"""
stats = await get_cf_stats(session, user_id)
if outcome < 0:
stats.games_lost += 1
stats.dinks_lost += abs(outcome)
else:
stats.games_won += 1
stats.dinks_won += outcome
session.add(stats)
await session.commit()

View File

@ -15,6 +15,7 @@ __all__ = [
"BankSavings",
"Birthday",
"Bookmark",
"CFStats",
"CommandStats",
"CustomCommand",
"CustomCommandAlias",
@ -106,6 +107,21 @@ class Bookmark(Base):
user: Mapped[User] = relationship(back_populates="bookmarks", uselist=False, lazy="selectin")
class CFStats(Base):
"""A user's coinflipping stats"""
__tablename__ = "cf_stats"
cf_stats_id: Mapped[int] = mapped_column(primary_key=True)
games_won: Mapped[int] = mapped_column(BigInteger, server_default="0", nullable=False)
games_lost: Mapped[int] = mapped_column(BigInteger, server_default="0", nullable=False)
dinks_won: Mapped[int] = mapped_column(BigInteger, server_default="0", nullable=False)
dinks_lost: Mapped[int] = mapped_column(BigInteger, server_default="0", nullable=False)
user_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("users.user_id"))
user: Mapped[User] = relationship(back_populates="cf_stats", uselist=False, lazy="selectin")
class CommandStats(Base):
"""Metrics on how often commands are used"""
@ -349,6 +365,9 @@ class User(Base):
bookmarks: Mapped[List[Bookmark]] = relationship(
back_populates="user", uselist=True, lazy="selectin", cascade="all, delete-orphan"
)
cf_stats: Mapped[CFStats] = relationship(
back_populates="user", uselist=True, lazy="selectin", cascade="all, delete-orphan"
)
command_stats: Mapped[List[CommandStats]] = relationship(
back_populates="user", uselist=True, lazy="selectin", cascade="all, delete-orphan"
)

View File

@ -3,6 +3,7 @@ from typing import Annotated, Optional, Union
from discord.ext import commands
from database.crud.cf_stats import update_cf_stats
from database.crud.currency import gamble_dinks
from didier import Didier
from didier.utils.discord.converters import abbreviated_number
@ -17,6 +18,7 @@ class Gambling(commands.Cog):
def __init__(self, client: Didier):
self.client = client
@commands.max_concurrency(1, commands.BucketType.user, wait=True)
@commands.command(name="coinflip", aliases=["cf", "flip"]) # type: ignore[arg-type]
async def coinflip(
self,
@ -57,8 +59,11 @@ class Gambling(commands.Cog):
async with self.client.postgres_session as session:
received = await gamble_dinks(session, ctx.author.id, amount, 2, won)
if received == 0:
return await ctx.reply("You don't have any Didier Dinks to wager.", mention_author=False)
if received == 0:
return await ctx.reply("You don't have any Didier Dinks to wager.", mention_author=False)
sign = 1 if won else -1
await update_cf_stats(session, ctx.author.id, received * sign)
plural = pluralize("Didier Dink", received)

View File

@ -0,0 +1,27 @@
from discord.ext import commands
from didier import Didier
class Leaderboards(commands.Cog):
"""Cog for various leaderboards"""
client: Didier
def __init__(self, client: Didier):
self.client = client
@commands.hybrid_group(name="leaderboard", aliases=["lb"], invoke_without_command=True)
async def leaderboard(self, ctx: commands.Context):
"""List the top X for a given category"""
# TODO
@leaderboard.command(name="dinks", aliases=["d"])
async def dinks(self, ctx: commands.Context):
"""See the users with the most Didier Dinks"""
# TODO
async def setup(client: Didier):
"""Load the cog"""
await client.add_cog(Leaderboards(client))

View File

@ -0,0 +1,54 @@
from typing import Optional
import discord
from discord.ext import commands
from database.crud.cf_stats import get_cf_stats
from didier import Didier
class Stats(commands.Cog):
"""Cog for various stats that Didier tracks"""
client: Didier
def __init__(self, client: Didier):
self.client = client
@commands.hybrid_group(name="stats", invoke_without_command=True)
async def stats(self, ctx: commands.Context):
"""See stats about yourself or another user"""
@stats.command(name="cf", aliases=["coinflip"])
async def _cf_stats(self, ctx: commands.Context, user: Optional[discord.User] = None):
"""See a user's `Didier CF` stats"""
async with ctx.typing(), self.client.postgres_session as session:
user = user or ctx.author
cf_stats = await get_cf_stats(session, user.id)
embed = discord.Embed(title="Didier CF Stats", colour=discord.Colour.blue())
embed.set_author(name=user.display_name)
if user.avatar is not None:
embed.set_thumbnail(url=user.avatar.url)
played = cf_stats.games_won + cf_stats.games_lost
if played == 0:
return await ctx.reply("This user hasn't played any games yet.", mention_author=False)
embed.add_field(name="Games played", value=played)
embed.add_field(
name="Winrate", value=f"{round(100 * cf_stats.games_won / played, 2)}% ({cf_stats.games_won}/{played})"
)
embed.add_field(name="Dinks won", value=cf_stats.dinks_won)
embed.add_field(name="Dinks lost", value=cf_stats.dinks_lost)
embed.add_field(name="Profit", value=cf_stats.dinks_won - cf_stats.dinks_lost)
await ctx.reply(embed=embed, mention_author=False)
async def setup(client: Didier):
"""Load the cog"""
await client.add_cog(Stats(client))

View File

@ -0,0 +1,11 @@
from didier.menus.common import Menu
# TODO
class Leaderboard(Menu):
pass
class DinksLeaderboard(Leaderboard):
pass

View File

@ -12,10 +12,11 @@ from didier.utils.types.datetime import tz_aware_now
__all__ = ["Timer"]
REMINDER_PREDELAY = timedelta(minutes=settings.REMINDER_PRE)
# TODO make this generic
# TODO add timer for jail freeing
class Timer:
"""Class for scheduled timers"""