nodes/hud.pyΒΆ

Part of Tanks of Freedom.

  1"""HUD: top status bar + right-side info panel + end-turn button."""
  2
  3from __future__ import annotations
  4
  5from simvx.core import Node2D, Signal
  6from simvx.core.ui.widgets import Button, Label
  7
  8from .data import (
  9    BUILDING_STATS,
 10    HUD_HEIGHT,
 11    INFO_PANEL_W,
 12    PLAYER_COLOUR,
 13    PLAYER_NAME,
 14    UNIT_STATS,
 15    WINDOW_HEIGHT,
 16    WINDOW_WIDTH,
 17)
 18
 19
 20class Hud(Node2D):
 21    def __init__(self, **kwargs):
 22        super().__init__(name="Hud", **kwargs)
 23        self.end_turn_pressed = Signal()
 24        self.new_game_pressed = Signal()
 25
 26        # Label children so we can update them live.
 27        self._turn_label = Label("Turn 1: BLUE")
 28        self._turn_label.position = (16, 16)
 29        self._turn_label.size = (300, 28)
 30        self._turn_label.font_size = 22.0
 31        self._turn_label.text_colour = (1.0, 1.0, 1.0, 1.0)
 32        self.add_child(self._turn_label)
 33
 34        self._status_label = Label("")
 35        self._status_label.position = (340, 18)
 36        self._status_label.size = (700, 22)
 37        self._status_label.font_size = 16.0
 38        self._status_label.text_colour = (0.9, 0.92, 0.95, 1.0)
 39        self.add_child(self._status_label)
 40
 41        # End-turn button (top right of the top bar).
 42        self._end_btn = Button("End Turn")
 43        self._end_btn.position = (WINDOW_WIDTH - INFO_PANEL_W - 130, 12)
 44        self._end_btn.size = (120, 36)
 45        self._end_btn.font_size = 16.0
 46        self._end_btn.pressed.connect(lambda: self.end_turn_pressed())
 47        self.add_child(self._end_btn)
 48
 49        # Right-hand info panel labels (unit selection / building info).
 50        panel_x = WINDOW_WIDTH - INFO_PANEL_W + 12
 51        self._panel_title = Label("")
 52        self._panel_title.position = (panel_x, HUD_HEIGHT + 14)
 53        self._panel_title.size = (INFO_PANEL_W - 24, 28)
 54        self._panel_title.font_size = 22.0
 55        self._panel_title.text_colour = (1.0, 1.0, 1.0, 1.0)
 56        self.add_child(self._panel_title)
 57
 58        self._panel_lines: list[Label] = []
 59        for i in range(8):
 60            lbl = Label("")
 61            lbl.position = (panel_x, HUD_HEIGHT + 50 + i * 22)
 62            lbl.size = (INFO_PANEL_W - 24, 22)
 63            lbl.font_size = 14.0
 64            lbl.text_colour = (0.85, 0.88, 0.92, 1.0)
 65            self.add_child(lbl)
 66            self._panel_lines.append(lbl)
 67
 68        self._victory_label = Label("")
 69        self._victory_label.position = (WINDOW_WIDTH / 2 - 220, WINDOW_HEIGHT / 2 - 30)
 70        self._victory_label.size = (440, 60)
 71        self._victory_label.font_size = 36.0
 72        self._victory_label.text_colour = (1.0, 1.0, 1.0, 1.0)
 73        self._victory_label.alignment = "center"
 74        self.add_child(self._victory_label)
 75
 76        self._restart_btn = Button("Play Again")
 77        self._restart_btn.position = (WINDOW_WIDTH / 2 - 75, WINDOW_HEIGHT / 2 + 30)
 78        self._restart_btn.size = (150, 40)
 79        self._restart_btn.font_size = 16.0
 80        self._restart_btn.visible = False
 81        self._restart_btn.pressed.connect(lambda: self.new_game_pressed())
 82        self.add_child(self._restart_btn)
 83
 84    # ----------------------------------------------------------- updates
 85    def set_turn(self, player: int, turn_no: int) -> None:
 86        col = PLAYER_COLOUR[player]
 87        self._turn_label.text = f"Turn {turn_no}: {PLAYER_NAME[player]}"
 88        self._turn_label.text_colour = (col[0], col[1], col[2], 1.0)
 89
 90    def set_status(self, msg: str) -> None:
 91        self._status_label.text = msg
 92
 93    def set_unit_panel(self, unit) -> None:
 94        if unit is None:
 95            self._panel_title.text = ""
 96            for lbl in self._panel_lines:
 97                lbl.text = ""
 98            return
 99        stats = UNIT_STATS[unit.type]
