WIP but now simulating games.

This commit is contained in:
Michael Pilosov 2022-11-25 22:13:04 -07:00
parent b1b56355f6
commit 8f7cb2c2d2
2 changed files with 331 additions and 139 deletions

190
app.py
View File

@ -1,145 +1,57 @@
from typing import List, Dict
from functools import reduce
from roulette import init_bet, place_bet, interpret_bet
FEASIBLE_MOVES = sorted({
*[f"street-{i}" for i in range(1,14)],
*[f"col-{i}" for i in range(1,4)],
*[f"corner-{i}-{i+1}-{i+3}-{i+4}" for i in range(1,36) if (i - 1)%3 < 2],
*["1-12", "13-24", "25-36", "1-18", "19-36", "even", "odd", "red", "black"],
})
ALIASES = {"reds", "blacks", "evens", "odds", "first-half", "last-half", "second-half", "first-18", "last-18", "second-18"}
def expectation(bet):
odds = 0
pmnt = 0
return odds * pmnt
if __name__ == "__main__":
# 38 numbers, 6 street bets, 2 half-bets,
bet = init_bet()
#bet = place_bet(bet, 21, 20)
print(bet[21])
#bet = interpret_bet("red", 36, bet)
#bet = interpret_bet("25-36", 1, bet)
#bet = interpret_bet("street-1", 3, bet)
#bet = interpret_bet("street-10", 3, bet)
#bet = interpret_bet("col-1", 12, bet)
# james bond
bet = place_bet(bet, 0, 1)
for n in range(13,19):
bet = place_bet(bet, n, 5)
bet = interpret_bet("19-36", 14, bet)
# payout grid based on bets placed.
# a street bet is the same as splitting the bet across all the numbers in the group.
# will use a function to distribute / interpret the bets, but it seems like we only need to track the numbers on the wheel.
#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
def init_bet() -> Dict[int, float]:
D = {i: 0 for i in range(-1, 37)}
return D
def place_bet(bet: Dict[str, float], on: int, amount: float):
bet = bet.copy()
bet[on] += amount
return bet
def interpret_bet(on="red", amount=0, bet=None):
assert (on in FEASIBLE_MOVES) or (on in ALIASES), f"Bet `{on}` not understood. Choose from feasible moves:\n {FEASIBLE_MOVES}"
if bet is None:
bet = init_bet()
else:
bet = bet.copy()
REDS = {1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36}
BLACKS = set(range(37)) - REDS
NUMS = {}
on = on.strip().replace(" ", "-")
div = 18
if on in ("red", "reds"):
NUMS = REDS
if on in ("black", "blacks"):
NUMS = BLACKS
if on in ("odd", "odds"):
NUMS = {i for i in range(1,37) if i % 2 == 0}
if on in ("even", "evens"):
NUMS = {i for i in range(1,37) if i % 2}
if on in ("1-18", "first-18", "first-half"):
NUMS = set(range(1, 19))
if on in ("19-36", "last-18", "last-half", "second-half", "second-18"):
NUMS = set(range(19, 37))
if on in ("1-12", "13-24", "25-36"):
low, high = on.split("-")
NUMS = set(range(int(low), int(high)+1))
div = 12
if not NUMS:
other_bet = on.split("-")
if other_bet[0] == "street":
street = int(other_bet[1]) - 1
assert street in list(range(13))
NUMS = {i for i in range(street+1, street+4)}
div = 3
elif other_bet[0] == "col":
col = int(other_bet[1]) - 1
assert col in list(range(0,3))
NUMS = {i for i in range(1, 37) if (i-1) % 3 == col}
div = 12
elif other_bet[0] == "split": # TODO: validate choices
num_1, num_2 = int(other_bet[1]), int(other_bet[2])
NUMS = {num_1, num_2}
div = 2
elif other_bet[0] == "corner": # TODO: validate choices
num_1, num_2 = int(other_bet[1]), int(other_bet[2])
num_3, num_4 = int(other_bet[3]), int(other_bet[4])
NUMS = {num_1, num_2, num_3, num_4}
div = 4
else:
raise ValueError("unsupported bet")
bet = reduce(lambda bet, num: place_bet(bet, num, amount / div), NUMS, bet)
return bet
bet = init_bet()
#bet = place_bet(bet, 21, 20)
print(bet[21])
#bet = interpret_bet("red", 36, bet)
#bet = interpret_bet("25-36", 1, bet)
#bet = interpret_bet("street-1", 3, bet)
#bet = interpret_bet("street-10", 3, bet)
#bet = interpret_bet("col-1", 12, bet)
# james bond
bet = place_bet(bet, 0, 1)
for n in range(13,19):
bet = place_bet(bet, n, 5)
bet = interpret_bet("19-36", 14, bet)
#print(bet[21])
import numpy as np
def expected(bet) -> float:
bets = np.array(list(bet.values()))
cond_bets = bets[bets > 0]
amt = sum(bets)
payout = amt*36/38
print(f"bet {amt:.2f} to win {payout:.2f}: {payout/amt:2.4f} with std {np.std(bets*36)} mean win of {36*np.mean(cond_bets)} {sum(bets>0)}/38 times.")
return payout
print("bond")
print(bet)
print(expected(bet))
print()
print("unknown")
bet = init_bet()
bet = interpret_bet("1-12", 15, bet)
bet = interpret_bet("13-24", 15, bet)
bet = interpret_bet("corner-26-27-29-30", 5, bet)
bet = interpret_bet("corner-32-33-35-36", 5, bet)
print(bet)
print(expected(bet))
print()
print("singles")
bet = init_bet()
bet = place_bet(bet, 21, 40)
#bet = place_bet(bet, 1, 1)
print(expected(bet))
print()
print("stupid")
bet = init_bet()
bet = interpret_bet("odd", 18, bet)
bet = interpret_bet("even", 18, bet)
#bet = place_bet(bet, -1, 1)
#bet = place_bet(bet, 0, 1)
print(expected(bet))
print("bond")
print(bet)
print(expected(bet))
print()
print("unknown")
bet = init_bet()
bet = interpret_bet("1-12", 15, bet)
bet = interpret_bet("13-24", 15, bet)
bet = interpret_bet("corner-26-27-29-30", 5, bet)
bet = interpret_bet("corner-32-33-35-36", 5, bet)
print(bet)
print(expected(bet))
print()
print("singles")
bet = init_bet()
bet = place_bet(bet, 21, 40)
#bet = place_bet(bet, 1, 1)
print(expected(bet))
print()
print("stupid")
bet = init_bet()
bet = interpret_bet("odd", 18, bet)
bet = interpret_bet("even", 18, bet)
#bet = place_bet(bet, -1, 1)
#bet = place_bet(bet, 0, 1)
print(expected(bet))

