import random

def make_deck(num):
    '''(int)->list of int
        Returns a list of integers representing the strange deck with 4 suits and num ranks.

        Precondition: 3=<num<=99

    >>> deck=make_deck(13)
    >>> deck
    [101, 201, 301, 401, 102, 202, 302, 402, 103, 203, 303, 403, 104, 204, 304, 404, 105, 205, 305, 405, 106, 206, 306, 406, 107, 207, 307, 407, 108, 208, 308, 408, 109, 209, 309, 409, 110, 210, 310, 410, 111, 211, 311, 411, 112, 212, 312, 412, 113, 213, 313, 413]

    >>> deck=make_deck(4)
    >>> deck
    [101, 201, 301, 401, 102, 202, 302, 402, 103, 203, 303, 403, 104, 204, 304, 404]
    
    '''
    deck=[]
    for i in range(1,num+1): 
        deck=deck+[100+i,200+i,300+i,400+i]
    return deck

def shuffle_deck(deck):
    '''(list of int)->None
       Shuffles the given list of strings representing the playing deck

    Since shufflling is random, exceptionally in this function
    your output does not need to match that show in examples below:

    >>> deck=[101, 201, 301, 401, 102, 202, 302, 402, 103, 203, 303, 403, 104, 204, 304, 404]
    >>> shuffle_deck(deck)
    >>> deck
    [102, 101, 302, 104, 304, 103, 301, 403, 401, 404, 203, 204, 303, 202, 402, 201]
    >>> shuffle_deck(deck)
    >>> deck
    [402, 302, 303, 102, 104, 103, 203, 301, 401, 403, 204, 101, 304, 201, 404, 202]
    '''
    random.shuffle(deck)

def deal_cards_start(deck):
     '''(list of int)-> list of int

     Returns a list representing the player's starting hand.
     It is  obtained by dealing the first 7 cards from the top of the deck.
     Precondition: len(dec)>=7
     '''
     
     player=[]
     for i in range (7):
          player.append(deck.pop())
     return player


def deal_new_cards(deck, player, num):
    '''(list of int, list of int, int)-> None
    Given the remaining deck, current player's hand and an integer num,
    the function deals num cards to the player from the top of the deck.
    If len(deck)<num then len(deck) cards is dealt, in particular
    all the remaining cards from the deck are dealt.

    Precondition: 1<=num<=6 deck and player are disjoint subsets of the strange deck. 
    
    >>> deck=[201, 303, 210, 407, 213, 313]
    >>> player=[302, 304, 404]
    >>> deal_new_cards(deck, player, 4)
    >>> player
    [302, 304, 404, 313, 213, 407, 210]
    >>> deck
    [201, 303]
    >>>

    >>> deck=[201, 303]
    >>> player=[302, 304, 404]
    >>> deal_new_cards(deck, player, 4)
    >>> player
    [302, 304, 404, 303, 201]
    >>> deck
    []
    '''
    i=1
    while i<=num and len(deck)!=0:
        player.append(deck.pop())
        i=i+1


def print_deck_twice(hand):
    '''(list)->None
    Prints elements of a given list deck in two useful ways.
    First way: sorted by suit and then rank.
    Second way: sorted by rank.
    Precondition: hand is a subset of the strange deck.
    
    Your function should not change the order of elements in list hand.
    You should first make a copy of the list and then call sorting functions/methods.

    Example run:
    >>> a=[311, 409, 305, 104, 301, 204, 101, 306, 313, 202, 303, 410, 401, 105, 407, 408]
    >>> print_deck_twice(a)

    101 104 105 202 204 301 303 305 306 311 313 401 407 408 409 410 

    101 301 401 202 303 104 204 105 305 306 407 408 409 410 311 313 
    >>> a
    [311, 409, 305, 104, 301, 204, 101, 306, 313, 202, 303, 410, 401, 105, 407, 408]

    '''
    print()
    tmp=hand[:]
    tmp.sort()
    for item in tmp:
        print(item, end=' ')
    print("\n")

    tmp=[]
    for card in hand:
        card=str(card)
        tmp.append(card[1:]+card[0])
    tmp.sort()
    for card in tmp:
        print(card[-1]+card[:2], end=" ")
    print()
        


def is_valid(cards, player):
    '''(list of int, list of int)->bool
    Function returns True if every card in cards is the player's hand.
    Otherwise it prints an error message and then returns False,
    as illustrated in the followinng example runs.

    Precondition: cards and player are subsets of the strange deck.
    
    >>> is_valid([210,310],[201, 201, 210, 302, 311])
    310 not in your hand. Invalid input
    False

    >>> is_valid([210,310],[201, 201, 210, 302, 310, 401])
    True
    '''
    for card in cards:
        if card not in player:
            print(card, "not in your hand. Invalid input")
            return False
    return True

