import random

# CSC104h Winter 2008 Assignment 4
# A series of functions to deal and asses hands dealt for partlyPoker.com

deck = [ \
'2c' , '3c' , '4c' , '5c' , '6c' , '7c' , '8c' , '9c' , '10c' , 'Jc' , 'Qc' , 'Kc',  'Ac', \
'2h' , '3h' , '4h' , '5h' , '6h' , '7h' , '8h' , '9h' , '10h' , 'Jh' , 'Qh' , 'Kh' , 'Ah', \
'2s' , '3s' , '4s' , '5s' , '6s' , '7s' , '8s' , '9s' , '10s' , 'Js' , 'Qs' , 'Ks' , 'As', \
'2d' , '3d' , '4d' , '5d' , '6d' , '7d' , '8d' , '9d' , '10d' , 'Jd' , 'Qd' , 'Kd' , 'Ad' ]

# This function accepts a hand as a list, 
# sorts it (in descending order) according to rank, and 
# returns the sorted hand as a list.
# Remember that unlike strings, lists are mutable, 
# therefore we can move the items in a list around.
# In this function, we sort all aces as high.
def sortRank(hand):
    
    # assign each card a numeric value in a parallel list called ranks.
    ranks = list()
    # Isolate the ranks for each card
    # assign a rank to the cards with a letter rank
    for card in hand:
        if len(card) == 3:
            ranks.append(10)
        elif card[0] == 'J':
            ranks.append(11)
        elif card[0] == 'Q':
            ranks.append(12)
        elif card[0] == 'K':
            ranks.append(13)
        elif card[0] == 'A':
            ranks.append(14)
        else:
            ranks.append(int(card[0]))
  
    # now as you sort the cards, remember
    # that every time you move a card, 
    # you must also move its parallel rank
    for i in range(4):
        for j in range(i+1, 5):
            if ranks[j] > ranks[i]:
                temp = hand[i]
                tempR = ranks[i]
                hand[i] = hand[j]
                ranks[i] = ranks[j]
                hand[j] = temp
                ranks[j] = tempR
    return hand

# This function accepts a hand as a list, 
# sorts it by suit (clubs, hearts, spades then diamonds, and 
# then (in descending order) according to rank, and 
# returns the sorted hand as a list.
def sortSuit(hand):
    # assign each card a numeric value in a parallel list called ranks.
    ranks = list()
    for card in hand:
        # isolate suits by adding a different multiple
        # of thirteen to cards from each suit.
        if card[1] == 'h' or (card[1] == '0' and card[2] == 'h' ):
            addval = 26
        elif card[1] == 's' or (card[1] == '0' and card[2] == 's' ):
            addval = 13
        elif card[1] == 'd' or (card[1] == '0' and card[2] == 'd' ):
            addval = 0
        else:
            addval = 39
        # Isolate the ranks for each card
        # assign a rank to the cards with a letter rank
        # be sure to add the suit isolator (addval) to
        # the established rank
        if len(card) == 3:
            ranks.append(10 + addval)
        elif card[0] == 'J':
            ranks.append(11 + addval)
        elif card[0] == 'Q':
            ranks.append(12 + addval)
        elif card[0] == 'K':
            ranks.append(13 + addval)
        elif card[0] == 'A':
            ranks.append(14 + addval)
        else:
            ranks.append(int(card[0]) + addval)
        
    # now as you sort the cards, remember
    # that every time you move a card, 
    # you must also move its parallel rank
    for i in range(4):
        for j in range(i+1, 5):
            if ranks[j] > ranks[i]:
                temp = hand[i]
                tempR = ranks[i]
                hand[i] = hand[j]
                ranks[i] = ranks[j]
                hand[j] = temp
                ranks[j] = tempR
    return hand

def suit(card):
    if len(card)==3:
        return card[2]
    return card[1]

def rank(hand):
    ranks = list()
    for card in hand:
        if card[0]== '1':
            ranks.append(10)
        elif card[0] == 'J':
            ranks.append(11)
        elif card[0] == 'Q':
            ranks.append(12)
        elif card[0] == 'K':
            ranks.append(13)
        elif card[0] == 'A':
            ranks.append(14)
        else:
            ranks.append(int(card[0]))
    return ranks

