Getting fine-grained control of the game
You can run the game and get game result like this.
>>> from pypokerengine.api.game import setup_config, start_poker
>>> config = setup_config(max_round=10, initial_stack=100, small_blind_amount=5)
>>> config.register_player(name="fish_player", algorithm=FishPlayer())
>>> config.register_player(name="honest_player", algorithm=HonestPlayer())
>>> game_result = start_poker(config, verbose=1)
But sometimes you want more fine-grained control of the game like stepwise simulation from current state.
PyPokerEngine provides Emulator
class whcih enables you to simulate the game as you want.
In this tutorial, we will create EmulatorPlayer
to understand how to use Emulator
class.
(EmulatorPlayer
would not be practical AI but good example to learn about Emulator
)
How to use Emulator
Register game settings on Emulator
First we need to setup Emulator
object with game settings like number of player.
from pypokerengine.api.emulator import Emulator
emulator = Emulator()
emulator.set_game_rule(nb_player=2, max_round=10, sb_amount=5, ante_amount=0)
Register player's model on Emulator
To simulate the game, we need to create model of player.
model of player defines how player behaves in the game.
Creating model of player is completely same step to create a player.
(Override BasePokerPlayer
class and implement its abstracted methods.)
Here is the very simple model which always declares same action.
class OneActionModel(BasePokerPlayer):
FOLD, CALL, MIN_RAISE, MAX_RAISE = 0, 1, 2, 3
def set_action(self, action):
self.action = action
def declare_action(self, valid_actions, hole_card, round_state):
if self.FOLD == self.action:
return valid_actions[0]['action'], valid_actions[0]['amount']
elif self.CALL == self.action:
return valid_actions[1]['action'], valid_actions[1]['amount']
elif self.MIN_RAISE == self.action:
return valid_actions[2]['action'], valid_actions[2]['amount']['min']
elif self.MAX_RAISE == self.action:
return valid_actions[2]['action'], valid_actions[2]['amount']['max']
else:
raise Exception("Invalid action [ %s ] is set" % self.action)
Next thing to do is register our model with emulator by emulator.register_player
method.
In the simulation, Emulator
calls player_model.declare_action
and apply returned action
to the simulation game.
p1_uuid = # uuid of player1. you would get this information from game_info object
p1_model = # setup model of player1
emulator.register_player(uuid1, p1_model)
Set up game state object
Before start simulation, we need to setup the game_state
object
which represents the start point of the simulation.
You can setup game_state
object from round_state
object like this.
(You receive round_state
object in callback method of BasePokerPlayer
like declare_action
.)
from pypokerengine.utils.game_state_utils import\
restore_game_state, attach_hole_card, attach_hole_card_from_deck
def setup_game_state(round_state, my_hole_card):
game_state = restore_game_state(round_state)
for player_info in round_state['seats']:
if uuid == self.uuid:
# Hole card of my player should be fixed. Because we know it.
game_state = attach_hole_card(game_state, uuid, my_hole_card)
else:
# We don't know hole card of opponents. So attach them at random from deck.
game_state = attach_hole_card_from_deck(game_state, uuid)
round_state
object represents public information of the game.
This means that it does not include information about hole card of each player.
So we need to restore that information on game_state
object.
Start simulation and get updated game state object
Ok, everything is ready. Now you can progress or stop the game as you want by these methods.
emulator.apply_action(game_state, action, bet_amount)
- Use this method if you want to progress the game step-by-step.
emulator.run_until_round_finish(game_state)
- Use this method if you want to progress the game round-by-round.
emulator.run_until_game_finish(game_state)
- Use this method if you want to see the final result of the simulation.
Each method returns updated game state and events objects.
(events object contains information of what happend during simulation like "player1 declared call", "street is updated to FLOP")
>>> emulator.set_game_rule(nb_player, max_round=10, sb_amount=5, ante_amount=0)
>>> next_turn_state, events = emulator.apply_action(current_state, 'call', 10)
>>> round_finish_state, events = emulator.run_until_round_finish(current_state)
>>> game_finish_state, events = emulator.run_until_game_finish(current_state)
>>>
>>> current_state['round_count'], current_state['street'], current_state['next_player']
(1, 0, 0) # street_flg == 0 means PREFLOP
>>> next_turn_state['round_count'], next_turn_state['street'], next_turn_state['next_player']
(1, 0, 1)
>>> round_finish_state['round_count'], round_finish_state['street'], round_finish_state['next_player']
(1, 5, 0) # street_flg == 5 means SHOWDOWN
>>> game_finish_state['round_count'], game_finish_state['street'], game_finish_state['next_player']
(10, 5, 0) # simulation is finished at 10 round because we set max_round=10
For more detail about Emulator
or game_state, events objects,
please checkout Emulator documentation.
Create EmulatorPlayer
To sum up this tutorial, we will create sample AI EmulatorPlayer
which uses Emulator
to make a decision.
To get accurate simulation result, fine-tuned player modeling is necessary.
But in this tutorial, we will match EmulatorPlayer
against only FishPlayer
.
So we use FishPlayer
itself as model of opponent player.
The decision logic of EmulatorPlayer
is like this.
- fix action to evaluate (FOLD, CALL, MIN_RAISE or MAX_RAISE)
- keep declaring fixed action in the simulation until current round finishes
- remember the stack of EmulatorPlayer when simulation finished
- after tried all actions, choose one which leads highest stack after the simulation
The implementation would be like this.
try_actions = [FOLD, CALL, MIN_RAISE, MAX_RAISE]
action_score = [0, 0, 0, 0]
for try_action in try_actions:
# my_model <- setup my model to declare "try_action" anytime
simulation_results = []
for i in range(NB_SIMULATION):
# updated_state <- run the simulation until current round finishes
# result <- fetch stack of EmulatorPlayer in the updated_state
simulation_results.append(result)
# action_score <- average simulation_results
# best_action <- choose action which gets highest action_score
return best_action
We don't explaing implementation detail here.
Please check out complete implementation of EmulatorPlayer
from here.
Let's match the EmulatorPlayer
against our first AI FishPlayer
.
>>> from pypokerengine.api.game import setup_config, start_poker
>>> config = setup_config(max_round=10, initial_stack=100, small_blind_amount=5)
>>> config.register_player(name="fish_player", algorithm=FishPlayer())
>>> config.register_player(name="emulator_player", algorithm=EmulatorPlayer())
>>> game_result = start_poker(config, verbose=1)
Started the round 1
Street "preflop" started. (community card = [])
[debug_info] --> hole_card of emulator player is ['S6', 'H6']
[debug_info] --> average stack after simulation when declares FOLD : 95.0
[debug_info] --> average stack after simulation when declares CALL : 104.09
[debug_info] --> average stack after simulation when declares MIN_RAISE : 117.46
[debug_info] --> average stack after simulation when declares MAX_RAISE : 131.6
"emulator_player" declared "raise:100"
"fish_player" declared "call:100"
Street "flop" started. (community card = ['HQ', 'D2', 'D8'])
Street "turn" started. (community card = ['HQ', 'D2', 'D8', 'S7'])
Street "river" started. (community card = ['HQ', 'D2', 'D8', 'S7', 'SQ'])
"['emulator_player']" won the round 1 (stack = {'emulator_player': 200, 'fish_player': 0})
EmulatorPlayer
choosed ALLIN with hole card ['S6', 'H6'] and beated FishPlayer
!!
(This result may look good. But EmulatorPlayer
makes silly decision in most of the cases.)