Compare commits

...

15 Commits
v0.0.4 ... main

Author SHA1 Message Date
37f2dda0c3 remove unnecessary package 2023-04-14 16:11:23 -06:00
5ba5cedd02 fix release version
updates past this broke the app.
2023-04-14 15:58:36 -06:00
Michael Pilosov
d51382960a docstring improvements and some renamings 2022-11-28 18:56:57 -07:00
Michael Pilosov
5e776f2eac add some links and text 2022-11-27 11:32:12 -07:00
Michael Pilosov
0e744004c7 clean makefile 2022-11-27 03:44:42 -07:00
Michael Pilosov
da49513ac8 slight tweak 2022-11-27 03:14:52 -07:00
Michael Pilosov
8a400a3f30 different shuffle, quicker run 2022-11-27 03:09:22 -07:00
Michael Pilosov
ae65305d8d sky high values spotted 2022-11-27 03:03:29 -07:00
Michael Pilosov
69547c2b79 change dynamic to use decimals 2022-11-27 02:51:33 -07:00
Michael Pilosov
857338ba5f description cleanup and web example 2022-11-27 02:28:49 -07:00
Michael Pilosov
250649d439 better printout 2022-11-27 02:20:22 -07:00
Michael Pilosov
88c386d440 remove graphs 2022-11-27 01:45:31 -07:00
Michael Pilosov
6f3b00d266 print a graph 2022-11-27 01:41:02 -07:00
Michael Pilosov
6ccecba442 more games! 2022-11-27 01:28:01 -07:00
Michael Pilosov
d15b44340d whitespace 2022-11-27 01:24:17 -07:00
6 changed files with 228 additions and 86 deletions

View File

@ -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

View File

@ -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
View File

@ -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)

View File

@ -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>

View File

@ -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)

View File

@ -10,7 +10,7 @@ with open(BASEDIR.joinpath("README.md"), "r") as fp:
setup(
name="pyroulette",
version="0.0.4",
version="0.0.5",
description="A package for exploring roulette strategies.",
long_description=LONG_DESCRIPTION,
long_description_content_type="text/markdown",