100        col = PLAYER_COLOUR[unit.owner]
101        self._panel_title.text = stats["name"]
102        self._panel_title.text_colour = (col[0], col[1], col[2], 1.0)
103        lines = [
104            f"Owner   : {PLAYER_NAME[unit.owner]}",
105            f"HP      : {unit.life} / {unit.max_life}",
106            f"Attack  : {stats['attack']}",
107            f"AP      : {unit.ap} / {unit.max_ap}",
108            f"Capture : {'yes' if unit.can_capture else 'no'}",
109            "",
110            "Click highlighted tile to move",
111            "Click red tile to attack",
112        ]
113        for lbl, text in zip(self._panel_lines, lines, strict=False):
114            lbl.text = text
115        for lbl in self._panel_lines[len(lines) :]:
116            lbl.text = ""
117
118    def set_building_panel(self, building) -> None:
119        if building is None:
120            return
121        stats = BUILDING_STATS[building.type]
122        col = PLAYER_COLOUR[building.owner]
123        self._panel_title.text = stats["name"]
124        self._panel_title.text_colour = (col[0], col[1], col[2], 1.0)
125        lines = [
126            f"Owner   : {PLAYER_NAME[building.owner]}",
127            f"Role    : {'win condition' if building.is_hq else 'territory'}",
128            "",
129            "Capture by ending an infantry",
130            "move on this tile.",
131        ]
132        for lbl, text in zip(self._panel_lines, lines, strict=False):
133            lbl.text = text
134        for lbl in self._panel_lines[len(lines) :]:
135            lbl.text = ""
136
137    def show_victory(self, winner: int) -> None:
138        col = PLAYER_COLOUR[winner]
139        self._victory_label.text = f"{PLAYER_NAME[winner]} WINS"
140        self._victory_label.text_colour = (col[0], col[1], col[2], 1.0)
141        self._restart_btn.visible = True
142
143    def hide_victory(self) -> None:
144        self._victory_label.text = ""
145        self._restart_btn.visible = False
146
147    # -------------------------------------------------------------- draw
148    def on_draw(self, renderer) -> None:
149        # Top bar
150        renderer.draw_rect((0, 0), (WINDOW_WIDTH, HUD_HEIGHT), colour=(0.10, 0.13, 0.18, 0.95), filled=True)
151        renderer.draw_rect((0, HUD_HEIGHT - 2), (WINDOW_WIDTH, 2), colour=(0.30, 0.40, 0.55, 1.0), filled=True)
152        # Right info panel
153        renderer.draw_rect(
154            (WINDOW_WIDTH - INFO_PANEL_W, HUD_HEIGHT),
155            (INFO_PANEL_W, WINDOW_HEIGHT - HUD_HEIGHT),
156            colour=(0.10, 0.13, 0.18, 0.92),
157            filled=True,
158        )
159        renderer.draw_rect(
160            (WINDOW_WIDTH - INFO_PANEL_W, HUD_HEIGHT),
161            (2, WINDOW_HEIGHT - HUD_HEIGHT),
162            colour=(0.30, 0.40, 0.55, 1.0),
163            filled=True,
164        )