# This function is key - it defines an array that
# stores the singles, pairs, triples and quads
# that are found in a hand.
# A nested list is used - the first list contains
# the rank of the single cards; the second that of the pairs;
# the third, the triples; and the fourth the quads.
def handInfo(hand):
    sortRank(hand)
    info = list()
    for i in range(4):
        info.append(list())
    count = 0
    prev = 0
    ranks = rank(hand)
    for curr in range(1,5):
        if ranks[curr] == ranks[prev]:
            count +=1
        else:
            info[count] .append(ranks[prev])
            count = 0
        prev = curr
    info[count] .append(ranks[prev])
    return info

def assessRanks(allHands):
    handRanks = list()
    for hand in allHands:
        if royalFlush(hand):
            handRanks.append(9)
        elif straightFlush(hand):
            handRanks.append(8)
        elif fourOfAKind(hand):
            handRanks.append(7)
        elif fullHouse(hand):
            handRanks.append(6)
        elif flush(hand):
            handRanks.append(5)
        elif straight(hand):
            handRanks.append(4)
        elif threeOfAKind(hand):
            handRanks.append(3)
        elif twoPair(hand):
            handRanks.append(2)
        elif pair(hand):
            handRanks.append(1)
        else:
            handRanks.append(0)
        
    # Now sort them by ranks:
    # be sure to sort parallel arrays
    for i in range(len(handRanks)-1):
        for j in range(i+1, len(handRanks)):
            if handRanks[j] > handRanks[i]:
                temp = handRanks[j]
                handRanks[j] = handRanks[i]
                handRanks[i] = temp
                temp = allHands[j]
                allHands[j] = allHands[i]
                allHands[i] = temp
    return handRanks

def getHandsInfo(hands, handRankings):
    handsInfo = list()
    i = 0
    while i < len(handRankings) and handRankings[i] == handRankings[0] :
        handsInfo.append(handInfo(hands[i]))
        i += 1
    return handsInfo

def tiedRoyalFlush(hands, handRankings):
    winners = list()
    i = 0
    while handRankings[i] == 9 and i < len(hands):
        winners.append(handInfo(hands[i]))
        i += 1
    return winners
    
def tiedStraightFlush(hands, handRankings):
    return tiedStraight(hands, handRankings)

def tiedFourOfAKind(hands, handRankings):
    hi = getHandsInfo(hands, handRankings)
    #sort tied hands by highest 4-of-a-kind order
    for i in range(len(hi)-1):
        for j in range (i+1, len(hi)):
            if hi[j][3][0] > hi[i][3][0]:
                temp = hi[j]
                hi[j] = hi[i]
                hi[i] = temp
                temp = hands[j]
                hands[j] = hands[i]
                hands[i] = temp
    winners = list()
    winners.append(hands[0])
    return winners

def tiedFullHouse(hands, handRankings):
    return tiedThreeOfAKind(hands, handRankings)

def tiedFlush(hands, handRankings):
    return tiedHighCard(hands, handRankings)

def tiedStraight(hands, handRankings):
    hi = getHandsInfo(hands, handRankings)
    #sort tied hands on high card of straights
    for i in range(len(hi)-1):
        for j in range (i+1, len(hi)):
            if hi[j][0][0] > hi[i][0][0]:
                temp = hi[j]
                hi[j] = hi[i]
                hi[i] = temp
                temp = hands[j]
                hands[j] = hands[i]
                hands[i] = temp
    winners = list()
    i = 0
    while hi[i][0][0] == hi[0][0][0] and i < len(hi):
        winners.append(hands[i])
        i+=1
    return winners

def tiedThreeOfAKind(hands, handRankings):
    hi = getHandsInfo(hands, handRankings)
   #sort tied hands on rank of three of a kinds in each hand
    for i in range(len(hi)-1):
        for j in range (i+1, len(hi)):
            if hi[j][2][0] > hi[i][2][0]:
                temp = hi[j]
                hi[j] = hi[i]
                hi[i] = temp
                temp = hands[j]
                hands[j] = hands[i]
                hands[i] = temp
    winners = list()
    winners.append(hands[0])
    return winners

def tiedTwoPair(hands, handRankings):
    hi = getHandsInfo(hands, handRankings)
    # sort on second pair order within first pair order
    for i in range(len(hi)-1):
        for j in range (i+1, len(hi)):
            if hi[j][1][0] > hi[i][1][0] or (hi[j][1][0] == hi[i][1][0] and hi[j][1][1] > hi[i][1][1]):
                temp = hi[j]
                hi[j] = hi[i]
                hi[i] = temp
                temp = hands[j]
                hands[j] = hands[i]
                hands[i] = temp
    winners = list()
    winners.append(hands[0])
    if hi[0][1][0] == hi[1][1][0] and hi[0][1][1] == hi[1][1][1]:
        winners.append(hands[1])
    return winners
    

