modified: app.py

This commit is contained in:
SimolZimol
2025-10-26 11:38:53 +01:00
parent a86a321505
commit fb7847e0c4

139
app.py
View File

@@ -192,11 +192,22 @@ async def init_database():
new_elo INT NOT NULL, new_elo INT NOT NULL,
elo_change INT NOT NULL, elo_change INT NOT NULL,
won BOOLEAN NOT NULL, won BOOLEAN NOT NULL,
result_type VARCHAR(10) DEFAULT 'loss',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (game_id) REFERENCES games(id) FOREIGN KEY (game_id) REFERENCES games(id)
) )
''') ''')
# Add result_type column if it doesn't exist (for existing databases)
try:
await cursor.execute('''
ALTER TABLE game_results
ADD COLUMN result_type VARCHAR(10) DEFAULT 'loss'
''')
except:
# Column already exists, ignore error
pass
print("✅ Database initialized successfully") print("✅ Database initialized successfully")
except Exception as e: except Exception as e:
@@ -233,15 +244,41 @@ async def get_or_create_player(discord_id: int, username: str) -> Dict:
return dict(player) return dict(player)
def calculate_elo_change(player_elo: int, opponent_avg_elo: int, won: bool, t_level: int) -> int: def calculate_elo_change(player_elo: int, opponent_avg_elo: int, result: str, t_level: int) -> int:
"""Calculate ELO change using standard ELO formula with T-level multiplier""" """Calculate ELO change using standard ELO formula with T-level multiplier
expected_score = 1 / (1 + 10 ** ((opponent_avg_elo - player_elo) / 400)) result can be 'win', 'loss', or 'draw'
actual_score = 1 if won else 0
In draws:
- If you're expected to win (higher ELO), you lose points for drawing
- If you're expected to lose (lower ELO), you gain points for drawing
- The bigger the ELO difference, the bigger the swing
"""
expected_score = 1 / (1 + 10 ** ((opponent_avg_elo - player_elo) / 400))
if result == 'win':
actual_score = 1.0
elif result == 'draw':
actual_score = 0.5 # Draw = 50% score
else: # loss
actual_score = 0.0
# Calculate the base ELO change
base_change = K_FACTOR * (actual_score - expected_score) base_change = K_FACTOR * (actual_score - expected_score)
# Apply T-level multiplier
t_multiplier = T_LEVEL_MULTIPLIERS.get(t_level, 1.0) t_multiplier = T_LEVEL_MULTIPLIERS.get(t_level, 1.0)
return round(base_change * t_multiplier) final_change = base_change * t_multiplier
# For debugging/logging purposes, let's see what happens in draws
if result == 'draw':
elo_diff = player_elo - opponent_avg_elo
expected_percentage = expected_score * 100
print(f"📊 Draw calculation: Player ELO {player_elo} vs Opponent Avg {opponent_avg_elo}")
print(f" ELO Difference: {elo_diff:+d} | Expected win chance: {expected_percentage:.1f}%")
print(f" ELO change: {final_change:+.1f} (T{t_level} multiplier: {t_multiplier})")
return round(final_change)
# Owner only decorator # Owner only decorator
def is_owner(): def is_owner():
@@ -406,7 +443,7 @@ async def hoi4setup(ctx, game_name: str, user: discord.Member, team_name: str, t
@bot.hybrid_command(name='hoi4end', description='End a game and calculate ELO changes') @bot.hybrid_command(name='hoi4end', description='End a game and calculate ELO changes')
async def hoi4end(ctx, game_name: str, winner_team: str): async def hoi4end(ctx, game_name: str, winner_team: str):
"""End a game and calculate ELO changes""" """End a game and calculate ELO changes. Use 'draw' for ties."""
try: try:
async with db_pool.acquire() as conn: async with db_pool.acquire() as conn:
async with conn.cursor(aiomysql.DictCursor) as cursor: async with conn.cursor(aiomysql.DictCursor) as cursor:
@@ -427,10 +464,13 @@ async def hoi4end(ctx, game_name: str, winner_team: str):
await ctx.send("❌ Game needs at least 2 players to end!") await ctx.send("❌ Game needs at least 2 players to end!")
return return
# Check if winner team exists # Check if winner team exists or if it's a draw
teams = {p['team_name'] for p in players} teams = {p['team_name'] for p in players}
if winner_team not in teams: is_draw = winner_team.lower() == 'draw'
await ctx.send(f"❌ Team '{winner_team}' not found in game! Available teams: {', '.join(teams)}")
if not is_draw and winner_team not in teams:
available_teams = ', '.join(teams)
await ctx.send(f"❌ Team '{winner_team}' not found in game! Available teams: {available_teams}, draw")
return return
# Calculate team averages # Calculate team averages
@@ -454,7 +494,14 @@ async def hoi4end(ctx, game_name: str, winner_team: str):
# Calculate ELO changes for each player # Calculate ELO changes for each player
for player in players: for player in players:
team = player['team_name'] team = player['team_name']
won = team == winner_team
# Determine result for this player
if is_draw:
result = 'draw'
elif team == winner_team:
result = 'win'
else:
result = 'loss'
# Calculate opponent average (average of all other teams) # Calculate opponent average (average of all other teams)
opponent_elos = [] opponent_elos = []
@@ -467,7 +514,7 @@ async def hoi4end(ctx, game_name: str, winner_team: str):
elo_change = calculate_elo_change( elo_change = calculate_elo_change(
player['current_elo'], player['current_elo'],
opponent_avg, opponent_avg,
won, result,
player['t_level'] player['t_level']
) )
@@ -481,7 +528,7 @@ async def hoi4end(ctx, game_name: str, winner_team: str):
'old_elo': player['current_elo'], 'old_elo': player['current_elo'],
'new_elo': new_elo, 'new_elo': new_elo,
'elo_change': elo_change, 'elo_change': elo_change,
'won': won 'result': result
}) })
# Update player ELOs and save game results # Update player ELOs and save game results
@@ -494,22 +541,31 @@ async def hoi4end(ctx, game_name: str, winner_team: str):
) )
# Save game result # Save game result
won = change['result'] == 'win'
await cursor.execute( await cursor.execute(
"""INSERT INTO game_results """INSERT INTO game_results
(game_id, discord_id, team_name, t_level, old_elo, new_elo, elo_change, won) (game_id, discord_id, team_name, t_level, old_elo, new_elo, elo_change, won, result_type)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)""", VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)""",
(game['id'], change['discord_id'], change['team_name'], (game['id'], change['discord_id'], change['team_name'],
change['t_level'], change['old_elo'], change['new_elo'], change['t_level'], change['old_elo'], change['new_elo'],
change['elo_change'], change['won']) change['elo_change'], won, change['result'])
) )
# Mark game as finished # Mark game as finished
final_result = "Draw" if is_draw else winner_team
await cursor.execute( await cursor.execute(
"UPDATE games SET status = 'finished', winner_team = %s, finished_at = CURRENT_TIMESTAMP WHERE id = %s", "UPDATE games SET status = 'finished', winner_team = %s, finished_at = CURRENT_TIMESTAMP WHERE id = %s",
(winner_team, game['id']) (final_result, game['id'])
) )
# Create result embed # Create result embed
if is_draw:
embed = discord.Embed(
title="🤝 Game Finished!",
description=f"Game '{game_name}' has ended!\n**Result: Draw**",
color=discord.Color.orange()
)
else:
embed = discord.Embed( embed = discord.Embed(
title="🏆 Game Finished!", title="🏆 Game Finished!",
description=f"Game '{game_name}' has ended!\n**Winner: {winner_team}**", description=f"Game '{game_name}' has ended!\n**Winner: {winner_team}**",
@@ -528,10 +584,26 @@ async def hoi4end(ctx, game_name: str, winner_team: str):
team_text = "" team_text = ""
for change in team_changes: for change in team_changes:
emoji = "📈" if change['elo_change'] > 0 else "📉" if change['elo_change'] < 0 else "➡️" emoji = "📈" if change['elo_change'] > 0 else "📉" if change['elo_change'] < 0 else "➡️"
result_emoji = ""
if change['result'] == 'win':
result_emoji = "🏆"
elif change['result'] == 'draw':
result_emoji = "🤝"
else:
result_emoji = "💔"
team_text += f"{change['username']}: {change['old_elo']}{change['new_elo']} ({change['elo_change']:+d}) {emoji}\n" team_text += f"{change['username']}: {change['old_elo']}{change['new_elo']} ({change['elo_change']:+d}) {emoji}\n"
# Team header with appropriate icon
if is_draw:
team_header = f"🤝 Team {team}"
elif team == winner_team:
team_header = f"🏆 Team {team}"
else:
team_header = f"💔 Team {team}"
embed.add_field( embed.add_field(
name=f"{'🏆 ' if team == winner_team else ''}Team {team}", name=team_header,
value=team_text, value=team_text,
inline=False inline=False
) )
@@ -575,16 +647,22 @@ async def hoi4stats(ctx, user: Optional[discord.Member] = None):
total_players_result = await cursor.fetchone() total_players_result = await cursor.fetchone()
total_players = total_players_result['total'] total_players = total_players_result['total']
# Get game statistics # Get game statistics with proper draw detection
await cursor.execute( await cursor.execute(
"SELECT COUNT(*) as total_games, SUM(won) as games_won FROM game_results WHERE discord_id = %s", """SELECT
COUNT(*) as total_games,
SUM(CASE WHEN result_type = 'win' THEN 1 ELSE 0 END) as games_won,
SUM(CASE WHEN result_type = 'draw' THEN 1 ELSE 0 END) as games_drawn,
SUM(CASE WHEN result_type = 'loss' THEN 1 ELSE 0 END) as games_lost
FROM game_results WHERE discord_id = %s""",
(target_user.id,) (target_user.id,)
) )
game_stats = await cursor.fetchone() game_stats = await cursor.fetchone()
total_games = game_stats['total_games'] or 0 total_games = game_stats['total_games'] or 0
games_won = game_stats['games_won'] or 0 games_won = game_stats['games_won'] or 0
games_lost = total_games - games_won games_drawn = game_stats['games_drawn'] or 0
games_lost = game_stats['games_lost'] or 0
win_rate = (games_won / total_games * 100) if total_games > 0 else 0 win_rate = (games_won / total_games * 100) if total_games > 0 else 0
# Create rank indicators with medals # Create rank indicators with medals
@@ -627,11 +705,20 @@ async def hoi4stats(ctx, user: Optional[discord.Member] = None):
value=f"**{total_games}** total games", value=f"**{total_games}** total games",
inline=True inline=True
) )
if games_drawn > 0:
embed.add_field(
name="📊 W/D/L Record",
value=f"**{games_won}W** / **{games_drawn}D** / **{games_lost}L**",
inline=True
)
else:
embed.add_field( embed.add_field(
name="📈 Win/Loss", name="📈 Win/Loss",
value=f"**{games_won}W** / **{games_lost}L**", value=f"**{games_won}W** / **{games_lost}L**",
inline=True inline=True
) )
embed.add_field( embed.add_field(
name="📊 Win Rate", name="📊 Win Rate",
value=f"**{win_rate:.1f}%**", value=f"**{win_rate:.1f}%**",
@@ -841,18 +928,26 @@ async def hoi4leaderboard(ctx, game_type: Optional[str] = "standard", limit: Opt
# Get additional player stats # Get additional player stats
async with db_pool.acquire() as conn: async with db_pool.acquire() as conn:
async with conn.cursor(aiomysql.DictCursor) as cursor: async with conn.cursor(aiomysql.DictCursor) as cursor:
# Count games played # Count games played with proper result detection
await cursor.execute( await cursor.execute(
"SELECT COUNT(*) as games_played, SUM(won) as games_won FROM game_results WHERE discord_id = %s", """SELECT
COUNT(*) as games_played,
SUM(CASE WHEN result_type = 'win' THEN 1 ELSE 0 END) as games_won,
SUM(CASE WHEN result_type = 'draw' THEN 1 ELSE 0 END) as games_drawn
FROM game_results WHERE discord_id = %s""",
(player['discord_id'],) (player['discord_id'],)
) )
player_stats = await cursor.fetchone() player_stats = await cursor.fetchone()
games_played = player_stats['games_played'] or 0 games_played = player_stats['games_played'] or 0
games_won = player_stats['games_won'] or 0 games_won = player_stats['games_won'] or 0
games_drawn = player_stats['games_drawn'] or 0
win_rate = (games_won / games_played * 100) if games_played > 0 else 0 win_rate = (games_won / games_played * 100) if games_played > 0 else 0
leaderboard_text += f"{rank_indicator} **{username}** - {elo_value} ELO\n" leaderboard_text += f"{rank_indicator} **{username}** - {elo_value} ELO\n"
if games_drawn > 0:
leaderboard_text += f" 📊 {games_played} games | {games_won}W-{games_drawn}D-{games_played - games_won - games_drawn}L | {win_rate:.1f}% win rate\n\n"
else:
leaderboard_text += f" 📊 {games_played} games | {win_rate:.1f}% win rate\n\n" leaderboard_text += f" 📊 {games_played} games | {win_rate:.1f}% win rate\n\n"
embed.add_field( embed.add_field(