nodes/hand.pyΒΆ

Part of Claustrowordia.

 1"""Hand: the bottom row of letter tiles waiting to be placed.
 2
 3Lays out N tiles horizontally below the grid; tiles bounce-tween into
 4their slot positions. The Hand owns no input; `Game` resolves clicks
 5itself by querying `tile_at(world_pos)`.
 6"""
 7
 8from __future__ import annotations
 9
10from simvx.core import Node2D
11from simvx.core.math.types import Vec2
12
13from .textures import TILE_SIZE
14from .tile import Tile
15
16HAND_SIZE = 7
17HAND_SPACING = TILE_SIZE + 14
18
19
20class Hand(Node2D):
21    def __init__(self, centre: Vec2) -> None:
22        super().__init__(name="Hand")
23        self._centre = centre
24        self.tiles: list[Tile] = []
25
26    def on_ready(self) -> None:
27        # Tiles are added externally via `add_tile`.
28        pass
29
30    # ------------------------------------------------------------------
31    def add_tile(self, tile: Tile) -> None:
32        # Start tile off-screen below; layout will tween it up.
33        tile.set_position_immediate(Vec2(self._centre.x, self._centre.y + 200))
34        self.tiles.append(tile)
35        self.add_child(tile)
36        self.layout()
37
38    def remove_tile(self, tile: Tile) -> None:
39        if tile in self.tiles:
40            self.tiles.remove(tile)
41            self.remove_child(tile)
42            self.layout()
43
44    def layout(self) -> None:
45        n = len(self.tiles)
46        if n == 0:
47            return
48        cx = self._centre.x
49        cy = self._centre.y
50        for i, tile in enumerate(self.tiles):
51            x = cx + (i - (n - 1) / 2) * HAND_SPACING
52            tile.set_target(Vec2(x, cy))
53
54    def tile_at(self, world_pos: Vec2) -> Tile | None:
55        """Return topmost hand tile under `world_pos`, or None."""
56        for tile in reversed(self.tiles):
57            dx = world_pos.x - tile.position.x
58            dy = world_pos.y - tile.position.y
59            if abs(dx) <= TILE_SIZE / 2 and abs(dy) <= TILE_SIZE / 2:
60                return tile
61        return None
62
63    def clear(self) -> None:
64        for tile in list(self.tiles):
65            self.remove_child(tile)
66        self.tiles.clear()
67
68
69__all__ = ["Hand", "HAND_SIZE"]