Giter Club home page Giter Club logo

crapssim's Introduction

Hi! I'm Sean Kent ๐Ÿ‘‹

I love to write code at the intersection of statistics, machine learning, and new research. Here, I'll highlight a few of my favorite code projects.

SVM-based algorithms for Multiple Instance Learning

mildsvm: Weakly supervised, multiple instance data lives in numerous interesting applications such as drug discovery, object detection, and tumor prediction on whole slide images. The mildsvm package provides an easy way to learn from this data by training Support Vector Machine (SVM)-based classifiers. It also contains helpful functions for building and printing multiple instance data frames. mildsvm includes an implementation of MI-SMM from our research paper Kent and Yu (2022) "Non-convex SVM for cancer diagnosis based on morphologic features of tumor microenvironment". The package can be installed via install.packages("mildsvm") in R.

Causal Matching for Longitudinal Data

rsmatch is an R package designed to perform Risk Set Matching. Risk set matching is useful for causal inference in longitudinal studies where subjects are treated at varying time points. The main idea is that treated subjects can match with anyone who hasn't yet been treated and those who never get treatment, but each subject can only be used in one pair. This creates a mixed-integer programming problem that we implement based on Li, Propert, and Rosenbaum (2001) Balanced Risk Set Matching. This package can be installed via install_github("skent259/rsmatch").

Simulate the Game of Craps

I don't gamble oftenโ€”but, for me, the most entertaining way to lose money in a casino is playing craps. As a side-project, I developed a simulator in python (skent259/CrapsSim) to test various betting strategies. With it, I analyzed the best craps strategies for players on a budget, published on my blog.


The rest of my repositories are a mixture of machine learning implementations, visualizations from other contexts, talks that I've given, and more. Feel free to check out those projects below!

crapssim's People

Contributors

skent259 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

crapssim's Issues

Table payouts should be improved for consistency

There are many craps bets that have variable payouts at different casinos. How can we best incorporate this feature into crapssim?

List of bets that this involves:

  • Field (double and triple numbers)
  • Fire bet (25/100/1000 for 4+ points vs 7/25/100/300 for 3+ points)
  • ATS bet (35/35/175 vs 30/30/150)

Other related bet issues

  • For buy bets, some casinos charge the vig only on a win, while others always charge it. This amounts to changing the payouts.
  • Same for lay bets

Random Module

Are we married to using numpy.random for rolling the dice for some statistical reason, or can that be switched to using the standard python random library instead? I assume that if you need the dice roll result as a array you can use a numpy function to convert a tuple to an array.

I ask because it looks like numpy arrays aren't hashable so they can't be used for a dictionary key. It also isn't thread safe according to https://stackoverflow.com/questions/7029993/differences-between-numpy-random-and-random-random-in-python so for a multi threaded run (which would be seemingly much faster and was mentioned in #14) it wouldn't be usable. Also in most of our tests we use tuples as the type for dice rolls in fixed roll, however, in roll the result is a numpy array.

place682come possible bug

This came up in the testing for #29 with the original version of strategy.

If the point is on 9 and a player has these bets on the table:

[Place8(bet_amount=6.0), Come(bet_amount=5.0, point=5), Come(bet_amount=5.0, point=6)]

and place68_2come is run, no bets get placed.

This is because the current_numbers is set by

    current_numbers = []
    for bet in player.bets_on_table:
        current_numbers += bet.winning_numbers
    current_numbers = list(set(current_numbers))

making the current_numbers [8, 5, 6]. Correctly (in my opinion) no additional Place bet is placed, as both 6 and 8 are in current_numbers:

if table.point == "On" and len(player.bets_on_table) < 4:
        # always place 6 and 8 when they aren't come bets or place bets already
        if 6 not in current_numbers:
            player.bet(Place6(6 / 5 * unit))
        if 8 not in current_numbers:
            player.bet(Place8(6 / 5 * unit))

No additional Come bet gets placed in this block, as there are already 2 come bets:

    if player.num_bet("Come", "PassLine") < 2 and len(player.bets_on_table) < 4:
        if table.point == "On":
            player.bet(Come(unit))
        if table.point == "Off" and (
            player.has_bet("Place6") or player.has_bet("Place8")
        ):
            player.bet(PassLine(unit))

This block I believe incorrectly sets the pass_come_winning_numbers to [5] since player.get_bet("Come", "Any").winning_numbers only returns a single bet, in this case the Come(bet_amount=5, point=5). I would think it was expected to return [5, 6] but since only one come bet is retrieved from get_bet it only gets the [5].

    # if come bet or passline goes to 6 or 8, move place bets to 5 or 9
    pass_come_winning_numbers = []
    if player.has_bet("PassLine"):
        pass_come_winning_numbers += player.get_bet("PassLine").winning_numbers
    if player.has_bet("Come"):
        pass_come_winning_numbers += player.get_bet("Come", "Any").winning_numbers

