"""HUD and UI elements."""
import pygame
import sys
from ..utils.constants import *
from ..utils.localization import localization_manager
[docs]
class HUD:
"""Heads-up display for game information."""
def __init__(self):
pygame.font.init()
[docs]
self.font = pygame.font.Font(None, UI_FONT_SIZE)
[docs]
self.info_font = pygame.font.Font(None, 22)
[docs]
def handle_click(self, pos, game_manager, game_state_str):
if game_state_str == "MAIN_MENU":
if self.buttons['exit'].collidepoint(pos):
game_manager.quit_game()
elif game_state_str == "PLAYING":
if self.buttons['send_pig'].collidepoint(pos):
game_manager.send_pig_away()
elif self.buttons['emergency_feast'].collidepoint(pos):
game_manager.emergency_feast()
elif self.buttons['add_pigs'].collidepoint(pos):
game_manager.add_pigs(5)
elif game_state_str in ["PAUSED", "GAME_OVER"]:
if self.buttons['restart'].collidepoint(pos):
game_manager.restart_game()
[docs]
def render(self, screen, game_state):
"""Render the HUD."""
# Only render game info and action buttons if we are NOT on the main menu
if not game_state.get('main_menu', False):
self._render_info_panel(screen, game_state)
self._render_buttons(screen, game_state)
# Render overlays and context-specific buttons based on the current state
if game_state.get('paused', False):
self._render_pause_overlay(screen)
self._draw_restart_button(screen) # Draw RESTART button
elif game_state.get('game_over', False):
self._render_game_over_overlay(screen, game_state)
self._draw_restart_button(screen) # Draw RESTART button
elif game_state.get('main_menu', False):
self._draw_exit_button(screen) # Draw EXIT button
def _render_info_panel(self, screen, game_state):
"""Render a compact game information panel with a design similar to the stability graph."""
# Define panel properties to be small and unobtrusive
panel_width = 200
panel_height = 85
panel_rect = pygame.Rect(10, 10, panel_width, panel_height)
# Draw semi-transparent background, matching the graph style
panel_surface = pygame.Surface(panel_rect.size, pygame.SRCALPHA)
panel_surface.fill((20, 20, 20, 180))
screen.blit(panel_surface, panel_rect.topleft)
# --- Render Text using the new smaller font ---
line_height = self.info_font.get_linesize()
start_x = panel_rect.x + 10
start_y = panel_rect.y + 10
# Score
score_text = self.info_font.render(localization_manager.get('score_label', score=game_state['score']), True, (255, 255, 255))
screen.blit(score_text, (start_x, start_y))
# Time remaining - Pass the raw number, not a pre-formatted string
time_text = self.info_font.render(localization_manager.get('time_label', time=game_state['time_remaining']), True, (255, 255, 255))
screen.blit(time_text, (start_x, start_y + line_height))
# Feast cooldown
if game_state['feast_cooldown'] > 0:
# Pass the raw number here as well
cooldown_text = self.info_font.render(localization_manager.get('feast_cd_label', cooldown=game_state['feast_cooldown']), True, (255, 200, 200))
screen.blit(cooldown_text, (start_x, start_y + line_height * 2))
def _render_buttons(self, screen, game_state):
"""Render action buttons for gameplay ONLY."""
# Send Pig button
pygame.draw.rect(screen, (0, 150, 0), self.buttons['send_pig'])
pygame.draw.rect(screen, (255, 255, 255), self.buttons['send_pig'], 2)
send_text = self.button_font.render(localization_manager.get('send_pig_button'), True, (255, 255, 255))
screen.blit(send_text, send_text.get_rect(center=self.buttons['send_pig'].center))
# Emergency Feast button
pygame.draw.rect(screen, (150, 0, 0), self.buttons['emergency_feast'])
pygame.draw.rect(screen, (255, 255, 255), self.buttons['emergency_feast'], 2)
feast_text = self.button_font.render(localization_manager.get('feast_button'), True, (255, 255, 255))
screen.blit(feast_text, feast_text.get_rect(center=self.buttons['emergency_feast'].center))
# Add Pigs button
pygame.draw.rect(screen, (0, 100, 150), self.buttons['add_pigs'])
pygame.draw.rect(screen, (255, 255, 255), self.buttons['add_pigs'], 2)
add_text = self.button_font.render(localization_manager.get('add_pigs_button'), True, (255, 255, 255))
screen.blit(add_text, add_text.get_rect(center=self.buttons['add_pigs'].center))
def _render_degradation_bar(self, screen, game_state):
"""Render environmental degradation bar."""
bar_rect = pygame.Rect(SCREEN_WIDTH - 220, 10, 200, 20)
pygame.draw.rect(screen, (255, 255, 255), bar_rect, 1)
fill_width = (1.0 - game_state['degradation']) * (bar_rect.width - 2)
color = pygame.Color(0, 255, 0).lerp(pygame.Color(255, 0, 0), game_state['degradation'])
fill_rect = pygame.Rect(bar_rect.x + 1, bar_rect.y + 1, fill_width, 18)
pygame.draw.rect(screen, color, fill_rect)
label = self.font.render(localization_manager.get('health_label'), True, (255, 255, 255))
screen.blit(label, (SCREEN_WIDTH - 220, 35))
def _render_pause_overlay(self, screen):
"""Render the pause screen overlay."""
overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 128))
screen.blit(overlay, (0, 0))
pause_text = pygame.font.Font(None, 72).render(localization_manager.get('paused_title'), True, (255, 255, 255))
screen.blit(pause_text, pause_text.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2)))
instruction_text = self.font.render(localization_manager.get('resume_instruction'), True, (255, 255, 255))
inst_rect = instruction_text.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 50))
screen.blit(instruction_text, inst_rect)
def _render_game_over_overlay(self, screen, game_state):
"""Render the game over screen overlay, now with high scores."""
overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 180))
screen.blit(overlay, (0, 0))
# --- Game Over Title ---
game_over_text = pygame.font.Font(None, 72).render(localization_manager.get('game_over_title'), True, (255, 255, 255))
text_rect = game_over_text.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 4))
screen.blit(game_over_text, text_rect)
# --- Win/Loss Message ---
if game_state.get('is_win', False):
end_msg = localization_manager.get('win_message')
color = (152, 251, 152) # Light green for win
else:
end_msg = localization_manager.get('lose_message')
color = (255, 182, 193) # Light red for lose
end_text = pygame.font.Font(None, 36).render(end_msg, True, color)
end_rect = end_text.get_rect(center=(SCREEN_WIDTH // 2, text_rect.bottom + 40))
screen.blit(end_text, end_rect)
# --- Final Score ---
score_text = pygame.font.Font(None, 48).render(localization_manager.get('final_score_label', score=game_state['score']), True, (255, 255, 255))
score_rect = score_text.get_rect(center=(SCREEN_WIDTH // 2, end_rect.bottom + 40))
screen.blit(score_text, score_rect)
# --- Performance Feedback ---
y_offset = score_rect.bottom + 30
# Environment Feedback
final_degradation = game_state.get('final_degradation')
if final_degradation is not None:
if final_degradation < 0.25:
env_key = 'env_perf_excellent'
elif final_degradation < 0.5:
env_key = 'env_perf_good'
elif final_degradation < 0.9:
env_key = 'env_perf_bad'
else:
env_key = 'env_perf_disaster'
env_text = self.info_font.render(localization_manager.get(env_key), True, (220, 220, 220))
env_rect = env_text.get_rect(center=(SCREEN_WIDTH // 2, y_offset))
screen.blit(env_text, env_rect)
y_offset += self.info_font.get_linesize()
# Happiness Feedback
final_happiness = game_state.get('final_happiness')
if final_happiness is not None:
if final_happiness >= 0.75:
hap_key = 'hap_perf_ecstatic'
elif final_happiness >= 0.5:
hap_key = 'hap_perf_content'
elif final_happiness >= 0.25:
hap_key = 'hap_perf_unhappy'
else:
hap_key = 'hap_perf_miserable'
hap_text = self.info_font.render(localization_manager.get(hap_key), True, (220, 220, 220))
hap_rect = hap_text.get_rect(center=(SCREEN_WIDTH // 2, y_offset))
screen.blit(hap_text, hap_rect)
y_offset += self.info_font.get_linesize()
# --- High Scores Display ---
high_scores_title = pygame.font.Font(None, 48).render(localization_manager.get('high_scores_title'), True, (255, 255, 255))
hs_title_rect = high_scores_title.get_rect(center=(SCREEN_WIDTH // 2, y_offset + 40))
screen.blit(high_scores_title, hs_title_rect)
y_offset = hs_title_rect.bottom + 10
# Loop through only the top 3 scores
for i, entry in enumerate(game_state.get('high_scores', [])[:3]):
name = entry.get('name', '???')
score = entry.get('score', 0)
score_entry_text = f"{i + 1}. {name} - {score}"
score_entry = self.info_font.render(score_entry_text, True, (200, 200, 200))
entry_rect = score_entry.get_rect(center=(SCREEN_WIDTH // 2, y_offset))
screen.blit(score_entry, entry_rect)
y_offset += self.info_font.get_linesize()
def _draw_exit_button(self, screen):
"""Draws the exit button."""
rect = self.buttons['exit']
mouse_pos = pygame.mouse.get_pos()
color = (200, 80, 80) if rect.collidepoint(mouse_pos) else (150, 50, 50)
pygame.draw.rect(screen, color, rect, border_radius=15)
pygame.draw.rect(screen, (255, 255, 255), rect, 3, border_radius=15)
text_surf = self.button_font.render(localization_manager.get('exit_button', default="Exit"), True, (255, 255, 255))
text_rect = text_surf.get_rect(center=rect.center)
screen.blit(text_surf, text_rect)
def _draw_restart_button(self, screen):
"""Draws the restart button."""
rect = self.buttons['restart']
mouse_pos = pygame.mouse.get_pos()
color = (200, 150, 0) if rect.collidepoint(mouse_pos) else (150, 100, 0)
pygame.draw.rect(screen, color, rect, border_radius=15)
pygame.draw.rect(screen, (255, 255, 255), rect, 3, border_radius=15)
text_surf = self.button_font.render(localization_manager.get('restart_button', default="Restart"), True, (255, 255, 255))
text_rect = text_surf.get_rect(center=rect.center)
screen.blit(text_surf, text_rect)