def is_discardable_kind(cards):
    '''(list of int)->True
    Function returns True if cards form one meld of 2-, 3- or 4- of a kind of the strange deck.
    Otherwise it returns False. If there  is not enough cards for a meld it also prints  a message about it,
    as illustrated in the followinng example runs.
    
    Precondition: cards is a subset of the strange deck.

    In this function you CANNOT use strings except in calls to print function. 
    In particular, you cannot conver elements of cards to strings.
    
    >>> is_discardable_kind([207, 107, 407])
    True
    >>> is_discardable_kind([207, 107, 405, 305])
    False
    >>> is_discardable_kind([182, 382, 282, 182])
    True
    >>> is_discardable_kind([207])
    Invalid input. Discardable set needs to have at least 2 cards.
    False
    '''

    
    good=True
    if len(cards)<=1:
        print("Invalid input. Discardable set needs to have at least 2 cards.")
        good=False
    else:
        cards.sort()
        for i in range(len(cards)-1):
            if cards[i]%100!=cards[i+1]%100:
                good=False
    return good
    


def is_discardable_seq(cards):
    '''(list of int)->True
    Function returns True if cards form a progression of the strange deck.
    Otherwise it prints an error message and then returns False,
    as illustrated in the followinng example runs.
    Precondition: cards is a subset of the strange deck.

    In this function you CANNOT use strings except in calls to print function. 
    In particular, you cannot conver elements of cards to strings.

    >>> is_discardable_seq([313, 311, 312])
    True
    >>> is_discardable_seq([311, 312, 313, 414])
    Invalid sequence. Cards are not of same suit.
    False
    >>> is_discardable_seq([477, 478, 476, 475, 479])
    True
    >>> is_discardable_seq([311,312,313,316])
    Invalid sequence. While the cards are of the same suit the ranks are not consecutive integers.
    False
    >>> is_discardable_seq([201, 202])
    Invalid sequence. Discardable sequence needs to have at least 3 cards.
    False
    >>> is_discardable_seq([])
    Invalid sequence. Discardable sequence needs to have at least 3 cards.
    False
    '''
    
    good=True
    if len(cards)<=2:
        print("Invalid sequence. Discardable sequence needs to have at least 3 cards.")
        good=False
    else:
        for i in range(len(cards)-1): # test that they are of same suit
            if cards[i]//100!=cards[i+1]//100:
                good=False
        if not good:
            print("Invalid sequence. Cards are not of same suit.")
        else:
            cards.sort()
            for i in range(len(cards)-1): # test if increasing
                if cards[i]%100!=cards[i+1]%100-1:
                    print("Invalid sequence. While the cards are of the same suit the ranks are not consecutive integers.")
                    good=False
    return good

def rolled_one_round(player):
    '''(list of int)->None
    This function plays the part when the player rolls 1
    Precondition: player is a subset of the strange deck.

    In this funnction when you ask the player for one card to discard,
    you may assume they will indeed follow the instructions and enter one number.
    However you may not assume that that number is in the deck.

    >>> #example 1:
    >>> rolled_one_round(player)
    Discard any card of your choosing.
    Which card would you like to discard? 103
    103
    No such card in the deck. Try again.
    Which card would you like to discard? 102

    Here is your new hand printed in two ways:

    201 212 311 

    201 311 212 

    '''
    
    print("Discard any card of your choosing.")
    card=int(input("Which card would you like to discard? ").strip())
    print(card)
    while card not in player:
        print("No such card in your hand. Try again.")
        card=int(input("Which card would you like to discard? ").strip())
    player.remove(card)
    print("\nHere is your new hand printed in two ways:")
    print_deck_twice(player)    