This mean that the block below never gets hit and a Place5 or Place9 never gets placed as a replacement for the Place6.

    if 6 in pass_come_winning_numbers:
        if player.has_bet("Place6"):
            player.remove(player.get_bet("Place6"))
        if 5 not in current_numbers:
            player.bet(Place5(unit))
        elif 9 not in current_numbers:
            player.bet(Place9(unit))
    elif 8 in pass_come_winning_numbers:
        if player.has_bet("Place8"):
            player.remove(player.get_bet("Place8"))
        if 5 not in current_numbers:
            player.bet(Place5(unit))
        elif 9 not in current_numbers:
            player.bet(Place9(unit))

I believe that the correct outcome of this should be that the pass_come_winning_numbers is [5, 6] and that the final bets on the table would correctly be:

[Place8(bet_amount=6.0), Come(bet_amount=5.0, point=5), Come(bet_amount=5.0, point=6), Place9(bet_amount=6.0)]

Do Fire bets normally pay out each time that they are hit, or only when the shooter concluded?

So if the shooter has hit 4 points, does it pay out when the shooter has completed shooting, or does it pay out after the 4th point is made? I don't play fire often, but I thought that it payed out when x number of points are made so that is how it is in dev. However, at my local casino I asked them and it pays out when the shooter has concluded shooting, so I'm wondering if that varies, or if it should be changed?

Leave Up Winning Bets

At the casino, some bets (Place bets come to mind) are automatically left up if they are won and only the winnings are taken down. I think that it would be handy if we had some way to set these bets to automatically be left up.

Dont Come

Hi,

Is there a way to simulate the "Don't Come" bet?

Add a way for end users to test their strategies

I think that it would be useful if end users and developer had a way to test their strategies and ensure that they are working correctly without having to write Python tests for them.

How I picture this, is a function creating a CSV file with these fields.

  • Table Point
  • For each bet type chosen, that starting amount (ex. Starting PassLine, Starting Field, etc.)
  • Dice 1
  • Dice 2
  • for each bet type chosen, the ending amount (ex. Ending PassLine, Ending Field, etc.)

so the user could run

create_strategy_test_file(file='test_risk_12.csv', bet_types=(PassLine, Field, Place6, Place8))

which would create the blank CSV file with the headers: Table Point, Starting Bankroll, Starting PassLine, Starting Field, Starting Place6, Starting Place8, Dice1, Dice2, Ending Bankroll, Ending PassLine, Ending Field, Ending Place6, Ending Place8

Then the user would just need to enter their test into the CSV file, ex. if they wanted to simulate what happens for a Risk12 if the point is off and they roll a 4 they could do:

Off, 5, 5, 0, 0, 2, 2, 5, 0, 6, 6

This would show that for the point being off and rolling a 2, 2 the player would win the field bet and place the 6 and the 8 making the place6=6, the place8=8, and leaving the passline at 5.

then they could run the tests with

run_strategy_test_file(file='test_risk_12.csv', bet_types=(PassLine, Field, Place6, Place8), strategy=Risk12)

Which would give messages on tests that fail.

I would also envision this being run from the command prompt via argparse so users don't need to do any code. ex:

python crapssim.py create_strategy_test_file test_risk_12.csv --bet_types PassLine Field Place6 Place8
python crapssim.py run_strategy_test_file test_risk_12.csv -- --bet_types PassLine Field Place6 Place8

I think that this would be a useful way for players to write tests for their strategies without having to write any code.

I also think that it would be useful for development testing since in theory one could simulate entire games of running the strategies, record the outcomes to a CSV, and have integration tests (or even unit tests depending on how many and the length) running the entire games of each default Strategy.

Add a Strategy object type

I think that it would make sense for us to have a Strategy base class that could be derived from to create strategies.

Currently, for strategies that have attributes that need to be stored those attributes are passed to the player and stored as strat_info. These items then have to be passed back from the player to the strategy with a kwargs argument as it isn't always apparent what attributes will need to be passed.

If each players strategy was it's own object, the attributes could all be defined and stored by the Strategy which I think will be easier for end users creating strategies to understand. It would also let us remove unit from player, and just allow each strategy to define it's own init method with the necessary arguments allowing more flexibility.

It would also let us break up complex strategies into multiple smaller methods within the Strategy for code readability and sustainability.

How I envision the base class looking is

