refactor, improvements, documentation!
This commit is contained in:
parent
73167f78f4
commit
d08e362ecd
74
app.py
74
app.py
@ -8,8 +8,9 @@ from roulette import (
|
|||||||
Strategy,
|
Strategy,
|
||||||
Placement,
|
Placement,
|
||||||
FEASIBLE_MOVES,
|
FEASIBLE_MOVES,
|
||||||
|
expected,
|
||||||
)
|
)
|
||||||
from random import choice, randint
|
from random import choice, randint, seed
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
||||||
@ -29,44 +30,33 @@ if __name__ == "__main__":
|
|||||||
bet = interpret_bet("19-36", 14, bet)
|
bet = interpret_bet("19-36", 14, bet)
|
||||||
|
|
||||||
# print(bet[21])
|
# print(bet[21])
|
||||||
from statistics import stdev, mean
|
|
||||||
|
|
||||||
def expected(bet) -> float:
|
|
||||||
bets = list(bet.values())
|
|
||||||
cond_bets = filter(lambda x: x > 0, bets)
|
|
||||||
amt = sum(bets)
|
|
||||||
payout = amt * 36 / 38
|
|
||||||
print(
|
|
||||||
f"bet: {amt:.2f}, expected: {payout:.2f}: {payout/amt:2.4f} with std {stdev(bets*36)} mean win of {36*mean(cond_bets)} {sum(filter(lambda x: x > 0, bets))}/38 times."
|
|
||||||
)
|
|
||||||
return payout
|
|
||||||
|
|
||||||
print("bond")
|
print("bond")
|
||||||
print(bet)
|
print(bet)
|
||||||
print(expected(bet))
|
print(expected(bet))
|
||||||
print()
|
print()
|
||||||
print("unknown")
|
# print("unknown")
|
||||||
bet = init_bet()
|
# bet = init_bet()
|
||||||
bet = interpret_bet("1-12", 15, bet)
|
# bet = interpret_bet("1-12", 15, bet)
|
||||||
bet = interpret_bet("13-24", 15, bet)
|
# bet = interpret_bet("13-24", 15, bet)
|
||||||
bet = interpret_bet("corner-26-27-29-30", 5, bet)
|
# bet = interpret_bet("corner-26-27-29-30", 5, bet)
|
||||||
bet = interpret_bet("corner-32-33-35-36", 5, bet)
|
# bet = interpret_bet("corner-32-33-35-36", 5, bet)
|
||||||
print(bet)
|
# print(bet)
|
||||||
print(expected(bet))
|
# print(expected(bet))
|
||||||
print()
|
# print()
|
||||||
print("singles")
|
# print("singles")
|
||||||
bet = init_bet()
|
# bet = init_bet()
|
||||||
bet = place_bet(bet, 21, 40)
|
# bet = place_bet(bet, 21, 40)
|
||||||
# bet = place_bet(bet, 1, 1)
|
# # bet = place_bet(bet, 1, 1)
|
||||||
print(expected(bet))
|
# print(expected(bet))
|
||||||
print()
|
# print()
|
||||||
print("stupid")
|
# print("stupid")
|
||||||
bet = init_bet()
|
# bet = init_bet()
|
||||||
bet = interpret_bet("odd", 18, bet)
|
# bet = interpret_bet("odd", 18, bet)
|
||||||
bet = interpret_bet("even", 18, bet)
|
# bet = interpret_bet("even", 18, bet)
|
||||||
# bet = place_bet(bet, -1, 1)
|
# # bet = place_bet(bet, -1, 1)
|
||||||
# bet = place_bet(bet, 0, 1)
|
# # bet = place_bet(bet, 0, 1)
|
||||||
print(expected(bet))
|
# print(expected(bet))
|
||||||
|
|
||||||
# min_games = randint(1, 10)
|
# min_games = randint(1, 10)
|
||||||
# print(min_games, Player(200, simulate_random_strategy(min_num_games=min_games, total_budget=200)))
|
# print(min_games, Player(200, simulate_random_strategy(min_num_games=min_games, total_budget=200)))
|
||||||
@ -91,9 +81,12 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
# print(bet)
|
# print(bet)
|
||||||
|
|
||||||
|
# set a random seed
|
||||||
|
seed(42)
|
||||||
# generate players and print them out
|
# generate players and print them out
|
||||||
players = generate_players(num_players=3, min_num_games=4, total_budget=200)
|
players = generate_players(num_players=3, min_num_games=4, total_budget=200)
|
||||||
players[0] = Player(
|
players[0] = player = Player(
|
||||||
|
id=0,
|
||||||
budget=200.0,
|
budget=200.0,
|
||||||
strategy=Strategy(
|
strategy=Strategy(
|
||||||
budget=50,
|
budget=50,
|
||||||
@ -106,13 +99,18 @@ if __name__ == "__main__":
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
for p in players:
|
for p in sorted(players):
|
||||||
print(p, "\n")
|
print(p, "\n")
|
||||||
|
|
||||||
print("======================")
|
print("======================")
|
||||||
print("SIMULATING GAMES")
|
print("SIMULATING GAMES")
|
||||||
# simulate 10 games
|
# simulate 10 games
|
||||||
players = simulate_games(players, num_games=100)
|
# seed(59)
|
||||||
|
players = simulate_games(players, num_games=100000)
|
||||||
|
|
||||||
for p in players:
|
for p in sorted(players):
|
||||||
print(p, "\n")
|
print(p, "\n")
|
||||||
|
|
||||||
|
print(player.strategy.get_bet())
|
||||||
|
|
||||||
|
# use sum to add up a list of lists
|
||||||
|
244
roulette.py
244
roulette.py
@ -2,6 +2,7 @@ from typing import List, Dict, Optional
|
|||||||
from functools import reduce
|
from functools import reduce
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from random import choice, randint
|
from random import choice, randint
|
||||||
|
from statistics import stdev, mean
|
||||||
|
|
||||||
|
|
||||||
Bet = Dict[int, float]
|
Bet = Dict[int, float]
|
||||||
@ -36,10 +37,28 @@ ALIASES = {
|
|||||||
CHIP_VALUES = {0.25, 0.5, 1, 5, 10, 25, 50, 100}
|
CHIP_VALUES = {0.25, 0.5, 1, 5, 10, 25, 50, 100}
|
||||||
|
|
||||||
|
|
||||||
def expectation(bet):
|
def expected(bet) -> float:
|
||||||
odds = 0
|
"""
|
||||||
pmnt = 0
|
Returns the expected value of a bet.
|
||||||
return odds * pmnt
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
bet : Bet
|
||||||
|
The bet to calculate the expected value of.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
The expected value of the bet.
|
||||||
|
"""
|
||||||
|
bets = list(bet.values())
|
||||||
|
cond_bets = filter(lambda x: x > 0, bets)
|
||||||
|
amt = sum(bets)
|
||||||
|
payout = amt * 36 / 38
|
||||||
|
print(
|
||||||
|
f"bet: {amt:.2f}, expected: {payout:.2f}: {payout/amt:2.4f} with std {stdev(bets*36)} mean win of {36*mean(cond_bets)} {sum(filter(lambda x: x > 0, bets))}/38 times."
|
||||||
|
)
|
||||||
|
return payout
|
||||||
|
|
||||||
|
|
||||||
# 38 numbers, 6 street bets, 2 half-bets,
|
# 38 numbers, 6 street bets, 2 half-bets,
|
||||||
@ -51,17 +70,60 @@ def expectation(bet):
|
|||||||
|
|
||||||
|
|
||||||
def init_bet() -> Bet:
|
def init_bet() -> Bet:
|
||||||
|
"""
|
||||||
|
Initializes a bet with all individual placements set to 0.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
def place_bet(bet: Bet, on: int, amount: float):
|
def place_bet(bet: Bet, on: int, amount: float):
|
||||||
|
"""
|
||||||
|
Places a bet on a number.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
bet : Bet
|
||||||
|
The bet to place.
|
||||||
|
on : int
|
||||||
|
The number to bet on.
|
||||||
|
amount : float
|
||||||
|
The amount to bet.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Bet
|
||||||
|
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]):
|
def interpret_bet(on="red", amount=0, bet=Optional[Bet]) -> Bet:
|
||||||
|
"""
|
||||||
|
Interprets a bet and returns a dictionary representing the bet.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
on : str
|
||||||
|
The type of bet to place.
|
||||||
|
amount : float
|
||||||
|
The amount to bet.
|
||||||
|
bet : Bet
|
||||||
|
The bet to add to.
|
||||||
|
(default is None, which creates a new bet)
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Bet
|
||||||
|
A dictionary representing the bet.
|
||||||
|
"""
|
||||||
assert (on in FEASIBLE_MOVES) or (
|
assert (on in FEASIBLE_MOVES) or (
|
||||||
on in ALIASES
|
on in ALIASES
|
||||||
), f"Bet `{on}` not understood. Choose from feasible moves:\n {FEASIBLE_MOVES}"
|
), f"Bet `{on}` not understood. Choose from feasible moves:\n {FEASIBLE_MOVES}"
|
||||||
@ -135,13 +197,15 @@ 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.
|
||||||
|
|
||||||
Args:
|
Attributes
|
||||||
num (int): number of chips
|
----------
|
||||||
amt (float): value of each chip
|
num : int
|
||||||
on (str): bet type
|
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.
|
||||||
|
|
||||||
Returns:
|
|
||||||
Placement: an object representing the placement of a stack of chips on a particular bet type.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
num: int
|
num: int
|
||||||
@ -153,6 +217,17 @@ class Placement:
|
|||||||
self.on in ALIASES
|
self.on in ALIASES
|
||||||
), f"Bet `{self.on}` not understood. Choose from feasible moves:\n {FEASIBLE_MOVES}"
|
), f"Bet `{self.on}` not understood. Choose from feasible moves:\n {FEASIBLE_MOVES}"
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
return self.amt > other.amt
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
assert self.on == other.on, "Cannot add placements of different types."
|
||||||
|
assert self.amt == other.amt, "Cannot add placements of different values."
|
||||||
|
return Placement(self.num + other.num, self.amt, self.on)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.amt == other.amt and self.on == other.on
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self):
|
def value(self):
|
||||||
"""
|
"""
|
||||||
@ -160,7 +235,7 @@ class Placement:
|
|||||||
"""
|
"""
|
||||||
return self.num * self.amt
|
return self.num * self.amt
|
||||||
|
|
||||||
def place_bet(self, bet=None):
|
def place_bet(self, 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.
|
||||||
"""
|
"""
|
||||||
@ -168,12 +243,40 @@ class Placement:
|
|||||||
|
|
||||||
|
|
||||||
# for two bets of structure Dict[int, float], iterate through all the keys and add up the values, returning a new dict.
|
# for two bets of structure Dict[int, float], iterate through all the keys and add up the values, returning a new dict.
|
||||||
def combine_bets(bet_1, bet_2):
|
def combine_bets(bet_1: Bet, bet_2: Bet) -> Bet:
|
||||||
|
"""
|
||||||
|
Combines two bets into a single bet.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
bet_1 : Bet
|
||||||
|
The first bet to combine.
|
||||||
|
bet_2 : Bet
|
||||||
|
The second bet to combine.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Bet
|
||||||
|
The combined bet.
|
||||||
|
"""
|
||||||
return {k: bet_1.get(k, 0) + bet_2.get(k, 0) for k in set(bet_1) | set(bet_2)}
|
return {k: bet_1.get(k, 0) + bet_2.get(k, 0) for k in set(bet_1) | set(bet_2)}
|
||||||
|
|
||||||
|
|
||||||
# for a list of Placements, call the place_bet method on each one and combine the results using reduce and combine_bets, starting with an empty dictionary as the initial argument
|
# for a list of Placements, call the place_bet method on each one and combine the results using reduce and combine_bets, starting with an empty dictionary as the initial argument
|
||||||
def place_bets(placements):
|
def place_bets(placements: List[Placement]) -> Bet:
|
||||||
|
"""
|
||||||
|
Places a list of bets on the wheel given a list of Placements.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
placements : List[Placement]
|
||||||
|
A list of Placements to place on the wheel.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Bet
|
||||||
|
A dictionary representing the bet.
|
||||||
|
"""
|
||||||
return reduce(
|
return reduce(
|
||||||
lambda bet, placement: combine_bets(bet, placement.place_bet()), placements, {}
|
lambda bet, placement: combine_bets(bet, placement.place_bet()), placements, {}
|
||||||
)
|
)
|
||||||
@ -181,6 +284,18 @@ def place_bets(placements):
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Strategy:
|
class Strategy:
|
||||||
|
"""
|
||||||
|
A strategy is a list of placements, each of which is a bet on a particular number or group of numbers.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
budget : float
|
||||||
|
The amount of money to spend on the strategy.
|
||||||
|
placements : List[Placement]
|
||||||
|
A list of placements, each of which is a bet on a particular number or group of numbers.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
budget: float = 200
|
budget: float = 200
|
||||||
placements: List[Placement] = field(default_factory=list)
|
placements: List[Placement] = field(default_factory=list)
|
||||||
|
|
||||||
@ -195,10 +310,13 @@ class Strategy:
|
|||||||
def generate_random(cls, budget) -> "Strategy":
|
def generate_random(cls, budget) -> "Strategy":
|
||||||
placements = []
|
placements = []
|
||||||
initial_budget = budget
|
initial_budget = budget
|
||||||
while budget > 0:
|
num_placements = 0
|
||||||
|
max_placements = 10
|
||||||
|
while (budget > 0) and (num_placements < max_placements):
|
||||||
amt = choice([v for v in CHIP_VALUES if v <= budget])
|
amt = choice([v for v in CHIP_VALUES if v <= budget])
|
||||||
# guarantees the max bet cannot exceed budget:
|
# guarantees the max bet cannot exceed budget:
|
||||||
num = randint(1, budget // amt)
|
# 4 is the max number of chips because after that you might as well use a higher chip value.
|
||||||
|
num = randint(1, min(budget // amt, 4))
|
||||||
# select random bet type
|
# select random bet type
|
||||||
# todo: consider if this is the logic you want...
|
# todo: consider if this is the logic you want...
|
||||||
if randint(0, 1) == 0:
|
if randint(0, 1) == 0:
|
||||||
@ -211,6 +329,8 @@ class Strategy:
|
|||||||
placement = Placement(num, amt, on)
|
placement = Placement(num, amt, on)
|
||||||
placements.append(placement)
|
placements.append(placement)
|
||||||
budget -= placement.value
|
budget -= placement.value
|
||||||
|
num_placements += 1
|
||||||
|
|
||||||
return Strategy(budget=initial_budget, placements=placements)
|
return Strategy(budget=initial_budget, placements=placements)
|
||||||
|
|
||||||
def print_all(self) -> None:
|
def print_all(self) -> None:
|
||||||
@ -223,8 +343,35 @@ class Strategy:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Player:
|
class Player:
|
||||||
|
"""
|
||||||
|
A player of the game.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
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)
|
||||||
|
wallet : float
|
||||||
|
The amount of money the player has left.
|
||||||
|
(default: budget)
|
||||||
|
"""
|
||||||
|
|
||||||
budget: float
|
budget: float
|
||||||
strategy: Strategy
|
strategy: Strategy
|
||||||
|
id: int = field(default_factory=lambda: randint(1e8, 1e9 - 1))
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
self.wallet: float = self.budget
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"Player(id={self.id}, budget={self.budget}, wallet={self.wallet}, strategy={sorted(self.strategy.placements)}, strategy_cost={self.strategy.value}, strategy_budget={self.strategy.budget}, num_placements={len(self.strategy.placements)}"
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self.id < other.id
|
||||||
|
|
||||||
|
|
||||||
def simulate_random_strategy(min_num_games=1, total_budget=200):
|
def simulate_random_strategy(min_num_games=1, total_budget=200):
|
||||||
@ -233,23 +380,66 @@ def simulate_random_strategy(min_num_games=1, total_budget=200):
|
|||||||
|
|
||||||
|
|
||||||
# given BUDGET, generate a bunch of random players, each with a random strategy, and return a list of players
|
# given BUDGET, generate a bunch of random players, each with a random strategy, and return a list of players
|
||||||
def generate_players(num_players=10, min_num_games=1, total_budget=200):
|
def generate_players(num_players=10, min_num_games=1, total_budget=200) -> List[Player]:
|
||||||
return [
|
"""
|
||||||
|
Generates a list of players with random strategies.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
num_players : int
|
||||||
|
The number of players to generate.
|
||||||
|
min_num_games : int
|
||||||
|
The minimum number of games each player will play using their strategy and budget.
|
||||||
|
total_budget : float
|
||||||
|
The total budget for each player.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
List[Player]
|
||||||
|
"""
|
||||||
|
players = [
|
||||||
Player(
|
Player(
|
||||||
total_budget,
|
budget=total_budget,
|
||||||
simulate_random_strategy(
|
strategy=simulate_random_strategy(
|
||||||
min_num_games=min_num_games, total_budget=total_budget
|
min_num_games=min_num_games, total_budget=total_budget
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
for _ in range(num_players)
|
for i in range(num_players)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# if a player has placements with identical amt and on values, combine them into a single placement
|
||||||
|
for player in players:
|
||||||
|
placements = []
|
||||||
|
for placement in player.strategy.placements:
|
||||||
|
if placement in placements:
|
||||||
|
placements[placements.index(placement)] += placement
|
||||||
|
else:
|
||||||
|
placements.append(placement)
|
||||||
|
player.strategy.placements = placements
|
||||||
|
return players
|
||||||
|
|
||||||
|
|
||||||
# simulate a game of roulette, picking a random integer from -1 to 37, taking the players as inputs and returning their expected winnings
|
# simulate a game of roulette, picking a random integer from -1 to 37, taking the players as inputs and returning their expected winnings
|
||||||
def simulate_game(players):
|
def simulate_game(players, verbose=False) -> List[float]:
|
||||||
|
"""
|
||||||
|
Simulates a single game of roulette.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
players : List[Player]
|
||||||
|
The players in the game.
|
||||||
|
verbose : bool
|
||||||
|
Whether to print the winning number.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
List[float]
|
||||||
|
|
||||||
|
"""
|
||||||
# pick a random number
|
# pick a random number
|
||||||
num = randint(-1, 36)
|
num = randint(-1, 36)
|
||||||
# print("WINNER:", num)
|
if verbose:
|
||||||
|
print("WINNER:", num)
|
||||||
# for each player, place their bets on the wheel
|
# for each player, place their bets on the wheel
|
||||||
bets = [p.strategy.get_bet() for p in players]
|
bets = [p.strategy.get_bet() for p in players]
|
||||||
# for each player, calculate their winnings
|
# for each player, calculate their winnings
|
||||||
@ -264,17 +454,17 @@ def simulate_games(players, num_games=10):
|
|||||||
for g in range(num_games):
|
for g in range(num_games):
|
||||||
if not players:
|
if not players:
|
||||||
break
|
break
|
||||||
print(f"GAME {g}")
|
# print(f"GAME {g}")
|
||||||
winnings = simulate_game(players)
|
winnings = simulate_game(players)
|
||||||
new_losers = []
|
new_losers = []
|
||||||
for i, p in enumerate(players):
|
for i, p in enumerate(players):
|
||||||
p.budget -= p.strategy.value
|
p.wallet -= p.strategy.value
|
||||||
p.budget += winnings[
|
p.wallet += winnings[
|
||||||
i
|
i
|
||||||
] # TODO: reinvestment logic goes here. maybe add "reinvest" as a player attribute?
|
] # TODO: reinvestment logic goes here. 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 the list of losers
|
# remove them from the list of players and add them to the list of losers
|
||||||
if p.budget < p.strategy.value:
|
if p.wallet < p.strategy.value:
|
||||||
new_losers.append(p)
|
new_losers.append(p)
|
||||||
for l in new_losers:
|
for l in new_losers:
|
||||||
players.remove(l)
|
players.remove(l)
|
||||||
|
Loading…
Reference in New Issue
Block a user