def rolled_nonone_round(player):
    '''(list of int)->None
    This function plays the part when the player rolls 2, 3, 4, 5, or 6.
    Precondition: player is a subset of the strange deck

    >>> #example 1:
    >>> player=[401, 102, 403, 104, 203]
    >>> rolled_nonone_round(player)
    Yes or no, do you  have a sequences of three or more cards of the same suit or two or more of a kind? yes
    Which 3+ sequence or 2+ of a kind would you like to discard? Type in cards separated by space: 102 103 104
    103 not in your hand. Invalid input
    Yes or no, do you  have a sequences of three or more cards of the same suit or two or more of a kind? yes
    Which 3+ sequence or 2+ of a kind would you like to discard? Type in cards separated by space: 403 203

    Here is your new hand printed in two ways:

    102 104 401 

    401 102 104 
    Yes or no, do you  have a sequences of three or more cards of the same suit or two or more of a kind? no

    >>> #example 2:
    >>> player=[211, 412, 411, 103, 413]
    >>> rolled_nonone_round(player)
    Yes or no, do you  have a sequences of three or more cards of the same suit or two or more of a kind? yes
    Which 3+ sequence or 2+ of a kind would you like to discard? Type in cards separated by space: 411 412 413

    Here is your new hand printed in two ways:

    103 211 

    103 211 
    Yes or no, do you  have a sequences of three or more cards of the same suit or two or more of a kind? no

    >>> #example 3:
    >>> player=[211, 412, 411, 103, 413]
    >>> rolled_nonone_round(player)
    Yes or no, do you  have a sequences of three or more cards of the same suit or two or more of a kind? yes
    Which 3+ sequence or 2+ of a kind would you like to discard? Type in cards separated by space: 411 412
    Invalid sequence. Discardable sequence needs to have at least 3 cards.

    >>> #example 4:
    >>> player=[401, 102, 403, 104, 203]
    >>> rolled_nonone_round(player)
    Yes or no, do you  have a sequences of three or more cards of the same suit or two or more of a kind? alsj
    Yes or no, do you  have a sequences of three or more cards of the same suit or two or more of a kind? hlakj
    Yes or no, do you  have a sequences of three or more cards of the same suit or two or more of a kind? 22 33
    Yes or no, do you  have a sequences of three or more cards of the same suit or two or more of a kind? no
    '''

    answer=''
    while answer!='yes' and  answer!='no':
            answer=input("Yes or no, do you  have a sequences of three or more cards of the same suit or two or more of a kind? ")
            
    valid=True
    discardable=True
    while answer=='yes' and  valid and discardable:
        cards=input("Which 3+ sequence or 2+ of a kind would you like to discard? Type in cards separated by space: ")
        cards=cards.strip().split()
        tmp=[]
        for card in cards:
            tmp.append(int(card))
        cards=tmp
             
        valid=is_valid(cards, player)
        if valid:
            discardable=is_discardable_kind(cards) or is_discardable_seq(cards)  # cards meet discardability rules
        if valid and discardable:
            for card in cards:
                player.remove(card)
            print("\nHere is your new hand printed in two ways:")
            print_deck_twice(player)
        valid=True
        discardable=True
        answer=''
        while answer!='yes' and  answer!='no':
            answer=input("Yes or no, do you  have a sequences of three or more cards of the same suit or two or more of a kind? ")



# main
print("Welcome to Single Player Rummy with Dice and strange deck.\n")
size_change=input("The standard deck  has 52 cards: 13 ranks times 4 suits.\nWould you like to change the number of cards by changing the number of ranks? ").strip().lower()
if size_change=='yes':
    num_ranks=int(input("Enter a number between 3 and 99, for  the number  of ranks: "))
    while num_ranks<3 or num_ranks>99:
        num_ranks=int(input("Invalid input. Enter a number between 3 and 99, for the  number of ranks: "))
else:
    num_ranks=13 # standard deck has 13 ranks: A, 2, ..., 10, J, Q and K
    
deck=make_deck(num_ranks)
print("You are playing with a deck of", len(deck), "cards")
shuffle_deck(deck)
player=deal_cards_start(deck)

player.sort()
print("Here is your starting hand printed in two ways:")
print_deck_twice(player)

play_round=0
while len(player)>0:
    play_round=play_round+1
    
    print("Welcome to round "+str(play_round)+".")
    if len(deck)!=0:
        print("Please roll the dice.")
        num=random.randint(1,6)
        print("You rolled the dice and it shows:", num)
        player.sort()
        if num==1:
            rolled_one_round(player)
            print("Round", play_round, "completed.")
        else:
            print("Since your rolled,",num,"the following", num, "or", len(deck), "(if the deck has less than", num, "cards) will be added to your hand from the top of the deck.")
            deal_new_cards(deck, player, num)
            print("\nHere is your new hand printed in two ways:")
            print_deck_twice(player)
            rolled_nonone_round(player)
            print("Round", play_round, "completed.")
    else:
        print("The game is in empty deck phase.")
        rolled_one_round(player)
        print("Round", play_round, "completed.")
        
                
print("Congratulations, you completed the game in", play_round, "rounds.")
