root/games/holdem/holdemtable.py

Revision 47, 15.1 KB (checked in by mike, 4 years ago)

refactoring packages

Line 
1from games import deck
2import hand
3
4class InvalidActionException(Exception):
5    pass
6
7class InsufficientFundsException(Exception):
8    pass
9
10class ImproperPhaseException(Exception):
11    pass
12
13class player(object):
14    def __init__(self, id, user_id, seat, starting_money):
15        self.__seat = seat
16        self.__id = id
17        self.__user_id = user_id
18        self.__money = starting_money
19        self.__in_hand = True
20        self.__cards = []
21        self.__money_in_pot = None  #money in pot before previous round
22        self.__action_history = []
23        self.__has_acted = False
24        self.__has_folded = False
25        self.__action_log = []
26        self.__status = "need implementing"
27        self.__cards_shown = False
28
29    def nextHand(self):
30        self.__money_in_pot = 0.0
31        self.__has_folded = False
32        self.__cards = []
33        self.__cards_shown = False
34        self.__action_history = []
35
36
37
38    def nextPhase(self):
39        self.__action_history = []
40        self.__money_in_pot += self.money_in_phase
41        self.__has_acted = False
42        self.__in_hand = not self.__has_folded
43
44
45    def __publicData(self):
46        dat =  {"owner" : self.user_id,
47                "status": self.status,
48                "money" : self.money,
49                "seat"  : self.seat}
50        if self.__cards_shown:
51            dat["cards"] = self.cards
52        return dat
53
54    def __privateData(self):
55        dat = {"user_id" : self.user_id,
56               "id"      : self.id,
57               "cards"   : self.cards}
58        return dat
59
60    def dealCard(self, card):
61        self.cards.append(card)
62
63    #returns money added to pot if any
64    def doAction(self, action, value):
65        if value != None and value > self.money:
66            raise InsufficientFundsException("Insufficient Funds")
67        else:
68            if value != None: self.__money -= value
69            if not self.__has_acted and not action in ["small_blinds","large_blinds","ante"]:
70                self.__has_acted = True
71            if action == "fold" or action == "muck": self.__has_folded = True
72            if action == "show": self.__cards_shown = True
73            self.__action_history.append((action,value))
74
75    #lets make a bunch of getters
76    id = property( lambda self: self.__id )
77    cards = property( lambda self: self.__cards )
78    money_in_pot = property( lambda self: self.__money_in_pot )
79    action_history = property( lambda self: self.__action_history )
80    has_folded = property( lambda self: self.__has_folded )
81    has_acted = property( lambda self: self.__has_acted )
82    status = property( lambda self: self.__status )
83    user_id = property( lambda self: self.__user_id )
84    seat = property( lambda self: self.__seat )
85    money = property( lambda self: self.__money )
86    money_in_phase = property( 
87            lambda self: sum([ b for a,b in self.__action_history if b != None]) )
88    public_data = property(__publicData)
89    private_data = property(__privateData)
90    in_hand = property( lambda self: self.__in_hand )
91
92
93PHASES = ["blinds", "flop", "turn", "river", "showdown"]
94class holdemround(object):
95   
96
97    def __init__(self,players,format):
98        self.__deck = deck.deck()
99        self.__blinds = None
100        self.__round_length = format["round_length"]
101        self.__blind_structure = format["blinds"]
102        #keep both of these on hand so we can quickly find a player
103        self.__players = players
104        self.__player_array = players.values()
105        self.__player_array.sort( cmp=lambda x,y: x.seat - y.seat) 
106        self.__action_on = None
107        self.__phase = 0
108        self.__community_cards = None
109        self.__possible_actions = None #to cache possible actions
110        self.__hand_count = None
111        self.__last_bet_raise = None
112
113
114    #hand over?
115    def __isHandOver(self):
116        total = sum([ 1 for p in self.__player_array if not p.has_folded ])
117        if total == 0: return True
118
119        if self.__phase == len(PHASES): return True
120        return False
121
122    #wrap up the hand. Who wins?  Etc.
123    def finishHand(self):
124        if not self.hand_over:
125            raise ImproperPhaseException("Hand is not over")
126        #now, let's get a list of people who are still in.
127        still_in = [ p for p in self.__player_array if not p.has_folded ]
128        winner = None
129        if len(still_in) == 1:
130            winner = still_in[0]
131        else:
132            #create list of tuples of (id,hand)
133            hands = [ (p.id,hand.best_hand(deck.card_vals(p.cards + self.__community_cards)))
134                    for p in still_in ]
135            #TODO add ties
136            hands.sort(cmp = lambda a,b: cmp(a[1],b[1]))
137            winner = hands.pop()
138            print "%s withs with %s" % (winner[0],hand.format_hand(winner[1]))
139
140           
141
142
143    #return false if game is over
144    def nextHand(self):
145        self.__deck.shuffle()
146        #rotate the players
147        self.__player_array.append(self.__player_array.pop(0))
148        self.__phase = None
149        self.__community_cards = []
150        self.__last_bet_raise = None
151        if self.__hand_count == None:
152            self.__hand_count = 0
153        else: self.__hand_count += 1
154        #Next phase of blinds
155        if self.__hand_count % self.__round_length == 0 and\
156           self.__hand_count/self.__round_length < len(self.__blind_structure):
157            #Next blind phase
158            self.__blinds = self.__blind_structure[self.__hand_count/self.__round_length]
159        print self.__player_array
160        for p in self.__player_array: p.nextHand()
161        #deal cards
162        for _ in range(2):
163            for p in self.__player_array:
164                p.dealCard(self.__deck.deal())
165
166        return self.nextPhase()
167
168    #returns false if hand is over
169    def nextPhase(self):
170        self.__action_on = None
171
172        if self.__phase != None: self.__phase += 1
173        else: self.__phase = 0
174
175        for p in self.__player_array: p.nextPhase()
176
177        if self.hand_over: return False
178
179        pn = self.phase
180        if pn == "blinds":
181            self.__player_array[0].doAction("small_blinds",
182                    min(self.__player_array[0],self.__blinds[1]))
183
184            self.__player_array[1].doAction("large_blinds",
185                    min(self.__player_array[1],self.__blinds[2]))
186            #do antes for the rest
187            for p in self.__player_array[2:]:
188                p.doAction("ante",min(p.money,self.__blinds[0]))
189
190            self.__action_on = 1
191        elif pn == "flop":
192            for _ in range(3):
193                self.__community_cards.append(self.__deck.deal())
194        elif pn == "turn":
195            self.__community_cards.append(self.__deck.deal())
196        elif pn == "river":
197            self.__community_cards.append(self.__deck.deal())
198        elif pn == "showdown":
199            #If somebody bet or raised, they have to show
200            if self.__last_bet_raise != None:
201                self.__player_array[self.__last_bet_raise].doAction("show",None)
202                self.__action_on = self.__last_bet_raise
203            else:
204                #Else we start fromt he button
205                self.__action_on = None
206
207
208        return self.nextAction()
209   
210    #Are there any outstanding bets?
211    def outstandingBets(self):
212        max_money = max([p.money_in_phase for p in self.__player_array])
213        for p in self.__player_array:
214            if not p.has_folded and p.money_in_phase < max_money:
215                return True
216        return False
217
218    #returns false if phase is over
219    def nextAction(self):
220        self.__possible_actions = None
221
222        #first check if we have just one player left.  then we have an easy winner
223
224        if self.hand_over: return False
225
226        #increment the action
227        if self.__action_on != None:
228            self.__action_on = ( self.__action_on + 1 ) % len(self.__player_array)
229            while self.current_player.has_folded:
230                self.__action_on = ( self.__action_on + 1 ) % len(self.__player_array)
231        else: self.__action_on = 0
232        if self.current_player.has_acted\
233                and not self.outstandingBets():
234                    return False
235        else: return True
236
237    def requiredAction(self, player_id):
238        return self.__action_on != None and player_id == self.current_player.id
239
240    #returns possible actions for a player
241    def __getPossibleActions(self):
242        if self.__possible_actions != None:
243            return self.__possible_actions
244
245        me = self.current_player
246        if self.phase == "showdown":
247            self.__possible_actions = {"muck":(None,None),
248                                       "show":(None,None)}
249        else:
250            self.__possible_actions = {"fold":(None,None)}
251
252            max_money = max([p.money_in_phase for p in self.__player_array])
253           
254            money_in_phase = me.money_in_phase
255           
256            min_bet = self.__blinds[2]
257            if self.phase == "river": min_bet *= 2
258
259            if max_money == money_in_phase: self.__possible_actions["check"] = (None,None)
260
261            if max_money > money_in_phase: 
262                call = min(me.money, max_money-money_in_phase)
263                self.__possible_actions["call"] = (call,call)
264            bet_raise = min( me.money, min_bet + max_money - money_in_phase )
265            if max_money == 0 and me.money != 0.0:
266                self.__possible_actions["bet"] = (bet_raise, me.money)
267            #TODO this needs to change to reflect the last bet
268            else: self.__possible_actions["raise"] = (bet_raise, me.money)
269
270        return self.__possible_actions
271        #TODO fix so unlimited raises aren't allowed
272
273
274    def __getState(self):
275        state = {"phase": self.phase,
276                 "action_on": self.current_player.seat,
277                 "pot_before_phase": self.pot,
278                 "community_cards": self.__community_cards,
279                 "hand_number": self.__hand_count,
280                 "current_blinds": self.__blinds}
281        p_actions = {}
282
283        for (n,p) in self.__players.items():
284            if p.in_hand: p_actions[n] = p.action_history
285        state["player_actions"] = p_actions
286
287        return state
288
289
290    def doAction(self, p_id, action, value):
291        if p_id != self.current_player.id:
292            raise InvalidActionException("Action not on player " + p_id)
293
294        pa = self.__getPossibleActions()
295        if not action in pa:
296            raise InvalidActionException("Action not in possible list")
297
298        range = pa[action]
299        if not (range[0] <= value <= range[1]):
300            raise InvalidActionException("Value out of range")
301        self.current_player.doAction(action,value)
302        if action == "bet" or action == "raise":
303            self.__last_bet_raise = self.__action_on
304        if not self.nextAction():
305            if not self.nextPhase():
306                self.finishHand()
307                if not self.nextHand():
308                    #TODO add game end code handing hurr
309                    pass
310
311    #properties
312    state = property( __getState )
313    current_player = property( lambda self: self.__player_array[self.__action_on])
314    hand_over = property( __isHandOver )
315    possible_actions = property( __getPossibleActions )
316    phase = property( lambda self: PHASES[self.__phase] )
317
318    #returns the amount of money in pot before current phase
319    pot = property( lambda self: sum([ p.money_in_pot for p in self.__player_array] ))
320
321DEFAULT_FORMAT = {"max_players":2,
322                  "round_length":8,
323                  "starting_money":2000.0,
324                  "blinds":[(None,25,50),
325                            (None,50,100),
326                            (None,100,200),
327                            (25,100,200),
328                            (25,150,300),
329                            (50,200,400),
330                            (75,300,600),
331                            (100,500,1000),
332                            (200,500,1500),
333                            (300,1000,2000),
334                            (400,1500,3000),
335                            (500,2000,4000),
336                            (500,3000,6000),
337                            (1000,4000,8000),
338                            (1000,5000,10000),
339                            (2000,5000,15000),
340                            (3000,10000,20000),
341                            (4000,15000,30000),
342                            (5000,20000,40000)]}
343
344class holdemtable:
345    def __init__(self, name, format = DEFAULT_FORMAT):
346        self.__max_players = format["max_players"]
347        self.__players={}
348        self.__game_started = False
349        self.__name = name
350        self.__holdemgame = None
351        self.__deferreds = []
352        self.__format = format
353        self.__cached_response = None
354
355
356    def addPlayer(self, data, deferred ):
357        p_id = data['client_id']
358        self.__players[p_id]=player(p_id,
359                                    data['user_id'],
360                                    len(self.__players),
361                                    self.__format["starting_money"])
362        self.addDeferred( p_id, deferred )
363        self.stateChanged("Player Added")
364        self.checkStart()
365    #check to see if the game is full and possibly start it
366    def checkStart(self):
367        if self.isFull():
368            self.__game_state = holdemround(self.__players,self.__format)
369            self.__game_started = True
370            self.__game_state.nextHand()
371            self.stateChanged("Game Starting")
372            return True
373        return False
374
375
376    def isFull(self):
377        return len(self.__players) >= self.__max_players
378
379    def genStatus(self, p_id):
380        if self.__cached_response == None:
381            root = {}
382            root["stat"] = "ok"
383            root["type"] = "game_state"
384
385            table_info = {}
386            table_info["game_in_progress"] = self.__game_started
387            table_info["name"] = self.__name
388            table_info["format"] = self.__format
389
390            players = {}
391            for (n,d) in self.__players.items():
392                players[n] = d.public_data
393            table_info["players"] = players
394            table_info["player_names"] = players.keys()
395            root["table_info"] = table_info
396           
397            if self.__game_started:
398                root["game_state"] = self.__game_state.state
399            self.__cached_response = root
400
401        copy = self.__cached_response.copy()
402        you = self.__players[p_id].private_data
403
404        req_action = self.__game_started and self.__game_state.requiredAction(p_id)
405        if self.__game_started: print self.__game_state.requiredAction(p_id)
406
407        print req_action
408        you["required_action"] = req_action
409        if req_action: you["possible_actions"] = self.__game_state.possible_actions
410
411        copy["you"] = you
412        return copy
413
414    def action(self, p_id, action, value):
415        self.__game_state.doAction(p_id,action,value)
416        self.stateChanged("Actioned")
417
418    def addDeferred(self, p_id, deferred):
419        self.__deferreds.append((p_id, deferred))
420
421    #call this when the state changes to update all the deferred
422    def stateChanged(self,action):
423        dl = self.__deferreds[:]
424        self.__deferreds = []
425        self.__cached_response = None
426        for (p,d) in dl:
427            stat = self.genStatus(p)
428            stat["action"] = action
429            d.callback(stat)
430    name = property( lambda self: self.__name )
431
432
433
Note: See TracBrowser for help on using the browser.