modified: app.py
This commit is contained in:
161
app.py
161
app.py
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user