Skip to content

Fixednolimitholdem #327

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions rlcard/envs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@
entry_point='rlcard.envs.nolimitholdem:NolimitholdemEnv',
)

register(
env_id='fixed-no-limit-holdem',
entry_point='rlcard.envs.fixednolimitholdem:FixedNolimitholdemEnv',
)

register(
env_id='leduc-holdem',
entry_point='rlcard.envs.leducholdem:LeducholdemEnv'
Expand Down
2 changes: 1 addition & 1 deletion rlcard/envs/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def __init__(self, config):
# Game specific configurations
# Currently only support blackjack、limit-holdem、no-limit-holdem
# TODO support game configurations for all the games
supported_envs = ['blackjack', 'leduc-holdem', 'limit-holdem', 'no-limit-holdem']
supported_envs = ['blackjack', 'leduc-holdem', 'limit-holdem', 'no-limit-holdem', 'fixed-no-limit-holdem']
if self.name in supported_envs:
_game_config = self.default_game_config.copy()
for key in config:
Expand Down
119 changes: 119 additions & 0 deletions rlcard/envs/fixednolimitholdem.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import json
import os
import numpy as np
from collections import OrderedDict

import rlcard
from rlcard.envs import Env
from rlcard.games.fixednolimitholdem import Game
from rlcard.games.fixednolimitholdem.round import Action

DEFAULT_GAME_CONFIG = {
'game_num_players': 2,
'chips_for_each': 200,
'dealer_id': None,
}

class FixedNolimitholdemEnv(Env):
''' FixedNolimitholdem Environment
'''

def __init__(self, config):
''' Initialize the Limitholdem environment
'''
self.name = 'fixed-no-limit-holdem'
self.default_game_config = DEFAULT_GAME_CONFIG
self.game = Game()
super().__init__(config)
self.actions = Action
self.state_shape = [[54] for _ in range(self.num_players)]
self.action_shape = [None for _ in range(self.num_players)]
# for raise_amount in range(1, self.game.init_chips+1):
# self.actions.append(raise_amount)

with open(os.path.join(rlcard.__path__[0], 'games/limitholdem/card2index.json'), 'r') as file:
self.card2index = json.load(file)

def _get_legal_actions(self):
''' Get all leagal actions

Returns:
encoded_action_list (list): return encoded legal action list (from str to int)
'''
return self.game.get_legal_actions()

def _extract_state(self, state):
''' Extract the state representation from state dictionary for agent

Note: Currently the use the hand cards and the public cards. TODO: encode the states

Args:
state (dict): Original state from the game

Returns:
observation (list): combine the player's score and dealer's observable score for observation
'''
extracted_state = {}

legal_actions = OrderedDict({action.value: None for action in state['legal_actions']})
extracted_state['legal_actions'] = legal_actions

public_cards = state['public_cards']
hand = state['hand']
my_chips = state['my_chips']
all_chips = state['all_chips']
cards = public_cards + hand
idx = [self.card2index[card] for card in cards]
obs = np.zeros(54)
obs[idx] = 1
obs[52] = float(my_chips)
obs[53] = float(max(all_chips))
extracted_state['obs'] = obs

extracted_state['raw_obs'] = state
extracted_state['raw_legal_actions'] = [a for a in state['legal_actions']]
extracted_state['action_record'] = self.action_recorder

return extracted_state

def get_payoffs(self):
''' Get the payoff of a game

Returns:
payoffs (list): list of payoffs
'''
return np.array(self.game.get_payoffs())

def _decode_action(self, action_id):
''' Decode the action for applying to the game

Args:
action id (int): action id

Returns:
action (str): action for the game
'''
legal_actions = self.game.get_legal_actions()
if self.actions(action_id) not in legal_actions:
if Action.CHECK in legal_actions:
return Action.CHECK
else:
print("Tried non legal action", action_id, self.actions(action_id), legal_actions)
return Action.FOLD
return self.actions(action_id)

def get_perfect_information(self):
''' Get the perfect information of the current state

Returns:
(dict): A dictionary of all the perfect information of the current state
'''
state = {}
state['chips'] = [self.game.players[i].in_chips for i in range(self.num_players)]
state['public_card'] = [c.get_index() for c in self.game.public_cards] if self.game.public_cards else None
state['hand_cards'] = [[c.get_index() for c in self.game.players[i].hand] for i in range(self.num_players)]
state['current_player'] = self.game.game_pointer
state['legal_actions'] = self.game.get_legal_actions()
return state


7 changes: 7 additions & 0 deletions rlcard/games/fixednolimitholdem/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from rlcard.games.fixednolimitholdem.dealer import NolimitholdemDealer as Dealer
from rlcard.games.fixednolimitholdem.judger import NolimitholdemJudger as Judger
from rlcard.games.fixednolimitholdem.player import NolimitholdemPlayer as Player
from rlcard.games.fixednolimitholdem.round import Action
from rlcard.games.fixednolimitholdem.round import NolimitholdemRound as Round
from rlcard.games.fixednolimitholdem.game import NolimitholdemGame as Game

136 changes: 136 additions & 0 deletions rlcard/games/fixednolimitholdem/dealer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
from rlcard.games.limitholdem import Dealer


class NolimitholdemDealer(Dealer):
def __init__(self, np_random):
super().__init__(np_random)
# Initialize new properties with default values
self.preset_player0_hand = []
self.preset_flop = []
self.preset_turn = None
self.preset_river = None
self.current_stage = None
self.manual_mode = False # Default to automatic dealing
self.player0_cards_dealt = 0 # Track how many cards have been dealt to player 0

def enable_manual_mode(self):
"""Enable manual card selection mode"""
self.manual_mode = True

def set_player0_hand(self, cards):
"""Set specific cards for Player 0's hand

Args:
cards (list): List of card objects to be used as Player 0's hand
"""
if not self.manual_mode:
return # Do nothing if not in manual mode

self.preset_player0_hand = cards.copy() # Make a copy to avoid modifying the original
# Remove these cards from the deck
for card in cards:
if card in self.deck:
self.deck.remove(card)

def set_flop(self, cards):
"""Set specific flop cards

Args:
cards (list): List of 3 card objects to be used as flop
"""
if not self.manual_mode:
return # Do nothing if not in manual mode

if len(cards) != 3:
raise ValueError("Flop must consist of exactly 3 cards")
self.preset_flop = cards.copy() # Make a copy to avoid modifying the original
# Remove these cards from the deck
for card in cards:
if card in self.deck:
self.deck.remove(card)

def set_turn(self, card):
"""Set specific turn card

Args:
card (object): Card object to be used as turn
"""
if not self.manual_mode:
return # Do nothing if not in manual mode

self.preset_turn = card
# Remove this card from the deck
if card in self.deck:
self.deck.remove(card)

def set_river(self, card):
"""Set specific river card

Args:
card (object): Card object to be used as river
"""
if not self.manual_mode:
return # Do nothing if not in manual mode

self.preset_river = card
# Remove this card from the deck
if card in self.deck:
self.deck.remove(card)

def has_preset_cards(self, stage):
"""Check if dealer has preset cards for the given stage

Args:
stage (str): The stage to check ('flop', 'turn', or 'river')

Returns:
(bool): True if dealer has preset cards for the stage
"""
if not self.manual_mode:
return True # In automatic mode, we always have cards

if stage == 'flop':
return len(self.preset_flop) == 3
elif stage == 'turn':
return self.preset_turn is not None
elif stage == 'river':
return self.preset_river is not None
return False

def deal_card(self, player_id=None):
"""Deal a card from the deck

Args:
player_id (int, optional): The ID of the player to deal to

Returns:
(object): A card object
"""
# Only use preset cards if in manual mode
if self.manual_mode and player_id == 0 and self.player0_cards_dealt < 2 and len(self.preset_player0_hand) > 0:
# For player 0's hand (first two cards)
card = self.preset_player0_hand.pop(0)
self.player0_cards_dealt += 1
return card

# For community cards based on current stage
if self.manual_mode:
if self.current_stage == 'flop' and len(self.preset_flop) > 0:
return self.preset_flop.pop(0)
elif self.current_stage == 'turn' and self.preset_turn is not None:
card = self.preset_turn
self.preset_turn = None
return card
elif self.current_stage == 'river' and self.preset_river is not None:
card = self.preset_river
self.preset_river = None
return card

# Default behavior - deal from deck
return super().deal_card()

def shuffle(self):
"""Shuffle the deck"""
super().shuffle()
# Reset the player0_cards_dealt counter when shuffling
self.player0_cards_dealt = 0
Loading