def tiedPair(hands, handRankings):
    hi = getHandsInfo(hands, handRankings)
    #sort on single card order within pair order.
    for i in range(len(hi)-1):
        for j in range (i+1, len(hi)):
            if hi[j][1][0] > hi[i][1][0] or (hi[j][1][0] == hi[i][1][0] and equalSingles(hi[j][0] , hi[i][0]) == 1):
                temp = hi[j]
                hi[j] = hi[i]
                hi[i] = temp
                temp = hands[j]
                hands[j] = hands[i]
                hands[i] = temp
    winners = list()
    winners.append(hands[0])
    if hi[0][1][0] == hi[0][1][0] and equalSingles(hi[0][1], hi[1][1]) == 0:
        winners.append(hands[1])
    return winners    
    
def tiedHighCard(hands, handStats):
    hi = getHandsInfo(hands, handStats)
    #sort on single card order
    for i in range(len(hi)-1):
        for j in range (i+1, len(hi)):
            if equalSingles(hi[j][0] , hi[i][0]) == 1:
                temp = hi[j]
                hi[j] = hi[i]
                hi[i] = temp
                temp = hands[j]
                hands[j] = hands[i]
                hands[i] = temp
    winners = list()
    winners.append(hands[0])
    i = 1
    while i < len(hi) and equalSingles(hi[0][0] , hi[i][0]) == 0:
        winners.append(hands[i])
        i+=1
    return winners
        
# takes two lists of single cards (from handstats)
# returns 0 if they are equal, -1 if info1 is less than info2
# and 1 if info1 is greater than info2
# it is assumed that the lists are in sorted descending order
def equalSingles(info1, info2):
    for i in range(len(info1)):
        if info1[i] < info2[i]:
            return -1
        if info1[i] > info2[i]:
            return 1
    return 0




def dealHands(n):
    newDeck = list()
    for card in deck:
        newDeck.append(card)
    random.shuffle(newDeck)
    hands = list()
    for i in range(n):
        hands.append(list())
    card = 0
    for i in range(5):
        for j in range(n):
            hands[j].append(newDeck[card])
            card +=1
            
    return hands

def flush(hand):
    sortSuit(hand)
    return suit(hand[0]) == suit(hand[4])

def straight(hand):
    sortRank(hand)
    if hand[0][0] == 'A' and hand[1][0] == '5' and hand[2][0] == '4' and hand[3][0] == '3' and hand[4][0] == '2' :
        return True
    handRanks = rank(hand)
    for i in range(4):
        if handRanks[i] != handRanks[i+1]+1:
            return False
    return True

def straightFlush(hand):
    return flush(hand) and straight(hand)

def royalFlush(hand):
    sortRank(hand)
    return straightFlush(hand) and hand[4][1] == '1'

def fourOfAKind(hand):
   stats = handInfo(hand)
   return len(stats[3]) == 1

def threeOfAKind(hand):
   stats = handInfo(hand)
   return len(stats[2]) == 1 and len(stats[1]) == 0

def fullHouse(hand):
    stats = handInfo(hand)
    return len(stats[2]) == 1 and len(stats[1]) == 1

def twoPair(hand):
    stats = handInfo(hand)
    return len(stats[1]) == 2

def pair(hand):
    stats = handInfo(hand)
    return len(stats[1]) == 1 and len(stats[0]) == 3

def winner(hands):
    handStats = list()
    handRankings = assessRanks(hands)

    # if there is a clear winner, return it
    if handRankings[0] > handRankings[1]:
        winners = list()
        winners.append(hands[0])
        return winners
     if handRankings[0]  == 9:
        return tiedRoyalFlush(hands, handRankings)
    elif handRankings[0] == 8:
        return tiedStraightFlush(hands, handRankings)
    elif handRankings[0] == 7:
        return tiedFourOfAKind(hands, handRankings)
    elif handRankings[0] == 6:
        return tiedFullHouse(hands, handRankings)
    elif handRankings[0] == 5:
        return tiedFlush(hands, handRankings)
    elif handRankings[0] == 4:
        return tiedStraight(hands, handRankings)
    elif handRankings[0] == 3:
        return tiedThreeOfAKind(hands, handRankings)
    elif handRankings[0] == 2:
        return tiedTwoPair(hands, handRankings)
    elif handRankings[0] == 1:
        return tiedPair(hands, handRankings)
    else:
        return tiedHighCard(hands, handRankings)
   