| 1 | from games import deck |
|---|
| 2 | import hand |
|---|
| 3 | |
|---|
| 4 | class InvalidActionException(Exception): |
|---|
| 5 | pass |
|---|
| 6 | |
|---|
| 7 | class InsufficientFundsException(Exception): |
|---|
| 8 | pass |
|---|
| 9 | |
|---|
| 10 | class ImproperPhaseException(Exception): |
|---|
| 11 | pass |
|---|
| 12 | |
|---|
| 13 | class 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 | |
|---|
| 93 | PHASES = ["blinds", "flop", "turn", "river", "showdown"] |
|---|
| 94 | class 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 | |
|---|
| 321 | DEFAULT_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 | |
|---|
| 344 | class 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 | |
|---|