modified: app.py

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

161
app.py
View File

@@ -192,11 +192,22 @@ async def init_database():
new_elo INT NOT NULL,
elo_change INT NOT NULL,
won BOOLEAN NOT NULL,
result_type VARCHAR(10) DEFAULT 'loss',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
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")
except Exception as e:
@@ -233,15 +244,41 @@ async def get_or_create_player(discord_id: int, username: str) -> Dict:
return dict(player)
def calculate_elo_change(player_elo: int, opponent_avg_elo: int, won: bool, t_level: int) -> int:
"""Calculate ELO change using standard ELO formula with T-level multiplier"""
expected_score = 1 / (1 + 10 ** ((opponent_avg_elo - player_elo) / 400))
actual_score = 1 if won else 0
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
result can be 'win', 'loss', or 'draw'
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)
# Apply T-level multiplier
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
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')
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:
async with db_pool.acquire() as conn:
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!")
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}
if winner_team not in teams:
await ctx.send(f"❌ Team '{winner_team}' not found in game! Available teams: {', '.join(teams)}")
is_draw = winner_team.lower() == 'draw'
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
# Calculate team averages
@@ -454,7 +494,14 @@ async def hoi4end(ctx, game_name: str, winner_team: str):
# Calculate ELO changes for each player
for player in players:
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)
opponent_elos = []
@@ -467,7 +514,7 @@ async def hoi4end(ctx, game_name: str, winner_team: str):
elo_change = calculate_elo_change(
player['current_elo'],
opponent_avg,
won,
result,
player['t_level']
)
@@ -481,7 +528,7 @@ async def hoi4end(ctx, game_name: str, winner_team: str):
'old_elo': player['current_elo'],
'new_elo': new_elo,
'elo_change': elo_change,
'won': won
'result': result
})
# Update player ELOs and save game results
@@ -494,27 +541,36 @@ async def hoi4end(ctx, game_name: str, winner_team: str):
)
# Save game result
won = change['result'] == 'win'
await cursor.execute(
"""INSERT INTO game_results
(game_id, discord_id, team_name, t_level, old_elo, new_elo, elo_change, won)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)""",
(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, %s)""",
(game['id'], change['discord_id'], change['team_name'],
change['t_level'], change['old_elo'], change['new_elo'],
change['elo_change'], change['won'])
change['elo_change'], won, change['result'])
)
# Mark game as finished
final_result = "Draw" if is_draw else winner_team
await cursor.execute(
"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
embed = discord.Embed(
title="🏆 Game Finished!",
description=f"Game '{game_name}' has ended!\n**Winner: {winner_team}**",
color=discord.Color.gold()
)
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(
title="🏆 Game Finished!",
description=f"Game '{game_name}' has ended!\n**Winner: {winner_team}**",
color=discord.Color.gold()
)
# Group results by team
teams_results = {}
@@ -528,10 +584,26 @@ async def hoi4end(ctx, game_name: str, winner_team: str):
team_text = ""
for change in team_changes:
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 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(
name=f"{'🏆 ' if team == winner_team else ''}Team {team}",
name=team_header,
value=team_text,
inline=False
)
@@ -575,16 +647,22 @@ async def hoi4stats(ctx, user: Optional[discord.Member] = None):
total_players_result = await cursor.fetchone()
total_players = total_players_result['total']
# Get game statistics
# Get game statistics with proper draw detection
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,)
)
game_stats = await cursor.fetchone()
total_games = game_stats['total_games'] 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
# Create rank indicators with medals
@@ -627,11 +705,20 @@ async def hoi4stats(ctx, user: Optional[discord.Member] = None):
value=f"**{total_games}** total games",
inline=True
)
embed.add_field(
name="📈 Win/Loss",
value=f"**{games_won}W** / **{games_lost}L**",
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(
name="📈 Win/Loss",
value=f"**{games_won}W** / **{games_lost}L**",
inline=True
)
embed.add_field(
name="📊 Win Rate",
value=f"**{win_rate:.1f}%**",
@@ -841,19 +928,27 @@ async def hoi4leaderboard(ctx, game_type: Optional[str] = "standard", limit: Opt
# Get additional player stats
async with db_pool.acquire() as conn:
async with conn.cursor(aiomysql.DictCursor) as cursor:
# Count games played
# Count games played with proper result detection
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_stats = await cursor.fetchone()
games_played = player_stats['games_played'] 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
leaderboard_text += f"{rank_indicator} **{username}** - {elo_value} ELO\n"
leaderboard_text += f" 📊 {games_played} games | {win_rate:.1f}% win rate\n\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"
embed.add_field(
name="Rankings",