diff --git a/README.md b/README.md new file mode 100644 index 0000000..cc8ba7b --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# pyroulette + +## Explanation + +- A `Player` has a `Strategy` which is comprised of a list of `Placements`, which represent a collective `Bet`. +- The player will stick to their strategy. +- Winnings are re-invested (todo: allow specifying player's pyschology, e.g. pocket winnings of certain proportion) +- A player's placement cannot be too complicated (max is 10) +- A `Strategy` is formed at random based on exhausting the strategy budget, which is determined by considering the player's total budget and the minimum number of games they desire to play. + - It is possible to have some money left over (either due to reaching the maximum number of placements or not having enough money to place a bet with the remaining available chips), meaning the strategy budget is less than the cost to play the strategy. + - When players cannot play their strategy anymore, they leave the game, meaning they can end the simulation with some remaining money (e.g. $100 to play a $40 strategy that you lose twice in a row will leave you with $20 remaining). + +- When using `generate_players`, all players will have the same number of minimum games and budget. diff --git a/app.py b/app.py index e6ea63c..c67d289 100644 --- a/app.py +++ b/app.py @@ -1,16 +1,17 @@ +from random import choice, randint, seed + from roulette import ( - init_bet, - place_bet, - interpret_bet, - generate_players, - simulate_games, + FEASIBLE_MOVES, + Placement, Player, Strategy, - Placement, - FEASIBLE_MOVES, expected, + generate_players, + init_bet, + interpret_bet, + place_bet, + simulate_games, ) -from random import choice, randint, seed if __name__ == "__main__": @@ -62,9 +63,9 @@ if __name__ == "__main__": # print(min_games, Player(200, simulate_random_strategy(min_num_games=min_games, total_budget=200))) # create a list of random Placements - placements = [ - Placement(randint(1, 10), 1, choice(list(FEASIBLE_MOVES))) for _ in range(10) - ] + # placements = [ + # Placement(randint(1, 10), 1, choice(list(FEASIBLE_MOVES))) for _ in range(10) + # ] # strategy = Strategy.generate_random(50) @@ -77,7 +78,7 @@ if __name__ == "__main__": # print(sum([p.value for p in placements])) # # place the bets - # bet = place_bets(placements) + # bet = Strategy.place_bets(placements) # print(bet) diff --git a/roulette.py b/roulette.py index de55fce..f67d700 100644 --- a/roulette.py +++ b/roulette.py @@ -1,9 +1,10 @@ -from typing import List, Dict, Optional -from functools import reduce -from dataclasses import dataclass, field -from random import choice, randint -from statistics import stdev, mean +from __future__ import annotations +from dataclasses import dataclass, field +from functools import reduce +from random import choice, randint +from statistics import mean, stdev +from typing import Dict, List, Optional Bet = Dict[int, float] @@ -263,23 +264,6 @@ def combine_bets(bet_1: Bet, bet_2: Bet) -> Bet: # 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: 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( - lambda bet, placement: combine_bets(bet, placement.place_bet()), placements, {} - ) @dataclass @@ -307,25 +291,42 @@ class Strategy: return sum([p.value for p in self.placements]) @classmethod - def generate_random(cls, budget) -> "Strategy": + def generate_random(cls, budget: float, max_placements: int = 10) -> Strategy: + """ + Generates a random strategy. + + Parameters + ---------- + budget : float + The amount of money to spend on the strategy. + max_placements : int, optional + The maximum number of placements to include in the strategy. + (default is 10) + + Returns + ------- + Strategy + A random strategy. + + """ placements = [] initial_budget = budget 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]) # guarantees the max bet cannot exceed budget: # 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 - # todo: consider if this is the logic you want... - if randint(0, 1) == 0: - on = choice(list(FEASIBLE_MOVES)) - else: - on = choice(list(SINGLE_BETS)) + # TODO: consider if this is the logic you want... really let's define a player's profile / psychological disposition. + # if randint(0, 1) == 0: + # on = choice(list(FEASIBLE_MOVES)) + # else: + # on = choice(list(SINGLE_BETS)) on = choice( list(FEASIBLE_MOVES) - ) # todo: make a parameter, allow for just single bets. + ) # TODO: make a parameter, allow for just single bets. placement = Placement(num, amt, on) placements.append(placement) budget -= placement.value @@ -338,7 +339,31 @@ class Strategy: print(p) def get_bet(self): - return place_bets(self.placements) + return self.place_bets(self.placements) + + def get_placements(self): + return self.placements + + @staticmethod + 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( + lambda bet, placement: combine_bets(bet, placement.place_bet()), placements, {} + ) + + @dataclass @@ -374,7 +399,24 @@ class Player: 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) -> Strategy: + """ + Simulates a random strategy based on the minimum number of games that + the player wants to play and the total budget that the player has. + + Parameters + ---------- + min_num_games : int, optional + The minimum number of games that the player wants to play. + (default is 1) + total_budget : float, optional + The total budget that the player has. + (default is 200) + + Returns + ------- + + """ strategy_budget = total_budget // min_num_games return Strategy.generate_random(strategy_budget)