Repository: skent259/crapssim (language: python)

Issue:
Bet changes
Continuation of #18 and discussion in #13.

I changed Bet to add another layer of abstraction separating Bets that use winning_numbers and losing_numbers from those that don't (currently just HardWay and Fire) and making those a new type of abstract Bet called WinningLosingNumbersBet. I pushed the _update_bet logic from Bet to WinningLosingNumbersBet as it relied on winning and losing numbers. 

WinningLosingNumbersBet also makes the winning_numbers and losing_numbers attributes required for any class that inherits from it since all classes that inherit from it should use it in some fashion. 

I also made name as a property of Bet equal to __class__.__name__. This allowed me to remove all of the unecessary __init__ methods since everything being set at initiation was controlled by the Base class. For Example 
```
class Place4(Place):
    def __init__(self, bet_amount: float):
        super().__init__(bet_amount)
        self.name: str = "Place4"
        self.winning_numbers: list[int] = [4]
        self.losing_numbers: list[int] = [7]
        self.payoutratio: float = 9 / 5
```
Became:
```
class Place4(Place):
    payout_ratio: float = 9 / 5
    winning_numbers: list[int] = [4]
    losing_numbers: list[int] = [7]
```

It also let me remove a number of _update_bet methods and simplify them so something like Field went from:
```
class Field(Bet):
    def __init__(self, bet_amount: float):
        super().__init__(bet_amount, None)
        self.name: str = "Field"
        self.winning_numbers: list[int] = [2, 3, 4, 9, 10, 11, 12]
        self.losing_numbers: list[int] = [5, 6, 7, 8]

    @property
    def payout_ratio(self):
        return self.table.payouts['field_ratios'][self.table.dice.total]

    def _update_bet(self) -> tuple[str | None, float, bool]:
        win_amount: float = 0.0
        remove: bool = True

        if self.table.dice.total in self.winning_numbers:
            status = "win"
            win_amount = self.payout_ratio * self.bet_amount
        else:
            status = "lose"

        return status, win_amount, remove
```

to this:
```
class Field(WinningLosingNumbersBet):
    @property
    def payout_ratio(self):
        return self.table.payouts['field_payouts'][self.table.dice.total]

    @property
    def winning_numbers(self):
        return list(self.table.payouts['field_payouts'])

    @property
    def losing_numbers(self):
        return list(x for x in [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] if x not in self.table.payouts['field_payouts'])
```




Discussion / hints:
I made status, win_amount and remove properties of bet. This simplified a lot of repeating logic used in _update_bet and allows update_bet to actually update the bet given the table status instead of having to return something. This would allow end users to just look at bet properties instead of having to call update_bet to get those properties. 
@skent259 how do you feel about splitting up Odds bets into Odds4, Odds5 etc? This would apply to LayOdds as well. I think that there would be a lot of benefits that would go along with that:

- Remove subname requirement
- Easier for the users for player.bet(Odds6(6)) as opposed to having get the PassLine or Come bet object to pass in the Odds.
- Bet.allowed() can be used to determine whether the player has the required PassLine and Come bets.
> @skent259 how do you feel about splitting up Odds bets into Odds4, Odds5 etc? This would apply to LayOdds as well. I think that there would be a lot of benefits that would go along with that:
> 
> * Remove subname requirement
> * Easier for the users for player.bet(Odds6(6)) as opposed to having get the PassLine or Come bet object to pass in the Odds.
> * Bet.allowed() can be used to determine whether the player has the required PassLine and Come bets.

@amortization Wouldn't subname still be needed for the come bets?  I agree that `subname` is clunky, and it really only exists to distinguish the bets where a user can have a similar bet on multiple numbers (i.e. Come, DontCome, Odds).

My only question is how this will be accessed in strategy.py.  For example, when adding odds to the last come bet that moves to a number, what would that logic look like? How would we change `passline_odds()`? My preference is for the strategies to be easy to code to encourage users to build their own; as long as I can see a way forward with that, I'm all for it

Fix patch:
diff --git a/Python/run_simulations.py b/Python/run_simulations.py
deleted file mode 100644
index 3158b635..00000000
--- a/Python/run_simulations.py
+++ /dev/null
@@ -1,126 +0,0 @@
-from crapssim import Table
-from crapssim import Player
-from crapssim import strategy
-import sys 
-import os 
-
-
-def run_printout(n_roll, n_shooter, bankroll, strategy, strategy_name, runout):
-    runout_str = "-runout" if runout else ""
-    # Run one simulation with verbose=True to check strategy
-    outfile_name = f"./output/printout/{strategy_name}_roll-{n_roll}_shooter-{n_shooter}_br-{bankroll}{runout_str}.txt"
-    # outfile_name = "./output/printout/{}_roll-{}_br-{}{}.txt".format(strategy_name, n_roll, bankroll, runout_str)
-    print("Running printout for " + os.path.basename(outfile_name))
-    # print("Running printout for {}_sim-{}_roll-{}_br-{}{}.txt".format(strategy_name, n_sim, n_roll, bankroll, runout_str))
-    with open(outfile_name, 'w') as f_out:
-        sys.stdout = f_out
-        table = Table()
-        table.add_player(Player(bankroll, strategy))
-        table.run(n_roll, n_shooter, verbose=True)
-    sys.stdout = sys.__stdout__ # reset stdout
-
-def run_simulation(n_sim, n_roll, bankroll, strategy, strategy_name, runout):
-    runout_str = "_runout" if runout else ""
-    # Run simulation of n_roll rolls (estimated rolls/hour with 5 players) 1000 times
-    outfile_name = "./output/simulations/{}_sim-{}_roll-{}_br-{}{}.txt".format(strategy_name, n_sim, n_roll, bankroll, runout_str)
-    print("Running simulations for {}_sim-{}_roll-{}_br-{}{}.txt".format(strategy_name, n_sim, n_roll, bankroll, runout_str))
-    with open(outfile_name, 'w') as f_out:
-        # Headers to match out
-        f_out.write("total_cash,bankroll,n_rolls") 
-        f_out.write(str('\n'))
-        for _ in range(n_sim):
-            table = Table()
-            table.add_player(Player(bankroll, strategy))
-            table.run(n_roll, verbose=False, runout=runout)
-            # write data to file
-            out = "{},{},{}".format(table.total_player_cash, bankroll, table.dice.n_rolls_)
-            f_out.write(str(out))
-            f_out.write(str('\n'))
-
-def run_simulation_burnin(n_sim, n_roll, bankroll, strategy, strategy_name, burn_in=20, runout=True):
-    runout_str = "_runout" if runout else ""
-    # Run simulation of n_roll rolls (estimated rolls/hour with 5 players) 1000 times
-    outfile_name = "./output/simulations/{}_sim-{}_roll-{}_br-{}_burnin{}.txt".format(strategy_name, n_sim, n_roll, bankroll, runout_str)
-    print("Running simulations for {}_sim-{}_roll-{}_br-{}_burnin{}.txt".format(strategy_name, n_sim, n_roll, bankroll, runout_str))
-    with open(outfile_name, 'w') as f_out:
-        # Headers to match out
-        f_out.write("total_cash,bankroll,n_rolls") 
-        f_out.write(str('\n'))
-        for _ in range(n_sim):
-            table = Table()
-            table.add_player(Player(bankroll, strategy))
-
-            table.run(burn_in, verbose=False, runout=False)
-            burn_in_bankroll = table.total_player_cash
-            table.run(n_roll, verbose=False, runout=runout)
-            # write data to file
-            out = "{},{},{}".format(table.total_player_cash, burn_in_bankroll, table.dice.n_rolls_)
-            f_out.write(str(out))
-            f_out.write(str('\n'))
-
-def run_multi_simulation(n_sim, n_roll, n_shooter, bankroll, strategy, name, runout=True):
-    runout_str = "_runout" if runout else ""
-    # Run simulation of n_roll rolls (estimated rolls/hour with 5 players) 1000 times
-    outfile_name = "./output/simulations/{}_sim-{}_roll-{}_br-{}_burnin{}.txt".format(name, n_sim, n_roll, bankroll, runout_str)
-    print("Running simulations for {}_sim-{}_roll-{}_br-{}_burnin{}.txt".format(name, n_sim, n_roll, bankroll, runout_str))
-    with open(outfile_name, 'w') as f_out:
-        # Headers to match out
-        f_out.write("simid,strategy,total_cash,bankroll,n_rolls") 
-        f_out.write(str('\n'))
-        for i in range(n_sim):
-            if i % 1000 == 0:
-                print(f"i: {i}")
-            table = Table() 
-            table.set_payouts("fielddouble", [2])
-            table.set_payouts("fieldtriple", [12])
-            for bank, s in zip(bankroll, strategy):
-                table.add_player(Player(bank, strategy[s], s))
-
-            # table.run(burn_in, verbose=False, runout=False)
-            # burn_in_bankroll = table.total_player_cash
-            table.run(n_roll, n_shooter, verbose=False, runout=runout)
-            # write data to file
-            # TODO: get actual player cash 
-            for bank, s in zip(bankroll, strategy):
-                out = f"{i},{s},{table.get_player(s).bankroll},{bank},{table.dice.n_rolls}"
-                # out = "{},{},{},{}".format(s, table.total_player_cash, bank, table.dice.n_rolls_)
-                f_out.write(str(out))
-                f_out.write(str('\n'))
-
-
-
-if __name__ == "__main__":
-    
-    sim = True 
-    printout = True
-
-    n_sim = 10
-    n_roll = float('inf')
-    n_shooter = 1
-    bankrolls= [10000]
-    strategies = {"passline": strategy.passline}
-    name = "testing"
-    # strategy = strat._strat_place68
-    # strategy_name = "place68" # don't include any "_" in this
-    runout = True
-    # runout_str = "-runout" if runout else ""
-
-    """ Run one strategy  """
-    if printout: 
-        for bank, s in zip(bankrolls, strategies):
-            run_printout(n_roll, n_shooter, bank, strategies[s], s, runout)    
-        # run_printout(n_roll, bankroll, strategy, strategy_name, runout)
-
-    if sim:
-        run_multi_simulation(n_sim, n_roll, n_shooter, bankrolls, strategies, name, runout)
-        # run_simulation_burnin(n_sim, n_roll, bankroll, strategy, strategy_name, burn_in=20, runout=runout)
-
-    
-
-    """ Run multiple strategies """
-    # strategies = [strat._strat_place68, strat._strat_place68_2come, strat._strat_pass2come, strat._strat_passline_odds2]
-    # strategy_names = ["place68", "place68-2come", "pass-2come", "passline-w-2odds"]
-
-    # for strategy, strategy_name in zip(strategies,strategy_names):
-    #     # run_simulation_burnin(n_sim, n_roll, bankroll, strategy, strategy_name, burn_in=20, runout=runout)
-    #     run_simulation(n_sim, n_roll, bankroll, strategy, strategy_name, runout=runout)
diff --git a/README.md b/README.md
index af93535a..33aab154 100644
--- a/README.md
+++ b/README.md
@@ -43,9 +43,8 @@ import crapssim as craps
 
 table = craps.Table()
 your_strat = craps.strategy.passline_odds2
-you = craps.Player(bankroll=200, bet_strategy=your_strat)
 
