Skip to content

Commit

Permalink
⛰️ Add GamblingPlayer class with Monte-Carlo simulation logic; update…
Browse files Browse the repository at this point in the history
… README and tests for integration
  • Loading branch information
Nikolay authored and Skripkon committed Jan 5, 2025
1 parent 48568d1 commit 6de46cf
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 14 deletions.
1 change: 1 addition & 0 deletions PokerBots/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from .players.BasePlayer import BasePlayer
from .players.CallingPlayer import CallingPlayer
from .players.RandomPlayer import RandomPlayer
from .players.GamblingPlayer import GamblingPlayer
from .game.Game import Game
95 changes: 95 additions & 0 deletions PokerBots/players/GamblingPlayer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import random
import pokerkit
import treys
from PokerBots.players.BasePlayer import BasePlayer

class GamblingPlayer(BasePlayer):
"""
A poker player that uses Monte-Carlo Simulation to determine when to go all-in.
"""

def __init__(self, name: str = "NPC", win_rate_threshold: float = 0.9):
super().__init__(name=name)
self.win_rate_threshold = win_rate_threshold

self.__cards_treys = (
treys.Card.new(card) for card in (
"2c", "2d", "2h", "2s", "3c", "3d", "3h", "3s", "4c", "4d", "4h", "4s",
"5c", "5d", "5h", "5s", "6c", "6d", "6h", "6s", "7c", "7d", "7h", "7s",
"8c", "8d", "8h", "8s", "9c", "9d", "9h", "9s", "Tc", "Td", "Th", "Ts",
"Jc", "Jd", "Jh", "Js", "Qc", "Qd", "Qh", "Qs", "Kc", "Kd", "Kh", "Ks",
"Ac", "Ad", "Ah", "As"
)
)

def play(self, valid_actions: dict[str, float], state: pokerkit.State) -> tuple[str, float]:

hole_cards = state.hole_cards[state.actor_index]
board_cards = [x[0] for x in state.board_cards]

n_active_players = state.player_count

if "complete_bet_or_raise_to" in valid_actions:
win_rate: float = self.__compute_win_rate_using_monte_carlo_simulation(
hole_cards=hole_cards,
board_cards=board_cards,
n_players=n_active_players,
n_simulations=100,
)
if win_rate >= self.win_rate_threshold:
return "complete_bet_or_raise_to", valid_actions["complete_bet_or_raise_to"][1]

if valid_actions.get("check_or_call") == 0:
return "check_or_call", 0

return "fold", 0.0

def __compute_win_rate_using_monte_carlo_simulation(
self, hole_cards: list[pokerkit.Card], board_cards: list[pokerkit.Card], n_players: int, n_simulations: int
) -> float:
"""
Estimate the win rate using Monte Carlo simulation.
Args:
hole_cards: The two cards in the player's hand.
board_cards: The cards currently on the board.
n_players: The number of active players.
n_simulations: The number of simulations to run.
Returns:
The estimated win rate (a float between 0 and 1).
"""
# Prepare the cards
evaluator = treys.Evaluator()
deck = self.__cards_treys.copy()

hole_cards = [treys.Card.new(f"{card.rank}{card.suit}") for card in hole_cards]
board_cards = [treys.Card.new(f"{card.rank}{card.suit}") for card in board_cards]

for card in hole_cards + board_cards:
deck.remove(card)

wins = 0

for _ in range(n_simulations):
sampled_cards = random.sample(deck, 5 - len(board_cards) + 2 * (n_players - 1))

# Create a simulated board
simulated_board = board_cards + sampled_cards[:5 - len(board_cards)]
my_score = evaluator.evaluate(hand=hole_cards, board=simulated_board)

# Simulate other players' hands
best_score = True
for i in range(n_players - 1):
start = 5 - len(board_cards) + i * 2
enemy_hole_cards = sampled_cards[start:start + 2]
enemy_score = evaluator.evaluate(hand=enemy_hole_cards, board=simulated_board)

if my_score > enemy_score:
best_score = False
break

if best_score:
wins += 1

