[
  {
    "path": ".gitignore",
    "content": "/*.egg-info\n/dist\n/build\n/test\n/examples\n__pycache__\n*.DS_Store\n"
  },
  {
    "path": "README.md",
    "content": "# holdem\n\n:warning: **This is an experimental API, it will most definitely contain bugs, but that's why you are here!**\n\n```sh\npip install holdem\n```\n\nAfaik, this is the first [OpenAI Gym](https://github.com/openai/gym) _No-Limit Texas Hold'em_* (NLTH)\nenvironment written in Python. It's an experiment to build a Gym environment that is synchronous and\ncan support any number of players but also appeal to the general public that wants to learn how to\n\"solve\" NLTH.\n\n*Python 3 supports arbitrary length integers :money_with_wings:\n\nRight now, this is a work in progress, but I believe the API is mature enough for some preliminary\nexperiments. Join me in making some interesting progress on multi-agent Gym environments.\n\n# Usage\n\nThere is limited documentation at the moment. I'll try to make this less painful to understand.\n\n## `env = holdem.TexasHoldemEnv(n_seats, max_limit=1e9, debug=False)`\n\nCreates a gym environment representation a NLTH Table from the parameters:\n\n+ `n_seats` - number of available players for the current table. No players are initially allocated\n  to the table. You must call `env.add_player(seat_id, ...)` to populate the table.\n+ `max_limit` - max_limit is used to define the `gym.spaces` API for the class. It does not actually\n  determine any NLTH limits; in support of `gym.spaces.Discrete`.\n+ `debug` - add debug statements to play, will probably be removed in the future.\n\n### `env.add_player(seat_id, stack=2000)`\n\nAdds a player to the table according to the specified seat (`seat_id`) and the initial amount of\nchips allocated to the player's `stack`. If the table does not have enough seats according to the\n`n_seats` used by the constructor, a `gym.error.Error` will be raised.\n\n### `(player_states, community_states) = env.reset()`\n\nCalling `env.reset` resets the NLTH table to a new hand state. It does not reset any of the players\nstacks, or, reset any of the blinds. New behavior is reserved for a special, future portion of the\nAPI that is yet another feature that is not standard in Gym environments and is a work in progress.\n\nThe observation returned is a `tuple` of the following by index:\n\n0. `player_states` - a `tuple` where each entry is `tuple(player_info, player_hand)`, this feature\n   can be used to gather all states and hands by `(player_infos, player_hands) = zip(*player_states)`.\n   + `player_infos` - is a `list` of `int` features describing the individual player. It contains\n     the following by index:\n     0. `[0, 1]` - `0` - seat is empty, `1` - seat is not empty.\n     1. `[0, n_seats - 1]` - player's id, where they are sitting.\n     2. `[0, inf]` - player's current stack.\n     3. `[0, 1]` - player is playing the current hand.\n     4. `[0, inf]` the player's current handrank according to `treys.Evaluator.evaluate(hand, community)`.\n     5. `[0, 1]` - `0` - player has not played this round, `1` - player has played this round.\n     6. `[0, 1]` - `0` - player is currently not betting, `1` - player is betting.\n     7. `[0, 1]` - `0` - player is currently not all-in, `1` - player is all-in.\n     8. `[0, inf]` - player's last sidepot.\n   + `player_hands` - is a `list` of `int` features describing the cards in the player's pocket.\n     The values are encoded based on the `treys.Card` integer representation.\n1. `community_states` - a `tuple(community_infos, community_cards)` where:\n   + `community_infos` - a `list` by index:\n     0. `[0, n_seats - 1]` - location of the dealer button, where big blind is posted.\n     1. `[0, inf]` - the current small blind amount.\n     2. `[0, inf]` - the current big blind amount.\n     3. `[0, inf]` - the current total amount in the community pot.\n     4. `[0, inf]` - the last posted raise amount.\n     5. `[0, inf]` - minimum required raise amount, if above 0.\n     6. `[0, inf]` - the amount required to call.\n     7. `[0, n_seats - 1]` - the current player required to take an action.\n   + `community_cards` - is a `list` of `int` features describing the cards in the community.\n     The values are encoded based on the `treys.Card` integer representation. There are 5 `int` in\n     the list, where `-1` represents that there is no card present.\n\n# Example\n\n```python\nimport gym\nimport holdem\n\ndef play_out_hand(env, n_seats):\n  # reset environment, gather relevant observations\n  (player_states, (community_infos, community_cards)) = env.reset()\n  (player_infos, player_hands) = zip(*player_states)\n\n  # display the table, cards and all\n  env.render(mode='human')\n\n  terminal = False\n  while not terminal:\n    # play safe actions, check when noone else has raised, call when raised.\n    actions = holdem.safe_actions(community_infos, n_seats=n_seats)\n    (player_states, (community_infos, community_cards)), rews, terminal, info = env.step(actions)\n    env.render(mode='human')\n\nenv = gym.make('TexasHoldem-v1') # holdem.TexasHoldemEnv(2)\n\n# start with 2 players\nenv.add_player(0, stack=2000) # add a player to seat 0 with 2000 \"chips\"\nenv.add_player(1, stack=2000) # add another player to seat 1 with 2000 \"chips\"\n# play out a hand\nplay_out_hand(env, env.n_seats)\n\n# add one more player\nenv.add_player(2, stack=2000) # add another player to seat 1 with 2000 \"chips\"\n# play out another hand\nplay_out_hand(env, env.n_seats)\n```\n"
  },
  {
    "path": "example.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright (c) 2018 Sam Wenke (samwenke@gmail.com)\n#\n# Permission is hereby granted, free of charge, to any person obtaining\n# a copy of this software and associated documentation files (the \"Software\"),\n# to deal in the Software without restriction, including without limitation\n# the rights to use, copy, modify, merge, publish, distribute, sublicense,\n# and/or sell copies of the Software, and to permit persons to whom the\n# Software is furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n# THE SOFTWARE.\nimport gym\nimport holdem\n\ndef play_out_hand(env, n_seats):\n  # reset environment, gather relevant observations\n  (player_states, (community_infos, community_cards)) = env.reset()\n  (player_infos, player_hands) = zip(*player_states)\n\n  # display the table, cards and all\n  env.render(mode='human')\n\n  terminal = False\n  while not terminal:\n    # play safe actions, check when noone else has raised, call when raised.\n    actions = holdem.safe_actions(community_infos, n_seats=n_seats)\n    (player_states, (community_infos, community_cards)), rews, terminal, info = env.step(actions)\n    env.render(mode='human')\n\nenv = gym.make('TexasHoldem-v1') # holdem.TexasHoldemEnv(2)\n\n# start with 2 players\nenv.add_player(0, stack=2000) # add a player to seat 0 with 2000 \"chips\"\nenv.add_player(1, stack=2000) # add another player to seat 1 with 2000 \"chips\"\n# play out a hand\nplay_out_hand(env, env.n_seats)\n\n# add one more player\nenv.add_player(2, stack=2000) # add another player to seat 1 with 2000 \"chips\"\n# play out another hand\nplay_out_hand(env, env.n_seats)\n"
  },
  {
    "path": "holdem/__init__.py",
    "content": "# -*- coding: utf-8 -*-\nfrom gym.envs.registration import register\n\nfrom .env import TexasHoldemEnv\nfrom .utils import card_to_str, hand_to_str, safe_actions, action_table\n\nregister(\n \tid='TexasHoldem-v0',\n \tentry_point='holdem.env:TexasHoldemEnv',\n  kwargs={'n_seats': 2, 'debug': False},\n)\n\nregister(\n \tid='TexasHoldem-v1',\n \tentry_point='holdem.env:TexasHoldemEnv',\n  kwargs={'n_seats': 4, 'debug': False},\n)\n\nregister(\n \tid='TexasHoldem-v2',\n \tentry_point='holdem.env:TexasHoldemEnv',\n  kwargs={'n_seats': 8, 'debug': False},\n)\n"
  },
  {
    "path": "holdem/env.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright (c) 2016 Aleksander Beloi (beloi.alex@gmail.com)\n# Copyright (c) 2018 Sam Wenke (samwenke@gmail.com)\n#\n# Permission is hereby granted, free of charge, to any person obtaining\n# a copy of this software and associated documentation files (the \"Software\"),\n# to deal in the Software without restriction, including without limitation\n# the rights to use, copy, modify, merge, publish, distribute, sublicense,\n# and/or sell copies of the Software, and to permit persons to whom the\n# Software is furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n# THE SOFTWARE.\nfrom gym import Env, error, spaces, utils\nfrom gym.utils import seeding\n\nfrom treys import Card, Deck, Evaluator\n\nfrom .player import Player\nfrom .utils import hand_to_str, format_action\n\n\nclass TexasHoldemEnv(Env, utils.EzPickle):\n  BLIND_INCREMENTS = [[10,25], [25,50], [50,100], [75,150], [100,200],\n                      [150,300], [200,400], [300,600], [400,800], [500,10000],\n                      [600,1200], [800,1600], [1000,2000]]\n\n  def __init__(self, n_seats, max_limit=100000, debug=False):\n    n_suits = 4                     # s,h,d,c\n    n_ranks = 13                    # 2,3,4,5,6,7,8,9,T,J,Q,K,A\n    n_community_cards = 5           # flop, turn, river\n    n_pocket_cards = 2\n    n_stud = 5\n\n    self.n_seats = n_seats\n\n    self._blind_index = 0\n    [self._smallblind, self._bigblind] = TexasHoldemEnv.BLIND_INCREMENTS[0]\n    self._deck = Deck()\n    self._evaluator = Evaluator()\n\n    self.community = []\n    self._round = 0\n    self._button = 0\n    self._discard = []\n\n    self._side_pots = [0] * n_seats\n    self._current_sidepot = 0 # index of _side_pots\n    self._totalpot = 0\n    self._tocall = 0\n    self._lastraise = 0\n    self._number_of_hands = 0\n\n    # fill seats with dummy players\n    self._seats = [Player(i, stack=0, emptyplayer=True) for i in range(n_seats)]\n    self.emptyseats = n_seats\n    self._player_dict = {}\n    self._current_player = None\n    self._debug = debug\n    self._last_player = None\n    self._last_actions = None\n\n    self.observation_space = spaces.Tuple([\n      spaces.Tuple([                # players\n        spaces.MultiDiscrete([\n          1,                   # emptyplayer\n          n_seats - 1,         # seat\n          max_limit,           # stack\n          1,                   # is_playing_hand\n          max_limit,           # handrank\n          1,                   # playedthisround\n          1,                   # is_betting\n          1,                   # isallin\n          max_limit,           # last side pot\n        ]),\n        spaces.Tuple([\n          spaces.MultiDiscrete([    # hand\n            n_suits,          # suit, can be negative one if it's not avaiable.\n            n_ranks,          # rank, can be negative one if it's not avaiable.\n          ])\n        ] * n_pocket_cards)\n      ] * n_seats),\n      spaces.Tuple([\n        spaces.Discrete(n_seats - 1), # big blind location\n        spaces.Discrete(max_limit),   # small blind\n        spaces.Discrete(max_limit),   # big blind\n        spaces.Discrete(max_limit),   # pot amount\n        spaces.Discrete(max_limit),   # last raise\n        spaces.Discrete(max_limit),   # minimum amount to raise\n        spaces.Discrete(max_limit),   # how much needed to call by current player.\n        spaces.Discrete(n_seats - 1), # current player seat location.\n        spaces.MultiDiscrete([        # community cards\n          n_suits - 1,          # suit\n          n_ranks - 1,          # rank\n          1,                     # is_flopped\n        ]),\n      ] * n_stud),\n    ])\n\n    self.action_space = spaces.Tuple([\n      spaces.MultiDiscrete([\n        3,                     # action_id\n        max_limit,             # raise_amount\n      ]),\n    ] * n_seats)\n\n  def seed(self, seed=None):\n    _, seed = seeding.np_random(seed)\n    return [seed]\n\n  def add_player(self, seat_id, stack=2000):\n    \"\"\"Add a player to the environment seat with the given stack (chipcount)\"\"\"\n    player_id = seat_id\n    if player_id not in self._player_dict:\n      new_player = Player(player_id, stack=stack, emptyplayer=False)\n      if self._seats[player_id].emptyplayer:\n        self._seats[player_id] = new_player\n        new_player.set_seat(player_id)\n      else:\n        raise error.Error('Seat already taken.')\n      self._player_dict[player_id] = new_player\n      self.emptyseats -= 1\n\n  def remove_player(self, seat_id):\n    \"\"\"Remove a player from the environment seat.\"\"\"\n    player_id = seat_id\n    try:\n      idx = self._seats.index(self._player_dict[player_id])\n      self._seats[idx] = Player(0, stack=0, emptyplayer=True)\n      del self._player_dict[player_id]\n      self.emptyseats += 1\n    except ValueError:\n      pass\n\n  def reset(self):\n    self._reset_game()\n    self._ready_players()\n    self._number_of_hands = 1\n    [self._smallblind, self._bigblind] = TexasHoldemEnv.BLIND_INCREMENTS[0]\n    if (self.emptyseats < len(self._seats) - 1):\n      players = [p for p in self._seats if p.playing_hand]\n      self._new_round()\n      self._round = 0\n      self._current_player = self._first_to_act(players)\n      self._post_smallblind(self._current_player)\n      self._current_player = self._next(players, self._current_player)\n      self._post_bigblind(self._current_player)\n      self._current_player = self._next(players, self._current_player)\n      self._tocall = self._bigblind\n      self._round = 0\n      self._deal_next_round()\n      self._folded_players = []\n    return self._get_current_reset_returns()\n\n  def step(self, actions):\n    \"\"\"\n    CHECK = 0\n    CALL = 1\n    RAISE = 2\n    FOLD = 3\n\n    RAISE_AMT = [0, minraise]\n    \"\"\"\n    if len(actions) != len(self._seats):\n      raise error.Error('actions must be same shape as number of seats.')\n\n    if self._current_player is None:\n      raise error.Error('Round cannot be played without 2 or more players.')\n\n    if self._round == 4:\n      raise error.Error('Rounds already finished, needs to be reset.')\n\n    players = [p for p in self._seats if p.playing_hand]\n    if len(players) == 1:\n      raise error.Error('Round cannot be played with one player.')\n\n    self._last_player = self._current_player\n    self._last_actions = actions\n\n    if not self._current_player.playedthisround and len([p for p in players if not p.isallin]) >= 1:\n      if self._current_player.isallin:\n        self._current_player = self._next(players, self._current_player)\n        return self._get_current_step_returns(False)\n\n      move = self._current_player.player_move(\n          self._output_state(self._current_player), actions[self._current_player.player_id])\n\n      if move[0] == 'call':\n        self._player_bet(self._current_player, self._tocall)\n        if self._debug:\n          print('Player', self._current_player.player_id, move)\n        self._current_player = self._next(players, self._current_player)\n      elif move[0] == 'check':\n        self._player_bet(self._current_player, self._current_player.currentbet)\n        if self._debug:\n          print('Player', self._current_player.player_id, move)\n        self._current_player = self._next(players, self._current_player)\n      elif move[0] == 'raise':\n        self._player_bet(self._current_player, move[1]+self._current_player.currentbet)\n        if self._debug:\n          print('Player', self._current_player.player_id, move)\n        for p in players:\n          if p != self._current_player:\n            p.playedthisround = False\n        self._current_player = self._next(players, self._current_player)\n      elif move[0] == 'fold':\n        self._current_player.playing_hand = False\n        folded_player = self._current_player\n        if self._debug:\n          print('Player', self._current_player.player_id, move)\n        self._current_player = self._next(players, self._current_player)\n        players.remove(folded_player)\n        self._folded_players.append(folded_player)\n        # break if a single player left\n        if len(players) == 1:\n          self._resolve(players)\n    if all([player.playedthisround for player in players]):\n      self._resolve(players)\n\n    terminal = False\n    if all([player.isallin for player in players]):\n      while self._round < 4:\n        self._deal_next_round()\n        self._round += 1\n    if self._round == 4 or len(players) == 1:\n      terminal = True\n      self._resolve_round(players)\n    return self._get_current_step_returns(terminal)\n\n  def render(self, mode='human', close=False):\n    print('total pot: {}'.format(self._totalpot))\n    if self._last_actions is not None:\n      pid = self._last_player.player_id\n      print('last action by player {}:'.format(pid))\n      print(format_action(self._last_player, self._last_actions[pid]))\n\n    (player_states, community_states) = self._get_current_state()\n    (player_infos, player_hands) = zip(*player_states)\n    (community_infos, community_cards) = community_states\n\n    print('community:')\n    print('-' + hand_to_str(community_cards))\n    print('players:')\n    for idx, hand in enumerate(player_hands):\n      print('{}{}stack: {}'.format(idx, hand_to_str(hand), self._seats[idx].stack))\n\n  def _resolve(self, players):\n    self._current_player = self._first_to_act(players)\n    self._resolve_sidepots(players + self._folded_players)\n    self._new_round()\n    self._deal_next_round()\n    if self._debug:\n      print('totalpot', self._totalpot)\n\n  def _deal_next_round(self):\n    if self._round == 0:\n      self._deal()\n    elif self._round == 1:\n      self._flop()\n    elif self._round == 2:\n      self._turn()\n    elif self._round == 3:\n      self._river()\n\n  def _increment_blinds(self):\n    self._blind_index = min(self._blind_index + 1, len(TexasHoldemEnv.BLIND_INCREMENTS) - 1)\n    [self._smallblind, self._bigblind] = TexasHoldemEnv.BLIND_INCREMENTS[self._blind_index]\n\n  def _post_smallblind(self, player):\n    if self._debug:\n      print('player ', player.player_id, 'small blind', self._smallblind)\n    self._player_bet(player, self._smallblind)\n    player.playedthisround = False\n\n  def _post_bigblind(self, player):\n    if self._debug:\n      print('player ', player.player_id, 'big blind', self._bigblind)\n    self._player_bet(player, self._bigblind)\n    player.playedthisround = False\n    self._lastraise = self._bigblind\n\n  def _player_bet(self, player, total_bet):\n    # relative_bet is how much _additional_ money is the player betting this turn,\n    # on top of what they have already contributed\n    # total_bet is the total contribution by player to pot in this round\n    relative_bet = min(player.stack, total_bet - player.currentbet)\n    player.bet(relative_bet + player.currentbet)\n\n    self._totalpot += relative_bet\n    self._tocall = max(self._tocall, total_bet)\n    if self._tocall > 0:\n      self._tocall = max(self._tocall, self._bigblind)\n    self._lastraise = max(self._lastraise, relative_bet  - self._lastraise)\n\n  def _first_to_act(self, players):\n    if self._round == 0 and len(players) == 2:\n      return self._next(sorted(\n          players + [self._seats[self._button]], key=lambda x:x.get_seat()),\n          self._seats[self._button])\n    try:\n      first = [player for player in players if player.get_seat() > self._button][0]\n    except IndexError:\n      first = players[0]\n    return first\n\n  def _next(self, players, current_player):\n    idx = players.index(current_player)\n    return players[(idx+1) % len(players)]\n\n  def _deal(self):\n    for player in self._seats:\n      if player.playing_hand:\n        player.hand = self._deck.draw(2)\n\n  def _flop(self):\n    self._discard.append(self._deck.draw(1)) #burn\n    self.community = self._deck.draw(3)\n\n  def _turn(self):\n    self._discard.append(self._deck.draw(1)) #burn\n    self.community.append(self._deck.draw(1))\n\n  def _river(self):\n    self._discard.append(self._deck.draw(1)) #burn\n    self.community.append(self._deck.draw(1))\n\n  def _ready_players(self):\n    for p in self._seats:\n      if not p.emptyplayer and p.sitting_out:\n        p.sitting_out = False\n        p.playing_hand = True\n\n  def _resolve_sidepots(self, players_playing):\n    players = [p for p in players_playing if p.currentbet]\n    if self._debug:\n      print('current bets: ', [p.currentbet for p in players])\n      print('playing hand: ', [p.playing_hand for p in players])\n    if not players:\n      return\n    try:\n      smallest_bet = min([p.currentbet for p in players if p.playing_hand])\n    except ValueError:\n      for p in players:\n        self._side_pots[self._current_sidepot] += p.currentbet\n        p.currentbet = 0\n      return\n\n    smallest_players_allin = [p for p, bet in zip(players, [p.currentbet for p in players]) if bet == smallest_bet and p.isallin]\n\n    for p in players:\n      self._side_pots[self._current_sidepot] += min(smallest_bet, p.currentbet)\n      p.currentbet -= min(smallest_bet, p.currentbet)\n      p.lastsidepot = self._current_sidepot\n\n    if smallest_players_allin:\n      self._current_sidepot += 1\n      self._resolve_sidepots(players)\n    if self._debug:\n      print('sidepots: ', self._side_pots)\n\n  def _new_round(self):\n    for player in self._player_dict.values():\n      player.currentbet = 0\n      player.playedthisround = False\n    self._round += 1\n    self._tocall = 0\n    self._lastraise = 0\n\n  def _resolve_round(self, players):\n    if len(players) == 1:\n      players[0].refund(sum(self._side_pots))\n      self._totalpot = 0\n    else:\n      # compute hand ranks\n      for player in players:\n        player.handrank = self._evaluator.evaluate(player.hand, self.community)\n\n      # trim side_pots to only include the non-empty side pots\n      temp_pots = [pot for pot in self._side_pots if pot > 0]\n\n      # compute who wins each side pot and pay winners\n      for pot_idx,_ in enumerate(temp_pots):\n        # find players involved in given side_pot, compute the winner(s)\n        pot_contributors = [p for p in players if p.lastsidepot >= pot_idx]\n        winning_rank = min([p.handrank for p in pot_contributors])\n        winning_players = [p for p in pot_contributors if p.handrank == winning_rank]\n\n        for player in winning_players:\n          split_amount = int(self._side_pots[pot_idx]/len(winning_players))\n          if self._debug:\n            print('Player', player.player_id, 'wins side pot (', int(self._side_pots[pot_idx]/len(winning_players)), ')')\n          player.refund(split_amount)\n          self._side_pots[pot_idx] -= split_amount\n\n        # any remaining chips after splitting go to the winner in the earliest position\n        if self._side_pots[pot_idx]:\n          earliest = self._first_to_act([player for player in winning_players])\n          earliest.refund(self._side_pots[pot_idx])\n\n  def _reset_game(self):\n    playing = 0\n    for player in self._seats:\n      if not player.emptyplayer and not player.sitting_out:\n        player.reset_hand()\n        playing += 1\n    self.community = []\n    self._current_sidepot = 0\n    self._totalpot = 0\n    self._side_pots = [0] * len(self._seats)\n    self._deck.shuffle()\n\n    if playing:\n      self._button = (self._button + 1) % len(self._seats)\n      while not self._seats[self._button].playing_hand:\n        self._button = (self._button + 1) % len(self._seats)\n\n  def _output_state(self, current_player):\n    return {\n      'players': [player.player_state() for player in self._seats],\n      'community': self.community,\n      'my_seat': current_player.get_seat(),\n      'pocket_cards': current_player.hand,\n      'pot': self._totalpot,\n      'button': self._button,\n      'tocall': (self._tocall - current_player.currentbet),\n      'stack': current_player.stack,\n      'bigblind': self._bigblind,\n      'player_id': current_player.player_id,\n      'lastraise': self._lastraise,\n      'minraise': max(self._bigblind, self._lastraise + self._tocall),\n    }\n\n  def _pad(self, l, n, v):\n    if (not l) or (l is None):\n      l = []\n    return l + [v] * (n - len(l))\n\n  def _get_current_state(self):\n    player_states = []\n    for player in self._seats:\n      player_features = [\n        int(player.emptyplayer),\n        int(player.get_seat()),\n        int(player.stack),\n        int(player.playing_hand),\n        int(player.handrank),\n        int(player.playedthisround),\n        int(player.betting),\n        int(player.isallin),\n        int(player.lastsidepot),\n      ]\n      player_states.append((player_features, self._pad(player.hand, 2, -1)))\n    community_states = ([\n      int(self._button),\n      int(self._smallblind),\n      int(self._bigblind),\n      int(self._totalpot),\n      int(self._lastraise),\n      int(max(self._bigblind, self._lastraise + self._tocall)),\n      int(self._tocall - self._current_player.currentbet),\n      int(self._current_player.player_id),\n    ], self._pad(self.community, 5, -1))\n    return (tuple(player_states), community_states)\n\n  def _get_current_reset_returns(self):\n    return self._get_current_state()\n\n  def _get_current_step_returns(self, terminal):\n    obs = self._get_current_state()\n    # TODO, make this something else?\n    rew = [player.stack for player in self._seats]\n    return obs, rew, terminal, [] # TODO, return some info?\n"
  },
  {
    "path": "holdem/player.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright (c) 2016 Aleksander Beloi (beloi.alex@gmail.com)\n# Copyright (c) 2018 Sam Wenke (samwenke@gmail.com)\n#\n# Permission is hereby granted, free of charge, to any person obtaining\n# a copy of this software and associated documentation files (the \"Software\"),\n# to deal in the Software without restriction, including without limitation\n# the rights to use, copy, modify, merge, publish, distribute, sublicense,\n# and/or sell copies of the Software, and to permit persons to whom the\n# Software is furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n# THE SOFTWARE.\nfrom random import randint\n\nfrom gym import error\n\nfrom treys import Card\n\n\nclass Player(object):\n\n  CHECK = 0\n  CALL = 1\n  RAISE = 2\n  FOLD = 3\n\n  def __init__(self, player_id, stack=2000, emptyplayer=False):\n    self.player_id = player_id\n\n    self.hand = []\n    self.stack = stack\n    self.currentbet = 0\n    self.lastsidepot = 0\n    self._seat = -1\n    self.handrank = -1\n\n    # flags for table management\n    self.emptyplayer = emptyplayer\n    self.betting = False\n    self.isallin = False\n    self.playing_hand = False\n    self.playedthisround = False\n    self.sitting_out = True\n\n  def get_seat(self):\n    return self._seat\n\n  def set_seat(self, value):\n    self._seat = value\n\n  def reset_hand(self):\n    self._hand = []\n    self.playedthisround = False\n    self.betting = False\n    self.isallin = False\n    self.currentbet = 0\n    self.lastsidepot = 0\n    self.playing_hand = (self.stack != 0)\n\n  def bet(self, bet_size):\n    self.playedthisround = True\n    if not bet_size:\n      return\n    self.stack -= (bet_size - self.currentbet)\n    self.currentbet = bet_size\n    if self.stack == 0:\n      self.isallin = True\n\n  def refund(self, ammount):\n    self.stack += ammount\n\n  def player_state(self):\n    return (self.get_seat(), self.stack, self.playing_hand, self.betting, self.player_id)\n\n  def reset_stack(self):\n    self.stack = 2000\n\n  def update_localstate(self, table_state):\n    self.stack = table_state.get('stack')\n    self.hand = table_state.get('pocket_cards')\n\n  # cleanup\n  def player_move(self, table_state, action):\n    self.update_localstate(table_state)\n    bigblind = table_state.get('bigblind')\n    tocall = min(table_state.get('tocall', 0), self.stack)\n    minraise = table_state.get('minraise', 0)\n\n    [action_idx, raise_amount] = action\n    raise_amount = int(raise_amount)\n    action_idx = int(action_idx)\n\n    if tocall == 0:\n      assert action_idx in [Player.CHECK, Player.RAISE]\n      if action_idx == Player.RAISE:\n        if raise_amount < minraise:\n          raise error.Error('raise must be greater than minraise {}'.format(minraise))\n        if raise_amount > self.stack:\n          raise error.Error('raise must be less than maxraise {}'.format(self.stack))\n        move_tuple = ('raise', raise_amount)\n      elif action_idx == Player.CHECK:\n        move_tuple = ('check', 0)\n      else:\n        raise error.Error('invalid action ({}) must be check (0) or raise (2)'.format(action_idx))\n    else:\n      if action_idx not in [Player.RAISE, Player.CALL, Player.FOLD]:\n        raise error.Error('invalid action ({}) must be raise (2), call (1), or fold (3)'.format(action_idx))\n      if action_idx == Player.RAISE:\n        if raise_amount < minraise:\n          raise error.Error('raise must be greater than minraise {}'.format(minraise))\n        if raise_amount > self.stack:\n          raise error.Error('raise must be less than maxraise {}'.format(self.stack))\n        move_tuple = ('raise', raise_amount)\n      elif action_idx == Player.CALL:\n        move_tuple = ('call', tocall)\n      elif action_idx == Player.FOLD:\n        move_tuple = ('fold', -1)\n      else:\n        raise error.Error('invalid action ({}) must be raise (2), call (1), or fold (3)'.format(action_idx))\n    return move_tuple\n"
  },
  {
    "path": "holdem/utils.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Copyright (c) 2018 Sam Wenke (samwenke@gmail.com)\n#\n# Permission is hereby granted, free of charge, to any person obtaining\n# a copy of this software and associated documentation files (the \"Software\"),\n# to deal in the Software without restriction, including without limitation\n# the rights to use, copy, modify, merge, publish, distribute, sublicense,\n# and/or sell copies of the Software, and to permit persons to whom the\n# Software is furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n# THE SOFTWARE.\nfrom treys import Card\n\n\nclass action_table:\n  CHECK = 0\n  CALL = 1\n  RAISE = 2\n  FOLD = 3\n  NA = 0\n\n\ndef format_action(player, action):\n  color = False\n  try:\n    from termcolor import colored\n    # for mac, linux: http://pypi.python.org/pypi/termcolor\n    # can use for windows: http://pypi.python.org/pypi/colorama\n    color = True\n  except ImportError:\n    pass\n  [aid, raise_amt] = action\n  if aid == action_table.CHECK:\n    text = '_ check'\n    if color:\n      text = colored(text, 'white')\n    return text\n  if aid == action_table.CALL:\n    text = '- call, current bet: {}'.format(player.currentbet)\n    if color:\n      text = colored(text, 'yellow')\n    return text\n  if aid == action_table.RAISE:\n    text = '^ raise, current bet: {}'.format(raise_amt)\n    if color:\n      text = colored(text, 'green')\n    return text\n  if aid == action_table.FOLD:\n    text = 'x fold'\n    if color:\n      text = colored(text, 'red')\n    return text\n\n\ndef card_to_str(card):\n  if card == -1:\n    return ''\n  return Card.int_to_pretty_str(card)\n\n\ndef hand_to_str(hand):\n  output = \" \"\n  for i in range(len(hand)):\n    c = hand[i]\n    if c == -1:\n      if i != len(hand) - 1:\n        output += '[  ],'\n      else:\n        output += '[  ] '\n      continue\n    if i != len(hand) - 1:\n      output += str(Card.int_to_pretty_str(c)) + ','\n    else:\n      output += str(Card.int_to_pretty_str(c)) + ' '\n  return output\n\n\ndef safe_actions(community_infos, n_seats):\n  current_player = community_infos[-1]\n  to_call = community_infos[-2]\n  actions = [[action_table.CHECK, action_table.NA]] * n_seats\n  if to_call > 0:\n    actions[current_player] = [action_table.CALL, action_table.NA]\n  return actions\n"
  },
  {
    "path": "setup.py",
    "content": "# -*- coding: utf-8 -*-\n# \n# Copyright (c) 2018 Sam Wenke (samwenke@gmail.com)\n#\n# Permission is hereby granted, free of charge, to any person obtaining\n# a copy of this software and associated documentation files (the \"Software\"),\n# to deal in the Software without restriction, including without limitation\n# the rights to use, copy, modify, merge, publish, distribute, sublicense,\n# and/or sell copies of the Software, and to permit persons to whom the\n# Software is furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n# THE SOFTWARE.\nfrom setuptools import setup, find_packages\n\nwith open(\"README.md\") as readme:\n  long_description = readme.read()\n\nsetup(\n  name='holdem',\n  version='1.0.0',\n  long_description=long_description,\n  url='https://github.com/wenkesj/holdem',\n  author='Sam Wenke',\n  author_email='samwenke@gmail.com',\n  license='MIT',\n  description=('OpenAI Gym No-Limit Texas Holdem Environment.'),\n  packages=find_packages(exclude=['test', 'examples']),\n  install_requires=['treys', 'gym'],\n  platforms='any',\n)\n"
  }
]