-table.add_player(you)
+table.add_player(bankroll=100, strategy=your_strat)
 table.run(max_rolls=20)
 ```
 
@@ -64,7 +63,7 @@ strategies = {
 for i in range(n_sim):
     table = craps.Table()
     for s in strategies:
-        table.add_player(craps.Player(bankroll, strategies[s], s))
+        table.add_player(bankroll=100, strategy=s)
 
     table.run(max_rolls=float("inf"), max_shooter=10, verbose=False)
     for s in strategies:
diff --git a/crapssim/__init__.py b/crapssim/__init__.py
index 7d7e3d7d..f662ec7f 100644
--- a/crapssim/__init__.py
+++ b/crapssim/__init__.py
@@ -1,7 +1,6 @@
-__all__ = ["table", "player", "dice", "strategy", "bet"]
+__all__ = ["table", "dice", "strategy", "bet"]
 
 from crapssim.dice import Dice
-from crapssim.player import Player
-from crapssim.table import Table
+from crapssim.table import Table, Player
 from . import bet
 from . import strategy
diff --git a/crapssim/bet.py b/crapssim/bet.py
index ee5cc4f2..1b7e9f4f 100644
--- a/crapssim/bet.py
+++ b/crapssim/bet.py
@@ -3,7 +3,7 @@
 
 if typing.TYPE_CHECKING:
     from crapssim.table import Table
-    from crapssim.player import Player
+    from crapssim import Player
 
 
 class Bet(ABC):
@@ -19,19 +19,13 @@ class Bet(ABC):
     ----------
     bet_amount: float
         Wagered amount for the bet
-    subname : string
-        Subname, usually denotes number for a come/don't come bet
     """
 
-    def __init__(self, bet_amount: typing.SupportsFloat, player=None, table=None):
+    def __init__(self, bet_amount: typing.SupportsFloat):
         self.bet_amount: float = float(bet_amount)
-        self.subname: str = str()
-        self.player: Player | None = player
-        self.table: Table | None = table
 
-    @property
     @abstractmethod
-    def payout_ratio(self):
+    def get_payout_ratio(self, table: "Table"):
         pass
 
     @property
@@ -42,13 +36,14 @@ def name(self):
     def removable(self):
         return True
 
-    def allowed(self, player) -> bool:
+    def allowed(self, table: "Table", player: "Player") -> bool:
         """
         Checks whether the bet is allowed to be placed on the given table.
 
         Parameters
         ----------
         player
+        table
 
         Returns
         -------
@@ -57,24 +52,30 @@ def allowed(self, player) -> bool:
         """
         return True
 
-    @property
     @abstractmethod
-    def status(self) -> str | bool:
+    def get_status(self, table: "Table") -> str | bool:
         pass
 
-    @property
-    def win_amount(self) -> float:
-        if self.status == "win":
-            return self.payout_ratio * self.bet_amount
+    def get_win_amount(self, table: "Table") -> float:
+        if self.get_status(table) == "win":
+            return self.get_payout_ratio(table) * self.bet_amount
         return 0.0
 
-    @property
-    def remove(self) -> bool:
-        if self.status is not None:
+    def get_return_amount(self, table: "Table") -> float:
+        status = self.get_status(table)
+        if status == "win":
+            return self.get_win_amount(table) + self.bet_amount
+        if status is None and self.should_remove(table) is True:
+            return self.bet_amount
+        else:
+            return 0
+
+    def should_remove(self, table: "Table") -> bool:
+        if self.get_status(table) is not None:
             return True
         return False
 
-    def _update_bet(self) -> None:
+    def update(self, table: "Table") -> None:
         """
         Returns whether the bets status is win, lose or None and if win the amount won.
 
@@ -85,6 +86,9 @@ def _update_bet(self) -> None:
         """
         pass
 
+    def already_placed(self, player: "Player") -> bool:
+        return player.has_bets(type(self))
+
 
 class WinningLosingNumbersBet(Bet, ABC):
     @property
@@ -97,28 +101,119 @@ def winning_numbers(self):
     def losing_numbers(self):
         pass
 
-    @property
-    def status(self) -> str | None:
-        if self.table.dice.total in self.winning_numbers:
+    def get_status(self, table: "Table") -> str | None:
+        if table.dice.total in self.winning_numbers:
             return "win"
-        elif self.table.dice.total in self.losing_numbers:
+        elif table.dice.total in self.losing_numbers:
             return "lose"
         return None
 
 
+class SingleWinningNumberBet(WinningLosingNumbersBet, ABC):
+    """WinningLosingNumbersBet where only one number wins."""
+    @property
+    @abstractmethod
+    def winning_number(self):
+        pass
+
+    @property
+    def winning_numbers(self):
+        return [self.winning_number]
+
+
+class SingleLosingNumberBet(WinningLosingNumbersBet, ABC):
+    @property
+    @abstractmethod
+    def losing_number(self):
+        pass
+
+    @property
+    def losing_numbers(self):
+        return [self.losing_number]
+
+
+class StaticPayoutRatio(Bet, ABC):
+    @property
+    @abstractmethod
+    def payout_ratio(self):
+        pass
+
+    def get_payout_ratio(self, table: "Table"):
+        return self.payout_ratio
+
+
 """
 Passline and Come bets
 """
 
 
-class PassLine(WinningLosingNumbersBet):
+class BaseOdds(SingleWinningNumberBet, SingleLosingNumberBet, StaticPayoutRatio, ABC):
+    @property
+    @abstractmethod
+    def base_bet_types(self) -> tuple[typing.Type[WinningLosingNumbersBet]]:
+        pass
+
+    @property
+    @abstractmethod
+    def table_odds_setting(self) -> str:
+        pass
+
+    @property
+    @abstractmethod
+    def key_number(self):
+        pass
+
+    @staticmethod
+    @abstractmethod
+    def by_number(number: int, bet_amount: float):
+        pass
+
+    def get_base_bets(self, player: "Player") -> list[WinningLosingNumbersBet]:
+        base_bets = player.get_bets(*self.base_bet_types, winning_numbers=self.winning_numbers)
+        return base_bets
+
+    def get_max_odds(self, table: "Table") -> int:
+        return table.settings[self.table_odds_setting][self.key_number]
+
+    def get_max_bet(self, table: "Table", player: "Player") -> typing.SupportsFloat:
+        base_bet_amount = sum(x.bet_amount for x in self.get_base_bets(player))
+        max_odds = self.get_max_odds(table)
+        return base_bet_amount * max_odds
+
+    def allowed(self, table: "Table", player: "Player") -> bool:
+        return self.get_max_bet(table, player) >= self.bet_amount
+
+
+class AllowsOdds(WinningLosingNumbersBet, StaticPayoutRatio, ABC):
     payout_ratio: float = 1.0
 
     def __init__(self, bet_amount: float):
-        super().__init__(bet_amount, None)
-        self.point: int | None = None
+        super().__init__(bet_amount)
+        self.point: int = None
         self.new_point: bool = False
 
+    def place_odds(self, bet_amount: typing.SupportsFloat, player: "Player", table):
+        player.add_bet(self.get_odds_bet(bet_amount), table)
+
+    def get_odds_bet(self, bet_amount: typing.SupportsFloat):
+        return self.odds_type.by_number(self.point, bet_amount)
+
+    @property
+    @abstractmethod
+    def odds_type(self) -> typing.Type[BaseOdds]:
+        pass
+
+
+class PassLine(AllowsOdds):
+
+    @property
+    def odds_type(self) -> typing.Type[BaseOdds]:
+        return Odds
+
+    @property
+    def key_number(self) -> int:
+        return self.winning_numbers[0]
+
     @property
     def winning_numbers(self):
         if self.point is None:
@@ -131,94 +226,87 @@ def losing_numbers(self):
             return [2, 3, 12]
         return [7]
 
-    @property
-    def remove(self):
-        if self.new_point is True:
-            return False
-        return super().remove
+    def get_status(self, table: "Table"):
+        if self.new_point:
+            return None
+        return super().get_status(table)
 
-    def _update_bet(self) -> None:
+    def update(self, table: "Table") -> None:
         self.new_point = False
 
-        if self.point is None and self.status not in ("win", "lose"):
-            self.point = self.table.dice.total
+        if self.point is None and self.get_status(table) not in ("win", "lose"):
+            self.point = table.dice.total
             self.new_point = True
 
-
     @property
     def removable(self):
         if self.point is not None:
             return False
         return True
 
-    def allowed(self, player) -> bool:
-        if player.table.point.status == 'Off':
+    def allowed(self, table: "Table", player: "Player") -> bool:
+        if table.point.status == 'Off':
             return True
         return False
 
 
 class Come(PassLine):
-    def _update_bet(self) -> None:
-        super()._update_bet()
-        if self.point is not None and self.subname == "":
-            self.subname = str(self.point)
-
-    def allowed(self, player) -> bool:
-        if player.table.point.status == 'On':
+    def allowed(self, table: "Table", player: "Player") -> bool:
+        if table.point.status == 'On':
             return True
         return False
 
+    def already_placed(self, player: "Player") -> bool:
+        return player.has_bets(type(self), point=self.point)
+
 
 """
 Passline/Come bet odds
 """
 
 
-class BaseOdds(WinningLosingNumbersBet, ABC):
+class Odds(BaseOdds, ABC):
+    base_bet_types: tuple[typing.Type[WinningLosingNumbersBet]] = (PassLine, Come)
+    table_odds_setting: str = 'max_odds'
+    losing_number: list[int] = 7
+
     @property
-    @abstractmethod
-    def base_bet_type(self) -> type:
-        pass
+    def key_number(self):
+        return self.winning_number
 
-    def allowed(self, player) -> bool:
-        max_bet = self.bet_amount
+    @staticmethod
+    def by_number(number: int, bet_amount: float):
+        return {4: Odds4, 5: Odds5, 6: Odds6, 8: Odds8, 9: Odds9, 10: Odds10}[number](bet_amount)
 
 
+class Odds4(Odds):
+    winning_number: int = 4
+    payout_ratio: float = 2 / 1
 
-class Odds(WinningLosingNumbersBet):
-    def __init__(self, bet_amount: typing.SupportsFloat, bet_object: PassLine | Come):
-        super().__init__(bet_amount, None)
-        self.bet_object = bet_object
 
-        if not isinstance(bet_object, PassLine) and not isinstance(bet_object, Come):
-            raise TypeError('bet_object must be either a PassLine or Come Bet.')
+class Odds5(Odds):
+    winning_number: int = 5
+    payout_ratio: float = 3 / 2
 
-        self.subname: str = "".join(str(e) for e in bet_object.winning_numbers)
 
-    @property
-    def winning_numbers(self):
-        return self.bet_object.winning_numbers
+class Odds6(Odds):
+    winning_number: int = 6
+    payout_ratio: float = 6 / 5
 
-    @property
-    def losing_numbers(self):
-        return self.bet_object.losing_numbers
 
-    @property
-    def payout_ratio(self):
-        if self.winning_numbers in ([4], [10]):
-            return 2 / 1
-        elif self.winning_numbers in ([5], [9]):
-            return 3 / 2
-        elif self.winning_numbers in ([6], [8]):
-            return 6 / 5
-        else:
-            raise NotImplementedError
+class Odds8(Odds):
+    winning_number: int = 8
+    payout_ratio: float = 6 / 5
+
+
+class Odds9(Odds):
+    winning_number: int = 9
+    payout_ratio: float = 3 / 2
 
-    def allowed(self, player) -> bool:
-        if isinstance(self.bet_object, PassLine):
-            if player.table.point.status == 'Off':
-                return False
-            return True
+
+class Odds10(Odds):
+    winning_number: int = 10
+    payout_ratio: float = 2 / 1
 
 
 """
@@ -226,47 +314,47 @@ def allowed(self, player) -> bool:
 """
 
 
-class Place(WinningLosingNumbersBet, ABC):
-    def _update_bet(self) -> None:
+class Place(SingleWinningNumberBet, SingleLosingNumberBet, StaticPayoutRatio, ABC):
+    def update(self, table: "Table") -> None:
         # place bets are inactive when point is "Off"
-        if self.table.point == "On":
-            super()._update_bet()
+        if table.point == "On":
+            super().update(table)
 
 
 class Place4(Place):
     payout_ratio: float = 9 / 5
-    winning_numbers: list[int] = [4]
-    losing_numbers: list[int] = [7]
+    winning_number: int = 4
+    losing_number: list[int] = 7
 
 
 class Place5(Place):
     payout_ratio: float = 7 / 5
-    winning_numbers: list[int] = [5]
-    losing_numbers: list[int] = [7]
+    winning_number: int = 5
+    losing_number: list[int] = 7
 
 
 class Place6(Place):
     payout_ratio: float = 7 / 6
-    winning_numbers: list[int] = [6]
-    losing_numbers: list[int] = [7]
+    winning_number: int = 6
+    losing_number: list[int] = 7
 
 
 class Place8(Place):
     payout_ratio: float = 7 / 6
-    winning_numbers: list[int] = [8]
-    losing_numbers: list[int] = [7]
+    winning_number: int = 8
+    losing_number: list[int] = 7
 
 
 class Place9(Place):
     payout_ratio: float = 7 / 5
-    winning_numbers: list[int] = [9]
-    losing_numbers: list[int] = [7]
+    winning_number: int = 9
+    losing_number: list[int] = 7
 
 
 class Place10(Place):
     payout_ratio: float = 9 / 5
-    winning_numbers: list[int] = [10]
-    losing_numbers: list[int] = [7]
+    winning_number: int = 10
+    losing_number: list[int] = 7
 
 
 """
@@ -274,20 +362,22 @@ class Place10(Place):
 """
 
 
-class Field(WinningLosingNumbersBet):
+class OneRollBet(WinningLosingNumbersBet, ABC):
+    """WinningLosingNumbersBet where if the number isn't in the winning_numbers, it is in the losing_numbers."""
     @property
-    def payout_ratio(self):
-        if self.table.dice.total in self.table.settings['field_payouts']:
-            return self.table.settings['field_payouts'][self.table.dice.total]
+    def losing_numbers(self):
+        return [x for x in [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] if x not in self.winning_numbers]
+
+
+class Field(OneRollBet):
+    def get_payout_ratio(self, table: "Table"):
+        if table.dice.total in table.settings['field_payouts']:
+            return table.settings['field_payouts'][table.dice.total]
         return 0
 
     @property
     def winning_numbers(self):
-        return list(self.table.settings['field_payouts'])
-
-    @property
-    def losing_numbers(self):
-        return list(x for x in [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] if x not in self.table.settings['field_payouts'])
+        return [2, 3, 4, 9, 10, 11, 12]
 
 
 """
@@ -295,14 +385,14 @@ def losing_numbers(self):
 """
 
 
-class DontPass(WinningLosingNumbersBet):
-    payout_ratio: float = 1.0
+class DontPass(AllowsOdds):
+    @property
+    def odds_type(self) -> typing.Type[BaseOdds]:
+        return LayOdds
 
-    def __init__(self, bet_amount: float):
-        super().__init__(bet_amount, None)
-        self.push_numbers: list[int] = [12]
-        self.point: int | None = None
-        self.new_point: bool = False
+    @property
+    def key_number(self) -> int:
+        return self.losing_numbers[0]
 
     @property
     def winning_numbers(self):
@@ -316,34 +406,26 @@ def losing_numbers(self):
             return [7, 11]
         return [self.point]
 
-    @property
-    def remove(self) -> bool:
-        if self.point is None and self.table.dice.total == 12:
-            return True
-        if self.new_point is True:
-            return False
-        return super().remove
-
-    def _update_bet(self) -> None:
+    def update(self, table: "Table") -> None:
         self.new_point = False
-        if self.point is None and self.table.dice.total in (4, 5, 6, 8, 9, 10):
-            self.point = self.table.dice.total
+        if self.point is None and table.dice.total in (4, 5, 6, 8, 9, 10):
+            self.point = table.dice.total
             self.new_point = True
 
-    def allowed(self, player) -> bool:
-        if player.table.point.status == 'Off':
+    def allowed(self, table: "Table", player) -> bool:
+        if table.point.status == 'Off':
             return True
         return False
 
+    def get_status(self, table: "Table") -> str | None:
+        if self.new_point:
+            return None
+        return super().get_status(table)
 
-class DontCome(DontPass):
-    def _update_bet(self) -> None:
-        super()._update_bet()
-        if self.point is not None and self.subname == "":
-            self.subname = "".join(str(e) for e in self.losing_numbers)
 
-    def allowed(self, player) -> bool:
-        if player.table.point.status == 'On':
+class DontCome(DontPass):
+    def allowed(self, table: "Table", player) -> bool:
+        if table.point.status == 'On':
             return True
         return False
 
@@ -353,30 +435,53 @@ def allowed(self, player) -> bool:
 """
 
 
-class LayOdds(WinningLosingNumbersBet):
-    def __init__(self, bet_amount: float, bet_object: DontPass | DontCome):
-        super().__init__(bet_amount, None)
-        self.bet_object = bet_object
-        self.subname: str = "".join(str(e) for e in bet_object.losing_numbers)
+class LayOdds(BaseOdds, ABC):
+    base_bet_types: typing.Type[AllowsOdds] = (DontPass, DontCome)
+    table_odds_setting: str = 'max_dont_odds'
+    winning_number: int = 7
 
     @property
-    def winning_numbers(self):
-        return self.bet_object.winning_numbers
+    def key_number(self):
+        return self.losing_number
 
-    @property
-    def losing_numbers(self):
-        return self.bet_object.losing_numbers
+    @staticmethod
+    def by_number(number: int, bet_amount: float):
+        return {4: LayOdds4,
+                5: LayOdds5,
+                6: LayOdds6,
+                8: LayOdds8,
+                9: LayOdds9,
+                10: LayOdds10}[number](bet_amount)
+
+
+class LayOdds4(LayOdds):
+    losing_number: int = 4
+    payout_ratio: float = 1 / 2
+
+
+class LayOdds5(LayOdds):
+    losing_number: int = 5
+    payout_ratio: float = 2 / 3
+
+
+class LayOdds6(LayOdds):
+    losing_number: int = 6
+    payout_ratio: float = 5 / 6
 
-    @property
-    def payout_ratio(self):
-        if self.losing_numbers in ([4], [10]):
-            return 1 / 2
-        elif self.losing_numbers in ([5], [9]):
-            return 2 / 3
-        elif self.losing_numbers in ([6], [8]):
-            return 5 / 6
-        else:
-            raise NotImplementedError
+
+class LayOdds8(LayOdds):
+    losing_number: int = 8
+    payout_ratio: float = 5 / 6
+
+
+class LayOdds9(LayOdds):
+    losing_number: int = 9
+    payout_ratio: float = 2 / 3
+
+
+class LayOdds10(LayOdds):
+    losing_number: int = 10
+    payout_ratio: float = 1 / 2
 
 
 """
@@ -384,59 +489,51 @@ def payout_ratio(self):
 """
 
 
-class Any7(WinningLosingNumbersBet):
+class Any7(OneRollBet, SingleWinningNumberBet, StaticPayoutRatio):
     payout_ratio: int = 4
-    winning_numbers: list[int] = [7]
-    losing_numbers: list[int] = [2, 3, 4, 5, 6, 8, 9, 10, 11, 12]
+    winning_number: int = 7
 
 
-class Two(WinningLosingNumbersBet):
+class Two(OneRollBet, SingleWinningNumberBet, StaticPayoutRatio):
     payout_ratio: int = 30
-    winning_numbers: list[int] = [2]
-    losing_numbers: list[int] = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
+    winning_number: int = 2
 
 
-class Three(WinningLosingNumbersBet):
+class Three(OneRollBet, SingleWinningNumberBet, StaticPayoutRatio):
     payout_ratio: int = 15
-    winning_numbers: list[int] = [3]
-    losing_numbers: list[int] = [2, 4, 5, 6, 7, 8, 9, 10, 11, 12]
+    winning_number: int = 3
 
 
-class Yo(WinningLosingNumbersBet):
+class Yo(OneRollBet, SingleWinningNumberBet, StaticPayoutRatio):
     payout_ratio: int = 15
-    winning_numbers: list[int] = [11]
-    losing_numbers: list[int] = [2, 3, 4, 5, 6, 7, 8, 9, 10, 12]
+    winning_number: int = 11
 
 
-class Boxcars(WinningLosingNumbersBet):
+class Boxcars(OneRollBet, SingleWinningNumberBet, StaticPayoutRatio):
     payout_ratio: int = 30
-    winning_numbers: list[int] = [12]
-    losing_numbers: list[int] = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
+    winning_number: int = 12
 
 
-class AnyCraps(WinningLosingNumbersBet):
+class AnyCraps(OneRollBet, WinningLosingNumbersBet, StaticPayoutRatio):
     payout_ratio: int = 7
     winning_numbers: list[int] = [2, 3, 12]
-    losing_numbers: list[int] = [4, 5, 6, 7, 8, 9, 10, 11]
 
 
-class CAndE(WinningLosingNumbersBet):
+class CAndE(OneRollBet, WinningLosingNumbersBet):
     winning_numbers: list[int] = [2, 3, 11, 12]
-    losing_numbers: list[int] = [4, 5, 6, 7, 8, 9, 10]
 
-    @property
-    def payout_ratio(self):
-        if self.table.dice.total in [2, 3, 12]:
+    def get_payout_ratio(self, table: "Table"):
+        if table.dice.total in [2, 3, 12]:
             return 3
-        elif self.table.dice.total in [11]:
+        elif table.dice.total in [11]:
             return 7
         else:
             raise NotImplementedError
 
 
-class HardWay(Bet, ABC):
+class HardWay(StaticPayoutRatio, ABC):
     def __init__(self, bet_amount: float):
-        super().__init__(bet_amount, None)
+        super().__init__(bet_amount)
 
     @property
     @abstractmethod
@@ -447,11 +544,13 @@ def number(self):
     def winning_result(self):
         return [self.number / 2, self.number / 2]
 
-    @property
-    def status(self):
-        if self.table.dice.result == self.winning_result:
+    def get_payout_ratio(self, table: "Table"):
+        return self.payout_ratio
+
+    def get_status(self, table: "Table"):
+        if table.dice.result == self.winning_result:
             return "win"
-        elif self.table.dice.total in [self.number, 7]:
+        elif table.dice.total in [self.number, 7]:
             return "lose"
         return None
 
@@ -478,52 +577,42 @@ class Hard10(HardWay):
 
 class Fire(Bet):
     def __init__(self, bet_amount: float):
-        super().__init__(bet_amount, None)
+        super().__init__(bet_amount)
         self.bet_amount: float = bet_amount
         self.points_made: list[int] = []
         self.current_point: int | None = None
         self.new_point_made: bool = False
 
-    @property
-    def status(self):
-        if self.new_point_made and len(self.points_made) in self.table.settings['fire_points']:
+    def get_status(self, table: "Table"):
+        if self.new_point_made and len(self.points_made) in table.settings['fire_points']:
             return "win"
-        elif self.current_point is not None and self.table.dice.total == 7:
+        elif self.current_point is not None and table.dice.total == 7:
             return "lose"
         return None
 
-    @property
-    def win_amount(self) -> float:
-        if self.status == "win":
-            return self.payout_ratio * self.bet_amount
-        else:
-            return 0.0
-
-    @property
-    def remove(self) -> bool:
-        if len(self.points_made) == 6 or self.status == "lose":
+    def should_remove(self, table: "Table") -> bool:
+        if len(self.points_made) == 6 or self.get_status(table) == "lose":
             return True
         return False
 
-    def _update_bet(self) -> None:
+    def update(self, table: "Table") -> None:
         self.new_point_made = False
-        if self.current_point is None and self.table.dice.total in (4, 5, 6, 8, 9, 10):
-            self.current_point = self.table.dice.total
-        elif self.current_point is not None and self.current_point == self.table.dice.total:
-            self.point_made()
+        if self.current_point is None and table.dice.total in (4, 5, 6, 8, 9, 10):
+            self.current_point = table.dice.total
+        elif self.current_point is not None and self.current_point == table.dice.total:
+            self.point_made(table)
 
-    def point_made(self):
-        if self.table.dice.total not in self.points_made:
+    def point_made(self, table: "Table"):
+        if table.dice.total not in self.points_made:
             self.new_point_made = True
-            self.points_made = self.points_made + [self.table.dice.total]
+            self.points_made = self.points_made + [table.dice.total]
         self.current_point = None
 
-    def allowed(self, player) -> bool:
-        return player.table.new_shooter
+    def allowed(self, table: "Table", player) -> bool:
+        return table.new_shooter
 
-    @property
-    def payout_ratio(self):
-        if len(self.points_made) in self.table.settings['fire_points']:
-            return self.table.settings['fire_points'][len(self.points_made)]
+    def get_payout_ratio(self, table: "Table"):
+        if len(self.points_made) in table.settings['fire_points']:
+            return table.settings['fire_points'][len(self.points_made)]
         else:
             raise NotImplementedError
diff --git a/crapssim/player.py b/crapssim/player.py
deleted file mode 100644
index c1b7bd49..00000000
--- a/crapssim/player.py
+++ /dev/null
@@ -1,140 +0,0 @@
-import typing
-
-from crapssim.bet import Bet
-from crapssim.strategy import STRATEGY_TYPE, passline
-
-if typing.TYPE_CHECKING:
-    from crapssim.table import Table
-
-
-class Player:
-    """
-    Player standing at the craps table
-
-    Parameters
-    ----------
-    bankroll : typing.SupportsFloat
-        Starting amount of cash for the player
-    bet_strategy : function(table, player, unit=5)
-        A function that implements a particular betting strategy.  See betting_strategies.py
-    name : string, default = "Player"
-        Name of the player
-    unit : typing.SupportsFloat, default=5
-        Standard amount of bet to be used by bet_strategy
-
-    Attributes
-    ----------
-    bankroll : typing.SupportsFloat
-        Current amount of cash for the player
-    name : str
-        Name of the player
-    bet_strategy :
-        A function that implements a particular betting strategy. See betting_strategies.py.
-    strat_info : dict[str, typing.Any]
-        Variables to be used by the players bet_strategy
-    unit : typing.SupportsFloat
-        Standard amount of bet to be used by bet_strategy
-    bets_on_table : list
-        List of betting objects for the player
-    total_bet_amount : int
-        Sum of bet value for the player
-    """
-
-    def __init__(self, bankroll: typing.SupportsFloat,
-                 bet_strategy: STRATEGY_TYPE = passline,
-                 name: str = "Player",
-                 unit: typing.SupportsFloat = 5,
-                 table: 'Table' = None):
-        self.bankroll: float = bankroll
-        self.bet_strategy: STRATEGY_TYPE = bet_strategy
-        self.strat_info: dict[str, typing.Any] = {}
-        self.name: str = name
-        self.unit: typing.SupportsFloat = unit
-        self.table = None
-        if table is not None:
-            self.sit_at_table(table)
-
-        self.bets_on_table: list[Bet] = []
-        self.total_bet_amount: float = 0.0
-
-    def sit_at_table(self, table: "Table"):
-        table.add_player(self)
-
-    def bet(self, bet_object: Bet) -> None:
-        if not bet_object.allowed(self):
-            return
-
-        if self.bankroll >= bet_object.bet_amount:
-            self.bankroll -= bet_object.bet_amount
-
-            if (bet_object.name, bet_object.subname) in [(b.name, b.subname) for b in self.bets_on_table]:
-                existing_bet: Bet = self.get_bet(bet_object.name, bet_object.subname)
-                existing_bet.bet_amount += bet_object.bet_amount
-            else:
-                bet_object.player = self
-                bet_object.table = self.table
-                self.bets_on_table.append(bet_object)
-
-            self.total_bet_amount += bet_object.bet_amount
-
-    def remove(self, bet_object: Bet) -> None:
-        if bet_object in self.bets_on_table and bet_object.removable:
-            self.bankroll += bet_object.bet_amount
-            self.bets_on_table.remove(bet_object)
-            self.total_bet_amount -= bet_object.bet_amount
-
-    def has_bet(self, *bets_to_check: str) -> bool:
-        """ returns True if bets_to_check and self.bets_on_table has at least one thing in common """
-        bet_names = {b.name for b in self.bets_on_table}
-        return bool(bet_names.intersection(bets_to_check))
-
-    def get_bet(self, bet_name: str, bet_subname: str = "") -> Bet:
-        """returns first betting object matching bet_name and bet_subname.
-        If bet_subname="Any", returns first betting object matching bet_name"""
-        if bet_subname == "Any":
-            bet_name_list: list[str] = [b.name for b in self.bets_on_table]
-            ind: int = bet_name_list.index(bet_name)
-        else:
-            bet_name_subname_list: list[list[str]] = [[b.name, b.subname] for b in self.bets_on_table]
-            ind = bet_name_subname_list.index([bet_name, bet_subname])
-        return self.bets_on_table[ind]
-
-    def num_bet(self, *bets_to_check: str) -> int:
-        """ returns the total number of bets in self.bets_on_table that match bets_to_check """
-        bet_names = [b.name for b in self.bets_on_table]
-        return sum([i in bets_to_check for i in bet_names])
-
-    def remove_if_present(self, bet_name: str, bet_subname: str = "") -> None:
-        if self.has_bet(bet_name):
-            self.remove(self.get_bet(bet_name, bet_subname))
-
-    def add_strategy_bets(self) -> None:
-        """ Implement the given betting strategy """
-        if self.bet_strategy:
-            self.bet_strategy(self, self.table, **self.strat_info)
-
-    def update_bet(self, verbose: bool = False) -> \
-            dict[str, dict[str, str | None | float]]:
-        info = {}
-        for b in self.bets_on_table[:]:
-            b._update_bet()
-            status, win_amount, remove = b.status, b.win_amount, b.remove
-
-            if status == "win":
-                self.bankroll += win_amount + b.bet_amount
-                self.total_bet_amount -= b.bet_amount
-                if verbose:
-                    print(f"{self.name} won ${win_amount} on {b.name} bet!")
-            elif status == "lose":
-                self.total_bet_amount -= b.bet_amount
-                if verbose:
-                    print(f"{self.name} lost ${b.bet_amount} on {b.name} bet.")
-            elif status is None and remove is True:
-                self.bankroll += b.bet_amount
-                self.total_bet_amount -= b.bet_amount
-
-            if remove:
-                self.bets_on_table.remove(b)
-
-            info[b.name] = {"status": status, "win_amount": win_amount}
-        return info
diff --git a/crapssim/strategy.py b/crapssim/strategy.py
index 5d6cf8b9..d23fcec8 100644
--- a/crapssim/strategy.py
+++ b/crapssim/strategy.py
@@ -6,12 +6,11 @@
 from crapssim.bet import Place, Place4, Place5, Place6, Place8, Place9, Place10
 
 if typing.TYPE_CHECKING:
-    from crapssim.player import Player
-    from crapssim.table import Table
+    from crapssim import Player
 
 """
-Various betting strategies that are based on conditions of the CrapsTable.
-Each strategy must take a table and a player_object, and implicitly 
+Various betting strategies that are based on conditions of the Crapsplayer.table.
+Each strategy must take a player.table and a player_object, and implicitly 
 uses the methods from the player object.
 """
 
@@ -19,19 +18,17 @@
 Fundamental Strategies
 """
 
-STRATEGY_TYPE = typing.Union[typing.Callable[['Player', 'Table', int, dict[str, int] | None], dict[str, int] | None],
-                             typing.Callable[['Player', 'Table', int, None], None]]
+STRATEGY_TYPE = typing.Union[typing.Callable[['Player', 'player.table', int, dict[str, int] | None], dict[str, int] | None],
+                             typing.Callable[['Player', 'player.table', int, None], None]]
 
 
-def passline(player: 'Player', table: 'Table'):
+def passline(player: 'Player'):
     """ If the point is off place a bet on the Pass Line.
 
     Parameters
     ----------
     player
         Player object that is using the strategy.
-    table
-        Table object that the strategy is being used on.
 
     Returns
     -------
@@ -39,19 +36,17 @@ def passline(player: 'Player', table: 'Table'):
         Dictionary of strategy info.
     """
     # Pass line bet
-    if table.point == "Off" and not player.has_bet("PassLine"):
-        player.bet(PassLine(player.unit))
+    if player.table.point == "Off" and not player.has_bets(PassLine):
+        player.add_bet(PassLine(player.unit), player.table)
 
 
-def passline_odds(player: 'Player', table: 'Table', mult: int | str = 1) -> None:
+def passline_odds(player: 'Player', mult: int | str = 1) -> None:
     """ If the point is off place a bet on the Pass Line. If the point is on, bet the Pass Line Odds.
 
         Parameters
         ----------
         player
             Player object that is using the strategy.
-        table
-            Table object that the strategy is being used on.
         mult
             Multiplier integers for the odds or "345" for 345x odds.
 
@@ -60,87 +55,81 @@ def passline_odds(player: 'Player', table: 'Table', mult: int | str = 1) -> None
         None
             Dictionary of strategy info.
         """
-    passline(player, table)
+    passline(player)
 
     # Pass line odds
     if mult == "345":
-        if table.point == "On":
-            if table.point.number in [4, 10]:
+        if player.table.point == "On":
+            if player.table.point.number in [4, 10]:
                 mult = 3
-            elif table.point.number in [5, 9]:
+            elif player.table.point.number in [5, 9]:
                 mult = 4
-            elif table.point.number in [6, 8]:
+            elif player.table.point.number in [6, 8]:
                 mult = 5
     else:
         mult = float(mult)
 
     if (
-            table.point == "On"
-            and player.has_bet("PassLine")
-            and not player.has_bet("Odds")
+            player.table.point == "On"
+            and player.has_bets(PassLine)
+            and not player.has_bets(Odds)
     ):
-        player.bet(Odds(float(mult * player.unit), player.get_bet("PassLine")))
+        player.add_odds(player.table, mult * player.unit, [PassLine], point=player.table.point.number)
 
 
-def passline_odds2(player: 'Player', table: 'Table') -> None:
+def passline_odds2(player: 'Player') -> None:
     """ Pass Line bet when point is off, 2x odds bet when point is on.
 
         Parameters
         ----------
         player
             Player object that is using the strategy.
-        table
-            Table object that the strategy is being used on.
 
         Returns
         -------
         None
             Dictionary of strategy info.
         """
-    passline_odds(player, table, mult=2)
+    passline_odds(player, mult=2)
 
 
-def passline_odds345(player: 'Player', table: 'Table') -> None:
+def passline_odds345(player: 'Player') -> None:
     """ Pass Line bet when point is off, 345x odds bet when point is on.
 
         Parameters
         ----------
         player
             Player object that is using the strategy.
-        table
-            Table object that the strategy is being used on.
 
         Returns
         -------
         None
             Dictionary of strategy info.
         """
-    passline_odds(player, table, mult='345')
+    passline_odds(player, mult='345')
 
 
-def pass2come(player: 'Player', table: 'Table') -> None:
+def pass2come(player: 'Player') -> None:
     """ Pass Line bet followed by 2 come bets when point is on.
 
         Parameters
         ----------
         player
             Player object that is using the strategy.
-        table
-            Table object that the strategy is being used on.
 
         Returns
         -------
         None
             Dictionary of strategy info.
         """
-    passline(player, table)
+    passline(player)
 
     # Come bet (2)
-    if table.point == "On" and player.num_bet("Come") < 2:
-        player.bet(Come(player.unit))
+    if player.table.point == "On" and player.count_bets(Come) < 2:
+        player.add_bet(Come(player.unit), player.table)
 
 
-def place(player: 'Player', table: 'Table', skip_point: bool = True, numbers: set[int] | None = None) -> \
+def place(player: 'Player', skip_point: bool = True, numbers: set[int] | None = None) -> \
         None:
     """ Place bets, ie 3, 4, 5, 6, 8, 9, 10
 
@@ -148,10 +137,6 @@ def place(player: 'Player', table: 'Table', skip_point: bool = True, numbers: se
         ----------
         player
             Player object that is using the strategy.
-        table
-            Table object that the strategy is being used on.
-                skip_point: bool
-            If True, don't bet when the point is the given number.
         numbers: set
             Point numbers to place bets on.
 
@@ -167,78 +152,78 @@ def place(player: 'Player', table: 'Table', skip_point: bool = True, numbers: se
     numbers = set(numbers).intersection({4, 5, 6, 8, 9, 10})
 
     if skip_point:
-        numbers -= {table.point.number}
+        numbers -= {player.table.point.number}
 
     # Place the provided numbers when point is ON
-    if table.point == "On":
-        if not player.has_bet("Place4") and 4 in numbers:
-            player.bet(Place4(player.unit))
-        if not player.has_bet("Place5") and 5 in numbers:
-            player.bet(Place5(player.unit))
-        if not player.has_bet("Place6") and 6 in numbers:
-            player.bet(Place6(6 / 5 * player.unit))
-        if not player.has_bet("Place8") and 8 in numbers:
-            player.bet(Place8(6 / 5 * player.unit))
-        if not player.has_bet("Place9") and 9 in numbers:
-            player.bet(Place9(player.unit))
-        if not player.has_bet("Place10") and 10 in numbers:
-            player.bet(Place10(player.unit))
+    if player.table.point == "On":
+        if not player.has_bets(Place4) and 4 in numbers:
+            player.add_bet(Place4(player.unit), player.table)
+        if not player.has_bets(Place5) and 5 in numbers:
+            player.add_bet(Place5(player.unit), player.table)
+        if not player.has_bets(Place6) and 6 in numbers:
+            player.add_bet(Place6(6 / 5 * player.unit), player.table)
+        if not player.has_bets(Place8) and 8 in numbers:
+            player.add_bet(Place8(6 / 5 * player.unit), player.table)
+        if not player.has_bets(Place9) and 9 in numbers:
+            player.add_bet(Place9(player.unit), player.table)
+        if not player.has_bets(Place10) and 10 in numbers:
+            player.add_bet(Place10(player.unit), player.table)
 
     # Move the bets off the point number if it shows up later
-    if skip_point and table.point == "On":
-        if player.has_bet("Place4") and table.point.number == 4:
-            player.remove(player.get_bet("Place4"))
-        if player.has_bet("Place5") and table.point.number == 5:
-            player.remove(player.get_bet("Place5"))
-        if player.has_bet("Place6") and table.point.number == 6:
-            player.remove(player.get_bet("Place6"))
-        if player.has_bet("Place8") and table.point.number == 8:
-            player.remove(player.get_bet("Place8"))
-        if player.has_bet("Place9") and table.point.number == 9:
-            player.remove(player.get_bet("Place9"))
-        if player.has_bet("Place10") and table.point.number == 10:
-            player.remove(player.get_bet("Place10"))
-
-
-def place68(player: 'Player', table: 'Table') -> None:
+    if skip_point and player.table.point == "On":
+        if player.has_bets(Place4) and player.table.point.number == 4:
+            player.remove_bet(player.get_bet(Place4))
+        if player.has_bets(Place5) and player.table.point.number == 5:
+            player.remove_bet(player.get_bet(Place5))
+        if player.has_bets(Place6) and player.table.point.number == 6:
+            player.remove_bet(player.get_bet(Place6))
+        if player.has_bets(Place8) and player.table.point.number == 8:
+            player.remove_bet(player.get_bet(Place8))
+        if player.has_bets(Place9) and player.table.point.number == 9:
+            player.remove_bet(player.get_bet(Place9))
+        if player.has_bets(Place10) and player.table.point.number == 10:
+            player.remove_bet(player.get_bet(Place10))
+
+
+def place68(player: 'Player') -> None:
     """ Place the 6 and the 8.
 
         Parameters
         ----------
         player
             Player object that is using the strategy.
-        table
-            Table object that the strategy is being used on.
+        player.table
+            player.table object that the strategy is being used on.
 
         Returns
         -------
         None
             Dictionary of strategy info.
         """
-    passline(player, table)
+    passline(player)
     # Place 6 and 8 when point is ON
-    p_has_place_bets = player.has_bet(
-        "Place4", "Place5", "Place6", "Place8", "Place9", "Place10"
+    p_has_place_bets = player.has_bets(
+        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 * player.unit))
-        elif table.point.number == 8:
-            player.bet(Place6(6 / 5 * player.unit))
+    if player.table.point == "On" and not p_has_place_bets:
+        if player.table.point.number == 6:
+            player.add_bet(Place8(6 / 5 * player.unit), player.table)
+        elif player.table.point.number == 8:
+            player.add_bet(Place6(6 / 5 * player.unit), player.table)
         else:
-            player.bet(Place8(6 / 5 * player.unit))
-            player.bet(Place6(6 / 5 * player.unit))
+            player.add_bet(Place8(6 / 5 * player.unit), player.table)
+            player.add_bet(Place6(6 / 5 * player.unit), player.table)
 
 
-def dontpass(player: 'Player', table: 'Table') -> None:
+def dontpass(player: 'Player') -> None:
     """ Place a bet on the Don't Pass line.
 
         Parameters
         ----------
         player
             Player object that is using the strategy.
-        table
-            Table object that the strategy is being used on.
+        player.table
+            player.table object that the strategy is being used on.
 
         Returns
         -------
@@ -246,19 +231,19 @@ def dontpass(player: 'Player', table: 'Table') -> None:
             Dictionary of strategy info.
         """
     # Don't pass bet
-    if table.point == "Off" and not player.has_bet("DontPass"):
-        player.bet(DontPass(player.unit))
+    if player.table.point == "Off" and not player.has_bets(DontPass):
+        player.add_bet(DontPass(player.unit), player.table)
 
 
-def layodds(player: 'Player', table: 'Table', win_mult: int | str = 1) -> None:
+def layodds(player: 'Player', win_mult: int | str = 1) -> None:
     """ Place a lay bet.
 
         Parameters
         ----------
         player
             Player object that is using the strategy.
-        table
-            Table object that the strategy is being used on.
+        player.table
+            player.table object that the strategy is being used on.
         win_mult: str | int
             Either '345' or an integer that will be multiplied against the odds to calculate bet.
 
@@ -268,29 +253,29 @@ def layodds(player: 'Player', table: 'Table', win_mult: int | str = 1) -> None:
             Dictionary of strategy info.
         """
     # Assume that someone tries to win the `win_mult` times the unit on each bet, which corresponds
-    # well to the max_odds on a table.
+    # well to the max_odds on a player.table.
     # For `win_mult` = "345", this assumes max of 3-4-5x odds
-    dontpass(player, table)
+    dontpass(player)
     mult = 1
     # Lay odds for don't pass
     if win_mult == "345":
         mult = 6.0
     else:
         win_mult = float(win_mult)
-        if table.point == "On":
-            if table.point.number in [4, 10]:
+        if player.table.point == "On":
+            if player.table.point.number in [4, 10]:
                 mult = 2 * win_mult
-            elif table.point.number in [5, 9]:
+            elif player.table.point.number in [5, 9]:
                 mult = 3 / 2 * win_mult
-            elif table.point.number in [6, 8]:
+            elif player.table.point.number in [6, 8]:
                 mult = 6 / 5 * win_mult
 
     if (
-            table.point == "On"
-            and player.has_bet("DontPass")
-            and not player.has_bet("LayOdds")
+            player.table.point == "On"
+            and player.has_bets(DontPass)
+            and not player.has_bets(LayOdds)
     ):
-        player.bet(LayOdds(mult * player.unit, player.get_bet("DontPass")))
+        player.add_odds(player.table, mult * player.unit, [DontPass], player.table.point.number)
 
 
 """
@@ -298,7 +283,7 @@ def layodds(player: 'Player', table: 'Table', win_mult: int | str = 1) -> None:
 """
 
 
-def place68_2come(player: 'Player', table: 'Table') -> None:
+def place68_2come(player: 'Player') -> None:
     """ Once point is established, place 6 and 8, with 2 additional come bets.
         The goal is to be on four distinct numbers, moving place bets if necessary
 
@@ -306,8 +291,8 @@ def place68_2come(player: 'Player', table: 'Table') -> None:
         ----------
         player
             Player object that is using the strategy.
-        table
-            Table object that the strategy is being used on.
+        player.table
+            player.table object that the strategy is being used on.
 
         Returns
         -------
@@ -322,46 +307,46 @@ def place68_2come(player: 'Player', table: 'Table') -> None:
         current_numbers += bet.winning_numbers
     current_numbers = list(set(current_numbers))
 
-    if table.point == "On" and len(player.bets_on_table) < 4:
+    if player.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 * player.unit))
+            player.add_bet(Place6(6 / 5 * player.unit), player.table)
         if 8 not in current_numbers:
-            player.bet(Place8(6 / 5 * player.unit))
+            player.add_bet(Place8(6 / 5 * player.unit), player.table)
 
     # add come of passline bets to get on 4 numbers
-    if player.num_bet("Come", "PassLine") < 2 and len(player.bets_on_table) < 4:
-        if table.point == "On":
-            player.bet(Come(player.unit))
-        if table.point == "Off" and (
-                player.has_bet("Place6") or player.has_bet("Place8")
+    if player.count_bets(Come, PassLine) < 2 and len(player.bets_on_table) < 4:
+        if player.table.point == "On":
+            player.add_bet(Come(player.unit), player.table)
+        if player.table.point == "Off" and (
+                player.has_bets(Place6) or player.has_bets(Place8)
         ):
-            player.bet(PassLine(player.unit))
+            player.add_bet(PassLine(player.unit), player.table)
 
     # 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
+    if player.has_bets(PassLine):
+        pass_come_winning_numbers += player.get_bet(PassLine).winning_numbers
+    if player.has_bets(Come):
+        pass_come_winning_numbers += player.get_bet(Come).winning_numbers
 
     if 6 in pass_come_winning_numbers:
-        if player.has_bet("Place6"):
-            player.remove(player.get_bet("Place6"))
+        if player.has_bets(Place6):
+            player.remove_bet(player.get_bet(Place6))
         if 5 not in current_numbers:
-            player.bet(Place5(player.unit))
+            player.add_bet(Place5(player.unit), player.table)
         elif 9 not in current_numbers:
-            player.bet(Place9(player.unit))
+            player.add_bet(Place9(player.unit), player.table)
     elif 8 in pass_come_winning_numbers:
-        if player.has_bet("Place8"):
-            player.remove(player.get_bet("Place8"))
+        if player.has_bets(Place8):
+            player.remove_bet(player.get_bet(Place8))
         if 5 not in current_numbers:
-            player.bet(Place5(player.unit))
+            player.add_bet(Place5(player.unit), player.table)
         elif 9 not in current_numbers:
-            player.bet(Place9(player.unit))
+            player.add_bet(Place9(player.unit), player.table)
 
 
-def ironcross(player: 'Player', table: 'Table', mult: int | str = 1) -> None:
+def ironcross(player: 'Player', mult: int | str = 1) -> None:
     """ Bet the Pass Line, the Pass Line Odds, and Place on 5, 6, and 8. If point is on and no bet on the field, place
         a bet on the field.
 
@@ -369,8 +354,8 @@ def ironcross(player: 'Player', table: 'Table', mult: int | str = 1) -> None:
         ----------
         player
             Player object that is using the strategy.
-        table
-            Table object that the strategy is being used on.
+        player.table
+            player.table object that the strategy is being used on.
         mult
             Either '345' or an integer that will be multiplied against the odds to calculate bet.
 
@@ -379,16 +364,16 @@ def ironcross(player: 'Player', table: 'Table', mult: int | str = 1) -> None:
         None
             Dictionary of strategy info.
         """
-    passline(player, table)
-    passline_odds(player, table, mult)
-    place(player, table, numbers={5, 6, 8})
+    passline(player)
+    passline_odds(player, mult)
+    place(player, numbers={5, 6, 8})
 
-    if table.point == "On":
-        if not player.has_bet("Field"):
-            player.bet(Field(player.unit))
+    if player.table.point == "On":
+        if not player.has_bets(Field):
+            player.add_bet(Field(player.unit), player.table)
 
 
-def hammerlock(player: 'Player', table: 'Table', mode: str | None = None) -> dict[str, str]:
+def hammerlock(player: 'Player', mode: str | None = None) -> dict[str, str]:
     """ Pass Line Bet, Don't Pass bet with a lay of odds. A phased place bet approach, starting inside and then
         shifting outside eventually taking bet down if two place bets win.
 
@@ -396,8 +381,8 @@ def hammerlock(player: 'Player', table: 'Table', mode: str | None = None) -> dic
         ----------
         player
             Player object that is using the strategy.
-        table
-            Table object that the strategy is being used on.
+        player.table
+            player.table object that the strategy is being used on.
         mode: str
             Current phase of the Hammerlock, either "place68", "place_inside" or "takedown"
 
@@ -406,61 +391,61 @@ def hammerlock(player: 'Player', table: 'Table', mode: str | None = None) -> dic
         None
             Dictionary of strategy info.
         """
-    passline(player, table)
-    layodds(player, table, win_mult="345")
+    passline(player)
+    layodds(player, win_mult="345")
 
     place_nums = set()
     for bet in player.bets_on_table:
         if isinstance(bet, Place):
             place_nums.add(bet.winning_numbers[0])
     place_point_nums = place_nums.copy()
-    if table.point.number:
-        place_point_nums.add(table.point.number)
+    if player.table.point.number:
+        place_point_nums.add(player.table.point.number)
 
     has_place68 = (6 in place_nums) or (8 in place_nums)
     has_place5689 = (
             (5 in place_nums) or (6 in place_nums) or (8 in place_nums) or (9 in place_nums)
     )
     # 3 phases, place68, place_inside, takedown
-    if mode is None or table.point == "Off":
+    if mode is None or player.table.point == "Off":
         mode = 'place68'
-        for bet_nm in ["Place5", "Place6", "Place8", "Place9"]:
+        for bet_nm in [Place5, Place6, Place8, Place9]:
             player.remove_if_present(bet_nm)
 
     if mode == "place68":
-        if table.point == "On" and has_place68 and place_nums != {6, 8}:
+        if player.table.point == "On" and has_place68 and place_nums != {6, 8}:
             # assume that a place 6/8 has won
-            if player.has_bet("Place6"):
-                player.remove(player.get_bet("Place6"))
-            if player.has_bet("Place8"):
-                player.remove(player.get_bet("Place8"))
+            if player.has_bets(Place6):
+                player.remove_bet(player.get_bet(Place6))
+            if player.has_bets(Place8):
+                player.remove_bet(player.get_bet(Place8))
             mode = "place_inside"
-            place(player, table, numbers={5, 6, 8, 9}, skip_point=False)
+            place(player, skip_point=False, numbers={5, 6, 8, 9})
         else:
-            place(player, table, numbers={6, 8}, skip_point=False)
+            place(player, skip_point=False, numbers={6, 8})
     elif mode == "place_inside":
-        if table.point == "On" and has_place5689 and place_nums != {5, 6, 8, 9}:
+        if player.table.point == "On" and has_place5689 and place_nums != {5, 6, 8, 9}:
             # assume that a place 5/6/8/9 has won
-            for bet_nm in ["Place5", "Place6", "Place8", "Place9"]:
+            for bet_nm in [Place5, Place6, Place8, Place9]:
                 player.remove_if_present(bet_nm)
             mode = "takedown"
         else:
-            place(player, table, numbers={5, 6, 8, 9}, skip_point=False)
-    elif mode == "takedown" and table.point == "Off":
+            place(player, skip_point=False, numbers={5, 6, 8, 9})
+    elif mode == "takedown" and player.table.point == "Off":
         mode = None
 
     player.strat_info['mode'] = mode
 
 
-def risk12(player: 'Player', table: 'Table', winnings: typing.SupportsFloat = 0) -> dict[str, int]:
+def risk12(player: 'Player', winnings: typing.SupportsFloat = 0) -> dict[str, int]:
     """ Pass line and field bet before the point is established. Once the point is established place the 6 and 8.
 
         Parameters
         ----------
         player
             Player object that is using the strategy.
-        table
-            Table object that the strategy is being used on.
+        player.table
+            player.table object that the strategy is being used on.
         winnings: float
             Amount that has been won thus far.
 
@@ -469,70 +454,70 @@ def risk12(player: 'Player', table: 'Table', winnings: typing.SupportsFloat = 0)
         None
             Dictionary of strategy info.
         """
-    passline(player, table)
+    passline(player)
 
-    if table.pass_rolls == 0:
+    if player.table.pass_rolls == 0:
         winnings = 0
 
-    if table.point == "Off" and table.last_roll in table.settings["field_payouts"]:
-        if table.settings["field_payouts"][table.last_roll] == 2:
+    if player.table.point == "Off" and player.table.last_roll in player.table.settings["field_payouts"]:
+        if player.table.settings["field_payouts"][player.table.last_roll] == 2:
             # win double from the field, lose pass line, for a net of 1 unit win
             winnings += player.unit
-        elif table.settings["field_payouts"][table.last_roll] == 3:
+        elif player.table.settings["field_payouts"][player.table.last_roll] == 3:
             # win triple from the field, lose pass line, for a net of 2 unit win
             winnings += 2 * player.unit
-        elif table.last_roll == 11:
+        elif player.table.last_roll == 11:
             # win the field and pass line, for a net of 2 units win
             winnings += 2 * player.unit
 
-    if table.point == "Off":
-        player.bet(Field(player.unit))
-        if table.last_roll == 7:
-            for bet_nm in ["Place6", "Place8"]:
+    if player.table.point == "Off":
+        player.add_bet(Field(player.unit), player.table)
+        if player.table.last_roll == 7:
+            for bet_nm in [Place6, Place8]:
                 player.remove_if_present(bet_nm)
-    elif table.point.number in [4, 9, 10]:
-        place(player, table, numbers={6, 8})
-    elif table.point.number in [5, 6, 8]:
+    elif player.table.point.number in [4, 9, 10]:
+        place(player, numbers={6, 8})
+    elif player.table.point.number in [5, 6, 8]:
         # lost field bet, so can't automatically cover the 6/8 bets.  Need to rely on potential early winnings
         if winnings >= 2 * player.unit:
-            place(player, table, numbers={6, 8})
+            place(player, numbers={6, 8})
         elif winnings >= 1 * player.unit:
-            if table.point.number != 6:
-                place(player, table, numbers={6})
+            if player.table.point.number != 6:
+                place(player, numbers={6})
             else:
-                place(player, table, numbers={8})
+                place(player, numbers={8})
 
     player.strat_info['winnings'] = winnings
 
 
-def knockout(player: 'Player', table: 'Table') -> None:
+def knockout(player: 'Player') -> None:
     """ Pass line bet, don't pass bet, 345x odds behind the pass line bet.
 
         Parameters
         ----------
         player
             Player object that is using the strategy.
-        table
-            Table object that the strategy is being used on.
+        player.table
+            player.table object that the strategy is being used on.
 
         Returns
         -------
         None
             Dictionary of strategy info.
         """
-    passline_odds345(player, table)
-    dontpass(player, table)
+    passline_odds345(player)
+    dontpass(player)
 
 
-def dicedoctor(player: 'Player', table: 'Table', progression: int = 0) -> dict[str, int]:
+def dicedoctor(player: 'Player', progression: int = 0) -> dict[str, int]:
     """ Field bet with a progression if you win.
 
         Parameters
         ----------
         player
             Player object that is using the strategy.
-        table
-            Table object that the strategy is being used on.
+        player.table
+            player.table object that the strategy is being used on.
         progression
             How far into the progression list the strategy is
 
@@ -541,7 +526,7 @@ def dicedoctor(player: 'Player', table: 'Table', progression: int = 0) -> dict[s
         None
             Dictionary of strategy info.
         """
-    if table.last_roll not in table.settings['field_payouts']:
+    if player.table.last_roll not in player.table.settings['field_payouts']:
         progression = 0
 
     bet_progression = [10, 20, 15, 30, 25, 50, 35, 70, 50, 100, 75, 150]
@@ -553,64 +538,64 @@ def dicedoctor(player: 'Player', table: 'Table', progression: int = 0) -> dict[s
     else:
         amount = bet_progression[len(bet_progression) - 1] * player.unit / 5
 
-    player.bet(Field(amount))
+    player.add_bet(Field(amount), player.table)
 
     progression += 1
 
     player.strat_info['progression'] = progression
 
 
-# def place68_cpr(player: 'Player', table: 'Table', unit: int = 5, strat_info: dict[]=None) -> dict[str, str]:
+# def place68_cpr(player: 'Player', player.table: 'player.table', unit: int = 5, strat_info: dict[]=None) -> dict[str, str]:
 #     """ place 6 & 8 after point is establish.  Then collect, press, and regress (in that order) on each win """
 #     ## NOTE: NOT WORKING
 #     if strat_info is None:
 #         strat_info = {"mode6": "collect", "mode8": "collect"}
 #
-#     if table.point == "On":
+#     if player.table.point == "On":
 #         # always place 6 and 8 when they aren't place bets already
-#         if not player.has_bet("Place6"):
+#         if not player.has_bets("Place6"):
 #             player.bet(Place6(6 / 5 * unit))
-#         if not player.has_bet("Place8"):
+#         if not player.has_bets("Place8"):
 #             player.bet(Place8(6 / 5 * unit))
 #
-#     if table.bet_update_info is not None:
+#     if player.table.bet_update_info is not None:
 #         # place6
-#         if player.has_bet("Place6"):
+#         if player.has_bets("Place6"):
 #             bet = player.get_bet("Place6")
 #             if (
-#                 table.bet_update_info[player].get(bet.name) is not None
+#                 player.table.bet_update_info[player].get(bet.name) is not None
 #             ):  # bet has not yet been updated; skip
 #                 # print("level3")
-#                 # print(table.bet_update_info[player][bet.name])
-#                 if table.bet_update_info[player][bet.name]["status"] == "win":
+#                 # print(player.table.bet_update_info[player][bet.name])
+#                 if player.table.bet_update_info[player][bet.name]["status"] == "win":
 #                     # print("place6 mode: {}".format(strat_info["mode6"]))
 #                     if strat_info["mode6"] == "press":
-#                         player.remove(bet)
+#                         player.remove_bet(bet)
 #                         player.bet(Place6(2 * bet.bet_amount))
 #                         strat_info["mode6"] = "regress"
 #                     elif strat_info["mode6"] == "regress":
-#                         player.remove(bet)
+#                         player.remove_bet(bet)
 #                         player.bet(Place6(6 / 5 * unit))
 #                         strat_info["mode6"] = "collect"
 #                     elif strat_info["mode6"] == "collect":
 #                         strat_info["mode6"] = "press"
 #                     # print("updated place6 mode: {}".format(strat_info["mode6"]))
 #         # place8
-#         if player.has_bet("Place8"):
+#         if player.has_bets("Place8"):
 #             bet = player.get_bet("Place8")
 #             if (
-#                 table.bet_update_info[player].get(bet.name) is not None
+#                 player.table.bet_update_info[player].get(bet.name) is not None
 #             ):  # bet has not yet been updated; skip
 #                 # print("level3")
-#                 # print(table.bet_update_info[player][bet.name])
-#                 if table.bet_update_info[player][bet.name]["status"] == "win":
+#                 # print(player.table.bet_update_info[player][bet.name])
+#                 if player.table.bet_update_info[player][bet.name]["status"] == "win":
 #                     # print("place8 mode: {}".format(strat_info["mode8"]))
 #                     if strat_info["mode8"] == "press":
-#                         player.remove(bet)
+#                         player.remove_bet(bet)
 #                         player.bet(Place8(2 * bet.bet_amount))
 #                         strat_info["mode8"] = "regress"
 #                     elif strat_info["mode8"] == "regress":
-#                         player.remove(bet)
+#                         player.remove_bet(bet)
 #                         player.bet(Place8(6 / 5 * unit))
 #                         strat_info["mode8"] = "collect"
 #                     elif strat_info["mode8"] == "collect":
@@ -620,22 +605,22 @@ def dicedoctor(player: 'Player', table: 'Table', progression: int = 0) -> dict[s
 #     return strat_info
 
 
-def place68_dontcome2odds(player: 'Player', table: 'Table') -> None:
+def place68_dontcome2odds(player: 'Player') -> None:
     """ Place the 6 and 8, bet don't come with 2x odds.
 
         Parameters
         ----------
         player
             Player object that is using the strategy.
-        table
-            Table object that the strategy is being used on.
+        player.table
+            player.table object that the strategy is being used on.
 
         Returns
         -------
         None
             Dictionary of strategy info.
         """
-    place(player, table, numbers={6, 8}, skip_point=False)
+    place(player, skip_point=False, numbers={6, 8})
 
     current_numbers = []
     for bet in player.bets_on_table:
@@ -643,25 +628,25 @@ def place68_dontcome2odds(player: 'Player', table: 'Table') -> None:
     current_numbers = list(set(current_numbers))
 
     dont_come_losing_numbers = []
-    if player.has_bet("DontCome"):
-        dont_come_losing_numbers += player.get_bet("DontCome", "Any").losing_numbers
+    if player.has_bets(DontCome):
+        dont_come_losing_numbers += player.get_bet(DontCome).losing_numbers
 
     if 6 in dont_come_losing_numbers:
-        if player.has_bet("Place6"):
-            player.remove(player.get_bet("Place6"))
+        if player.has_bets(Place6):
+            player.remove_bet(player.get_bet(Place6))
         if 5 not in current_numbers:
-            player.bet(Place5(player.unit))
+            player.add_bet(Place5(player.unit), player.table)
     elif 8 in dont_come_losing_numbers:
-        if player.has_bet("Place8"):
-            player.remove(player.get_bet("Place8"))
+        if player.has_bets(Place8):
+            player.remove_bet(player.get_bet(Place8))
         if 9 not in current_numbers:
-            player.bet(Place9(player.unit))
+            player.add_bet(Place9(player.unit), player.table)
 
-    if table.point == "On" and player.num_bet("DontCome") < 1:
-        player.bet(DontCome(player.unit))
+    if player.table.point == "On" and player.count_bets(DontCome) < 1:
+        player.add_bet(DontCome(player.unit), player.table)
 
-    if player.has_bet("DontCome"):
-        dc: Bet = player.get_bet("DontCome", "Any")
+    if player.has_bets(DontCome):
+        dc: Bet = player.get_bet(DontCome)
 
         if not isinstance(dc, DontCome):
             raise TypeError
@@ -672,7 +657,7 @@ def place68_dontcome2odds(player: 'Player', table: 'Table') -> None:
             mult = 6.0
         else:
             win_mult = float(win_mult)
-            # print([[b.name, b.subname] for b in player.bets_on_table])
+            # print([[b.name, b.subname] for b in player.bets_on_player.table])
             if not dc.point is None:
                 lose_num = dc.losing_numbers[0]
                 if lose_num in [4, 10]:
@@ -682,25 +667,5 @@ def place68_dontcome2odds(player: 'Player', table: 'Table') -> None:
                 elif lose_num in [6, 8]:
                     mult = 6 / 5 * win_mult
 
-        if not player.has_bet("LayOdds") and not dc.point is None:
-            player.bet(LayOdds(mult * player.unit, dc))
-
-
-if __name__ == "__main__":
-    # Test a betting strategy
-
-    from crapssim.player import Player
-    from crapssim.dice import Dice
-    from crapssim.table import Table
-
-    # table = CrapsTable()
-    # table._add_player(Player(500, place68_2come))
-
-    d = Dice()
-    p = Player(500, place68_2come)
-    t = Table()
-    p.bet(PassLine(5))
-    p.bet(Place6(6))
-    print(p.bets_on_table)
-    print(p.bankroll)
-    print(p.total_bet_amount)
+        if not player.has_bets(LayOdds) and not dc.point is None:
+            player.add_odds(player.table, mult * player.unit, [DontCome], dc.point)
diff --git a/crapssim/table.py b/crapssim/table.py
index a4ef10a1..74ef1400 100644
--- a/crapssim/table.py
+++ b/crapssim/table.py
@@ -1,7 +1,8 @@
 import typing
 
 from crapssim.dice import Dice
-from crapssim.player import Player
+from .bet import Bet, AllowsOdds
+from .strategy import STRATEGY_TYPE, passline
 
 
 class Table(object):
@@ -39,7 +40,6 @@ def __init__(self) -> None:
         self.players: list[Player] = []
         self.point: Point = Point()
         self.dice: Dice = Dice()
-        self.bet_update_info: dict | None = None
         self.settings: dict[str, typing.Any] = {'field_payouts': {2: 2, 3: 1, 4: 1, 9: 1, 10: 1, 11: 1, 12: 2},
                                                 'fire_points': {4: 24, 5: 249, 6: 999},
                                                 'max_odds': {4: 3, 5: 4, 6: 5, 8: 5, 9: 4, 10: 3},
@@ -81,17 +81,28 @@ def set_payouts(self, name: str, value: list[int]) -> None:
         """
         self.settings[name] = value
 
-    def add_player(self, player_object: Player) -> None:
+    def add_player(self, bankroll: typing.SupportsFloat = 100,
+                   strategy: STRATEGY_TYPE = passline,
+                   name: str = None,
+                   unit: typing.SupportsFloat = 5) -> None:
         """ Add player object to the table
 
         Parameters
         ----------
-        player_object : Player
-            Player object to add to the table.
+        bankroll
+            The players bankroll, defaults to 100.
+        strategy
+            The players strategy, defaults to passline.
+        name
+            The players name, if None defaults to "Player x" with x being the current number
+            of players starting with 0 (ex. Player 0, Player 1, Player 2).
+        unit
+            The unit to use for strategies, defaults to 5.
+
         """
-        if player_object not in self.players:
-            self.players.append(player_object)
-        player_object.table = self
+        if name is None:
+            name = f'Player {len(self.players)}'
+        self.players.append(Player(table=self, bankroll=bankroll, bet_strategy=strategy, name=name, unit=unit))
 
     def _setup_run(self, verbose: bool) -> None:
         """
@@ -250,7 +261,7 @@ def ensure_one_player(self) -> None:
         """ Make sure there is at least one player at the table
         """
         if len(self.players) == 0:
-            self.add_player(Player(500.0, name="Player1"))
+            self.add_player()
 
     def add_player_bets(self, verbose: bool = False) -> None:
         """ Implement each player's betting strategy.
@@ -264,22 +275,20 @@ def add_player_bets(self, verbose: bool = False) -> None:
             p.add_strategy_bets()
 
             if verbose:
-                bets = [f"{b.name}{b.subname}: ${b.bet_amount}" for b in p.bets_on_table]
+                bets = [f"{b.name}: ${b.bet_amount}" for b in p.bets_on_table]
                 if verbose:
                     print(f"{p.name}'s current bets: {bets}")
 
     def update_player_bets(self, verbose: bool = False) -> None:
-        """ Check bets for wins/losses, payout wins to their bankroll, remove bets that have resolved
+        """ Check bets for wins/losses, payout wins to their bankroll, remove_bet bets that have resolved
 
         Parameters
         ----------
         verbose : bool
             If True, prints whether the player won, lost, etc and the amount
         """
-        self.bet_update_info = {}
         for p in self.players:
-            info = p.update_bet(verbose)
-            self.bet_update_info[p] = info
+            p.update_bet(self, verbose)
 
     def update_table(self, verbose: bool = False) -> None:
         """ update table attributes based on previous dice roll
@@ -420,52 +429,159 @@ def update(self, dice_object: Dice) -> None:
             self.number = None
 
 
-if __name__ == "__main__":
-    import sys
-
-    # import strategy
-    from crapssim.strategy import dicedoctor
-
-    sim = False
-    printout = True
-
-    n_sim = 100
-    n_roll = 144
-    n_shooter = 2
-    bankroll = 1000
-    strategy = dicedoctor
-    strategy_name = "dicedoctor"  # don't include any "_" in this
-    runout = True
-    runout_str = "-runout" if runout else ""
-
-    if sim:
-        # Run simulation of n_roll rolls (estimated rolls/hour with 5 players) 1000 times
-        outfile_name = f"./output/simulations/{strategy_name}_sim-{n_sim}_roll-{n_roll}_br-{bankroll}{runout_str}.txt"
-        with open(outfile_name, "w") as f_out:
-            f_out.write("total_cash,n_rolls")
-            f_out.write(str("\n"))
-            for i in range(n_sim):
-                table = Table()
-                table.add_player(Player(bankroll, strategy))
-                table.run(n_roll, n_shooter, verbose=False, runout=runout)
-                out = f"{table.total_player_cash},{table.dice.n_rolls}"
-                f_out.write(str(out))
-                f_out.write(str("\n"))
-
-    if printout:
-        # Run one simulation with verbose=True to check strategy
-        outfile_name = f"./output/printout/{strategy_name}_roll-{n_roll}_br-{bankroll}{runout_str}.txt"
-        with open(outfile_name, "w") as f_out:
-            sys.stdout = f_out
-            table = Table()
-            table.add_player(Player(bankroll, strategy))
-            table.run(n_roll, verbose=True)
-            # out = table.total_player_cash
-            # f_out.write(str(out))
-            # f_out.write(str('\n'))
-
-    sys.stdout = sys.__stdout__  # reset stdout
-
-    # table = Table().with_payouts(fielddouble=[2], fieldtriple=[12])
-    # print(table)
-    # print(table.settings)
+class Player:
+    """
+    Player standing at the craps table
+
+    Parameters
+    ----------
+    bankroll : typing.SupportsFloat
+        Starting amount of cash for the player
+    bet_strategy : function(table, player, unit=5)
+        A function that implements a particular betting strategy.  See betting_strategies.py
+    name : string, default = "Player"
+        Name of the player
+    unit : typing.SupportsFloat, default=5
+        Standard amount of bet to be used by bet_strategy
+
+    Attributes
+    ----------
+    bankroll : typing.SupportsFloat
+        Current amount of cash for the player
+    name : str
+        Name of the player
+    bet_strategy :
+        A function that implements a particular betting strategy. See betting_strategies.py.
+    strat_info : dict[str, typing.Any]
+        Variables to be used by the players bet_strategy
+    unit : typing.SupportsFloat
+        Standard amount of bet to be used by bet_strategy
+    bets_on_table : list
+        List of betting objects for the player
+    total_bet_amount : int
+        Sum of bet value for the player
+    """
+
+    def __init__(self, table,
+                 bankroll: typing.SupportsFloat, bet_strategy: STRATEGY_TYPE = passline,
+                 name: str = "Player", unit: typing.SupportsFloat = 5):
+        self.bankroll: float = bankroll
+        self.bet_strategy: STRATEGY_TYPE = bet_strategy
+        self.strat_info: dict[str, typing.Any] = {}
+        self.name: str = name
+        self.unit: typing.SupportsFloat = unit
+        self.bets_on_table: list[Bet] = []
+        self._table: Table = table
+
+    @property
+    def total_bet_amount(self) -> float:
+        return sum(x.bet_amount for x in self.bets_on_table)
+
+    @property
+    def table(self):
+        return self._table
+
+    def add_bet(self, bet: Bet, table: "Table") -> None:
+        if bet.already_placed(self):
+            existing_bet: Bet = self.get_bet(type(bet))
+            existing_bet.bet_amount += bet.bet_amount
+            self.bankroll -= bet.bet_amount
+            if not existing_bet.allowed(table=table, player=self):
+                existing_bet -= bet.bet_amount
+                self.bankroll += bet.bet_amount
+        else:
+            if bet.allowed(table=table, player=self):
+                self.bets_on_table.append(bet)
+                self.bankroll -= bet.bet_amount
+
+    def remove_bet(self, bet: Bet) -> None:
+        if bet in self.bets_on_table and bet.removable:
+            self.bankroll += bet.bet_amount
+            self.bets_on_table.remove(bet)
+
+    def get_bets(self, *bet_types: typing.Type[Bet], **bet_attributes) -> list[Bet]:
+        if len(bet_types) == 0:
+            bet_types = (Bet,)
+        else:
+            bet_types = tuple(bet_types)
+        bets = []
+
+        for bet in self.bets_on_table:
+            if isinstance(bet, bet_types):
+                if all(hasattr(bet, a) and getattr(bet, a) == bet_attributes[a] for a in bet_attributes):
+                    bets.append(bet)
+        return bets
+
+    def has_bets(self, *bet_types: typing.Type[Bet], **bet_attributes) -> bool:
+        """ returns True if bets_to_check and self.bets_on_table
+        has at least one thing in common """
+        return len(self.get_bets(*bet_types, **bet_attributes)) > 0
+
+    def get_bet(self, bet_type: typing.Type[Bet], **bet_attributes) -> Bet:
+        """returns first betting object matching bet and bet_subname.
+        If bet_subname="Any", returns first betting object matching bet"""
+        return self.get_bets(bet_type, **bet_attributes)[0]
+
+    def count_bets(self, *bet_types: typing.Type[Bet], **bet_attributes) -> int:
+        """ returns the total number of bets in self.bets_on_table that match bets_to_check """
+        return len(self.get_bets(*bet_types, **bet_attributes))
+
+    def remove_if_present(self, bet_type: typing.Type[Bet]) -> None:
+        if self.has_bets(bet_type):
+            self.remove_bet(self.get_bet(bet_type))
+
+    def add_strategy_bets(self) -> None:
+        """ Implement the given betting strategy
+
+        """
+        if self.bet_strategy:
+            self.bet_strategy(self, **self.strat_info)
+
+    def update_bet(self, table, verbose: bool = False) -> None:
+        info = {}
+        for bet in self.bets_on_table[:]:
+            bet.update(table)
+
+            self.bankroll += bet.get_return_amount(table)
+
+            if verbose:
+                self.print_bet_update(bet, table)
+
+            if bet.should_remove(table):
+                self.bets_on_table.remove(bet)
+
+            info[bet.name] = {"status": bet.get_status(table),
+                              "win_amount": bet.get_win_amount(table)}
+
+    def print_bet_update(self, bet, table):
+        status = bet.get_status(table)
+        win_amount = bet.get_win_amount(table)
+        if status == "win":
+            print(f"{self.name} won ${win_amount} on {bet.name} bet!")
+        elif status == "lose":
+            print(f"{self.name} lost ${bet.bet_amount} on {bet.name} bet.")
+
+    def add_odds(self,
+                 table: "Table",
+                 bet_amount: float = None,
+                 bet_types: typing.Iterable[AllowsOdds] = AllowsOdds,
+                 point: int = None):
+        if point is None:
+            allows_odds_bets = self.get_bets(*bet_types)
+            if len(allows_odds_bets) == 0:
+                raise ValueError(f'No {", ".join(x.__name__ for x in bet_types)} bets found to put odds on.')
+            if len(allows_odds_bets) > 1:
+                raise ValueError(f'If there is more than one {", ".join(x.__name__ for x in bet_types)} for this'
+                                 f' player you must specify a point.')
+        else:
+            allows_odds_bets = self.get_bets(*bet_types, point=point)
+            if len(allows_odds_bets) == 0:
+                raise ValueError(f'No {", ".join(x.__name__ for x in bet_types)} bets found with point={point}'
+                                 f' to put odds on.')
+
+        allows_odds_bet: AllowsOdds = allows_odds_bets[0]
+        point = allows_odds_bet.point
+
+        if bet_amount is None:
+            bet_amount = table.settings['max_odds'][point] * allows_odds_bet.bet_amount
+        allows_odds_bet.place_odds(bet_amount, self, table)
\ No newline at end of file


Test patch:
diff --git a/Python/craps-test.py b/Python/craps-test.py
deleted file mode 100644
index 21929f75..00000000
--- a/Python/craps-test.py
+++ /dev/null
@@ -1,48 +0,0 @@
-import crapssim as craps
-
-if __name__ == "__main__":
-
-    # # Testing the rolling of dice
-    # d1 = Dice()
-
-    # d1.roll()
-    # d1.roll()
-    # d1.roll()
-
-    # print("Number of rolls: {}".format(d1.n_rolls_))
-    # print("Last Roll: {}".format(d1.result_))
-    # print("Last Roll Total: {}".format(d1.total_))
-
-    # Test making a player
-    # Sean = Player(500)
-    # print("Sean's bankroll: {}".format(Sean.bankroll))
-
-    # Test making a bet
-    # Sean.bet(passline(5))
-    # print("Sean's bankroll: {}".format(Sean.bankroll))
-    # print("Sean's current bets: {}".format(
-    #   {b.name: b.bet_amount for b in Sean.bets_on_table}
-    # ))
-    # print("Sean's total bet amount: {}".format(sum(
-    #  [b.bet_amount for b in Sean.bets_on_table]
-    # )))
-
-    table = craps.Table()
-
-    # place8 bet
-    Sean = craps.player.Player(100, "Sean")
-    d = craps.dice.Dice()
-    # Sean.bet(come(5))
-    # # d.fixed_roll([4,4])
-    # Sean.bet(come(6))
-    Sean.bet(craps.bet.Come(5))
-
-    d.fixed_roll([4, 4])
-    Sean.update_bet()
-    Sean.bet(craps.bet.Come(10))
-    print("Sean's current bets: {}".format(
-        {b.name: b.bet_amount for b in Sean.bets_on_table}
-    ))  # NTS: this will not show duplicate bets, but they still exist
-    print(Sean.bets_on_table)
-
-    print(Sean.get_bet("Come", "8"))
diff --git a/tests/test_bet.py b/tests/test_bet.py
index 9fa154ca..13257aa8 100644
--- a/tests/test_bet.py
+++ b/tests/test_bet.py
@@ -32,16 +32,14 @@
 ])
 def test_ev_oneroll(bet, ev):
     t = Table()
-    p = Player(100)
-    p.sit_at_table(t)
+    t.add_player()
     t.point.status = "On"  # for place bets to pay properly
     outcomes = []
-    bet.player = p
-    bet.table = t
+    t.players[0].add_bet(bet, t)
     for d1 in range(1, 7):
         for d2 in range(1, 7):
             t.dice.fixed_roll([d1, d2])
-            status, win_amt, remove = bet.status, bet.win_amount, bet.remove
+            status, win_amt, remove = bet.get_status(t), bet.get_win_amount(t), bet.should_remove(t)
 
             outcomes.append(win_amt if status == "win" else -1 if status == "lose" else 0)
 
@@ -61,16 +59,15 @@ def test_ev_oneroll(bet, ev):
 ])
 def test_fire(rolls, correct_status, correct_win_amt, correct_remove):
     table = Table()
-    player = Player(100)
+    table.add_player()
     bet = Fire(1)
-    player.sit_at_table(table)
-    player.bet(bet)
+    table.players[0].add_bet(bet, table)
 
     # table.fixed_run(rolls)
     for roll in rolls:
         table.fixed_roll_and_update(roll)
 
-    status, win_amt, remove = bet.status, bet.win_amount, bet.remove
+    status, win_amt, remove = bet.get_status(table), bet.get_win_amount(table), bet.should_remove(table)
 
     assert (status, win_amt, remove) == (correct_status, correct_win_amt, correct_remove)
 
@@ -89,8 +86,7 @@ def test_fire(rolls, correct_status, correct_win_amt, correct_remove):
 ])
 def test_bet_allowed_point(bet, point_number, allowed):
     table = Table()
-    player = Player(100)
-    player.sit_at_table(table)
+    table.add_player()
     dice = Dice()
     dice.total = point_number
 
@@ -99,7 +95,7 @@ def test_bet_allowed_point(bet, point_number, allowed):
 
     table.point = point
 
-    assert bet.allowed(player) == allowed
+    assert bet.allowed(table=table, player=table.players[0]) == allowed
 
 
 @pytest.mark.parametrize('bet, new_shooter, allowed', [
@@ -110,10 +106,9 @@ def test_bet_allowed_point(bet, point_number, allowed):
 ])
 def test_bet_allowed_new_shooter(bet, new_shooter, allowed):
     table = Table()
-    player = Player(100)
-    player.sit_at_table(table)
+    table.add_player()
 
     if new_shooter is False:
         table.fixed_roll((3, 4))
 
-    assert bet.allowed(player) == allowed
+    assert bet.allowed(table=table, player=table.players[0]) == allowed
diff --git a/tests/test_player.py b/tests/test_player.py
index 373591aa..c64c2dfc 100644
--- a/tests/test_player.py
+++ b/tests/test_player.py
@@ -1,40 +1,33 @@
-from crapssim import Table, Dice
+from crapssim import Table, Dice, Player
 from crapssim.bet import PassLine
-from crapssim.player import Player
 from crapssim.strategy import passline
 
 
 def test_default_strategy():
-    player = Player(100)
-    assert player.bet_strategy == passline
+    table = Table()
+    table.add_player()
+    assert table.players[0].bet_strategy == passline
 
 
 def test_irremovable_bet():
-    player = Player(100)
     bet = PassLine(50)
     table = Table()
-    table.add_player(player)
-    player.bet(bet)
-    table = Table()
-    dice = Dice()
-    dice.fixed_roll([2, 2])
-    player.get_bet('PassLine')._update_bet()
-    player.remove_if_present('PassLine')
-    assert len(player.bets_on_table) == 1
+    table.fixed_roll([2, 2])
+    bet.update(table)
+    assert bet.removable is False
 
 
 def test_existing_bet():
-    player = Player(100)
     table = Table()
-    table.add_player(player)
+    table.add_player()
     bet_one = PassLine(50)
-    player.bet(bet_one)
+    table.players[0].add_bet(bet_one, table)
     bet_two = PassLine(50)
-    player.bet(bet_two)
+    table.players[0].add_bet(bet_two, table)
 
-    bet_count = len(player.bets_on_table)
-    bet_amount = player.bets_on_table[0].bet_amount
-    bankroll = player.bankroll
-    total_bet_amount = player.total_bet_amount
+    bet_count = len(table.players[0].bets_on_table)
+    bet_amount = table.players[0].bets_on_table[0].bet_amount
+    bankroll = table.players[0].bankroll
+    total_bet_amount = table.players[0].total_bet_amount
 
     assert (bet_count, bet_amount, bankroll, total_bet_amount) == (1, 100, 0, 100)
diff --git a/tests/test_readme.py b/tests/test_readme.py
index ca6b1a8c..03e52e27 100644
--- a/tests/test_readme.py
+++ b/tests/test_readme.py
@@ -5,9 +5,8 @@
 def test_first_chunk():
     table = craps.Table()
     your_strat = craps.strategy.passline_odds2
-    you = craps.Player(bankroll=200, bet_strategy=your_strat, table=table)
 
-    table.add_player(you)
+    table.add_player(strategy=your_strat)
     table.run(max_rolls=20, verbose=False)
 
 
@@ -22,7 +21,7 @@ def test_second_chunk():
     for i in range(n_sim):
         table = craps.Table()
         for s in strategies:
-            table.add_player(craps.Player(bankroll, strategies[s], s))
+            table.add_player(name=s, strategy=strategies[s])
 
         table.run(max_rolls=float("inf"), max_shooter=10, verbose=False)
         for s in strategies:
diff --git a/tests/test_strategy.py b/tests/test_strategy.py
index 1f80c00f..abab2b09 100644
--- a/tests/test_strategy.py
+++ b/tests/test_strategy.py
@@ -3,6 +3,7 @@
 import pytest
 
 from crapssim import Player, Table
+from crapssim.bet import Come, LayOdds, DontCome
 from crapssim.strategy import passline, passline_odds, passline_odds2, passline_odds345, pass2come, place, place68, \
     dontpass, layodds, place68_2come, ironcross, hammerlock, risk12, knockout, dicedoctor, place68_dontcome2odds
 
@@ -12,16 +13,16 @@
     (passline, {}, [(4, 4)], {('PassLine', '', 5)}),
     (passline_odds, {}, [], {('PassLine', '', 5)}),
     (passline_odds, {}, [(4, 4)], {('PassLine', '', 5),
-                                   ('Odds', '8', 5)}),
+                                   ('Odds8', '', 5)}),
     (passline_odds, {}, [(4, 4), (3, 3)], {('PassLine', '', 5),
-                                           ('Odds', '8', 5)}),
+                                           ('Odds8', '', 5)}),
     (passline_odds, {'mult': '345'}, [], {('PassLine', '', 5)}),
     (passline_odds, {'mult': '345'}, [(6, 4)], {('PassLine', '', 5),
-                                                ('Odds', '10', 15)}),
+                                                ('Odds10', '', 15)}),
     (passline_odds2, {}, [(2, 2)], {('PassLine', '', 5),
-                                    ('Odds', '4', 10)}),
+                                    ('Odds4', '', 10)}),
     (passline_odds345, {}, [(3, 4), (3, 3)], {('PassLine', '', 5),
-                                              ('Odds', '6', 25)}),
+                                              ('Odds6', '', 25)}),
     (pass2come, {}, [], {('PassLine', '', 5)}),
     (pass2come, {}, [(4, 5)], {('PassLine', '', 5),
                                ('Come', '', 5)}),
@@ -42,7 +43,7 @@
     (dontpass, {}, [], {('DontPass', '', 5)}),
     (layodds, {'win_mult': 1}, [], {('DontPass', '', 5)}),
     (layodds, {'win_mult': '345'}, [(3, 3)], {('DontPass', '', 5),
-                                              ('LayOdds', '6', 30)}),
+                                              ('LayOdds6', '', 30)}),
     (place68_2come, {}, [], set()),
     (place68_2come, {}, [(3, 3)], {('Place6', '', 6),
                                    ('Place8', '', 6),
@@ -57,7 +58,7 @@
                                            ('Come', '', 5)}),
     (ironcross, {}, [], {('PassLine', '', 5)}),
     (ironcross, {'mult': '2'}, [(4, 4)], {('PassLine', '', 5),
-                                          ('Odds', '8', 10),
+                                          ('Odds8', '', 10),
                                           ('Place5', '', 5),
                                           ('Place6', '', 6),
                                           ('Field', '', 5)}),
@@ -66,12 +67,12 @@
                                 ('DontPass', '', 5),
                                 ('Place6', '', 6),
                                 ('Place8', '', 6),
-                                ('LayOdds', '6', 30)}),
+                                ('LayOdds6', '', 30)}),
     (hammerlock, {}, [(3, 3), (4, 4)], {('PassLine', '', 5),
                                         ('DontPass', '', 5),
                                         ('Place6', '', 6),
                                         ('Place8', '', 6),
-                                        ('LayOdds', '6', 30),
+                                        ('LayOdds6', '', 30),
                                         ('Place5', '', 5),
                                         ('Place9', '', 5)}),
     (risk12, {}, [], {('PassLine', '', 5), ('Field', '', 5)}),
@@ -85,7 +86,7 @@
                         ('DontPass', '', 5)}),
     (knockout, {}, [(4, 2)], {('PassLine', '', 5),
                               ('DontPass', '', 5),
-                              ('Odds', '6', 25)}),
+                              ('Odds6', '', 25)}),
     (dicedoctor, {}, [], {('Field', '', 10)}),
     (dicedoctor, {}, [(1, 1), (5, 6), (5, 5)], {('Field', '', 30)}),
     (place68_dontcome2odds, {}, [], set()),
@@ -95,22 +96,27 @@
     (place68_dontcome2odds, {}, [(4, 4), (2, 2)], {('Place6', '', 6),
                                                    ('Place8', '', 6),
                                                    ('DontCome', '4', 5),
-                                                   ('LayOdds', '4', 20)})
+                                                   ('LayOdds4', '', 20)})
 ])
 def test_strategies_compare_bets(strategy, strategy_info, rolls: list[tuple[int, int]],
                                  correct_bets: {(str, str, float)}):
-    def strat(player, table, **strat_info):
+    def strat(player, **strat_info):
         if strat_info != {}:
-            return strategy(player, table, **strat_info)
-        return strategy(player, table, **strategy_info)
+            return strategy(player, **strat_info)
+        return strategy(player, **strategy_info)
 
     table = Table()
-    player = Player(100, bet_strategy=strat)
-    table.add_player(player)
-
+    table.add_player(strategy=strat)
     table.fixed_run(rolls)
     table.add_player_bets(verbose=False)
 
     bets = table.players[0].bets_on_table
 
-    assert {(b.name, b.subname, b.bet_amount) for b in bets} == correct_bets
+    check_list = []
+    for bet in bets:
+        if isinstance(bet, (Come, DontCome)) and bet.point is not None:
+            subname = str(bet.point)
+        else:
+            subname = ''
+        check_list.append((bet.name, subname, bet.bet_amount))
+    assert set(check_list) == correct_bets
diff --git a/tests/test_table.py b/tests/test_table.py
index b2454bb8..0cede4a2 100644
--- a/tests/test_table.py
+++ b/tests/test_table.py
@@ -13,24 +13,25 @@ def test_ensure_one_player():
     count_one = len(table.players)
     bankroll = table.players[0].bankroll
     strategy = table.players[0].bet_strategy
-    assert (count_zero, count_one, bankroll, strategy) == (0, 1, 500, passline)
+    assert (count_zero, count_one, bankroll, strategy) == (0, 1, 100, passline)
 
 
 def test_wrong_point_off():
     table = Table()
     table.point.status = 'Off'
-    player = Player(500, table=table)
-    player.bet(Come(100))
-    assert (len(player.bets_on_table), player.bankroll) == (0, 500)
+    table.add_player(bankroll=500)
+    table.players[0].add_bet(Come(100), table)
+    assert (len(table.players[0].bets_on_table),
+            table.players[0].bankroll) == (0, 500)
 
 
 def test_wrong_point_on():
     table = Table()
     table.point.status = 'On'
     table.point.number = 4
-    player = Player(500, table=table)
-    player.bet(PassLine(100))
-    assert (len(player.bets_on_table), player.bankroll) == (0, 500)
+    table.add_player(bankroll=500)
+    assert (len(table.players[0].bets_on_table),
+            table.players[0].bankroll) == (0, 500)
 
 
 @pytest.mark.parametrize(['status', 'number', 'comparison'], [