return wins / n_simulations
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ $ pip install PokerBots
## 2. Explore a simple example
```python

from PokerBots import Game, CallingPlayer, RandomPlayer
from PokerBots import Game, CallingPlayer, RandomPlayer, GamblingPlayer

# Define three vanila players
player1 = CallingPlayer(name="Igor")
player1 = GamblingPlayer(name="Igor")
player2 = CallingPlayer(name="Ivan")
player3 = RandomPlayer(name="Maria")

Expand Down Expand Up @@ -92,7 +92,7 @@ state.board_cards
Or, the player's hole cards:

```python
state.hole_cards
state.hole_cards[state.actor_index]
```

### All official bots can be found in ```PokerBots/players/```
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pokerkit==0.5.4
pokerkit==0.5.4
treys==0.1.8
37 changes: 27 additions & 10 deletions tests/test_players.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from PokerBots import Game
from PokerBots import CallingPlayer, RandomPlayer
from PokerBots import CallingPlayer, RandomPlayer, GamblingPlayer


def simulate_game(players, rounds=100, verbose: bool = False):
Expand All @@ -12,14 +12,13 @@ def simulate_game(players, rounds=100, verbose: bool = False):
verbose (bool): If positive, prints logs.
"""
# Set up the game with an initial stack for each player
game = Game(players=players, initial_stack=50_000)
game = Game(players=players, initial_stack=300_000)

# Play up to the specified number of rounds
for _ in range(rounds):
if game.play_round(verbose=verbose):
break


def simulate_multiple_games(players, num_simulations=100, rounds=100, verbose: bool = False):
"""
Simulates multiple poker games with the given players.
Expand All @@ -33,7 +32,6 @@ def simulate_multiple_games(players, num_simulations=100, rounds=100, verbose: b
for _ in range(num_simulations):
simulate_game(players, rounds, verbose=verbose)


def create_calling_players():
"""
Creates a list of CallingPlayer objects with predefined names.
Expand All @@ -42,10 +40,9 @@ def create_calling_players():
list: A list of CallingPlayer objects.
"""
players = [CallingPlayer(), CallingPlayer(), CallingPlayer()]
players[0].name, players[1].name, players[2].name = "Ivan", "Daniel", "Andrew"
players[0].name, players[1].name, players[2].name = "Calling 1", "Calling 2", "Calling 3"
return players


def create_random_players():
"""
Creates a list of RandomPlayer objects with predefined names.
Expand All @@ -54,17 +51,37 @@ def create_random_players():
list: A list of RandomPlayer objects.
"""
players = [RandomPlayer(), RandomPlayer(), RandomPlayer()]
players[0].name, players[1].name, players[2].name = "Ivan", "Daniel", "Andrew"
players[0].name, players[1].name, players[2].name = "Random 1", "Random 2", "Random 3"
return players

def create_gambling_players():
"""
Creates a list of GamblingPlayer objects with predefined names.
Returns:
list: A list of GamblingPlayer objects.
"""
players = [GamblingPlayer(), GamblingPlayer(), GamblingPlayer()]
players[0].name, players[1].name, players[2].name = "Gamble 1", "Gamble 2", "Gamble 3"
return players

# Test with calling players
def test_multiple_game_simulations_with_calling_players(num_simulations=100, rounds=10):
def test_multiple_game_simulations_with_calling_players(num_simulations=10, rounds=20):
simulate_multiple_games(create_calling_players(), num_simulations, rounds, verbose=True)
simulate_multiple_games(create_calling_players(), num_simulations, rounds, verbose=False)


# Test with random players
def test_multiple_game_simulations_with_random_players(num_simulations=100, rounds=10):
def test_multiple_game_simulations_with_random_players(num_simulations=10, rounds=20):
simulate_multiple_games(create_random_players(), num_simulations, rounds, verbose=True)
simulate_multiple_games(create_random_players(), num_simulations, rounds, verbose=False)

# Test with gambling players
def test_multiple_game_simulations_with_gambling_players(num_simulations=10, rounds=100):
simulate_multiple_games(create_gambling_players(), num_simulations, rounds, verbose=True)
simulate_multiple_games(create_gambling_players(), num_simulations, rounds, verbose=False)

# Test with all players
def test_multiple_game_simulations_with_different_players(num_simulations=10, rounds=100):
players = [GamblingPlayer(name="Gambler"), RandomPlayer(name="Random"), CallingPlayer(name="Caller")]
simulate_multiple_games(players, num_simulations, rounds, verbose=False)
simulate_multiple_games(players, num_simulations, rounds, verbose=False)

0 comments on commit 6de46cf

Please sign in to comment.