Exemple #1
0
class Table():
	
	PRE_FLOP = 0
	FLOP = 1
	TURN = 2
	RIVER = 3
	SHOWDOWN = 4
		
	def __init__(self):
		self.initialiseTable()
		self.evaluator = SevenEval()
		
	def initialiseTable(self):
		self.stateDict = {}
		self.playerRemoveList = [] # Add player to be removed here. Removed at next round
		self.playerList = [None, None, None, None, None, None] # Used for everyone in game and has money to play
		self.deck = []
		self.reinitDeck()
		self.communityCards = []
		self.pots = [0]
		self.currentBet = [0] # Bet for each pot if there are side pots
		self.isGameEnd = False
		self.ante = 0
		self.bigBlind = 0
		self.smallBlind = 0
		self.curRaise = 0
		self.curDealerSeatNo = 0
		self.turn = 0
		self.roundEndSeat = 0
		self.roundNo = 0
		self.potwinQ = deque([], 100)
		self.allinQ = deque([], 100)
		self.gameState = Table.PRE_FLOP
		
	def setState(self):
		"""
		Sets up a dictionary of the current state of the game that will be sent and used by the clients
		"""
		self.stateDict = {'playerlist': self.playerList[:], \
							'comcards': self.communityCards[:], \
							'pots':		self.pots[:], \
							'curbet':	self.currentBet[:], \
							'turn':		self.turn, \
							'isGameEnd': self.isGameEnd}
		
	def addPlayer(self, player):
		"""
		Adds a ``player`` to the game
		
		Raises a ``MaxBoundError`` if the player list is full and cannot be added
		"""
		for i in range(len(self.playerList)):
			if self.playerList[i] == None:
				self.playerList[i] = player
				return i
		
		# At this point exhausted the list and it's full of players, raise exception
		raise MaxBoundError
		
	def getPlayers(self):
		"""
		Makes a call to :func:`getAndFilterPlayers` and returns all players in the game
		"""
		return self.getAndFilterPlayers(lambda x: x)
		
	def getLivePlayers(self):
		"""
		Makes a call to :func:`getAndFilterPlayers` and returns all players that are still live 
		(a.k.a have not folded)
		"""
		return self.getAndFilterPlayers(lambda x: x if x.isHandLive == True else None)
		
	def getSuitablePlayers(self):
		"""
		Makes a call to :func:`getAndFilterPlayers` and returns all players that are live and have
		more than 0 money
		"""
		return self.getAndFilterPlayers(lambda x: x if x.isHandLive == True and x.money > 0 else None)
		
	def getAndFilterPlayers(self, filterFunc):
		"""
		Central function that is called by other 'getPlayer' like functions
		
		``filterFunc`` is a function which other functions can specify (using lambdas usually)
		in order to filter specific types of players in the game.
		"""
		playerlist = []
		for x in self.playerList:
			if x != None:
				player = filterFunc(x)
				if player != None:
					playerlist.append(x)
		return playerlist
		
	def _debugDirectAssign(self, player, pos):
		self.playerList[pos] = player
	
	def removePlayer(self, player):
		"""
		Removes a player from the playerlist
		"""
		for i in range(len(self.playerList)):
			if self.playerList [i] == player:
				self.playerList[i] = None
				return
				
	def removeFromPlayerList(self):
		"""
		Removes all the players that are defined in ``self.playerRemoveList``
		"""
		for x in self.playerRemoveList:
			self.removePlayer(x)
				
	def reinitDeck(self):
		"""
		Creates a new shuffled deck of cards
		"""
		self.deck = range(52)
		random.shuffle(self.deck)
		
	def addSidePot(self):
		"""
		Adds a new side pot
		"""
		self.pots.append(0)
		self.currentBet.append(0)
		
	def addToPot(self, amount, index):
		"""
		Add to pot at ``index`` by ``amount``
		"""
		self.pots[index] = self.pots[index] + amount
		
	def clearPot(self):
		"""
		Resets the pot completely so there is just one mainpot and no sidepots
		"""
		self.pots = [0]
		
	def comparePlayerBet(self, player, i):
		"""
		Compares a player's betAmount with the current bet at index ``i``. Used to determine if the ``player``
		has met the current bet completely
		
		Returns:
		
		* ``1`` if the player's bet amount is less than the current bet amount for the pot
		* ``0`` if the player's bet amount is equal
		* ``-1`` if the player's bet amount is greater
		"""
		if player.betAmount[i] < self.currentBet[i]:
			return 1
		elif player.betAmount[i] == self.currentBet[i]:
			return 0
		else:
			return -1
			
	def removePlayerMoney(self, player, amount):
		"""
		Invoke the function in the ``Player`` class to remove the money from the `player` specified by 
		the `amount`. Then checks if the player's amount is 0 and if it is, will report to the all in 
		event queue that `player` went all in
		"""
		player.removeMoney(amount)
		if player.money == 0:
			self.allinQ.append(player.name)
			
	def collectAnte(self):
		"""
		Collects the ante from all players
		
		If a player has a total amount of money that is less than the defined ante, then it uses that value
		as the new ante that is taken from everyone else
		"""
		smallest = self._findSmallestMoney()
					
		if smallest < self.ante:
			newAnte = smallest
		else:
			newAnte = self.ante
	
		for x in self.playerList:
			if x != None:
				x.removeMoney(newAnte)
				self.pots[0] = self.pots[0] + newAnte
				
	def _findSmallestMoney(self):
		"""
		Find who has the least amount of money out of all the players and returns the amount that was smallest
		"""
		smallest = 99999999 # Just some high number
		for x in self.playerList:
			if x != None:
				if x.money < smallest:
					smallest = x.money
					
		return smallest
	
	def assignDealer(self):
		"""
		Finds the next player next to the current dealer to be the new dealer
		"""
		_, index = self.findNthPlayerFromSeat(self.curDealerSeatNo, 1)
		self.curDealerSeatNo = index
					
	def findNthPlayerFromSeat(self, seat, n):
		"""
		From a starting point ``seat``, it goes around in clockwise order to find the next player that
		is ``n`` seats away
		"""
		for i in range(1,7):
			index = (seat + i) % 6
			if self.playerList[index] != None:
				if n > 1:
					n = n - 1
				else:
					return (self.playerList[index], index)
	
	def noOfPlayers(self):
		"""
		Return the number of players in the game
		"""
		number = 0
		for n in range(6):
			if self.playerList[n] != None:
				number = number + 1
		return number
	
	def collectSmallBlind(self):
		"""
		Determines who is responsible for paying the small blind and collects it
		"""
		if self.noOfPlayers() == 2:
			player = self.playerList[self.curDealerSeatNo]
		else:
			player, seatNo = self.findNthPlayerFromSeat(self.curDealerSeatNo, 1)
			
		if player.money < self.smallBlind:
			self.pots[-1:-1] = [player.money]
			self.currentBet[-1:-1] = [player.money]
			player.betAmount.append(player.money)
			player.money = 0
		else:
			player.removeMoney(self.smallBlind)
			self.pots[0] = self.pots[0] + self.smallBlind
			player.betAmount.append(self.smallBlind)
	
	def collectBigBlind(self):
		"""
		Determines who is responsible for paying the big blind and collects it.
		
		Resets the 'call' value so the next player still has to pay the full amount even if
		the player who pays the big blind couldn't do so because of a lack of money
		"""
		self.setBigBlindBetAmount()
		if self.noOfPlayers() == 2:
			player, seatNo = self.findNthPlayerFromSeat(self.curDealerSeatNo, 1)
		else:
			player, seatNo = self.findNthPlayerFromSeat(self.curDealerSeatNo, 2)
		self.collectMoney(player, self.bigBlind)
		self.setBigBlindBetAmount() # Need to do this again because even if blind cant be paid, 
									# next player still has to pay full blind
		
	def setBigBlindBetAmount(self):
		"""
		Sets the big blind amount
		"""
		if sum(self.currentBet) < self.bigBlind:
			if len(self.pots) > 1:
				newbet = self.bigBlind - sum(self.currentBet)
			else:
				newbet = self.bigBlind
			self.currentBet[-1] = newbet
			
	def collectMoney(self, player, amount):
		"""
		This function is responsible for collecting the money and accurately setting the bet amounts for
		the pots and the players
		
		Steps through all the side bets in turn and pays the current bet for each side pot. If a player
		cannot pay, then the another side pot is created using :func:`_slicePot`
		"""
		
		# First, step through all the pots that player is apart of to check for consistency and
		# to call to any pots that player is apart of but doesn't met the current bet because others
		# have raised ahead. Usually happens on the last pot the player is apart of
		for i in range(0, len(player.betAmount)):
			if player.betAmount[i] < self.currentBet[i]:
				sidePotToCall = self.currentBet[i] - player.betAmount[i]
				if player.money < sidePotToCall:
					self._slicePot(i, player)
					return
				else:
					self.removePlayerMoney(player, sidePotToCall)
					self.pots[i] = self.pots[i] + sidePotToCall
					player.betAmount[i] = player.betAmount[i] + sidePotToCall
					amount = amount - sidePotToCall
		
		# Step through all pots that the player has not been apart of yet, and create them for the player
		# when the money is taken from that specific pot
		for j in range(len(player.betAmount), len(self.currentBet)):
			if player.money < self.currentBet[j]:
				self._slicePot(j, player)
				return
			else:
				self.removePlayerMoney(player, self.currentBet[j])
				self.pots[j] = self.pots[j] + self.currentBet[j]
				player.betAmount.append(self.currentBet[j])
				amount = amount - self.currentBet[j]
				
		# Handle any excess 'amount' for players who are raising. Can still go all in on a raise so need to add a 
		# new side pot if that happens, so other players can bet ahead while keeping the side pots
		# consistent
		if amount > 0:
			self.removePlayerMoney(player, amount)
			self.pots[-1] = self.pots[-1] + amount
			player.betAmount[-1] = player.betAmount[-1] + amount
			self.currentBet[-1] = player.betAmount[-1]
			if player.money == 0:
				self.addSidePot()
				
	def _slicePot(self, i, player):
		"""
		Auxiliary function to :func:`collectMoney`. Will create a new side pot and re-evaluates the necessary side
		pots and every player's bet amount so that they accurate reflect the true state of the pots
		"""
		if len(player.betAmount) < len(self.currentBet):
			player.betAmount.append(player.money)
		else:
			player.betAmount[i] = player.betAmount[i] + player.money
		player.money = 0
		self.pots[i:i] = [0]
		self.pots[i+1] = 0 # Going to re-evaluate this soon
		self.currentBet[i:i] = [player.betAmount[i]]
		self.currentBet[i+1] = self.currentBet[i+1] - self.currentBet[i]
		for x in self.getPlayers():
			if len(x.betAmount)-1 >= i:
				xBetAmount = x.betAmount[i]
				x.betAmount[i:i] = [player.betAmount[i]] if player.betAmount[i] < xBetAmount else [xBetAmount]
				x.betAmount[i+1] = xBetAmount - x.betAmount[i]
				self.pots[i] = self.pots[i] + x.betAmount[i]
				self.pots[i+1] = self.pots[i+1] + x.betAmount[i+1]
				self._pruneBetAmount(x)
		
	def _pruneBetAmount(self, player):
		"""
		Auxiliary function to :func:`_slicePot`. Just pops the bet amount list if there is an extra ``0``
		on the end
		"""
		if player.betAmount[-1] == 0:
			player.betAmount.pop()

	def determineAmountToCall(self, player):
		"""
		Determines the amount of money the ``player`` has to call to stay in the round
		"""
		return sum(self.currentBet) - sum(player.betAmount)
	
	def makeBet(self, player, amount):
		"""
		Collects the money for the bet through :func:'collectMoney' and sets the next turn through
		:func:`setNextTurn`
		"""
		self.collectMoney(player, amount)
		self.setNextTurn()
		
	def setNextTurn(self):
		"""
		Determines whose turn it is next from the list of live players
		
		Can determine the winner if all previous players have folded and there is only one live player left
		"""
		liveplayers = self.getLivePlayers()
		if len(liveplayers) == 1:
			winner = liveplayers.pop()
			for i in range(len(winner.betAmount)):
				self.handOutMoney([winner], i)
			self.setUpNextGameRound()
		elif len(self.getSuitablePlayers()) == 0:
			self.earlyEvaluation()
		else:
			playerUnsuitable = True
			while playerUnsuitable:
				if self.roundEndSeat == self.turn:
					self.setUpNextBetRound()
					playerUnsuitable = False
				else:
					_, self.turn = self.findNthPlayerFromSeat(self.turn, 1)
					if self.playerList[self.turn] in self.playerRemoveList:
						self.playerList[self.turn].isHandLive = False
					if self.playerList[self.turn].money > 0 and self.playerList[self.turn].isHandLive == True:
						playerUnsuitable = False
					
	def findNextSuitablePlayer(self, n):
		"""
		Finds the next suitable player that can take action.
		"""
		for _ in range(len(self.getPlayers())):
			player, seat = self.findNthPlayerFromSeat(n, 1)
			if self.playerList[seat].money > 0 and self.playerList[seat].isHandLive == True:
				return (player, seat)
			else:
				n = seat

	def deal(self):
		"""
		Deals the cards out from ``self.deck``
		
		Emulates real poker in the sense that it goes through each player one card at a time
		until they all have 2 cards
		"""
		playerList = self.getPlayers()
		start = self.curDealerSeatNo + 1
		for i in range(len(playerList)*2):
			playerList[(start + i) % len(playerList)].hand.append(self.deck.pop())
			playerList[(start + i) % len(playerList)].isHandLive = True
			
	def dealCommunity(self, num):
		"""
		Deal a certain number of community cards from ``self.deck``. The number to deal is determined by the
		parameter ``num``
		"""
		for _ in range(num):
			self.communityCards.append(self.deck.pop())
			
	def determineBlinds(self):
		"""
		Determine the initial big and small blind amounts
		"""
		if self.roundNo <= 1:
			self.smallBlind = 5
			self.bigBlind = 10
		elif self.roundNo == 2:
			self.smallBlind = 10
			self.bigBlind = 20
		elif self.roundNo == 3:
			self.smallBlind = 20
			self.bigBlind = 40
		elif self.roundNo == 4:
			self.smallBlind = 40
			self.bigBlind = 80
		elif self.roundNo == 5:
			self.smallBlind = 80
			self.bigBlind = 160
		elif self.roundNo >= 6:
			self.smallBlind = 160
			self.bigBlind = 320
			
		
	def call(self, player):
		"""
		Makes a call to :func:`makeBet` using the value from :func:`determineAmountToCall`
		"""
		self.makeBet(player, self.determineAmountToCall(player))
		self.setState()
		
	def raiseBet(self, player, amount):
		"""
		Raises the bet. The raise bet is the value returned from :func:`determineAmountToCall` + ``amount``
		"""
		fullamount = self.determineAmountToCall(player)+amount
		if amount >= self.curRaise or fullamount == player.money:
			_, self.roundEndSeat = self.findNthPlayerFromSeat(self.turn, self.noOfPlayers()-1)
			self.curRaise = amount
			self.makeBet(player, self.determineAmountToCall(player)+amount)
			self.setState()
		
	def fold(self, player):
		"""
		Fold the ``player``'s hand
		"""
		player.isHandLive = False
		self.setNextTurn()
		self.setState()
	
	def setUpNextBetRound(self):
		"""
		Determines at what betting round the current game is at (preflop, flop, turn, river).
		
		Depending on what betting round it is, community cards are dealt here and also determines the player
		who starts off the betting round
		"""
		if self.gameState == Table.PRE_FLOP:
			self.gameState = Table.FLOP
			self.dealCommunity(3)
			self.curRaise = self.bigBlind
			player = self.findNextSuitablePlayer(self.curDealerSeatNo)
			_, self.turn = self.findNextSuitablePlayer(self.curDealerSeatNo)
			self.roundEndSeat = self.curDealerSeatNo
		elif self.gameState == Table.FLOP:
			self.gameState = Table.TURN
			self.dealCommunity(1)
			self.curRaise = self.bigBlind
			_, self.turn = self.findNextSuitablePlayer(self.curDealerSeatNo)
			self.roundEndSeat = self.curDealerSeatNo
		elif self.gameState == Table.TURN:
			self.gameState = Table.RIVER
			self.dealCommunity(1)
			self.curRaise = self.bigBlind
			_, self.turn = self.findNextSuitablePlayer(self.curDealerSeatNo)
			self.roundEndSeat = self.curDealerSeatNo
		elif self.gameState == Table.RIVER:
			self.gameState = Table.SHOWDOWN
			self.evaluateWinner()
			self.setUpNextGameRound()
			# Do showdown stuff here (evaluate hands, hand out pot, get ready for next game)
			
	def earlyEvaluation(self):
		"""
		Called from :func:`makeBet` when there is a situation where there are no suitable players
		but there are still live hands that need to be won. This happens when multiple players are all in
		and there is no one to check through the rest of the betting rounds
		"""
		self.dealCommunity(5 - len(self.communityCards))
		self.gameState = Table.SHOWDOWN
		self.evaluateWinner()
		self.setUpNextGameRound()
		
	def getPlayersInPot(self, potIndex, livePlayers):
		"""
		Called from :func:`evaluateWinner`. From the list of `livePlayers`, gets all of the players in the
		pot at `potIndex`
		"""
		players = []
		for x in livePlayers:
			if len(x.betAmount) > potIndex:
				players.append(x)
		return players
	
	
	def getWinners(self, evaluations, potIndex):
		"""
		Called from :func:`evaluateWinner`. Determines the winner(s) of the point defined at `potIndex`.
		
		There can be more than one winner if 2 or more players have a hand that has the same rank in poker
		"""
		# What evaluations looks like -> [(playerObj, handScore)]
		winners = []
		evaluations.sort(key=lambda x: x[1], reverse=True)
		highest = evaluations[0][1]
		for x in evaluations:
			if x[1] == highest:
				winners.append(x[0])
			else:
				break
		return winners
		
	def handOutMoney(self, winners, potIndex):
		"""
		Called from :func:`evaluateWinner`. Hands out the spoils to all of the winners. This is at the side pot
		level, not the whole pot, so the spoils are distributed evenly if there is more than one winner.
		
		If there is a remainder after the money has been distributed, a call to :func:`findWinnerNextToDealer`
		is made and the remainder goes to that player
		"""
		amountWon = math.floor(self.pots[potIndex] / len(winners))
		remainder = self.pots[potIndex] - (amountWon * len(winners))
		for x in winners:
			x.money = x.money + amountWon
		
		# Player left of dealer is considered 'worst position', so remainder goes to that player
		player = self.findWinnerNextToDealer(winners)
		player.money = player.money + remainder
			
	def findWinnerNextToDealer(self, winners):
		"""
		Called from :func:`handOutMoney`. From the list of `winners`, then winner closest to the dealer 
		is found
		"""
		curIndex = self.curDealerSeatNo
		while True:
			player, seat = self.findNthPlayerFromSeat(curIndex, 1)
			for x in winners:
				if x == player:
					return player
			curIndex = seat
		
	
	def evaluateWinner(self):
		"""
		Top level function for evaluating the winner(s). Gets all the current live players and evaluates
		their 7-card hand in terms of rank and the pot is distributed among the winners 
		"""
		if self.pots[-1] == 0:
			self.pots.pop()
		livePlayers = self.getLivePlayers()	
		for i in range(len(self.pots)):
			players = self.getPlayersInPot(i, livePlayers)
			evaluations = []
			for x in players:
				combined = x.hand + self.communityCards
				evaluations.append((x, self.evaluator.getRankOfSeven(	combined[0], \
																		combined[1], \
																		combined[2], \
																		combined[3], \
																		combined[4], \
																		combined[5], \
																		combined[6] )))
			winners = self.getWinners(evaluations, i)
			self.handOutMoney(winners, i)
			self.potwinQ.append(winners[0].name)
	
	
	# ############ Game Loop Logic ############
	
	def setUpNextGameRound(self):
		"""
		Resets various variables to make a clean game for the next round.
		
		Also responsible for determining the correct seat to be the next dealer
		"""
		self.pots = [0]
		self.currentBet = [0]
		self.reinitDeck()
		self.communityCards = []
		allPlayers = self.getPlayers()
		self.resetPlayerHands(allPlayers)
		self.resetPlayerBetAmount(allPlayers)
		_, seat = self.findNthPlayerFromSeat(self.curDealerSeatNo, 1)
		self.curDealerSeatNo = seat
		self.beginRound()
		
	def resetPlayerHands(self, players):
		"""
		From the list of `players`, resets their current hand ready for the next round
		"""
		for x in players:
			x.hand = []
			
	def resetPlayerBetAmount(self, players):
		"""
		From the list of `players`, resets their current bet amount ready for the next round
		"""
		for x in players:
			x.betAmount = []
	
	def beginRound(self):
		"""
		Removes any players who have no money left for this round and starts collecting the blinds 
		"""
		self.gameState = Table.PRE_FLOP
		for p in self.getPlayers():
			if p.money <= 0:
				print p.name
				self.playerRemoveList.append(p)
		self.removeFromPlayerList()
		if len(self.getPlayers()) == 1:
			self.isGameEnd = True
		else:
			self.roundNo += 1
			self.determineBlinds()
			self.curRaise = self.bigBlind
			self.collectSmallBlind()
			self.collectBigBlind()
			self.deal()
			self.setState()
			if self.noOfPlayers() == 2:
				self.turn = self.curDealerSeatNo
				_, self.roundEndSeat = self.findNthPlayerFromSeat(self.turn, 1)
			else:
				_, self.turn = self.findNthPlayerFromSeat(self.curDealerSeatNo, 3)
				_, self.roundEndSeat = self.findNthPlayerFromSeat(self.curDealerSeatNo, 2)
Exemple #2
0
	def __init__(self):
		self.initialiseTable()
		self.evaluator = SevenEval()