Source code for gachapy.controller

"""The controller used for all high level management of the gacha game. 
Instances of this class should be created for each instance of a gacha game 
you would like to run. These instances can then be used to access and manage 
the Item, Banner, and Player objects in the game directly. Once accessed, the 
objects can then be modified directly using class methods (see objects.py for 
more info).

This object operates as the Controller of a gachapy game 

Author: Jacob Kerr, 2021
"""

from typing import Dict, Optional
from .objects import *
import warnings


[docs]DEFAULT_KEY = "1 / R"
"""The default key function converting rarity to drop rate (1 / rarity)""" def _sort_item_key(item: Item) -> float: """The key used to sort items in a list of items Parameters ---------- item : Item the item to extract the key from Returns ------- float the key of the item """ return item.rarity
[docs]def _sort_player_key(player: Player) -> float: """The key used to sort players in a list of players Parameters ---------- player : Player the player to extract the key from Returns ------- float the key of the player """ return player.get_net_worth()
[docs]class PullError(Exception): """An exception thrown when pulling from a banner""" pass
[docs]class Controller: """A controller used to control an instance of a gacha game. Used to perform all actions needed in a game and manage game information in the form of gachapy objects Fields ------ items : Dict[str,Item] the list of items that are in the item pool for the gacha banners : Dict[str,Banner] the list of banners that are available for the gacha players : Dict[str,Player] the list of players enrolled in the gacha """
[docs] items: Dict[str, Item]
"""The list of items that are in the item pool for the gacha"""
[docs] banners: Dict[str, Banner]
"""The list of banners that are available for the gacha"""
[docs] players: Dict[str, Player]
"""The list of players enrolled in the gacha""" def __init__( self, items: Dict[str, Item] = {}, banners: Dict[str, Banner] = {}, players: Dict[str, Player] = {}, ) -> None: """Creates an instance of a gacha controller Parameters ---------- items : Dict[str,Item] the dict of id, item pairs that are in the item pool for the gacha banners : Dict[str,Banner] the dict of id, banner pairs that are available for the gacha players : Dict,str[Player] the dict of id, player pairs enrolled in the gacha """ self.items = items self.banners = banners self.players = players
[docs] def find_item_by_name(self, item_name: str) -> Optional[Item]: """Returns the Item object with the given name or None if not found DEPRICATED: consider using find_item_by_id Parameters ---------- item_name : str the name of the item Returns ------- Optional[Item] the item object with the given name or None if not found """ warnings.warn( "find_item_by_name is depricated, consider using find_item_by_id", DeprecationWarning) item = [i for i in self.items.values() if i.name == item_name] if len(item) < 1: return None return item[0]
[docs] def find_banner_by_name(self, banner_name: str) -> Optional[Banner]: """Returns the Banner object with the given name or None if not found DEPRICATED: consider using find_banner_by_id Parameters ---------- banner_name : str the name of the banner Returns ------- Optional[Banner] the banner object with the given name or None if not found """ warnings.warn( "find_banner_by_name is depricated, consider using find_banner_by_id", DeprecationWarning) banner = [i for i in self.banners.values() if i.name == banner_name] if len(banner) < 1: return None return banner[0]
[docs] def find_player_by_name(self, player_name: str) -> Optional[Player]: """Returns the Player object with the given name or None if not found DEPRICATED: consider using find_player_by_id Parameters ---------- player_name : str the name of the player Returns ------- Optional[player] the player object with the given name or None if not found """ warnings.warn( "find_player_by_name is depricated, consider using find_player_by_id", DeprecationWarning) player = [i for i in self.players.values() if i.name == player_name] if len(player) < 1: return None return player[0]
[docs] def find_item_by_id(self, item_id: str) -> Optional[Item]: """Returns the Item object with the given id or None if not found Parameters ---------- item_id : str the id of the item Returns ------- Optional[Item] the item object with the given id or None if not found """ return self.items.get(item_id)
[docs] def find_banner_by_id(self, banner_id: str) -> Optional[Banner]: """Returns the Banner object with the given id or None if not found Parameters ---------- banner_id : str the id of the banner Returns ------- Optional[Banner] the banner object with the given id or None if not found """ return self.banners.get(banner_id)
[docs] def find_player_by_id(self, player_id: str) -> Optional[Player]: """Returns the Player object with the given id or None if not found Parameters ---------- player_id : str the id of the player Returns ------- Optional[player] the player object with the given id or None if not found """ return self.players.get(player_id)
[docs] def pull(self, player_id: str, banner_id: str) -> Optional[Item]: """Pulls and returns an item from the specified banner for the specified player Parameters ---------- player_id : str the id of the selected player Preconditon: must be a valid id banner_id : str the id of the selected banner Preconditon: must be a valid id Returns ------- Optional[Item] the item if the pull is successful or None if the player does not have enough money Raises ------ PullError if player or banner are not valid """ player = self.find_player_by_id(player_id) if player == None: raise PullError("Player not found") banner = self.find_banner_by_id(banner_id) if banner == None: raise PullError("Banner not found") if player.change_money(-1 * banner.price): item = banner.pull() player.add_item(item) return item return None
[docs] def add_new_item(self, name: str, id: str, rarity: float) -> Optional[Item]: """Adds a new item to the gacha game Parameters ---------- name : str the name of the new item id : str the id of the new item Precondition: must be unique rarity : float rarity of the item where the higher the number, the higher the rarity Precondition: must be >= 1 Returns ------- Optional[Item] the Item object representing the new item or None if an item with the specified id already exists """ if id in self.items: return None new_item = Item(name, id, rarity) self.items[id] = new_item return new_item
[docs] def add_new_banner( self, name: str, id: str, items_str: List[str], price: float, key: str = DEFAULT_KEY, ) -> Optional[Banner]: """Adds a new banner to the gacha game Parameters ---------- name : str the name of the new banner id : str the id of the new banner Precondition: must be unique items_str : List[str] the list of the ids of the items in the banner price : float the price of pulling from the banner key : str function that takes in rarity and returns the drop rate of the item, written in KeyLang Returns ------- Optional[Banner] the Banner object representing the new banner or None if a banner with the specified id already exists """ if id in self.banners: return None items = [self.find_item_by_id(i) for i in items_str] new_banner = Banner(name, id, items, price, key) self.banners[id] = new_banner return new_banner
[docs] def add_new_player( self, name: str, id: str, start_money: float, items_str: List[str] = [] ) -> Optional[Player]: """Adds a new player to the gacha game Parameters ---------- name : str the name of the new player id : str the id of the new player start_money : float the amount of money the new player will start with items_str : List[str] the list of the ids of the items the player has Returns ------- Optional[Player] the Player object representing the new player or None if a player with the specified id already exists """ if id in self.players: return None items_list = [self.find_item_by_id(i) for i in items_str] new_player = Player(name, id, items_list, start_money) self.players[id] = new_player return new_player
[docs] def remove_item(self, item_id: str) -> Optional[Item]: """Removes the specified item from the gacha game WARNING: Will also remove from banners and players if found, costly operation Parameters ---------- item_id : str the name of the item to remove Returns ------- Optional[Item] the removed item or None if item does not exist """ item = self.items.pop(item_id, None) if item == None: return item for banner in self.banners: while self.banners[banner].remove_item(item): pass for player in self.players: while self.players[player].remove_item(item): pass return item
[docs] def remove_banner(self, banner_id: str) -> Optional[Banner]: """Removes the specified banner from the gacha game Parameters ---------- banner_id : str the id of the banner to remove Returns ------- Optional[Banner] the removed banner or None if banner does not exist """ return self.banners.pop(banner_id, None)
[docs] def remove_player(self, player_id: str) -> Optional[Player]: """Removes the specified player from the gacha game Parameters ---------- player_id : str the id of the player to remove Returns ------- Optional[Player] the removed player or None if player does not exist """ return self.players.pop(player_id, None)
[docs] def create_random_banner( self, name: str, id: str, num_items: int, price: float = -1, key: str = DEFAULT_KEY, ) -> Optional[Banner]: """Creates a random banner with the given name and number of items The price is automatically determined by the average of the rarities of the items selected if a price is not specified Parameters ---------- name : str the name of the random banner id : str the id of the random banner Precondition: must be unique num_items : int the number of items in the banner price : float the price of the banner key : str function that takes in rarity and returns the drop rate of the item, written in KeyLang Returns ------- Optional[Banner] the banner created or None if a banner with the name specified already exists """ items = random.choices(list(self.items.values()), k=num_items) items_str = [item.id for item in items] if price < 0: price = 0 for item in items: price += item.rarity price /= len(items) return self.add_new_banner(name, id, items_str, price, key)
[docs] def remove_all_banners(self) -> None: """Removes all of the banners in the game Returns ------- None """ self.banners = {}
[docs] def top_items(self, num_items: int) -> List[Item]: """Returns the top specified number of items in the game sorted by rarity Parameters ---------- num_items : int the number of items to return Precondition: must be >= 1 Returns ------- List[Item] the list of top items """ sort_list = sorted(list(self.items.values()), key=_sort_item_key, reverse=True) return sort_list[:num_items]
[docs] def top_players(self, num_players: int) -> List[Player]: """Returns the top specified number of players in the game sorted by net worth Parameters ---------- num_players : int the number of players to return Precondition: must be >= 1 Returns ------- List[Player] the list of top players """ sort_list = sorted( list(self.players.values()), key=_sort_player_key, reverse=True ) return sort_list[:num_players]
[docs] def __str__(self) -> str: return f"""Items\n{[str(item) for item in self.items.values()]}\n Banners\n{[str(banner) for banner in self.banners.values()]}\n Players\n{[str(player) for player in self.players.values()]}"""