280
roulette.py Normal file
View File

@ -0,0 +1,280 @@
from typing import List, Dict, Optional
from functools import reduce
from dataclasses import dataclass, field
Bet = Dict[int, float]
FEASIBLE_MOVES = sorted({
*[f"street-{i}" for i in range(1,14)],
*[f"col-{i}" for i in range(1,4)],
*[f"corner-{i}-{i+1}-{i+3}-{i+4}" for i in range(1,33) if (i - 1)%3 < 2],
*["1-12", "13-24", "25-36", "1-18", "19-36", "even", "odd", "red", "black"],
*["triple-0", "triple-00"]
})
ALIASES = {"reds", "blacks", "evens", "odds", "first-half", "last-half", "second-half", "first-18", "last-18", "second-18"}
CHIP_VALUES = { 0.25, 0.5, 1, 5, 10, 25, 50, 100}
def expectation(bet):
odds = 0
pmnt = 0
return odds * pmnt
# 38 numbers, 6 street bets, 2 half-bets,
# payout grid based on bets placed.
# a street bet is the same as splitting the bet across all the numbers in the group.
# will use a function to distribute / interpret the bets, but it seems like we only need to track the numbers on the wheel.
def init_bet() -> Bet:
D = {i: 0 for i in range(-1, 37)}
return D
def place_bet(bet: Bet, on: int, amount: float):
bet = bet.copy()
bet[on] += amount
return bet
def interpret_bet(on="red", amount=0, bet=Optional[Bet]):
assert (on in FEASIBLE_MOVES) or (on in ALIASES), f"Bet `{on}` not understood. Choose from feasible moves:\n {FEASIBLE_MOVES}"
if bet is None:
bet = init_bet()
else:
bet = bet.copy()
REDS = {1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36}
BLACKS = set(range(37)) - REDS
NUMS = {}
on = on.strip().replace(" ", "-")
div = 18
if on in ("red", "reds"):
NUMS = REDS
if on in ("black", "blacks"):
NUMS = BLACKS
if on in ("odd", "odds"):
NUMS = {i for i in range(1,37) if i % 2 == 0}
if on in ("even", "evens"):
NUMS = {i for i in range(1,37) if i % 2}
if on in ("1-18", "first-18", "first-half"):
NUMS = set(range(1, 19))
if on in ("19-36", "last-18", "last-half", "second-half", "second-18"):
NUMS = set(range(19, 37))
if on in ("1-12", "13-24", "25-36"):
low, high = on.split("-")
NUMS = set(range(int(low), int(high)+1))
div = 12
if on in ["triple-0", "triple-00"]:
NUMS = {0, 1, 2} if on == "triple-0" else {-1, 2, 3}
div = 3
if not NUMS:
other_bet = on.split("-")
if other_bet[0] == "street":
street = int(other_bet[1]) - 1
assert street in list(range(13))
NUMS = {i for i in range(street+1, street+4)}
div = 3
elif other_bet[0] == "col":
col = int(other_bet[1]) - 1
assert col in list(range(0,3))
NUMS = {i for i in range(1, 37) if (i-1) % 3 == col}
div = 12
elif other_bet[0] == "split": # TODO: validate choices, for now we disallow these.
num_1, num_2 = int(other_bet[1]), int(other_bet[2])
NUMS = {num_1, num_2}
div = 2
elif other_bet[0] == "corner":
num_1, num_2 = int(other_bet[1]), int(other_bet[2])
num_3, num_4 = int(other_bet[3]), int(other_bet[4])
NUMS = {num_1, num_2, num_3, num_4}
div = 4
else:
raise ValueError("unsupported bet")
bet = reduce(lambda bet, num: place_bet(bet, num, amount / div), NUMS, bet)
return bet
@dataclass
class Placement:
"""
Defines a bet based on the number of chips and value of each chip.
Args:
num (int): number of chips
amt (float): value of each chip
on (str): bet type
Returns:
Placement: an object representing the placement of a stack of chips on a particular bet type.
"""
num: int
amt: float
on: str
def __post_init__(self):
assert (self.on in FEASIBLE_MOVES) or (self.on in ALIASES), f"Bet `{self.on}` not understood. Choose from feasible moves:\n {FEASIBLE_MOVES}"
@property
def value(self):
"""
Returns the value of the bet.
"""
return self.num*self.amt
def place_bet(self, bet=None):
"""
Places a bet on the wheel based on the bet type.
"""
return interpret_bet(self.on, self.num*self.amt, bet)
# 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):
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
def place_bets(placements):
return reduce(lambda bet, placement: combine_bets(bet, placement.place_bet()), placements, {})
# create a list of random Placements
from random import choice, randint
placements = [Placement(randint(1, 10), 1, choice(list(FEASIBLE_MOVES))) for _ in range(10)]
# for a given budget, generate placements until you run out of money, where the value of each placement is the number of chips times the value of each chip.
@dataclass
class Strategy:
budget: float = 200
placements: List[Placement] = field(default_factory=list)
def __repr__(self) -> str:
return f"Strategy(budget={self.budget}, value={self.value}, placements={self.placements})"
@property
def value(self):
return sum([p.value for p in self.placements])
@classmethod
def generate_random(cls, budget) -> "Strategy":
placements = []
initial_budget = budget
while budget > 0:
amt = choice([v for v in CHIP_VALUES if v <= budget])
# guarantees the max bet cannot exceed budget:
num = randint(1, budget // amt)
# select random bet type
on = choice(list(FEASIBLE_MOVES))
placement = Placement(num, amt, on)
placements.append(placement)
budget -= placement.value
return Strategy(budget=initial_budget, placements=placements)
def print_all(self) -> None:
for p in self.placements:
print(p)
def get_bet(self):
return place_bets(self.placements)
@dataclass
class Player:
budget: float
strategy: Strategy
def simulate_random_strategy(
min_num_games = 1,
total_budget = 200
):
strategy_budget = total_budget // min_num_games
return Strategy.generate_random(strategy_budget)
# strategy = Strategy.generate_random(50)
# strategy.print_all()
# 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]))
# # place the bets
# bet = place_bets(placements)
# print(bet)
min_games = randint(1, 10)
print(min_games, Player(200, simulate_random_strategy(min_num_games=min_games, total_budget=200)))
# 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
):
return [Player(total_budget, simulate_random_strategy(min_num_games=min_num_games, total_budget=total_budget)) for _ in range(num_players)]
# 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):
# pick a random number
num = randint(-1, 36)
# print("WINNER:", num)
# for each player, place their bets on the wheel
bets = [p.strategy.get_bet() for p in players]
# for each player, calculate their winnings
winnings = [36 * bet.get(num, 0) for bet in bets]
# for each player, calculate their expected winnings
return winnings
# simulate multiple games, reducing each player's budget by the amount of their bet and adding the amount of their winnings
def simulate_games(
players,
num_games = 10
):
losers = []
for g in range(num_games):
if not players:
break
print(f"GAME {g}")
winnings = simulate_game(players)
new_losers = []
for i, p in enumerate(players):
p.budget -= p.strategy.value
p.budget += winnings[i]
# 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
if p.budget < p.strategy.value:
new_losers.append(p)
for l in new_losers:
players.remove(l)
losers.extend(new_losers)
return players + losers
# generate players and print them out
players = generate_players(num_players=3, min_num_games=4, total_budget=200)
for p in players:
print(p,'\n')
print("======================")
print("SIMULATING GAMES")
# simulate 10 games
players = simulate_games(players, num_games=100)
for p in players:
print(p,'\n')