Source code for archeryutils.rounds

"""Module to define a Pass and Round classes for archery applications."""

from collections.abc import Iterable

from archeryutils.targets import Quantity, ScoringSystem, Target


[docs] class Pass: """ A class used to represent a Pass. This class represents a pass of arrows shot at a target. It is the sub-unit of a Round. e.g. a single distance in a round or half of a single-distance round. Parameters ---------- n_arrows : int number of arrows in this pass. target : Target A Target object. Attributes ---------- n_arrows : int number of arrows in this pass. target : Target A Target object. Examples -------- A Pass can be defined simply as: >>> my720pass = au.Pass(36, au.Target("10_zone", 122, 70.0)) See Also -------- archeryutils.Target : The `Target` class. """ def __init__(self, n_arrows: int, target: Target) -> None: self.n_arrows = abs(n_arrows) self.target = target if not isinstance(self.target, Target): msg = "The target passed to a Pass should be of type Target." raise TypeError(msg)
[docs] @classmethod def at_target( cls, n_arrows: int, scoring_system: ScoringSystem, diameter: float | tuple[float, str], distance: float | tuple[float, str], indoor: bool = False, ) -> "Pass": """ Initalise a Pass directly with the parameters of its target. The parameters are passed directly to the default Target constuctor and therefore share the same behaviours and defaults. Parameters ---------- n_arrows : int number of arrows in this pass scoring_system : ScoringSystem Literal string value of target face/scoring system type. diameter : float | tuple[float, str] Target diameter size (and units, default [cm]) distance : float | tuple[float, str] Target distance (and units, default [metres]) indoor : bool is round indoors for arrow diameter purposes? default = False Returns ------- Pass The constructed Pass instance Examples -------- >>> pass_ = au.Pass.at_target(36, "10_zone", 122, 70.0) Like with the Target class, the units for diameter and distance can be explicitly specified using tuples: >>> myWA18pass = au.Pass.at_target( ... 30, "10_zone", (40, "cm"), (18.0, "m"), indoor=True ... ) """ target = Target(scoring_system, diameter, distance, indoor) return cls(n_arrows, target)
def __repr__(self) -> str: """Return a representation of a Pass instance.""" return f"Pass({self.n_arrows}, {self.target})" def __eq__(self, other: object) -> bool: """Check equality of Passes based on parameters.""" if not isinstance(other, Pass): return NotImplemented return self.n_arrows == other.n_arrows and self.target == other.target def __hash__(self) -> int: """Generate hash for the Pass object.""" return hash((self.n_arrows, self.target)) @property def scoring_system(self) -> ScoringSystem: """Get target scoring_system.""" return self.target.scoring_system @property def diameter(self) -> float: """Get target diameter in [metres].""" return self.target.diameter @property def distance(self) -> float: """Get target distance in [metres].""" return self.target.distance @property def native_diameter(self) -> Quantity: """Get diameter of target in native units.""" return self.target.native_diameter @property def native_distance(self) -> Quantity: """Get distance of target in native units.""" return self.target.native_distance @property def indoor(self) -> bool: """Get indoor attribute of target.""" return self.target.indoor
[docs] def max_score(self) -> float: """ Return the maximum numerical score possible on this pass (not counting x's). Returns ------- float maximum score possible on this pass """ return self.n_arrows * self.target.max_score()
[docs] class Round: """ Class representing a Round. Describes an archer round made up of a number of Passes. e.g. for different distances. Parameters ---------- name : str Formal name of the round passes : iterable of Pass an iterable of Pass classes making up the round codename : str, default="" A machine readable identifier for the round location : str or None, default=None string identifing where the round is shot body : str or None, default=None string identifing the governing body the round belongs to family : str or None, default=None string identifing the family the round belongs to (e.g. wa1440, western, etc.) Attributes ---------- name : str Formal name of the round passes : list of Pass a list of Pass classes making up the round location : str | None string identifing where the round is shot body : str | None string identifing the governing body the round belongs to family : str | None string identifing the family the round belongs to (e.g. wa1440, western, etc.) n_arrows : int total number of arrows in this round Examples -------- Before defining a Round we need to first define the passes that make it up: >>> my720pass = au.Pass.at_target(36, "10_zone", 122, 70.0) These can now be passed to the Round constructor as any iterable, they will be stored as a list: >>> my720round = au.Round("WA 720", [my720pass, my720pass]) >>> my720round2 = au.Round("WA 720", (my720pass, my720pass)) >>> assert my720round.passes == my720round2.passes == [my720pass, my720pass] Additional, optional parameters can be used to provide 'metadata' about the round. """ def __init__( self, name: str, passes: Iterable[Pass], codename: str = "", location: str | None = None, body: str | None = None, family: str | None = None, ) -> None: self.name = name self.codename = codename self.passes = list(passes) if not self.passes: msg = "passes must contain at least one Pass object but none supplied." raise ValueError(msg) if any(not isinstance(x, Pass) for x in self.passes): msg = "passes in a Round object should be an iterable of Pass objects." raise TypeError(msg) self.location = location self.body = body self.family = family self.n_arrows: int = sum(pass_i.n_arrows for pass_i in self.passes) def __repr__(self) -> str: """Return a representation of a Round instance.""" return f"<Round: '{self.name}'>" def __eq__(self, other: object) -> bool: """Check equality of Rounds based on name and passes. Does not consider optional labels of location/body/family as these do not affect behaviour. """ if not isinstance(other, Round): return NotImplemented return self.name == other.name and self.passes == other.passes def __hash__(self) -> int: """Generate hash for the Round object.""" return hash((self.name, tuple(self.passes)))
[docs] def max_score(self) -> float: """ Return the maximum numerical score possible on this round (not counting x's). Returns ------- max_score : float maximum score possible on this round """ return sum(pass_i.max_score() for pass_i in self.passes)
[docs] def max_distance(self) -> Quantity: """ Return the maximum distance shot on this round along with the unit (optional). Returns ------- max_dist : Quantity maximum distance and units shot in this round Notes ----- This does not convert the units of the result. Rather the maximum distance shot in the round is returned in whatever units it was defined in. """ longest_pass = max(self.passes, key=lambda p: p.distance) return longest_pass.native_distance
[docs] def get_info(self) -> None: """ Print information about the Round. Prints a summary of each Pass in the round giving number of arrows, distance, and target size. """ print(f"A {self.name} consists of {len(self.passes)} passes:") for pass_i in self.passes: diam, diam_units = pass_i.native_diameter dist, dist_units = pass_i.native_distance print( f"\t- {pass_i.n_arrows} arrows " f"at a {diam:.1f} {diam_units} target " f"at {dist:.1f} {dist_units}s.", )