class Strategy(ABC):
    @abstractmethod
    def update_bets(self, player: Player, table: Table):
        pass

Then have each strategy subclass from it. For strategies without complex inputs or outputs, it will be as simple as taking what is already defined in the method and adding necessary inputs. For example with passline:

class PassLineStrategy(Strategy):
    def __init__(self, bet_amount: typing.SupportsFloat):
        self.bet_amount = bet_amount

    def update_bets(self, player: Player, table: Table):
        if table.point == "Off" and not player.has_bets(PassLine):
            player.place_bet(PassLine(self.bet_amount), table)

As you can see, it separates the unit parameter from the player and allows any bet amount to be used for the Strategy. This way if the player wanted to use a strategy where they place $5 dollar pass line bets but $100 come bets they wouldn't be beholden to the player.unit when re-using strategies.

I think that it would really allow you to simplify strategies that are required to store information. For example hammerlock could look something like (untested and just for illustration):

class HammerLock(Strategy):
    def __init__(self, bet_amount, win_mult="345"):
        self.bet_amount = bet_amount
        self.win_mult = win_mult
        self.mode = None

    def update_bets(self, player: Player, table: Table):
        PassLineStrategy(self.bet_amount).update_bets(player, table)
        LayOddsStrategy(self.bet_amount).update_bets(player, table)

        self.update_mode(player, table)
        
        if self.mode == 'place68':
            PlaceStrategy(self.bet_amount, numbers={6, 8}, skip_point=False).update_bets(player, table)
        elif self.mode == 'place_inside':
            PlaceStrategy(self.bet_amount, numbers={5, 6, 8, 9}, skip_point=False).update_bets(player, table)
        elif self.mode == 'takedown':
            self.takedown(player)

    def update_mode(self, player: Player, table: Table):
        if self.mode is None and table.point.status == 'On':
            self.mode = 'place68'
        elif self.mode == 'place68' and not player.has_bets(Place6, Place8):
            self.mode = 'place_inside'
        elif self.mode == 'place_inside' and not player.has_bets(Place5, Place6, Place8, Place9):
            self.mode = 'takedown'
            
    def takedown(self, player):
        for bet_type in (Place5, Place6, Place8, Place9):
            player.remove_if_present(bet_type)

We could then add other object related methods to the Strategy if we wanted, for instance we could have a add method that could combine multiple strategies so something like the PassLine(5) and Come(100) described above could be represented as just PassLine(5) + Come(100).

Thoughts on this implementation of strategy?

What are craps-test.py and run_simulations.py in the Python directory for?

It looks like craps-test.py might just be some pre unit testing smoke tests, so I would think that this could be removed or refactored and moved to the test directory.

run_simulations.py looks like convenience methods to run simulations without having to set up the tables and players etc. I think that these would be good to have somewhere so maybe they can be refined and moved into crapssim?

Add fixed roll files to package

