Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
37f2dda0c3 | |||
5ba5cedd02 | |||
|
d51382960a | ||
|
5e776f2eac | ||
|
0e744004c7 | ||
|
da49513ac8 | ||
|
8a400a3f30 | ||
|
ae65305d8d | ||
|
69547c2b79 | ||
|
857338ba5f | ||
|
250649d439 | ||
|
88c386d440 | ||
|
6f3b00d266 | ||
|
6ccecba442 | ||
|
d15b44340d |
4
Makefile
4
Makefile
@ -18,8 +18,4 @@ test: # TODO: replace with real tests
|
||||
serve:
|
||||
python -m http.server 1337
|
||||
|
||||
demo:
|
||||
scp -P 10044 index.html git@mlden.com:/data/f/play.html
|
||||
@echo "visit http://clfx.cc/play.html"
|
||||
|
||||
.PHONY: build publish clean lint test
|
||||
|
48
README.md
48
README.md
@ -23,27 +23,41 @@ pip install pyroulette
|
||||
from pyroulette import generate_players, play_roulette
|
||||
|
||||
players = generate_players(
|
||||
number_of_players=10,
|
||||
minimum_number_of_games=10,
|
||||
budget=100,
|
||||
num_players=50,
|
||||
min_num_games=min_games,
|
||||
total_budget=100
|
||||
)
|
||||
|
||||
for player in results:
|
||||
print(player)
|
||||
|
||||
results = play_roulette(
|
||||
players = play_roulette(
|
||||
players=players,
|
||||
number_of_games=1000,
|
||||
games=1000,
|
||||
)
|
||||
|
||||
print("Results:")
|
||||
for p in sorted(players, reverse=True):
|
||||
print("\n", p)
|
||||
|
||||
print("Statistics")
|
||||
# get the wallet values for all players as a list
|
||||
wallets = [player.wallet for player in players]
|
||||
|
||||
# calculate some statistics
|
||||
avg_wallet = sum(wallets) / len(wallets)
|
||||
median_wallet = sorted(wallets)[len(wallets) // 2]
|
||||
|
||||
# calculate winnings
|
||||
winnings = [p.wallet - p.budget for p in players]
|
||||
|
||||
num_losers = len([w for w in winnings if w <= 0])
|
||||
num_winners = len([w for w in winnings if w > 0])
|
||||
num_bankrupt = len([l for l in wallets if l == 0])
|
||||
|
||||
# print the results
|
||||
print(f"Average wallet value: {avg_wallet}\n")
|
||||
print(f"Median wallet value: {median_wallet}\n")
|
||||
print(f"Number of players who lost money: {num_losers}, proportion: {num_losers / len(players):.2f}")
|
||||
print(f"Number of players who went bankrupt: {num_bankrupt}, proportion: {num_bankrupt / len(players):.2f}")
|
||||
print()
|
||||
print(f"Number of players who won more than they started with: {num_winners}, proportion: {num_winners / len(players):.2f}")
|
||||
|
||||
```
|
||||
|
||||
|
||||
# uploading to pypi
|
||||
|
||||
```
|
||||
pip install build
|
||||
python -m build --sdist --wheel -n
|
||||
twine upload dist/*
|
||||
```
|
||||
|
4
app.py
4
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)
|
||||
|
72
index.html
72
index.html
@ -7,9 +7,9 @@
|
||||
<title>Roulette Demo</title>
|
||||
|
||||
<!-- <link rel="icon" type="image/png" href="favicon.png" /> -->
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<link rel="stylesheet" href="https://pyscript.net/releases/2022.09.1/pyscript.css" />
|
||||
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<script defer src="https://pyscript.net/releases/2022.09.1/pyscript.js"></script>
|
||||
<link rel="stylesheet" href="./assets/css/examples.css" />
|
||||
<link rel="stylesheet" href="./assets/prism/prism.css" />
|
||||
<script defer src="./assets/prism/prism.js"></script>
|
||||
@ -21,41 +21,81 @@
|
||||
<!-- <a href="/">
|
||||
<img src="./logo.png" class="logo">
|
||||
</a> -->
|
||||
<a class="title" href="" style="color: #f0ab3c;">Roulette</a>
|
||||
<a class="title" href="" style="color: #0d88c2;">Roulette</a>
|
||||
<a href="https://git.clfx.cc/mm/roulette/src/branch/main/pyroulette/roulette.py" style="color: #ffffff;">(roulette.py)</a>
|
||||
<a href="https://git.clfx.cc/mm/roulette/src/branch/main/index.html" style="color: #a6dc28;">(index.html)</a>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="pyscript">
|
||||
<div class="font-mono">Simulating 100 Games for 500 Players: <label id="outputDiv"></label></div>
|
||||
<div class="font-mono">
|
||||
<h1 align="center">Let's Hack Roulette!</h1>
|
||||
<h2 align="center">
|
||||
Simulating 100 Games for 500 Players
|
||||
</h2>
|
||||
<p>
|
||||
Note: winnings are re-invested, each player starts with $100 and chooses a strategy at random.
|
||||
</p>
|
||||
<label id="outputDiv"></label>
|
||||
</div>
|
||||
<center>
|
||||
<div id="outputDiv2" class="font-mono"></div>
|
||||
</center>
|
||||
<br>
|
||||
<div id="outputDiv3" class="font-mono"></div>
|
||||
<br>
|
||||
<py-config>
|
||||
packages = [
|
||||
"pyroulette",
|
||||
]
|
||||
</py-config>
|
||||
<py-script>
|
||||
from pyroulette import *
|
||||
<py-script output="outputDiv2">
|
||||
import pyroulette as pr
|
||||
|
||||
# seed(59)
|
||||
from random import randint
|
||||
from random import random, randint
|
||||
|
||||
players = []
|
||||
for _ in range(1, 20):
|
||||
c = randint(0, 3)
|
||||
if c == 0:
|
||||
# iterations * num_per_group = num_players
|
||||
for _ in range(1, 50):
|
||||
c = random()
|
||||
if c < 0.25:
|
||||
min_games = randint(1, 100)
|
||||
elif c == 1:
|
||||
elif c < 0.5:
|
||||
min_games = randint(1, 25)
|
||||
else:
|
||||
else: # bold half
|
||||
min_games = randint(1, 2)
|
||||
|
||||
players.extend(generate_players(num_players=25, min_num_games=min_games, total_budget=100))
|
||||
# min_games = 100 # this caps out at about $250 wallet. need to bet bigger to win bigger.
|
||||
players.extend(pr.generate_players(num_players=10, min_num_games=min_games, total_budget=100))
|
||||
|
||||
players = play_roulette(players, games=100)
|
||||
players = pr.play_roulette(players, games=100)
|
||||
|
||||
for p in sorted(players, reverse=True):
|
||||
print(p, "\n<br>")
|
||||
# get the wallet values for all players as a list
|
||||
wallets = [player.wallet for player in players]
|
||||
# calculate some statistics
|
||||
avg_wallet = sum(wallets) / len(wallets)
|
||||
median_wallet = sorted(wallets)[len(wallets) // 2]
|
||||
|
||||
# calculate winnings
|
||||
winnings = [p.wallet - p.budget for p in players]
|
||||
|
||||
num_losers = len([w for w in winnings if w <= 0])
|
||||
num_winners = len([w for w in winnings if w > 0])
|
||||
num_bankrupt = len([l for l in wallets if l == 0])
|
||||
|
||||
# print the results
|
||||
print(f"Average wallet value: {avg_wallet}\n")
|
||||
print(f"Median wallet value: {median_wallet}\n")
|
||||
print(f"Number of players who lost money: {num_losers}, proportion: {num_losers / len(players):.2f}")
|
||||
print(f"Number of players who went bankrupt: {num_bankrupt}, proportion: {num_bankrupt / len(players):.2f}")
|
||||
print()
|
||||
print(f"Number of players who won more than they started with: {num_winners}, proportion: {num_winners / len(players):.2f}")
|
||||
</py-script>
|
||||
|
||||
<py-script output="outputDiv3">
|
||||
for p in sorted(players, reverse=True):
|
||||
print("\n<br>", p)
|
||||
</py-script>
|
||||
</section>
|
||||
|
||||
</body>
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user