Full Code of wenkesj/holdem for AI

master b2089d54122c cached
8 files
33.8 KB
9.2k tokens
47 symbols
1 requests
Download .txt
Repository: wenkesj/holdem
Branch: master
Commit: b2089d54122c
Files: 8
Total size: 33.8 KB

Directory structure:
gitextract_e7zk95ig/

├── .gitignore
├── README.md
├── example.py
├── holdem/
│   ├── __init__.py
│   ├── env.py
│   ├── player.py
│   └── utils.py
└── setup.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
/*.egg-info
/dist
/build
/test
/examples
__pycache__
*.DS_Store


================================================
FILE: README.md
================================================
# holdem

:warning: **This is an experimental API, it will most definitely contain bugs, but that's why you are here!**

```sh
pip install holdem
```

Afaik, this is the first [OpenAI Gym](https://github.com/openai/gym) _No-Limit Texas Hold'em_* (NLTH)
environment written in Python. It's an experiment to build a Gym environment that is synchronous and
can support any number of players but also appeal to the general public that wants to learn how to
"solve" NLTH.

*Python 3 supports arbitrary length integers :money_with_wings:

Right now, this is a work in progress, but I believe the API is mature enough for some preliminary
experiments. Join me in making some interesting progress on multi-agent Gym environments.

# Usage

There is limited documentation at the moment. I'll try to make this less painful to understand.

## `env = holdem.TexasHoldemEnv(n_seats, max_limit=1e9, debug=False)`

Creates a gym environment representation a NLTH Table from the parameters:

+ `n_seats` - number of available players for the current table. No players are initially allocated
  to the table. You must call `env.add_player(seat_id, ...)` to populate the table.
+ `max_limit` - max_limit is used to define the `gym.spaces` API for the class. It does not actually
  determine any NLTH limits; in support of `gym.spaces.Discrete`.
+ `debug` - add debug statements to play, will probably be removed in the future.

### `env.add_player(seat_id, stack=2000)`

Adds a player to the table according to the specified seat (`seat_id`) and the initial amount of
chips allocated to the player's `stack`. If the table does not have enough seats according to the
`n_seats` used by the constructor, a `gym.error.Error` will be raised.

### `(player_states, community_states) = env.reset()`

Calling `env.reset` resets the NLTH table to a new hand state. It does not reset any of the players
stacks, or, reset any of the blinds. New behavior is reserved for a special, future portion of the
API that is yet another feature that is not standard in Gym environments and is a work in progress.

The observation returned is a `tuple` of the following by index:

0. `player_states` - a `tuple` where each entry is `tuple(player_info, player_hand)`, this feature
   can be used to gather all states and hands by `(player_infos, player_hands) = zip(*player_states)`.
   + `player_infos` - is a `list` of `int` features describing the individual player. It contains
     the following by index:
     0. `[0, 1]` - `0` - seat is empty, `1` - seat is not empty.
     1. `[0, n_seats - 1]` - player's id, where they are sitting.
     2. `[0, inf]` - player's current stack.
     3. `[0, 1]` - player is playing the current hand.
     4. `[0, inf]` the player's current handrank according to `treys.Evaluator.evaluate(hand, community)`.
     5. `[0, 1]` - `0` - player has not played this round, `1` - player has played this round.
     6. `[0, 1]` - `0` - player is currently not betting, `1` - player is betting.
     7. `[0, 1]` - `0` - player is currently not all-in, `1` - player is all-in.
     8. `[0, inf]` - player's last sidepot.
   + `player_hands` - is a `list` of `int` features describing the cards in the player's pocket.
     The values are encoded based on the `treys.Card` integer representation.
1. `community_states` - a `tuple(community_infos, community_cards)` where:
   + `community_infos` - a `list` by index:
     0. `[0, n_seats - 1]` - location of the dealer button, where big blind is posted.
     1. `[0, inf]` - the current small blind amount.
     2. `[0, inf]` - the current big blind amount.
     3. `[0, inf]` - the current total amount in the community pot.
     4. `[0, inf]` - the last posted raise amount.
     5. `[0, inf]` - minimum required raise amount, if above 0.
     6. `[0, inf]` - the amount required to call.
     7. `[0, n_seats - 1]` - the current player required to take an action.
   + `community_cards` - is a `list` of `int` features describing the cards in the community.
     The values are encoded based on the `treys.Card` integer representation. There are 5 `int` in
     the list, where `-1` represents that there is no card present.

# Example

```python
import gym
import holdem

def play_out_hand(env, n_seats):
  # reset environment, gather relevant observations
  (player_states, (community_infos, community_cards)) = env.reset()
  (player_infos, player_hands) = zip(*player_states)

  # display the table, cards and all
  env.render(mode='human')

  terminal = False
  while not terminal:
    # play safe actions, check when noone else has raised, call when raised.
    actions = holdem.safe_actions(community_infos, n_seats=n_seats)
    (player_states, (community_infos, community_cards)), rews, terminal, info = env.step(actions)
    env.render(mode='human')

env = gym.make('TexasHoldem-v1') # holdem.TexasHoldemEnv(2)

# start with 2 players
env.add_player(0, stack=2000) # add a player to seat 0 with 2000 "chips"
env.add_player(1, stack=2000) # add another player to seat 1 with 2000 "chips"
# play out a hand
play_out_hand(env, env.n_seats)

# add one more player
env.add_player(2, stack=2000) # add another player to seat 1 with 2000 "chips"
# play out another hand
play_out_hand(env, env.n_seats)
```


================================================
FILE: example.py
================================================
# -*- coding: utf-8 -*-
#
# Copyright (c) 2018 Sam Wenke (samwenke@gmail.com)
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import gym
import holdem

def play_out_hand(env, n_seats):
  # reset environment, gather relevant observations
  (player_states, (community_infos, community_cards)) = env.reset()
  (player_infos, player_hands) = zip(*player_states)

  # display the table, cards and all
  env.render(mode='human')

  terminal = False
  while not terminal:
    # play safe actions, check when noone else has raised, call when raised.
    actions = holdem.safe_actions(community_infos, n_seats=n_seats)
    (player_states, (community_infos, community_cards)), rews, terminal, info = env.step(actions)
    env.render(mode='human')

env = gym.make('TexasHoldem-v1') # holdem.TexasHoldemEnv(2)

# start with 2 players
env.add_player(0, stack=2000) # add a player to seat 0 with 2000 "chips"
env.add_player(1, stack=2000) # add another player to seat 1 with 2000 "chips"
# play out a hand
play_out_hand(env, env.n_seats)

# add one more player
env.add_player(2, stack=2000) # add another player to seat 1 with 2000 "chips"
# play out another hand
play_out_hand(env, env.n_seats)


================================================
FILE: holdem/__init__.py
================================================
# -*- coding: utf-8 -*-
from gym.envs.registration import register

from .env import TexasHoldemEnv
from .utils import card_to_str, hand_to_str, safe_actions, action_table

register(
 	id='TexasHoldem-v0',
 	entry_point='holdem.env:TexasHoldemEnv',
  kwargs={'n_seats': 2, 'debug': False},
)

register(
 	id='TexasHoldem-v1',
 	entry_point='holdem.env:TexasHoldemEnv',
  kwargs={'n_seats': 4, 'debug': False},
)

register(
 	id='TexasHoldem-v2',
 	entry_point='holdem.env:TexasHoldemEnv',
  kwargs={'n_seats': 8, 'debug': False},
)


================================================
FILE: holdem/env.py
================================================
# -*- coding: utf-8 -*-
#
# Copyright (c) 2016 Aleksander Beloi (beloi.alex@gmail.com)
# Copyright (c) 2018 Sam Wenke (samwenke@gmail.com)
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from gym import Env, error, spaces, utils
from gym.utils import seeding

from treys import Card, Deck, Evaluator

from .player import Player
from .utils import hand_to_str, format_action


class TexasHoldemEnv(Env, utils.EzPickle):
  BLIND_INCREMENTS = [[10,25], [25,50], [50,100], [75,150], [100,200],
                      [150,300], [200,400], [300,600], [400,800], [500,10000],
                      [600,1200], [800,1600], [1000,2000]]

  def __init__(self, n_seats, max_limit=100000, debug=False):
    n_suits = 4                     # s,h,d,c
    n_ranks = 13                    # 2,3,4,5,6,7,8,9,T,J,Q,K,A
    n_community_cards = 5           # flop, turn, river
    n_pocket_cards = 2
    n_stud = 5

    self.n_seats = n_seats

    self._blind_index = 0
    [self._smallblind, self._bigblind] = TexasHoldemEnv.BLIND_INCREMENTS[0]
    self._deck = Deck()
    self._evaluator = Evaluator()

    self.community = []
    self._round = 0
    self._button = 0
    self._discard = []

    self._side_pots = [0] * n_seats
    self._current_sidepot = 0 # index of _side_pots
    self._totalpot = 0
    self._tocall = 0
    self._lastraise = 0
    self._number_of_hands = 0

    # fill seats with dummy players
    self._seats = [Player(i, stack=0, emptyplayer=True) for i in range(n_seats)]
    self.emptyseats = n_seats
    self._player_dict = {}
    self._current_player = None
    self._debug = debug
    self._last_player = None
    self._last_actions = None

    self.observation_space = spaces.Tuple([
      spaces.Tuple([                # players
        spaces.MultiDiscrete([
          1,                   # emptyplayer
          n_seats - 1,         # seat
          max_limit,           # stack
          1,                   # is_playing_hand
          max_limit,           # handrank
          1,                   # playedthisround
          1,                   # is_betting
          1,                   # isallin
          max_limit,           # last side pot
        ]),
        spaces.Tuple([
          spaces.MultiDiscrete([    # hand
            n_suits,          # suit, can be negative one if it's not avaiable.
            n_ranks,          # rank, can be negative one if it's not avaiable.
          ])
        ] * n_pocket_cards)
      ] * n_seats),
      spaces.Tuple([
        spaces.Discrete(n_seats - 1), # big blind location
        spaces.Discrete(max_limit),   # small blind
        spaces.Discrete(max_limit),   # big blind
        spaces.Discrete(max_limit),   # pot amount
        spaces.Discrete(max_limit),   # last raise
        spaces.Discrete(max_limit),   # minimum amount to raise
        spaces.Discrete(max_limit),   # how much needed to call by current player.
        spaces.Discrete(n_seats - 1), # current player seat location.
        spaces.MultiDiscrete([        # community cards
          n_suits - 1,          # suit
          n_ranks - 1,          # rank
          1,                     # is_flopped
        ]),
      ] * n_stud),
    ])

    self.action_space = spaces.Tuple([
      spaces.MultiDiscrete([
        3,                     # action_id
        max_limit,             # raise_amount
      ]),
    ] * n_seats)

  def seed(self, seed=None):
    _, seed = seeding.np_random(seed)
    return [seed]

  def add_player(self, seat_id, stack=2000):
    """Add a player to the environment seat with the given stack (chipcount)"""
    player_id = seat_id
    if player_id not in self._player_dict:
      new_player = Player(player_id, stack=stack, emptyplayer=False)
      if self._seats[player_id].emptyplayer:
        self._seats[player_id] = new_player
        new_player.set_seat(player_id)
      else:
        raise error.Error('Seat already taken.')
      self._player_dict[player_id] = new_player
      self.emptyseats -= 1

  def remove_player(self, seat_id):
    """Remove a player from the environment seat."""
    player_id = seat_id
    try:
      idx = self._seats.index(self._player_dict[player_id])
      self._seats[idx] = Player(0, stack=0, emptyplayer=True)
      del self._player_dict[player_id]
      self.emptyseats += 1
    except ValueError:
      pass

  def reset(self):
    self._reset_game()
    self._ready_players()
    self._number_of_hands = 1
    [self._smallblind, self._bigblind] = TexasHoldemEnv.BLIND_INCREMENTS[0]
    if (self.emptyseats < len(self._seats) - 1):
      players = [p for p in self._seats if p.playing_hand]
      self._new_round()
      self._round = 0
      self._current_player = self._first_to_act(players)
      self._post_smallblind(self._current_player)
      self._current_player = self._next(players, self._current_player)
      self._post_bigblind(self._current_player)
      self._current_player = self._next(players, self._current_player)
      self._tocall = self._bigblind
      self._round = 0
      self._deal_next_round()
      self._folded_players = []
    return self._get_current_reset_returns()

  def step(self, actions):
    """
    CHECK = 0
    CALL = 1
    RAISE = 2
    FOLD = 3

    RAISE_AMT = [0, minraise]
    """
    if len(actions) != len(self._seats):
      raise error.Error('actions must be same shape as number of seats.')

    if self._current_player is None:
      raise error.Error('Round cannot be played without 2 or more players.')

    if self._round == 4:
      raise error.Error('Rounds already finished, needs to be reset.')

    players = [p for p in self._seats if p.playing_hand]
    if len(players) == 1:
      raise error.Error('Round cannot be played with one player.')

    self._last_player = self._current_player
    self._last_actions = actions

    if not self._current_player.playedthisround and len([p for p in players if not p.isallin]) >= 1:
      if self._current_player.isallin:
        self._current_player = self._next(players, self._current_player)
        return self._get_current_step_returns(False)

      move = self._current_player.player_move(
          self._output_state(self._current_player), actions[self._current_player.player_id])

      if move[0] == 'call':
        self._player_bet(self._current_player, self._tocall)
        if self._debug:
          print('Player', self._current_player.player_id, move)
        self._current_player = self._next(players, self._current_player)
      elif move[0] == 'check':
        self._player_bet(self._current_player, self._current_player.currentbet)
        if self._debug:
          print('Player', self._current_player.player_id, move)
        self._current_player = self._next(players, self._current_player)
      elif move[0] == 'raise':
        self._player_bet(self._current_player, move[1]+self._current_player.currentbet)
        if self._debug:
          print('Player', self._current_player.player_id, move)
        for p in players:
          if p != self._current_player:
            p.playedthisround = False
        self._current_player = self._next(players, self._current_player)
      elif move[0] == 'fold':
        self._current_player.playing_hand = False
        folded_player = self._current_player
        if self._debug:
          print('Player', self._current_player.player_id, move)
        self._current_player = self._next(players, self._current_player)
        players.remove(folded_player)
        self._folded_players.append(folded_player)
        # break if a single player left
        if len(players) == 1:
          self._resolve(players)
    if all([player.playedthisround for player in players]):
      self._resolve(players)

    terminal = False
    if all([player.isallin for player in players]):
      while self._round < 4:
        self._deal_next_round()
        self._round += 1
    if self._round == 4 or len(players) == 1:
      terminal = True
      self._resolve_round(players)
    return self._get_current_step_returns(terminal)

  def render(self, mode='human', close=False):
    print('total pot: {}'.format(self._totalpot))
    if self._last_actions is not None:
      pid = self._last_player.player_id
      print('last action by player {}:'.format(pid))
      print(format_action(self._last_player, self._last_actions[pid]))

    (player_states, community_states) = self._get_current_state()
    (player_infos, player_hands) = zip(*player_states)
    (community_infos, community_cards) = community_states

    print('community:')
    print('-' + hand_to_str(community_cards))
    print('players:')
    for idx, hand in enumerate(player_hands):
      print('{}{}stack: {}'.format(idx, hand_to_str(hand), self._seats[idx].stack))

  def _resolve(self, players):
    self._current_player = self._first_to_act(players)
    self._resolve_sidepots(players + self._folded_players)
    self._new_round()
    self._deal_next_round()
    if self._debug:
      print('totalpot', self._totalpot)

  def _deal_next_round(self):
    if self._round == 0:
      self._deal()
    elif self._round == 1:
      self._flop()
    elif self._round == 2:
      self._turn()
    elif self._round == 3:
      self._river()

  def _increment_blinds(self):
    self._blind_index = min(self._blind_index + 1, len(TexasHoldemEnv.BLIND_INCREMENTS) - 1)
    [self._smallblind, self._bigblind] = TexasHoldemEnv.BLIND_INCREMENTS[self._blind_index]

  def _post_smallblind(self, player):
    if self._debug:
      print('player ', player.player_id, 'small blind', self._smallblind)
    self._player_bet(player, self._smallblind)
    player.playedthisround = False

  def _post_bigblind(self, player):
    if self._debug:
      print('player ', player.player_id, 'big blind', self._bigblind)
    self._player_bet(player, self._bigblind)
    player.playedthisround = False
    self._lastraise = self._bigblind

  def _player_bet(self, player, total_bet):
    # relative_bet is how much _additional_ money is the player betting this turn,
    # on top of what they have already contributed
    # total_bet is the total contribution by player to pot in this round
    relative_bet = min(player.stack, total_bet - player.currentbet)
    player.bet(relative_bet + player.currentbet)

    self._totalpot += relative_bet
    self._tocall = max(self._tocall, total_bet)
    if self._tocall > 0:
      self._tocall = max(self._tocall, self._bigblind)
    self._lastraise = max(self._lastraise, relative_bet  - self._lastraise)

  def _first_to_act(self, players):
    if self._round == 0 and len(players) == 2:
      return self._next(sorted(
          players + [self._seats[self._button]], key=lambda x:x.get_seat()),
          self._seats[self._button])
    try:
      first = [player for player in players if player.get_seat() > self._button][0]
    except IndexError:
      first = players[0]
    return first

  def _next(self, players, current_player):
    idx = players.index(current_player)
    return players[(idx+1) % len(players)]

  def _deal(self):
    for player in self._seats:
      if player.playing_hand:
        player.hand = self._deck.draw(2)

  def _flop(self):
    self._discard.append(self._deck.draw(1)) #burn
    self.community = self._deck.draw(3)

  def _turn(self):
    self._discard.append(self._deck.draw(1)) #burn
    self.community.append(self._deck.draw(1))

  def _river(self):
    self._discard.append(self._deck.draw(1)) #burn
    self.community.append(self._deck.draw(1))

  def _ready_players(self):
    for p in self._seats:
      if not p.emptyplayer and p.sitting_out:
        p.sitting_out = False
        p.playing_hand = True

  def _resolve_sidepots(self, players_playing):
    players = [p for p in players_playing if p.currentbet]
    if self._debug:
      print('current bets: ', [p.currentbet for p in players])
      print('playing hand: ', [p.playing_hand for p in players])
    if not players:
      return
    try:
      smallest_bet = min([p.currentbet for p in players if p.playing_hand])
    except ValueError:
      for p in players:
        self._side_pots[self._current_sidepot] += p.currentbet
        p.currentbet = 0
      return

    smallest_players_allin = [p for p, bet in zip(players, [p.currentbet for p in players]) if bet == smallest_bet and p.isallin]

    for p in players:
      self._side_pots[self._current_sidepot] += min(smallest_bet, p.currentbet)
      p.currentbet -= min(smallest_bet, p.currentbet)
      p.lastsidepot = self._current_sidepot

    if smallest_players_allin:
      self._current_sidepot += 1
      self._resolve_sidepots(players)
    if self._debug:
      print('sidepots: ', self._side_pots)

  def _new_round(self):
    for player in self._player_dict.values():
      player.currentbet = 0
      player.playedthisround = False
    self._round += 1
    self._tocall = 0
    self._lastraise = 0

  def _resolve_round(self, players):
    if len(players) == 1:
      players[0].refund(sum(self._side_pots))
      self._totalpot = 0
    else:
      # compute hand ranks
      for player in players:
        player.handrank = self._evaluator.evaluate(player.hand, self.community)

      # trim side_pots to only include the non-empty side pots
      temp_pots = [pot for pot in self._side_pots if pot > 0]

      # compute who wins each side pot and pay winners
      for pot_idx,_ in enumerate(temp_pots):
        # find players involved in given side_pot, compute the winner(s)
        pot_contributors = [p for p in players if p.lastsidepot >= pot_idx]
        winning_rank = min([p.handrank for p in pot_contributors])
        winning_players = [p for p in pot_contributors if p.handrank == winning_rank]

        for player in winning_players:
          split_amount = int(self._side_pots[pot_idx]/len(winning_players))
          if self._debug:
            print('Player', player.player_id, 'wins side pot (', int(self._side_pots[pot_idx]/len(winning_players)), ')')
          player.refund(split_amount)
          self._side_pots[pot_idx] -= split_amount

        # any remaining chips after splitting go to the winner in the earliest position
        if self._side_pots[pot_idx]:
          earliest = self._first_to_act([player for player in winning_players])
          earliest.refund(self._side_pots[pot_idx])

  def _reset_game(self):
    playing = 0
    for player in self._seats:
      if not player.emptyplayer and not player.sitting_out:
        player.reset_hand()
        playing += 1
    self.community = []
    self._current_sidepot = 0
    self._totalpot = 0
    self._side_pots = [0] * len(self._seats)
    self._deck.shuffle()

    if playing:
      self._button = (self._button + 1) % len(self._seats)
      while not self._seats[self._button].playing_hand:
        self._button = (self._button + 1) % len(self._seats)

  def _output_state(self, current_player):
    return {
      'players': [player.player_state() for player in self._seats],
      'community': self.community,
      'my_seat': current_player.get_seat(),
      'pocket_cards': current_player.hand,
      'pot': self._totalpot,
      'button': self._button,
      'tocall': (self._tocall - current_player.currentbet),
      'stack': current_player.stack,
      'bigblind': self._bigblind,
      'player_id': current_player.player_id,
      'lastraise': self._lastraise,
      'minraise': max(self._bigblind, self._lastraise + self._tocall),
    }

  def _pad(self, l, n, v):
    if (not l) or (l is None):
      l = []
    return l + [v] * (n - len(l))

  def _get_current_state(self):
    player_states = []
    for player in self._seats:
      player_features = [
        int(player.emptyplayer),
        int(player.get_seat()),
        int(player.stack),
        int(player.playing_hand),
        int(player.handrank),
        int(player.playedthisround),
        int(player.betting),
        int(player.isallin),
        int(player.lastsidepot),
      ]
      player_states.append((player_features, self._pad(player.hand, 2, -1)))
    community_states = ([
      int(self._button),
      int(self._smallblind),
      int(self._bigblind),
      int(self._totalpot),
      int(self._lastraise),
      int(max(self._bigblind, self._lastraise + self._tocall)),
      int(self._tocall - self._current_player.currentbet),
      int(self._current_player.player_id),
    ], self._pad(self.community, 5, -1))
    return (tuple(player_states), community_states)

  def _get_current_reset_returns(self):
    return self._get_current_state()

  def _get_current_step_returns(self, terminal):
    obs = self._get_current_state()
    # TODO, make this something else?
    rew = [player.stack for player in self._seats]
    return obs, rew, terminal, [] # TODO, return some info?


================================================
FILE: holdem/player.py
================================================
# -*- coding: utf-8 -*-
#
# Copyright (c) 2016 Aleksander Beloi (beloi.alex@gmail.com)
# Copyright (c) 2018 Sam Wenke (samwenke@gmail.com)
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from random import randint

from gym import error

from treys import Card


class Player(object):

  CHECK = 0
  CALL = 1
  RAISE = 2
  FOLD = 3

  def __init__(self, player_id, stack=2000, emptyplayer=False):
    self.player_id = player_id

    self.hand = []
    self.stack = stack
    self.currentbet = 0
    self.lastsidepot = 0
    self._seat = -1
    self.handrank = -1

    # flags for table management
    self.emptyplayer = emptyplayer
    self.betting = False
    self.isallin = False
    self.playing_hand = False
    self.playedthisround = False
    self.sitting_out = True

  def get_seat(self):
    return self._seat

  def set_seat(self, value):
    self._seat = value

  def reset_hand(self):
    self._hand = []
    self.playedthisround = False
    self.betting = False
    self.isallin = False
    self.currentbet = 0
    self.lastsidepot = 0
    self.playing_hand = (self.stack != 0)

  def bet(self, bet_size):
    self.playedthisround = True
    if not bet_size:
      return
    self.stack -= (bet_size - self.currentbet)
    self.currentbet = bet_size
    if self.stack == 0:
      self.isallin = True

  def refund(self, ammount):
    self.stack += ammount

  def player_state(self):
    return (self.get_seat(), self.stack, self.playing_hand, self.betting, self.player_id)

  def reset_stack(self):
    self.stack = 2000

  def update_localstate(self, table_state):
    self.stack = table_state.get('stack')
    self.hand = table_state.get('pocket_cards')

  # cleanup
  def player_move(self, table_state, action):
    self.update_localstate(table_state)
    bigblind = table_state.get('bigblind')
    tocall = min(table_state.get('tocall', 0), self.stack)
    minraise = table_state.get('minraise', 0)

    [action_idx, raise_amount] = action
    raise_amount = int(raise_amount)
    action_idx = int(action_idx)

    if tocall == 0:
      assert action_idx in [Player.CHECK, Player.RAISE]
      if action_idx == Player.RAISE:
        if raise_amount < minraise:
          raise error.Error('raise must be greater than minraise {}'.format(minraise))
        if raise_amount > self.stack:
          raise error.Error('raise must be less than maxraise {}'.format(self.stack))
        move_tuple = ('raise', raise_amount)
      elif action_idx == Player.CHECK:
        move_tuple = ('check', 0)
      else:
        raise error.Error('invalid action ({}) must be check (0) or raise (2)'.format(action_idx))
    else:
      if action_idx not in [Player.RAISE, Player.CALL, Player.FOLD]:
        raise error.Error('invalid action ({}) must be raise (2), call (1), or fold (3)'.format(action_idx))
      if action_idx == Player.RAISE:
        if raise_amount < minraise:
          raise error.Error('raise must be greater than minraise {}'.format(minraise))
        if raise_amount > self.stack:
          raise error.Error('raise must be less than maxraise {}'.format(self.stack))
        move_tuple = ('raise', raise_amount)
      elif action_idx == Player.CALL:
        move_tuple = ('call', tocall)
      elif action_idx == Player.FOLD:
        move_tuple = ('fold', -1)
      else:
        raise error.Error('invalid action ({}) must be raise (2), call (1), or fold (3)'.format(action_idx))
    return move_tuple


================================================
FILE: holdem/utils.py
================================================
# -*- coding: utf-8 -*-
#
# Copyright (c) 2018 Sam Wenke (samwenke@gmail.com)
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from treys import Card


class action_table:
  CHECK = 0
  CALL = 1
  RAISE = 2
  FOLD = 3
  NA = 0


def format_action(player, action):
  color = False
  try:
    from termcolor import colored
    # for mac, linux: http://pypi.python.org/pypi/termcolor
    # can use for windows: http://pypi.python.org/pypi/colorama
    color = True
  except ImportError:
    pass
  [aid, raise_amt] = action
  if aid == action_table.CHECK:
    text = '_ check'
    if color:
      text = colored(text, 'white')
    return text
  if aid == action_table.CALL:
    text = '- call, current bet: {}'.format(player.currentbet)
    if color:
      text = colored(text, 'yellow')
    return text
  if aid == action_table.RAISE:
    text = '^ raise, current bet: {}'.format(raise_amt)
    if color:
      text = colored(text, 'green')
    return text
  if aid == action_table.FOLD:
    text = 'x fold'
    if color:
      text = colored(text, 'red')
    return text


def card_to_str(card):
  if card == -1:
    return ''
  return Card.int_to_pretty_str(card)


def hand_to_str(hand):
  output = " "
  for i in range(len(hand)):
    c = hand[i]
    if c == -1:
      if i != len(hand) - 1:
        output += '[  ],'
      else:
        output += '[  ] '
      continue
    if i != len(hand) - 1:
      output += str(Card.int_to_pretty_str(c)) + ','
    else:
      output += str(Card.int_to_pretty_str(c)) + ' '
  return output


def safe_actions(community_infos, n_seats):
  current_player = community_infos[-1]
  to_call = community_infos[-2]
  actions = [[action_table.CHECK, action_table.NA]] * n_seats
  if to_call > 0:
    actions[current_player] = [action_table.CALL, action_table.NA]
  return actions


================================================
FILE: setup.py
================================================
# -*- coding: utf-8 -*-
# 
# Copyright (c) 2018 Sam Wenke (samwenke@gmail.com)
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from setuptools import setup, find_packages

with open("README.md") as readme:
  long_description = readme.read()

setup(
  name='holdem',
  version='1.0.0',
  long_description=long_description,
  url='https://github.com/wenkesj/holdem',
  author='Sam Wenke',
  author_email='samwenke@gmail.com',
  license='MIT',
  description=('OpenAI Gym No-Limit Texas Holdem Environment.'),
  packages=find_packages(exclude=['test', 'examples']),
  install_requires=['treys', 'gym'],
  platforms='any',
)
Download .txt
gitextract_e7zk95ig/

├── .gitignore
├── README.md
├── example.py
├── holdem/
│   ├── __init__.py
│   ├── env.py
│   ├── player.py
│   └── utils.py
└── setup.py
Download .txt
SYMBOL INDEX (47 symbols across 4 files)

FILE: example.py
  function play_out_hand (line 25) | def play_out_hand(env, n_seats):

FILE: holdem/env.py
  class TexasHoldemEnv (line 32) | class TexasHoldemEnv(Env, utils.EzPickle):
    method __init__ (line 37) | def __init__(self, n_seats, max_limit=100000, debug=False):
    method seed (line 116) | def seed(self, seed=None):
    method add_player (line 120) | def add_player(self, seat_id, stack=2000):
    method remove_player (line 133) | def remove_player(self, seat_id):
    method reset (line 144) | def reset(self):
    method step (line 164) | def step(self, actions):
    method render (line 239) | def render(self, mode='human', close=False):
    method _resolve (line 256) | def _resolve(self, players):
    method _deal_next_round (line 264) | def _deal_next_round(self):
    method _increment_blinds (line 274) | def _increment_blinds(self):
    method _post_smallblind (line 278) | def _post_smallblind(self, player):
    method _post_bigblind (line 284) | def _post_bigblind(self, player):
    method _player_bet (line 291) | def _player_bet(self, player, total_bet):
    method _first_to_act (line 304) | def _first_to_act(self, players):
    method _next (line 315) | def _next(self, players, current_player):
    method _deal (line 319) | def _deal(self):
    method _flop (line 324) | def _flop(self):
    method _turn (line 328) | def _turn(self):
    method _river (line 332) | def _river(self):
    method _ready_players (line 336) | def _ready_players(self):
    method _resolve_sidepots (line 342) | def _resolve_sidepots(self, players_playing):
    method _new_round (line 370) | def _new_round(self):
    method _resolve_round (line 378) | def _resolve_round(self, players):
    method _reset_game (line 409) | def _reset_game(self):
    method _output_state (line 426) | def _output_state(self, current_player):
    method _pad (line 442) | def _pad(self, l, n, v):
    method _get_current_state (line 447) | def _get_current_state(self):
    method _get_current_reset_returns (line 474) | def _get_current_reset_returns(self):
    method _get_current_step_returns (line 477) | def _get_current_step_returns(self, terminal):

FILE: holdem/player.py
  class Player (line 30) | class Player(object):
    method __init__ (line 37) | def __init__(self, player_id, stack=2000, emptyplayer=False):
    method get_seat (line 55) | def get_seat(self):
    method set_seat (line 58) | def set_seat(self, value):
    method reset_hand (line 61) | def reset_hand(self):
    method bet (line 70) | def bet(self, bet_size):
    method refund (line 79) | def refund(self, ammount):
    method player_state (line 82) | def player_state(self):
    method reset_stack (line 85) | def reset_stack(self):
    method update_localstate (line 88) | def update_localstate(self, table_state):
    method player_move (line 93) | def player_move(self, table_state, action):

FILE: holdem/utils.py
  class action_table (line 25) | class action_table:
  function format_action (line 33) | def format_action(player, action):
  function card_to_str (line 65) | def card_to_str(card):
  function hand_to_str (line 71) | def hand_to_str(hand):
  function safe_actions (line 88) | def safe_actions(community_infos, n_seats):
Condensed preview — 8 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (36K chars).
[
  {
    "path": ".gitignore",
    "chars": 64,
    "preview": "/*.egg-info\n/dist\n/build\n/test\n/examples\n__pycache__\n*.DS_Store\n"
  },
  {
    "path": "README.md",
    "chars": 5248,
    "preview": "# holdem\n\n:warning: **This is an experimental API, it will most definitely contain bugs, but that's why you are here!**\n"
  },
  {
    "path": "example.py",
    "chars": 2191,
    "preview": "# -*- coding: utf-8 -*-\n#\n# Copyright (c) 2018 Sam Wenke (samwenke@gmail.com)\n#\n# Permission is hereby granted, free of "
  },
  {
    "path": "holdem/__init__.py",
    "chars": 532,
    "preview": "# -*- coding: utf-8 -*-\nfrom gym.envs.registration import register\n\nfrom .env import TexasHoldemEnv\nfrom .utils import c"
  },
  {
    "path": "holdem/env.py",
    "chars": 17695,
    "preview": "# -*- coding: utf-8 -*-\n#\n# Copyright (c) 2016 Aleksander Beloi (beloi.alex@gmail.com)\n# Copyright (c) 2018 Sam Wenke (s"
  },
  {
    "path": "holdem/player.py",
    "chars": 4433,
    "preview": "# -*- coding: utf-8 -*-\n#\n# Copyright (c) 2016 Aleksander Beloi (beloi.alex@gmail.com)\n# Copyright (c) 2018 Sam Wenke (s"
  },
  {
    "path": "holdem/utils.py",
    "chars": 2821,
    "preview": "# -*- coding: utf-8 -*-\n#\n# Copyright (c) 2018 Sam Wenke (samwenke@gmail.com)\n#\n# Permission is hereby granted, free of "
  },
  {
    "path": "setup.py",
    "chars": 1629,
    "preview": "# -*- coding: utf-8 -*-\n# \n# Copyright (c) 2018 Sam Wenke (samwenke@gmail.com)\n#\n# Permission is hereby granted, free of"
  }
]

About this extraction

This page contains the full source code of the wenkesj/holdem GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 8 files (33.8 KB), approximately 9.2k tokens, and a symbol index with 47 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!