I can see several ways in which having fixed roll files could be useful:

  • Small roll files used for testing, to snapshot the results of a strategy
  • After incorporating a simulation module (see #14), users could test strategies against some standard roll files
  • For users who don't believe in the randomness of some computers. Other craps software offer rolls with different randomization schemes, e.g. Wincraps https://cloudcitysoftware.com/brfiles.htm.

Table can run forever if player's bankroll is greater than zero, but less than bet unit.

The stopping condition for the table is

crapssim/crapssim/table.py

Lines 116 to 128 in a75b997

# evaluate the stopping condition
if runout:
continue_rolling = (
self.dice.n_rolls < max_rolls
and self.n_shooters <= max_shooter
and self.total_player_cash > 0
) or self.player_has_bets
else:
continue_rolling = (
self.dice.n_rolls < max_rolls
and self.n_shooters <= max_shooter
and self.total_player_cash > 0
)

so if a player has a bankroll > 0, but it's less than their strategies bet unit the simulation will run longer than it should since the player is no longer able to bet. If we move the unit to the player we can do

https://github.com/amortization/crapssim/blob/cd1fcc001273feb1d66022d584f470d8f9eb251e/crapssim/table.py#L121-L133

which would check whether or not the player has enough units to continue betting. Or if we want to leave the old logic in place as well (although I don't think it's a good simulation that someone would sit and watch rolls and not bet) we could add a flag to the player or table like we do with runout that would allow either option.

Should there be a PassLine bet placed initially after a new shooter for the Place682Come Strategy?

Currently, if a player is using the Place682Come strategy and the table is new (meaning no bets and point is Off) or there is a new shooter they will not place a PassLine bet. They wait until the point is Off and the player has either a Place6 or a Place8 bet to place a PassLine bet. Is this the correct way this strategy is supposed to work?

It would seem to me like the Player would want to place the PassLine bet initially, and then if the PassLine point is 6 or 8 Place the 5 or 9 and either the Place6 or Place8 depending on the PassLine point, and then the added Come bet. This would have the same desired outcome of getting the four distinct numbers. Currently, it seems like it is artificially increasing the number of rolls the strategy would last as it is arbitrarily waiting to bet the PassLine until after a point has already been established and completed.

Place bets are automatically turned off when point is Off

I believe that at most craps tables they are automatically turned off, but you do have the option to tell them to leave them working if you desire. I think that Place bets should give the option to take them down or be left up when the point is working, otherwise, have them automatically left up and taken down when the point is off based on the players strategy.

Add methods for running simulations

I like the idea of having some convenience methods for running the simulations, and maybe even some convenience methods for simplifying the data through displaying with charts or giving helpful aggregation data (I'm a gambler not a statistician but for me average ROI, risk of ruin, coin-in, etc. could be valuable.)

Might make sense to have that as a separate package or something within crapssim, but I think that as long as it's well documented it could just be available in crapssim itself.

I think that this would be useful on the users end for simplicity, and allow us to add more features for running those simulations. For example, one thing that I would like to see on that front would be using the tqdm package to allow us to show a progress bar of the simulation completion status. I would think most end users would find this helpful, but it would be burdensome to document how to set that up in the readme for a user who just wants to run a simulation on their strategy. Similarly, I haven't tested it yet but I think that there could be some significant speed increases if when running multi simulations we utilized the multiprocessing module.

In theory, I think that one way to do it might be a Simulation object where you could add the table(s) and player(s) and a run method that would then run and store the results. Maybe with classmethods or optional arguments to initialize it for table, player, strategy, etc. For example to run 1000 passline simulations:

sim = Simulation.from_strategy(strategy=passline, bankroll=10000, runout=True)
sim.run(count=1000)

then it would show a tqdm progress bar with x number of simulations over 1000 being completed. And then there would be methods or properties with data ex:

print(sim.win_loss_amounts)
>>> [100.4, -55.6, 4000, ...]

Originally posted by @amortization in #8 (comment)

place68 question

I'm working on creating integration tests for #29 and a question came up.

What I'm doing is running a script to produce data from the Master branch, and then making sure that the data in the #29 branch matches what was output from master for each strategy. It is working through the passline and passlineodds strategies, but is running into issues with place68.

The original code for place68 was

def place68(player, table, unit=5, strat_info=None):
    passline(player, table, unit, strat_info=None)
    # Place 6 and 8 when point is ON
    p_has_place_bets = player.has_bet(
        "Place4", "Place5", "Place6", "Place8", "Place9", "Place10"
    )
    if table.point == "On" and not p_has_place_bets:
        if table.point.number == 6:
            player.bet(Place8(6 / 5 * unit))
        elif table.point.number == 8:
            player.bet(Place6(6 / 5 * unit))
        else:
            player.bet(Place8(6 / 5 * unit))
            player.bet(Place6(6 / 5 * unit))

So what is happening in the original script is that if a player has a Place6 or Place8 bet that wins and gets taken down the Place6 or Place8 doesn't get left up. For example:

point = 5
last_roll = 8
bets_before = [PassLine(bet_amount=5.0), Place6(bet_amount=6.0), Place8(bet_amount=6.0)]
dice_result = (4, 4)

after running table.add_player_bets() what was originally left up was:

bets_after = [PassLine(bet_amount=5.0), Place6(bet_amount=6.0)]

What I would normally expect would be that the Place8 bet gets re-added (since normally players don't take down place bets after winning them, the dealer just gives them their winnings) so the bets_after would be:

bets_after = [Place6(bet_amount=6.0), PassLine(bet_amount=5.0), Place8(bet_amount=6.0)]

I think what is probably happening is that it's hitting this logic

    p_has_place_bets = player.has_bet(
        "Place4", "Place5", "Place6", "Place8", "Place9", "Place10"
    )
    if table.point == "On" and not p_has_place_bets:

and seeing that the player has a Place6 and not ever placing the Place8 back up. It currently would wait until the player won the Place6, and then it would put both of those bets back up. I think that the more natural expectation for this strategy would be to leave the Place8 up in this case and just take the winnings since when most people talk about placing the 6 and 8 that's what they are referring to, but it would mean that the new implementation of this strategy would differ from the original. Thoughts on this?

Consider adding bet aliases

For example, Boxcars could be an alias for Twelve.

Should be easy to implement, just need a sense for what important aliases would be, and how best to document

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.