Create and Custom Player with Complex Strategy#
The notebook illustrates how you can utilize game information to design a complex strategy.
from maverick import (
Game,
Player,
ActionType,
PlayerLike,
Street,
HandType,
PlayerAction,
PlayerState,
)
from maverick.players import FoldBot, AggressiveBot
from maverick.utils import estimate_holding_strength, find_highest_scoring_hand
import sys
Custom Player#
class ComplexPlayer(Player):
"""A custom player with a complex strategy."""
def decide_action(
self,
*,
game: Game,
valid_actions: list["ActionType"],
min_raise_amount: int,
call_amount: int,
**_
) -> "PlayerAction":
print(f"\n{self.name} is requested to take action.")
# Information related to the game itself
print(" Current street:", game.state.street.name)
print(" Button position:", game.state.button_position)
print(" Current pot:", game.state.pot)
print(" Small blind:", game.state.small_blind)
print(" Big blind:", game.state.big_blind)
print(" Community cards:", game.state.community_cards)
# Information related to the player
print(" Player stack:", self.state.stack)
print(" Current player bet:", self.state.current_bet)
print(" Private cards:", self.state.holding.cards)
# Information related to the actions available for the player
print(" Valid actions:", [v.name for v in valid_actions])
print(" Minimum raise:", min_raise_amount)
# Private and community cards
private_cards = self.state.holding.cards
community_cards = game.state.community_cards
print(" Private cards:", private_cards)
print(" Community cards:", community_cards)
# Estimate the strongest possible hand of the player, considering
# all community cards.
hand_prob = estimate_holding_strength(
private_cards,
community_cards=community_cards,
n_private=game.rules.showdown.hole_cards_required,
n_simulations=1000,
n_players=len(game.state.get_players_in_hand()),
)
(
strongest_hand,
strongest_hand_type,
strongest_hand_score
) = find_highest_scoring_hand(
private_cards,
community_cards,
n_private=game.rules.showdown.hole_cards_required
)
print(" Strongest hand:", strongest_hand)
print(" Strongest hand type:", strongest_hand_type.name)
print(" Strongest hand score:", strongest_hand_score)
print(" Estimated holding strength:", hand_prob)
# This is the players strategy
match game.state.street:
case Street.PRE_FLOP:
if hand_prob < 0.2:
# This is the time to bluff if you want to
print(" Player decides to FOLD.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.FOLD)
elif hand_prob > 0.4 and strongest_hand_type >= HandType.PAIR:
if ActionType.BET in valid_actions:
bet_amount = max(game.state.big_blind * 4, min_raise_amount)
print(f" Player decides to BET {bet_amount}.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.BET, amount=bet_amount)
elif ActionType.RAISE in valid_actions:
raise_amount = max(game.state.big_blind * 4, min_raise_amount)
print(f" Player decides to RAISE {raise_amount}.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.RAISE, amount=raise_amount)
elif ActionType.CALL in valid_actions:
call_amount = max(game.state.current_bet - self.state.current_bet, self.state.stack)
print(f" Player decides to CALL {call_amount}.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.CALL, amount=call_amount)
elif ActionType.CHECK in valid_actions:
print(" Player decides to CHECK.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.CHECK)
else:
print(" Player decides to ALL IN.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.ALL_IN, amount=self.state.stack)
else:
if ActionType.CHECK in valid_actions:
print(" Player decides to CHECK.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.CHECK)
else:
print(" Player decides to FOLD.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.FOLD)
case Street.FLOP:
if strongest_hand_type in [
HandType.ROYAL_FLUSH,
HandType.STRAIGHT_FLUSH,
HandType.FOUR_OF_A_KIND,
HandType.FULL_HOUSE
]:
if ActionType.CHECK in valid_actions:
print(" Player decides to CHECK.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.CHECK)
assert ActionType.CALL in valid_actions
call_amount = max(game.state.current_bet - self.state.current_bet, self.state.stack)
print(f" Player decides to CALL {call_amount}.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.CALL, amount=call_amount)
else:
if hand_prob < 0.3:
print(" Player decides to FOLD.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.FOLD)
elif hand_prob > 0.6:
if ActionType.RAISE in valid_actions:
raise_amount = max(game.state.big_blind * 3, min_raise_amount)
print(f" Player decides to RAISE {raise_amount}.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.RAISE, amount=raise_amount)
elif ActionType.CALL in valid_actions:
call_amount = max(game.state.current_bet - self.state.current_bet, self.state.stack)
print(f" Player decides to CALL {call_amount}.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.CALL, amount=call_amount)
else:
if ActionType.CHECK in valid_actions:
print(" Player decides to CHECK.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.CHECK)
else:
print(" Player decides to FOLD.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.FOLD)
case Street.TURN:
to_call = max(0, game.state.current_bet - self.state.current_bet)
# Very strong made hands: play safely (slowplay), but don't fold.
if strongest_hand_type in [
HandType.ROYAL_FLUSH,
HandType.STRAIGHT_FLUSH,
HandType.FOUR_OF_A_KIND,
HandType.FULL_HOUSE,
]:
if ActionType.CHECK in valid_actions:
print(" Player decides to CHECK.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.CHECK)
if ActionType.CALL in valid_actions:
call_amount = min(to_call, self.state.stack)
print(f" Player decides to CALL {call_amount}.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.CALL, amount=call_amount)
# Weak: avoid putting more money in.
if hand_prob < 0.25:
if ActionType.CHECK in valid_actions:
print(" Player decides to CHECK.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.CHECK)
print(" Player decides to FOLD.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.FOLD)
# Strong: value bet / raise.
if hand_prob > 0.65:
if ActionType.BET in valid_actions:
bet_amount = min(self.state.stack, max(game.state.big_blind * 3, min_raise_amount))
print(f" Player decides to BET {bet_amount}.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.BET, amount=bet_amount)
if ActionType.RAISE in valid_actions:
raise_amount = min(self.state.stack, max(game.state.big_blind * 3, min_raise_amount))
print(f" Player decides to RAISE {raise_amount}.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.RAISE, amount=raise_amount)
if ActionType.CALL in valid_actions:
call_amount = min(to_call, self.state.stack)
print(f" Player decides to CALL {call_amount}.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.CALL, amount=call_amount)
# Medium: prefer checking, otherwise call small commitments.
if ActionType.CHECK in valid_actions:
print(" Player decides to CHECK.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.CHECK)
if ActionType.CALL in valid_actions:
call_amount = min(to_call, self.state.stack)
print(f" Player decides to CALL {call_amount}.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.CALL, amount=call_amount)
print(" Player decides to FOLD.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.FOLD)
case Street.RIVER:
to_call = max(0, game.state.current_bet - self.state.current_bet)
# On the river, be even more conservative with weak hands.
if hand_prob < 0.35:
if ActionType.CHECK in valid_actions:
print(" Player decides to CHECK.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.CHECK)
print(" Player decides to FOLD.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.FOLD)
# Value bet thin only when strong.
if hand_prob > 0.75:
if ActionType.BET in valid_actions:
bet_amount = min(self.state.stack, max(game.state.big_blind * 3, min_raise_amount))
print(f" Player decides to BET {bet_amount}.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.BET, amount=bet_amount)
if ActionType.RAISE in valid_actions:
raise_amount = min(self.state.stack, max(game.state.big_blind * 3, min_raise_amount))
print(f" Player decides to RAISE {raise_amount}.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.RAISE, amount=raise_amount)
if ActionType.CALL in valid_actions:
call_amount = min(to_call, self.state.stack)
print(f" Player decides to CALL {call_amount}.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.CALL, amount=call_amount)
# Medium strength: check if possible, otherwise call.
if ActionType.CHECK in valid_actions:
print(" Player decides to CHECK.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.CHECK)
if ActionType.CALL in valid_actions:
call_amount = min(to_call, self.state.stack)
print(f" Player decides to CALL {call_amount}.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.CALL, amount=call_amount)
print(" Player decides to FOLD.\n")
return PlayerAction(player_id=self.id, action_type=ActionType.FOLD)
# Make sure to always return a valid action. Folding is always available.
print(" Player decides to FOLD.\n")
sys.stdout.flush() # Ensure all prints are output before returning
return PlayerAction(player_id=self.id, action_type=ActionType.FOLD)
# Create game with blinds
game = Game(small_blind=10, big_blind=20, max_hands=1)
# Create and add players with different strategies
players: list[PlayerLike] = [
ComplexPlayer(id="p1", name="ComplexPlayer", state=PlayerState(stack=1000)),
FoldBot(id="p2", name="FoldBot", state=PlayerState(stack=1000)),
AggressiveBot(id="p3", name="AggressiveBot", state=PlayerState(stack=1000)),
]
for player in players:
game.add_player(player)
game.start()
ComplexPlayer is requested to take action.
Current street: PRE_FLOP
Button position: 1
Current pot: 60
Small blind: 10
Big blind: 20
Community cards: []
Player stack: 980
Current player bet: 20
Private cards: [Card(2d), Card(5c)]
Valid actions: ['FOLD', 'CALL', 'RAISE', 'ALL_IN']
Minimum raise: 40
Private cards: [Card(2d), Card(5c)]
Community cards: []
Strongest hand: [Card(2d), Card(5c)]
Strongest hand type: HIGH_CARD
Strongest hand score: 100.0502
Estimated holding strength: 0.376
Player decides to FOLD.
ComplexPlayer is requested to take action.
Current street: PRE_FLOP
Button position: 2
Current pot: 70
Small blind: 10
Big blind: 20
Community cards: []
Player stack: 970
Current player bet: 10
Private cards: [Card(8h), Card(9c)]
Valid actions: ['FOLD', 'CALL', 'RAISE', 'ALL_IN']
Minimum raise: 50
Private cards: [Card(8h), Card(9c)]
Community cards: []
Strongest hand: [Card(8h), Card(9c)]
Strongest hand type: HIGH_CARD
Strongest hand score: 100.0908
Estimated holding strength: 0.359
Player decides to FOLD.