docstring improvements and some renamings

This commit is contained in:
Michael Pilosov 2022-11-28 18:56:57 -07:00
parent 5e776f2eac
commit d51382960a
2 changed files with 140 additions and 48 deletions

4
app.py
View File

@ -76,13 +76,13 @@ if __name__ == "__main__":
strategy = Strategy.generate_random(50) strategy = Strategy.generate_random(50)
strategy.print_all() strategy.print()
# define the minimum number of games that you want players to play # define the minimum number of games that you want players to play
# print the total sum of all the placements # print the total sum of all the placements
print("SUM") print("SUM")
print(sum([p.value for p in placements])) print(sum([p.cost for p in placements]))
# place the bets # place the bets
bet = Strategy.place_bets(placements) bet = Strategy.place_bets(placements)

View File

@ -7,8 +7,14 @@ from random import choice, randint
from statistics import mean, stdev from statistics import mean, stdev
from typing import Dict, List, Optional from typing import Dict, List, Optional
"""Set[str]:
Bets on single numbers.
"""
SINGLE_BETS = {str(i) for i in range(-1, 37)} 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( FEASIBLE_MOVES = sorted(
{ {
*[f"street-{i}" for i in range(1, 14)], *[f"street-{i}" for i in range(1, 14)],
@ -20,7 +26,9 @@ FEASIBLE_MOVES = sorted(
} }
) )
"""Set[str]:
Aliases for different placements.
"""
ALIASES = { ALIASES = {
"reds", "reds",
"blacks", "blacks",
@ -34,7 +42,11 @@ ALIASES = {
"second-18", "second-18",
} }
CHIP_VALUES = {0.25, 0.5, 1, 5, 10, 25, 50, 100} 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]: def init_spread() -> Dict[int, float]:
@ -45,6 +57,7 @@ def init_spread() -> Dict[int, float]:
------- -------
Bet Bet
A dictionary representing the bet. A dictionary representing the bet.
""" """
D = {i: 0 for i in range(-1, 37)} D = {i: 0 for i in range(-1, 37)}
return D return D
@ -52,7 +65,19 @@ def init_spread() -> Dict[int, float]:
@dataclass @dataclass
class Bet: 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) spread: Dict[int, float] = field(default_factory=init_spread)
@ -92,7 +117,7 @@ class Bet:
"""Return a copy of the bet.""" """Return a copy of the bet."""
return Bet(self.spread.copy()) return Bet(self.spread.copy())
def __iter__(self): def __iter__(self) -> iter:
return iter(self.spread.keys()) return iter(self.spread.keys())
def get(self, __name: int) -> float: def get(self, __name: int) -> float:
@ -101,8 +126,17 @@ class Bet:
@dataclass @dataclass
class Placement: 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 Attributes
---------- ----------
@ -137,24 +171,41 @@ class Placement:
return self.amt == other.amt and self.on == other.on return self.amt == other.amt and self.on == other.on
@property @property
def value(self): def cost(self) -> float:
""" """
Returns the value of the bet. Returns the value of the bet.
""" """
return self.num * self.amt 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. 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) return interpret_bet(self.on, self.num * self.amt, bet)
@dataclass @dataclass
class Strategy: class Strategy:
""" """Represents list of placements on the roulette wheel.
A strategy is a list of placements, each of which is a bet on a Each strategy is a bet on a particular number or group of numbers.
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 Attributes
---------- ----------
@ -171,17 +222,19 @@ class Strategy:
def __repr__(self) -> str: def __repr__(self) -> str:
return ( return (
f"Strategy(budget={self.budget}," f"Strategy(budget={self.budget},"
+ f"value={self.value}, placements={self.placements})" + f"cost={self.cost}, placements={self.placements})"
) )
@property @property
def value(self): def cost(self) -> float:
return sum([p.value for p in self.placements]) """
Returns the total cost of the strategy.
"""
return sum([p.cost for p in self.placements])
@classmethod @classmethod
def generate_random(cls, budget: float, max_placements: int = 10) -> Strategy: def generate_random(cls, budget: float, max_placements: int = 10) -> Strategy:
""" """Generates a random strategy.
Generates a random strategy.
Parameters Parameters
---------- ----------
@ -218,25 +271,42 @@ class Strategy:
) # TODO: make a parameter, allow for just single bets. ) # TODO: make a parameter, allow for just single bets.
placement = Placement(num, amt, on) placement = Placement(num, amt, on)
placements.append(placement) placements.append(placement)
budget -= placement.value budget -= placement.cost
num_placements += 1 num_placements += 1
return Strategy(budget=initial_budget, placements=placements) 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: for p in self.placements:
print(p) 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) 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 return self.placements
@staticmethod @staticmethod
def place_bets(placements: List[Placement]) -> Bet: 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 Parameters
---------- ----------
@ -247,14 +317,26 @@ class Strategy:
------- -------
Bet Bet
A dictionary representing the bet. A dictionary representing the bet.
""" """
return sum([p.bet() for p in placements], Bet()) return sum([p.bet() for p in placements], Bet())
@dataclass @dataclass
class Player: 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 Attributes
---------- ----------
@ -267,7 +349,8 @@ class Player:
(default: random int of length 8) (default: random int of length 8)
wallet : float wallet : float
The amount of money the player has left. The amount of money the player has left.
(default: budget) (initialized to equal the budget)
""" """
budget: float budget: float
@ -285,18 +368,17 @@ class Player:
+ f"wallet={round(self.wallet, 2)}, " + f"wallet={round(self.wallet, 2)}, "
+ f"num_placements={len(self.strategy.placements)}, " + f"num_placements={len(self.strategy.placements)}, "
+ f"strategy_budget={round(self.strategy.budget, 2)}, " + 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)))}" + f"\nstrategy:{_nl}{_nl.join(map(str, sorted(self.strategy.placements)))}"
+ "\n)" + "\n)"
) )
def __lt__(self, other): def __lt__(self, other: Player) -> bool:
return self.wallet < other.wallet return self.wallet < other.wallet
def expected(bet: Bet) -> float: def expected(bet: Bet) -> float: # todo: move into a stats module.
""" """Returns the expected value of a bet.
Returns the expected value of a bet.
Parameters Parameters
---------- ----------
@ -307,6 +389,7 @@ def expected(bet: Bet) -> float:
------- -------
float float
The expected value of the bet. The expected value of the bet.
""" """
bets = list(bet.spread.values()) bets = list(bet.spread.values())
cond_bets = filter(lambda x: x > 0, bets) 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: 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 Parameters
---------- ----------
@ -337,15 +421,15 @@ def place_bet(bet: Bet, on: int, amount: float) -> Bet:
------- -------
Bet Bet
A dictionary representing the bet with the new bet placed. A dictionary representing the bet with the new bet placed.
""" """
bet = bet.copy() bet = bet.copy()
bet[on] += amount bet[on] += amount
return bet return bet
def interpret_bet(on="red", amount=0, bet=Optional[Bet]) -> 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.
Interprets a bet and returns a dictionary representing the bet.
Parameters Parameters
---------- ----------
@ -361,6 +445,7 @@ def interpret_bet(on="red", amount=0, bet=Optional[Bet]) -> Bet:
------- -------
Bet Bet
A dictionary representing the bet. A dictionary representing the bet.
""" """
assert (on in FEASIBLE_MOVES) or ( assert (on in FEASIBLE_MOVES) or (
on in ALIASES on in ALIASES
@ -431,9 +516,11 @@ def interpret_bet(on="red", amount=0, bet=Optional[Bet]) -> Bet:
return bet return bet
def simulate_random_strategy(min_num_games=1, total_budget=200) -> Strategy: def simulate_random_strategy(
""" min_num_games: int = 1, total_budget: float = 200
Simulates a random strategy based on the minimum number of games that ) -> 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. the player wants to play and the total budget that the player has.
Parameters Parameters
@ -447,15 +534,18 @@ def simulate_random_strategy(min_num_games=1, total_budget=200) -> Strategy:
Returns Returns
------- -------
Strategy
The random strategy that the player will follow.
""" """
strategy_budget = total_budget // min_num_games strategy_budget = total_budget // min_num_games
return Strategy.generate_random(strategy_budget) return Strategy.generate_random(strategy_budget)
def generate_players(num_players=10, min_num_games=1, total_budget=200) -> List[Player]: def generate_players(
""" num_players: int = 10, min_num_games: int = 1, total_budget: float = 200
Generates a list of players with random strategies. ) -> List[Player]:
"""Generates a list of players with random strategies.
Parameters Parameters
---------- ----------
@ -470,6 +560,8 @@ def generate_players(num_players=10, min_num_games=1, total_budget=200) -> List[
Returns Returns
------- -------
List[Player] List[Player]
A list of players with random strategies.
""" """
players = [ players = [
Player( 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]: def spin_wheel(players, verbose=False) -> List[float]:
""" """Simulates a single game of roulette.
Simulates a single game of roulette.
Parameters Parameters
---------- ----------
@ -508,6 +599,7 @@ def spin_wheel(players, verbose=False) -> List[float]:
Returns Returns
------- -------
List[float] List[float]
The amount of money each player won.
""" """
# pick a random number # 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]: def play_roulette(players: List[Player], games: int = 10) -> List[Player]:
""" """Simulates playing multiple games of roulette.
Simulates playing multiple games of roulette.
Parameters Parameters
---------- ----------
@ -537,6 +628,7 @@ def play_roulette(players: List[Player], games: int = 10) -> List[Player]:
------- -------
List[Player] List[Player]
The players after playing the games. The players after playing the games.
""" """
losers = [] losers = []
for g in range(games): for g in range(games):
@ -546,13 +638,13 @@ def play_roulette(players: List[Player], games: int = 10) -> List[Player]:
winnings = spin_wheel(players) winnings = spin_wheel(players)
new_losers = [] new_losers = []
for i, p in enumerate(players): for i, p in enumerate(players):
p.wallet -= p.strategy.value p.wallet -= p.strategy.cost
p.wallet += winnings[i] p.wallet += winnings[i]
# TODO: reinvestment logic goes here. # TODO: reinvestment logic goes here.
# maybe add "reinvest" as a player attribute? # maybe add "reinvest" as a player attribute?
# if a player runs out of money to keep using their strategy, # if a player runs out of money to keep using their strategy,
# remove them from the list of players and add them to losers. # 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) new_losers.append(p)
for losing_player in new_losers: for losing_player in new_losers:
players.remove(losing_player) players.remove(losing_player)