Source code for DnD_5e.team

import warnings
from copy import copy, deepcopy

from DnD_5e import combatant, tactics

[docs] class Team: """ A container class for :py:class:`Combatant` s, to be used by :py:class:`Encounter` """ def __init__(self, **kwargs): """ Validate the input and set the instance variables :param kwargs: keyword arguments. Valid arguments are as follows: :param name: what *self* is called :type name: str :param combatant_list: list or tuple of team members :type combatant_list: list or tuple of :py:class:`Combatant` s :param attack_tactic: Tactic to decide who/what to attack :type attack_tactic: :py:class:`Tactic` :raise: ValueError if input is invalid """ self._name = kwargs.get("name", "team") if not isinstance(self._name, str): raise ValueError("Name must be a string") # a tactic for selecting who/what to attack. If none provided, use default (random) tactic self._enemy_tactic = kwargs.get("enemy_tactic", tactics.Tactic()) if not isinstance(self._enemy_tactic, tactics.Tactic): raise ValueError("Enemy tactic must be a Tactic") combatant_list = kwargs.get("combatant_list") if not combatant_list or not isinstance(combatant_list, (list, tuple)): raise ValueError("Combatant list must be a non-empty list of Combatants") self._combatants = [] # copy the list so the user doesn't accidentally mess with it later for item in combatant_list: self.add_combatant(item) def __eq__(self, other) -> bool: """ Compare *self* and *other* to determine if they are equal based on :py:attr:`combatant_list` .. Note:: Does not consider :py:attr:`attack_tactic` when determining equality :param other: the Team to be compared :type other: :py:class:`Team` :return: True if *self* equals *other*, False otherwise :rtype: bool """ if other is self: return True if type(other) != type(self): # pylint: disable=unidiomatic-typecheck return False if len(self.get_combatants()) != len(other.get_combatants()): return False for i, the_combatant in enumerate(self.get_combatants()): if not the_combatant == other.get_combatants()[i]: return False return True
[docs] def get_name(self) -> str: """ :return: self._name :rtype: str """ return self._name
[docs] def set_name(self, name: str): if not isinstance(name, str): raise ValueError("name must be a string") self._name = name
[docs] def get_combatants(self) -> list: """ :return: self._combatants :rtype: list of :py:class:`Combatant` s """ return self._combatants
[docs] def get_enemy_tactic(self): """ :return: self._enemy_tactic :rtype: :py:class:`Tactic` """ return self._enemy_tactic
[docs] def has_member(self, member: combatant.Combatant) -> bool: """ :param member: the member who may or may not be on the team :type member: Combatant :return: True if *member* is in the list of combatants, False otherwise """ return member in self.get_combatants()
[docs] def get_stats(self): """ Calculate and return num_conscious, num_unconscious, num_dead :return: """ result = {"num_conscious": 0, "num_unconscious": 0, "num_dead": 0} for comb in self.get_combatants(): if comb.has_condition("unconscious"): result["num_unconscious"] += 1 elif comb.has_condition("dead"): result["num_dead"] += 1 else: result["num_conscious"] += 1 return result
[docs] def has_all_with_condition(self, condition: str): """ :return: True if all combatants have the given condition, False otherwise """ for item in self.get_combatants(): if not item.has_condition(condition): return False return True
[docs] def has_any_with_condition(self, condition: str): """ :return: True if at least one combatant has the given condition, False otherwise """ for item in self.get_combatants(): if item.has_condition(condition): return True return False
[docs] def has_some_not_all_with_condition(self, condition: str): """ :return: True if some combatants have the condition and other combatants don't, False otherwise """ found_true = False found_false = False for item in self.get_combatants(): if item.has_condition(condition): found_true = True else: found_false = True return found_true and found_false
[docs] def has_all_unconscious(self) -> bool: """ :return: True if all combatants are unconscious, False otherwise """ return self.has_all_with_condition("unconscious")
[docs] def has_all_dead(self) -> bool: """ :return: True if all combatants are dead, False otherwise """ return self.has_all_with_condition("dead")
[docs] def has_all_alive(self) -> bool: """ :return: True if all combatants are alive, False otherwise """ return not self.has_any_with_condition("dead")
[docs] def has_all_conscious(self) -> bool: """ :return: True if all combatants are conscious, False otherwise """ for item in self.get_combatants(): if not item.is_conscious(): return False return True
[docs] def has_any_unconscious(self) -> bool: """ :return: True if at least 1 combatant is unconscious, False otherwise """ return self.has_any_with_condition("unconscious")
[docs] def has_any_dead(self) -> bool: """ :return: True if at least 1 combatant is dead, False otherwise """ return self.has_any_with_condition("dead")
[docs] def has_some_alive(self) -> bool: """ :return: True if at least 1 combatant is alive, False otherwise """ return not self.has_all_with_condition("dead")
[docs] def has_some_not_all_unconscious(self): """ :return: True if some but not all combatants are unconscious """ return self.has_some_not_all_with_condition("unconscious")
[docs] def has_some_not_all_dead(self): """ :return: True if some but not all combatants are dead """ return self.has_some_not_all_with_condition("dead")
[docs] def has_any_conscious(self) -> bool: """ :return: True if at least 1 combatant is conscious, False otherwise """ for item in self.get_combatants(): if item.is_conscious(): return True return False
[docs] def add_combatant(self, new_combatant: combatant.Combatant): """ Add the specified :py:class:`Combatant` to the list of combatants. Does not check to see if *new_combatant* is already on the team :param new_combatant: the Combatant to add :type new_combatant: :py:class:`Combatant` :return: None :raise: ValueError if input is invalid """ if not isinstance(new_combatant, combatant.Combatant): raise ValueError("New combatant to add must be a Combatant") self._combatants.append(new_combatant) new_combatant.set_team(self)
[docs] def remove_combatant(self, old_combatant: combatant.Combatant): """ Remove the specified :py:class:`Combatant` from the list of combatants. Checks first to see if *old_combatant* is on the team :param old_combatant: the Combatant to remove :type old_combatant: :py:class:`Combatant` :return: None """ if self.has_member(old_combatant): self.get_combatants().remove(old_combatant) old_combatant.set_team(None) else: warnings.warn(f"Tried to remove combatant that is not on team {self.get_name()}")