Source code for pooltool.objects.table.datatypes

from __future__ import annotations

import numpy as np
from attrs import define, evolve, field

from pooltool.game.datatypes import GameType
from pooltool.objects.table.collection import (
    TableName,
    default_specs_from_game_type,
    default_specs_from_table_type,
    prebuilt_specs,
)
from pooltool.objects.table.components import (
    CircularCushionSegment,
    CushionSegments,
    LinearCushionSegment,
    Pocket,
)
from pooltool.objects.table.layout import (
    create_billiard_table_cushion_segments,
    create_pocket_table_cushion_segments,
    create_pocket_table_pockets,
)
from pooltool.objects.table.specs import (
    BilliardTableSpecs,
    PocketTableSpecs,
    SnookerTableSpecs,
    TableModelDescr,
    TableSpecs,
    TableType,
)


[docs] @define class Table: """A table. While a table can be constructed by passing all of the following initialization parameters, there are many easier ways, all of which are detailed in the `Table Specification </resources/table_specs>` resource. Attributes: cushion_segments: The table's linear and circular cushion segments. pockets: The table's pockets. table_type: An Enum specifying the type of table. height: The height of the playing surface (measured from the ground). This is just used for visualization. lights_height: The height of the table lights (measured from the playing surface). This is just used for visualization. """ cushion_segments: CushionSegments pockets: dict[str, Pocket] table_type: TableType model_descr: TableModelDescr | None = field(default=None) height: float = field(default=0.708) lights_height: float = field(default=1.99)
[docs] def set_cushion_height(self, height: float) -> None: """Set the height of all cushion segments. Args: height: The new height to set for all cushion segments. """ linear: dict[str, LinearCushionSegment] = {} circular: dict[str, CircularCushionSegment] = {} for id, segment in self.cushion_segments.linear.items(): p1 = np.array([segment.p1[0], segment.p1[1], height], dtype=np.float64) p2 = np.array([segment.p2[0], segment.p2[1], height], dtype=np.float64) linear[id] = evolve(segment, p1=p1, p2=p2) for id, segment in self.cushion_segments.circular.items(): center = np.array( [segment.center[0], segment.center[1], height], dtype=np.float64 ) circular[id] = evolve(segment, center=center) self.cushion_segments.linear = linear self.cushion_segments.circular = circular
@property def w(self) -> float: """The width of the table. Warning: This assumes the table follows the layout similar to `this diagram <https://ekiefl.github.io/images/pooltool/pooltool-alg/cushion_count.jpg>`_. Specifically, it must have the linear cushion segments with IDs ``"3"``` and ``"12"``. """ assert "12" in self.cushion_segments.linear assert "3" in self.cushion_segments.linear x2 = self.cushion_segments.linear["12"].p1[0] x1 = self.cushion_segments.linear["3"].p1[0] return x2 - x1 @property def l(self) -> float: # noqa F743 """The length of the table. Warning: This assumes the table follows the layout similar to `this diagram <https://ekiefl.github.io/images/pooltool/pooltool-alg/cushion_count.jpg>`_. Specifically, it must have the linear cushion segments with IDs ``"9"``` and ``"18"``. """ assert "9" in self.cushion_segments.linear assert "18" in self.cushion_segments.linear y2 = self.cushion_segments.linear["9"].p1[1] y1 = self.cushion_segments.linear["18"].p1[1] return y2 - y1 @property def center(self) -> tuple[float, float]: """Return the 2D coordinates of the table's center Warning: This assumes :meth:`l` and :meth:`w` are defined. """ return self.w / 2, self.l / 2 @property def has_linear_cushions(self) -> bool: return bool(len(self.cushion_segments.linear)) @property def has_circular_cushions(self) -> bool: return bool(len(self.cushion_segments.circular)) @property def has_pockets(self) -> bool: return bool(len(self.pockets))
[docs] def copy(self) -> Table: """Create a copy.""" # Delegates the deep-ish copying of CushionSegments and Pocket to their respective # copy() methods. Uses dictionary comprehension to construct equal but different # `pockets` attribute. All other attributes are frozen or immutable. return evolve( self, cushion_segments=self.cushion_segments.copy(), pockets={k: v.copy() for k, v in self.pockets.items()}, )
[docs] @staticmethod def from_table_specs(specs: TableSpecs) -> Table: """Build a table from a table specifications object Args: specs: A valid table specification. Accepted objects: - :class:`pooltool.objects.PocketTableSpecs` - :class:`pooltool.objects.BilliardTableSpecs` - :class:`pooltool.objects.SnookerTableSpecs` Returns: Table: A table matching the specifications of the input. - :class:`pooltool.objects.PocketTableSpecs` has :attr:`table_type` set to `pooltool.objects.TableType.POCKET` - :class:`pooltool.objects.BilliardTableSpecs` has :attr:`table_type` set to `pooltool.objects.TableType.BILLIARD` - :class:`pooltool.objects.SnookerTableSpecs` has :attr:`table_type` set to `pooltool.objects.TableType.SNOOKER` """ if specs.table_type == TableType.BILLIARD: assert isinstance(specs, BilliardTableSpecs) segments = create_billiard_table_cushion_segments(specs) pockets = {} elif specs.table_type == TableType.POCKET: assert isinstance(specs, PocketTableSpecs) segments = create_pocket_table_cushion_segments(specs) pockets = create_pocket_table_pockets(specs) elif specs.table_type == TableType.SNOOKER: assert isinstance(specs, SnookerTableSpecs) segments = create_pocket_table_cushion_segments(specs) pockets = create_pocket_table_pockets(specs) else: raise NotImplementedError(f"Unknown table type: {specs.table_type}") return Table( cushion_segments=segments, pockets=pockets, table_type=specs.table_type, model_descr=specs.model_descr, height=specs.height, lights_height=specs.lights_height, )
[docs] @classmethod def prebuilt(cls, name: TableName) -> Table: """Create a default table based on name Args: name: The name of the prebuilt table specs. Returns: Table: A prebuilt table. """ return cls.from_table_specs(prebuilt_specs(name))
[docs] @classmethod def default(cls, table_type: TableType = TableType.POCKET) -> Table: """Create a default table based on table type A default table is associated to each table type. Args: table_type: The type of table. Returns: Table: The default table for the given table type. """ return cls.from_table_specs(default_specs_from_table_type(table_type))
[docs] @classmethod def from_game_type(cls, game_type: GameType) -> Table: """Create a default table based on table type A default table is associated with each game type. Args: game_type: The game type. Returns: Table: The default table for the given game type. """ return cls.from_table_specs(default_specs_from_game_type(game_type))
__all__ = [ "BilliardTableSpecs", "PocketTableSpecs", "SnookerTableSpecs", "TableModelDescr", "TableSpecs", "TableType", ]