UI/UX: Fix alignment with right-aligned stat bars + optimize combat display

BREAKING: Changed format_stat_bar() to right-aligned format
- Bars now left-aligned, emoji+label on right
- Works with Telegram's proportional font (spaces don't work)
- Format: {bar} {percentage}% ({current}/{max}) {emoji} {label}

Combat improvements:
- Show BOTH HP bars on every turn (player + enemy)
- Eliminates redundant enemy HP display
- Better tactical awareness with complete state info

Files modified:
- bot/utils.py: Right-aligned format_stat_bar()
- bot/combat.py: Both HP bars on player/enemy turns
- bot/action_handlers.py: Fixed emoji handling
- bot/combat_handlers.py: Updated combat status display
- docs/development/UI_UX_IMPROVEMENTS.md: Complete documentation

Example output:
██████████ 100% (100/100) ❤️ Your HP
███░░░░░░░ 30% (15/50) 🐕 Feral Dog
This commit is contained in:
Joan
2025-10-20 12:58:41 +02:00
parent dfea27f9cb
commit c78c902b82
5 changed files with 179 additions and 155 deletions

View File

@@ -124,8 +124,8 @@ async def handle_attack_wandering(query, user_id: int, player: dict, data: list)
npc_def = NPCS.get(npc_id)
message = f"⚔️ You engage the {npc_def.emoji} {npc_def.name}!\n\n"
message += f"{npc_def.description}\n\n"
message += format_stat_bar(f"{npc_def.emoji} Enemy HP", "", combat_data['npc_hp'], combat_data['npc_max_hp']) + "\n"
message += format_stat_bar("Your HP", "❤️", player['hp'], player['max_hp']) + "\n\n"
message += format_stat_bar("Your HP", "❤️", player['hp'], player['max_hp']) + "\n"
message += format_stat_bar("Enemy HP", npc_def.emoji, combat_data['npc_hp'], combat_data['npc_max_hp']) + "\n\n"
message += "🎯 Your turn! What will you do?"
keyboard = await keyboards.combat_keyboard(user_id)
@@ -343,8 +343,8 @@ async def handle_move(query, user_id: int, player: dict, data: list):
npc_def = NPCS.get(npc_id)
message = f"⚠️ A {npc_def.emoji} {npc_def.name} appears!\n\n"
message += f"{npc_def.description}\n\n"
message += format_stat_bar(f"{npc_def.emoji} Enemy HP", "", combat_data['npc_hp'], combat_data['npc_max_hp']) + "\n"
message += format_stat_bar("Your HP", "❤️", player['hp'], player['max_hp']) + "\n\n"
message += format_stat_bar("Your HP", "❤️", player['hp'], player['max_hp']) + "\n"
message += format_stat_bar("Enemy HP", npc_def.emoji, combat_data['npc_hp'], combat_data['npc_max_hp']) + "\n\n"
message += "🎯 Your turn! What will you do?"
keyboard = await keyboards.combat_keyboard(user_id)

View File

@@ -175,8 +175,10 @@ async def player_attack(player_id: int) -> Tuple[str, bool, bool]:
'player_status_effects': json.dumps(player_effects)
})
# Show both health bars after player's turn
message += "\n━━━━━━━━━━━━━━━━━━━━\n"
message += format_stat_bar(f"{npc_def.emoji} {npc_def.name}", "", new_npc_hp, combat['npc_max_hp'])
message += format_stat_bar("Your HP", "❤️", player['hp'], player['max_hp']) + "\n"
message += format_stat_bar(npc_def.name, npc_def.emoji, new_npc_hp, combat['npc_max_hp'])
return (message, False, True)
@@ -255,9 +257,10 @@ async def npc_attack(player_id: int) -> Tuple[str, bool]:
'npc_status_effects': json.dumps(npc_effects)
})
# Show both health bars after enemy's turn
message += "\n━━━━━━━━━━━━━━━━━━━━\n"
message += format_stat_bar("Your HP", "❤️", new_player_hp, player['max_hp']) + "\n"
message += format_stat_bar(f"{npc_def.emoji} {npc_def.name}", "", combat['npc_hp'], combat['npc_max_hp'])
message += format_stat_bar(npc_def.name, npc_def.emoji, combat['npc_hp'], combat['npc_max_hp'])
return (message, False)

View File

@@ -151,8 +151,8 @@ async def handle_combat_back(query, user_id: int, player: dict, data: list = Non
keyboard = await keyboards.combat_keyboard(user_id)
message = f"⚔️ Combat with {npc_def.emoji} {npc_def.name}!\n"
message += format_stat_bar(f"{npc_def.emoji} Enemy HP", "", combat_data['npc_hp'], combat_data['npc_max_hp']) + "\n"
message += format_stat_bar("Your HP", "❤️", player['hp'], player['max_hp']) + "\n\n"
message += format_stat_bar("Your HP", "❤️", player['hp'], player['max_hp']) + "\n"
message += format_stat_bar("Enemy HP", npc_def.emoji, combat_data['npc_hp'], combat_data['npc_max_hp']) + "\n\n"
message += "🎯 Your turn!" if combat_data['turn'] == 'player' else "⏳ Enemy's turn..."
from .handlers import send_or_edit_with_image

View File

@@ -46,31 +46,35 @@ def create_progress_bar(current: int, maximum: int, length: int = 10, filled_cha
def format_stat_bar(label: str, emoji: str, current: int, maximum: int, bar_length: int = 10, label_width: int = 7) -> str:
"""
Format a stat (HP, Stamina, etc.) with visual progress bar.
Uses right-aligned label format to avoid alignment issues with Telegram's proportional font.
Args:
label: Stat label (e.g., "HP", "Stamina")
emoji: Emoji to display (e.g., "❤️", "")
label: Stat label (e.g., "HP", "Stamina", "Your HP")
emoji: Emoji to display (e.g., "❤️", "", "🐕")
current: Current value
maximum: Maximum value
bar_length: Length of the progress bar
label_width: Width to pad label to for alignment (default 7)
label_width: Not used, kept for backwards compatibility
Returns:
Formatted string with bar and percentage
Formatted string with bar on left, label on right
Examples:
>>> format_stat_bar("HP", "❤️", 75, 100)
"❤️ HP: ███████░░░ 75% (75/100)"
"███████░░░ 75% (75/100) ❤️ HP"
>>> format_stat_bar("Stamina", "", 50, 100)
"⚡ Stamina: █████░░░░░ 50% (50/100)"
"█████░░░░░ 50% (50/100) ⚡ Stamina"
"""
bar = create_progress_bar(current, maximum, bar_length)
percentage = int((current / maximum * 100)) if maximum > 0 else 0
# Pad label for alignment
padded_label = f"{label}:".ljust(label_width + 1)
return f"{emoji} {padded_label} {bar} {percentage}% ({current}/{maximum})"
# Right-aligned format: bar first, then stats, then emoji + label
# This way bars are always left-aligned regardless of label length
if emoji:
return f"{bar} {percentage}% ({current}/{maximum}) {emoji} {label}"
else:
# If no emoji provided, just use label
return f"{bar} {percentage}% ({current}/{maximum}) {label}"