From d51382960a1a7542ea97fd78a1197aa467167d57 Mon Sep 17 00:00:00 2001 From: Michael Pilosov Date: Mon, 28 Nov 2022 18:56:57 -0700 Subject: [PATCH] docstring improvements and some renamings --- app.py | 4 +- pyroulette/roulette.py | 184 ++++++++++++++++++++++++++++++----------- 2 files changed, 140 insertions(+), 48 deletions(-) diff --git a/app.py b/app.py index 2234a5b..491e72a 100644 --- a/app.py +++ b/app.py @@ -76,13 +76,13 @@ if __name__ == "__main__": strategy = Strategy.generate_random(50) - strategy.print_all() + strategy.print() # define the minimum number of games that you want players to play # print the total sum of all the placements print("SUM") - print(sum([p.value for p in placements])) + print(sum([p.cost for p in placements])) # place the bets bet = Strategy.place_bets(placements) diff --git a/pyroulette/roulette.py b/pyroulette/roulette.py index d042d92..088e56d 100644 --- a/pyroulette/roulette.py +++ b/pyroulette/roulette.py @@ -7,8 +7,14 @@ from random import choice, randint from statistics import mean, stdev from typing import Dict, List, Optional +"""Set[str]: +Bets on single numbers. +""" SINGLE_BETS = {str(i) for i in range(-1, 37)} +"""Set[str]: +The possible moves that can be made in a game of roulette. +""" FEASIBLE_MOVES = sorted( { *[f"street-{i}" for i in range(1, 14)], @@ -20,7 +26,9 @@ FEASIBLE_MOVES = sorted( } ) - +"""Set[str]: +Aliases for different placements. +""" ALIASES = { "reds", "blacks", @@ -34,7 +42,11 @@ ALIASES = { "second-18", } + CHIP_VALUES = {0.25, 0.5, 1, 5, 10, 25, 50, 100} +"""Set[float]: +The possible chip values that a player can place on a given placement. +""" def init_spread() -> Dict[int, float]: @@ -45,6 +57,7 @@ def init_spread() -> Dict[int, float]: ------- Bet A dictionary representing the bet. + """ D = {i: 0 for i in range(-1, 37)} return D @@ -52,7 +65,19 @@ def init_spread() -> Dict[int, float]: @dataclass class Bet: - """A class for representing a bet.""" + """A class for representing a bet. + + Parameters + ---------- + spread : Dict[int, float], optional + A dictionary representing the bet, by default init_spread() + + Attributes + ---------- + spread : Dict[int, float] + A dictionary representing the bet. + + """ spread: Dict[int, float] = field(default_factory=init_spread) @@ -92,7 +117,7 @@ class Bet: """Return a copy of the bet.""" return Bet(self.spread.copy()) - def __iter__(self): + def __iter__(self) -> iter: return iter(self.spread.keys()) def get(self, __name: int) -> float: @@ -101,8 +126,17 @@ class Bet: @dataclass class Placement: - """ - Defines a bet based on the number of chips and value of each chip. + """Defines a bet based on the number of chips and value of each chip. + + Parameters + ---------- + num : int + The number of chips to bet. + amt : float + The value of each chip. + on : str + The type of bet to place for which the chips are being used. + Attributes ---------- @@ -137,24 +171,41 @@ class Placement: return self.amt == other.amt and self.on == other.on @property - def value(self): + def cost(self) -> float: """ Returns the value of the bet. """ return self.num * self.amt - def bet(self, bet=None) -> Bet: + def bet(self, bet: Optional[Bet] = None) -> Bet: """ Places a bet on the wheel based on the bet type. + + Parameters + ---------- + bet : Bet, optional + The bet to place, by default None + + Returns + ------- + Bet + The bet placed on the wheel. """ return interpret_bet(self.on, self.num * self.amt, bet) @dataclass class Strategy: - """ - A strategy is a list of placements, each of which is a bet on a - particular number or group of numbers. + """Represents list of placements on the roulette wheel. + Each strategy is a bet on a particular number or group of numbers. + + Parameters + ---------- + budget : float + The amount of money to spend on the strategy. + placements : List[Placement] + A list of placements the player will make. + Attributes ---------- @@ -171,17 +222,19 @@ class Strategy: def __repr__(self) -> str: return ( f"Strategy(budget={self.budget}," - + f"value={self.value}, placements={self.placements})" + + f"cost={self.cost}, placements={self.placements})" ) @property - def value(self): - return sum([p.value for p in self.placements]) + def cost(self) -> float: + """ + Returns the total cost of the strategy. + """ + return sum([p.cost for p in self.placements]) @classmethod def generate_random(cls, budget: float, max_placements: int = 10) -> Strategy: - """ - Generates a random strategy. + """Generates a random strategy. Parameters ---------- @@ -218,25 +271,42 @@ class Strategy: ) # TODO: make a parameter, allow for just single bets. placement = Placement(num, amt, on) placements.append(placement) - budget -= placement.value + budget -= placement.cost num_placements += 1 return Strategy(budget=initial_budget, placements=placements) - def print_all(self) -> None: + def print(self) -> None: + """Prints all of the placements involved in the strategy.""" for p in self.placements: print(p) - def get_bet(self): + def get_bet(self) -> Bet: + """Returns a bet object based on the strategy's placements. + + Returns + ------- + Bet + A bet object representing the individual spread \ + on the inside of the table. + + """ return self.place_bets(self.placements) - def get_placements(self): + def get_placements(self) -> List[Placement]: + """Returns the placements in the strategy. + + Returns + ------- + List[Placement] + A list of placements. + + """ return self.placements @staticmethod def place_bets(placements: List[Placement]) -> Bet: - """ - Places a list of bets on the wheel given a list of Placements. + """Places a list of bets on the wheel given a list of Placements. Parameters ---------- @@ -247,14 +317,26 @@ class Strategy: ------- Bet A dictionary representing the bet. + """ return sum([p.bet() for p in placements], Bet()) @dataclass class Player: - """ - A player of the game. + """A player of the game. + + Note: this dataclass is mutable. + + Parameters + ---------- + budget : float + The amount of money the player starts with. + strategy : Strategy + The strategy the player uses to place bets. + id: int + The id of the player. + (default: random int of length 8) Attributes ---------- @@ -267,7 +349,8 @@ class Player: (default: random int of length 8) wallet : float The amount of money the player has left. - (default: budget) + (initialized to equal the budget) + """ budget: float @@ -285,18 +368,17 @@ class Player: + f"wallet={round(self.wallet, 2)}, " + f"num_placements={len(self.strategy.placements)}, " + f"strategy_budget={round(self.strategy.budget, 2)}, " - + f"strategy_cost={round(self.strategy.value, 2)}" + + f"strategy_cost={round(self.strategy.cost, 2)}" + f"\nstrategy:{_nl}{_nl.join(map(str, sorted(self.strategy.placements)))}" + "\n)" ) - def __lt__(self, other): + def __lt__(self, other: Player) -> bool: return self.wallet < other.wallet -def expected(bet: Bet) -> float: - """ - Returns the expected value of a bet. +def expected(bet: Bet) -> float: # todo: move into a stats module. + """Returns the expected value of a bet. Parameters ---------- @@ -307,6 +389,7 @@ def expected(bet: Bet) -> float: ------- float The expected value of the bet. + """ bets = list(bet.spread.values()) cond_bets = filter(lambda x: x > 0, bets) @@ -321,8 +404,9 @@ def expected(bet: Bet) -> float: def place_bet(bet: Bet, on: int, amount: float) -> Bet: - """ - Places a bet on a number. + """Places a bet on a number. + + Note: this function creates a copy of the Bet so as not to mutate the original. Parameters ---------- @@ -337,15 +421,15 @@ def place_bet(bet: Bet, on: int, amount: float) -> Bet: ------- Bet A dictionary representing the bet with the new bet placed. + """ bet = bet.copy() bet[on] += amount return bet -def interpret_bet(on="red", amount=0, bet=Optional[Bet]) -> Bet: - """ - Interprets a bet and returns a dictionary representing the bet. +def interpret_bet(on: str = "red", amount: float = 0, bet: Optional[Bet] = None) -> Bet: + """Interprets a bet and returns a dictionary representing the bet. Parameters ---------- @@ -361,6 +445,7 @@ def interpret_bet(on="red", amount=0, bet=Optional[Bet]) -> Bet: ------- Bet A dictionary representing the bet. + """ assert (on in FEASIBLE_MOVES) or ( on in ALIASES @@ -431,9 +516,11 @@ def interpret_bet(on="red", amount=0, bet=Optional[Bet]) -> Bet: return bet -def simulate_random_strategy(min_num_games=1, total_budget=200) -> Strategy: - """ - Simulates a random strategy based on the minimum number of games that +def simulate_random_strategy( + min_num_games: int = 1, total_budget: float = 200 +) -> Strategy: + """Simulates a random strategy for playing roulette. + The strategy is based on the minimum number of games that the player wants to play and the total budget that the player has. Parameters @@ -447,15 +534,18 @@ def simulate_random_strategy(min_num_games=1, total_budget=200) -> Strategy: Returns ------- + Strategy + The random strategy that the player will follow. """ strategy_budget = total_budget // min_num_games return Strategy.generate_random(strategy_budget) -def generate_players(num_players=10, min_num_games=1, total_budget=200) -> List[Player]: - """ - Generates a list of players with random strategies. +def generate_players( + num_players: int = 10, min_num_games: int = 1, total_budget: float = 200 +) -> List[Player]: + """Generates a list of players with random strategies. Parameters ---------- @@ -470,6 +560,8 @@ def generate_players(num_players=10, min_num_games=1, total_budget=200) -> List[ Returns ------- List[Player] + A list of players with random strategies. + """ players = [ Player( @@ -495,8 +587,7 @@ def generate_players(num_players=10, min_num_games=1, total_budget=200) -> List[ def spin_wheel(players, verbose=False) -> List[float]: - """ - Simulates a single game of roulette. + """Simulates a single game of roulette. Parameters ---------- @@ -508,6 +599,7 @@ def spin_wheel(players, verbose=False) -> List[float]: Returns ------- List[float] + The amount of money each player won. """ # pick a random number @@ -523,8 +615,7 @@ def spin_wheel(players, verbose=False) -> List[float]: def play_roulette(players: List[Player], games: int = 10) -> List[Player]: - """ - Simulates playing multiple games of roulette. + """Simulates playing multiple games of roulette. Parameters ---------- @@ -537,6 +628,7 @@ def play_roulette(players: List[Player], games: int = 10) -> List[Player]: ------- List[Player] The players after playing the games. + """ losers = [] for g in range(games): @@ -546,13 +638,13 @@ def play_roulette(players: List[Player], games: int = 10) -> List[Player]: winnings = spin_wheel(players) new_losers = [] for i, p in enumerate(players): - p.wallet -= p.strategy.value + p.wallet -= p.strategy.cost p.wallet += winnings[i] # TODO: reinvestment logic goes here. # maybe add "reinvest" as a player attribute? # if a player runs out of money to keep using their strategy, # remove them from the list of players and add them to losers. - if p.wallet < p.strategy.value: + if p.wallet < p.strategy.cost: new_losers.append(p) for losing_player in new_losers: players.remove(losing_player)