Как можно улучшить и сократить систему Levels и Xp в discord.py?

У меня есть такой код:

from webserver import keep_alive
import discord
import json
import sqlite3
import re
import os
from discord.ext import commands
import datetime
import random
from pymongo import MongoClient
import asyncio
import levelsys

cogs = [levelsys]

client = commands.Bot(command_prefix = "!", intents=discord.Intents.all());

bot_channel = 702341394014011425
talk_channels = [702341394014011425]

level = ["?Bronze?", "?Iron?", "?Gold?", "?Diamond?", "?Elite?"]
levelnum = [5, 10, 20, 30, 50]

cluster = MongoClient("mongodb+srv://<ss>:<12345>@cluster0.vraiu.mongodb.net/Cluster0?retryWrites=true&w=majority")

levelling = cluster["discord"]["levelling"]

class levelsys(commands.Cog):
    def __init__(self, client):
        self.client = client

    @commands.Cog.listener()
    async def on_ready(self):
        print("ready!")

    @commands.Cog.listener()
    async def on_message(self, message):
        if message.channel.id in talk_channels:
            stats = levelling.find_one({"id" : message.author.id})
            if not message.author.bot:
                if stats is None:
                    newuser = {"id" : message.author.id, "xp" : 100}
                    levelling.insert_one(newuser)
                else:
                    xp = stats["xp"] + 5
                    levelling.update_one({"id":message.author.id}, {"$set":{"xp":xp}})
                    lvl = 0
                    while True:
                        if xp < ((50*(lvl**2))+(50*lvl)):
                            break
                        lvl += 1
                    xp -= ((50*((lvl-1)**2))+(50*(lvl-1)))
                    if xp == 0:
                        await message.channel.send(f'У {message.author.mention} повысился уровень до **{lvl}**!')
                        for i in range(len(level)):
                            if lvl == levelnum[1]:
                                await message.author.add_roles(discord.utils.get(message.author.guild.roles, name=level[1]))
                                embed = discord.Embed(description=f"{message.author.mention} ты получил роль **{level[1]}**!")
                                embed.set_thumbnail(url=message.author.avatar_url)
                                await message.channel.send(embed=embed)

    @commands.command()
    async def ранг(self, ctx):
        if ctx.channel.id == bot_channel:
            stats = levelling.find_one({"id" : ctx.author.id})
            if stats is None:
                embed = discord.Embed(description="Вы не отправляли ни одного сообщения!")
                await ctx.channel.send(embed=embed)
            else:
                xp = stats["xp"]
                lvl = 0
                rank = 0
                while True:
                        if xp < ((50*(lvl**2))+(50*lvl)):
                            break
                        lvl += 1
                xp -= ((50*((lvl-1)**2))+(50*(lvl-1)))
                boxes = int((xp/(200*((1/2) * lvl)))*20)
                rankings = levelling.find().sort("xp",-1)
                for x in rankings:
                    rank += 1
                    if stats["id"] == x["id"]:
                        break
                embed = discord.Embed(title="{} статистика уровней".format(ctx.author.name))
                embed.add_field(name="Имя", value=ctx.author.mention, inline=True)
                embed.add_field(name="XP", value=f"{xp}/{int(200*((1/2)*lvl))}", inline=True)
                embed.add_field(name="Ранг", value=f"{rank}/{ctx.guild.member_count}", inline=True)
                embed.add_field(name="Progress Bar [lvl]", value=boxes * ":blue_square:" + (20-boxes) * ":white_large_square:", inline=False)
                embed.set_thumbnail(url=ctx.author.avatar_url)
                await ctx.channel.send(embed=embed)

    @commands.command()
    async def лидеры(self, ctx):
        if (ctx.channel.id == bot_channel):
            rankings = levelling.find().sort("xp",-1)
            i = 1
            embed = discord.Embed(title="Рейтинг участнков:")
            for x in rankings:
                try:
                    temp = ctx.guild.get_member(x["id"])
                    tempxp = x["xp"]
                    embed.add_field(name=f"{i}: {temp.name}", value=f"Всего опыта: {tempxp}", inline=False)
                    i += 1
                except:
                    pass
                if i == 11:
                    break
            await ctx.channel.send(embed=embed)

Но он:

  1. Не работает
  2. Длинный (по моему мнению)

Как можно его улучшить и сократить?

Пишу на replit


Ответы (1 шт):

Автор решения: Be3y4uu_K0T

Я бы предложил Вам переписать вот эту часть:

lvl = 0
while True:
    if xp < ((50*(lvl**2))+(50*lvl)):
        break
    lvl += 1
xp -= ((50*((lvl-1)**2))+(50*(lvl-1)))

Простая математика:

(50*(lvl**2)) + (50*lvl) = xp
50*lvl**2 + 50*lvl = xp
50*(lvl**2 + lvl) = xp
lvl**2 + lvl = xp/50
lvl**2 + lvl - xp/50 = 0
D = b**2 -4*a*c = 1 + 4*xp/50
lvl = (math.sqrt(4*xp/50 + 1) - 1) / 2

И лучше вынести это в функции:

def get_level(xp: int) -> float:
    k = 50
    return (math.sqrt(4*xp/k + 1) - 1) / 2

def get_current_level(xp: int) -> float:
    k = 50
    return (math.sqrt(4*xp/k + 1) - 1) // 2

def get_xp(level: int) -> int:
    k = 50
    return k*level*(level + 1)
>>> get_level(2800)
7.0
>>> get_level(2700)
6.865459931328117
>>> get_current_level(2800)
7.0
>>> get_current_level(2700)
6.0 

Также поиск для ранга:

rankings = levelling.find().sort("xp",-1)
for user in rankings:
    rank += 1
    if stats['id'] == user['id']:
        break
rank = levelling.count_documents({'xp': {'$gt': xp}}) + 1

Также эти три строчки можно объединить:

stats = levelling.find_one({"id" : message.author.id})
...
xp = stats["xp"] + 5
levelling.update_one({"id":message.author.id}, {"$set":{"xp":xp}})
stats = levelling.find_one_and_update(
    {'_id': message.author.id}, {'$inc': {'xp': 5}},
    return_document=pymongo.ReturnDocument.AFTER
)

Итоговый мой код:

from discord.ext import commands
import pymongo as mg
import discord as ds
import math

bot = commands.Bot(command_prefix='!', intents=ds.Intents.all())

bot_channel = ...
talk_channels = {...}

levels = {
    5: '?Bronze?',
    10: '?Iron?',
    20: '?Gold?',
    30: '?Diamond?',
    50: '?Elite?',
}

client = mg.MongoClient('mongodb://127.0.0.1:27017/', connect=False)
db = client.discord

def get_level(xp: int) -> float:
    k = 50
    return (math.sqrt(4*xp/k + 1) - 1) / 2

def get_current_level(xp: int) -> float:
    k = 50
    return (math.sqrt(4*xp/k + 1) - 1) // 2

def get_xp(level: int) -> int:
    k = 50
    return k*level*(level + 1)

@bot.event
async def on_ready():
    print('ready!')

@bot.event
async def on_message(message):
    if message.channel.id not in talk_channels:
        return
    
    if message.author.bot:
        return 

    if message.content.startswith(bot.command_prefix):
        return await bot.process_commands(message)

    user = db.levelling.find_one_and_update(
        {'_id': message.author.id}, {'$inc': {'xp': 100}},
        return_document=mg.ReturnDocument.AFTER
    )

    if user is None:
        new_user = {'_id' : message.author.id, 'xp' : 100}
        db.levelling.insert_one(new_user)
    else:
        level = get_level(user['xp'])
        print(user['xp'], level, level.is_integer())
        if level.is_integer():
            await message.channel.send(
                f'У {message.author.mention} повысился уровень до **{int(level)}**!'
            )
            if rank := levels.get(level):
                await message.author.add_roles(ds.utils.get(message.author.guild.roles, name=rank))
                embed = ds.Embed(description=f'{message.author.mention} ты получил роль **{rank}**!')
                embed.set_thumbnail(url=message.author.avatar_url)
                await message.channel.send(embed=embed)

@bot.command()
async def ранг(ctx):
    if ctx.channel.id != bot_channel:
        return
    
    stats = db.levelling.find_one({'_id' : ctx.author.id})
    if stats is None:
        embed = ds.Embed(description='Вы не отправляли ни одного сообщения!')
        await ctx.channel.send(embed=embed)
    else:
        xp = stats['xp']
        lvl = get_current_level(xp)
        next_xp = get_xp(lvl + 1)
        rank = db.levelling.count_documents({'xp': {'$gt': xp}}) + 1

        length_bar = 20
        boxes = int(length_bar * xp / next_xp)
        
        embed = ds.Embed(title=f'{ctx.author.name} статистика уровней')
        embed.add_field(name='Имя', value=ctx.author.mention, inline=True)
        embed.add_field(name='XP', value=f'{xp}/{next_xp}', inline=True)
        embed.add_field(name='Ранг', value=f'{rank}/{ctx.guild.member_count}', inline=True)
        embed.add_field(
            name='Progress Bar [lvl]',
            value=boxes * ':blue_square:' + (length_bar-boxes) * ':white_large_square:',
            inline=False
        )
        embed.set_thumbnail(url=ctx.author.avatar_url)
        await ctx.channel.send(embed=embed)

@bot.command()
async def лидеры(ctx):
    if ctx.channel.id != bot_channel:
        return
    
    embed = ds.Embed(title='Рейтинг участнков:')
    rankings = db.levelling.aggregate([
        {'$limit': 10},
        {
            '$setWindowFields': {
                'sortBy': {'xp': mg.DESCENDING},
                'output': {
                    'rank': {'$rank': {}},
                },
            }
        }
    ])

    for user in rankings:
        xp = user['xp']
        member = ctx.guild.get_member(user['_id'])
        embed.add_field(
            name=f'{user["rank"]}: {member.name}',
            value=f'Всего опыта: {xp}',
            inline=False
        )
    await ctx.channel.send(embed=embed)

bot.run('<token>')
→ Ссылка