import random
history = {}            # ➊ 
all_options = {         # ➋
                                       # upper section:
           "Ones":            5,
           "Twos":            10,
           "Threes":          15,
           "Fours":           20,
           "Fives":           25,
           "Sixes":           30,
                                        # lower section
           "Three Of A Kind": 30,
           "Four Of A Kind":  30,
           "Full House":      25,
           "Small Straight":  30,
           "Large Straight":  40,
           "Yahtzee":         50,
           "Chance":          30,
           }

# functions to test options

def is_three_of_a_kind(dice):              # 
    """dice is a list of five integers"""  #  
    for number in dice:                    # 
        if dice.count(number) >= 3:        # 
            return True                    # 
    return False  

def is_four_of_a_kind(dice):               # 
    """dice is a list of five integers"""  #  
    for number in dice:                    # 
        if dice.count(number) >= 4:        # 
            return True                    # 
    return False  

def is_full_house(dice):                      # 
    """dice is a list of five integers"""     #  
    diceset = set(dice)                       # 
    if len(diceset) == 2:                     # 
        for number in diceset:                # 
            if dice.count(number) in (2,3):   # 
                return True                   # 
    return False                              # 

def is_small_straight(dice):                  # 
    """dice is a list of five integers"""     #  
    diceset = set(dice)                       # 
    if any((diceset.issuperset({1,2,3,4}),    # 
            diceset.issuperset({2,3,4,5}),
            diceset.issuperset({3,4,5,6}))):
        return True                           # 
    return False                          

def is_large_straight(dice):                  # 
    """dice is a list of five integers"""     #  
    diceset = set(dice)                       # 
    if (diceset.issuperset({1,2,3,4,5}) or    # 
        diceset.issuperset({2,3,4,5,6})):
        return True                           # 
    return False   

def is_yahtzee(dice):                      # 
    """dice is a list of five integers"""  #  
    return len(set(dice)) == 1             # 
    
def calculate_score(dice, name_of_option): # 
    """calculate how much points an option would score using dice"""    
    # assert expression, error_message
    assert len(dice) == 5, f"{dice} must have five elements" 
    error_message = f"{dice} must have only integers"
    assert [type(x) for x in dice] == [int,int,int,int,int], error_message 
    error_message = f"{name_of_option} must be in {all_options.keys()}"
    assert name_of_option in all_options, error_message 

    # joker rule?
    option_list = list(all_options) # 
    joker = False
    upper_name = option_list[dice[0]-1] # 
    if all((is_yahtzee(dice),           # 
            "Yahtzee" in history,
            upper_name in history)):
        joker = True
    match name_of_option:               # 
        # first test the lower section options
        case "Three Of A Kind":
            return sum(dice) if is_three_of_a_kind(dice) else 0 # 
        case "Four Of A Kind":
            return sum(dice) if is_four_of_a_kind(dice) else 0
        case  "Full House":
            return 25 if (is_full_house(dice) or joker) else 0
        case  "Small Straight":
            return 30 if (is_small_straight(dice) or joker) else 0
        case "Large Straight":
            return 40 if (is_large_straight(dice) or joker) else 0
        case "Yahtzee":
            return 50 if is_yahtzee(dice) else 0
        case "Chance":
            return sum(dice)
        case _:  
            # it's an upper section option:  Ones, Tows, ... Sixes
            number = option_list.index(name_of_option) + 1 # 
            return dice.count(number) * number             # 

def calculate_final_scores(history):   # 
    """returns a tuple of 4 integers: 
           sum_upper_section,
           upper_section_bonus,
           sum_lower_section,
           yahtzee_bonus
    """                                # 
    sum_upper_section = 0              # 
    sum_lower_section = 0
    upper_section_bonus = 0
    yahtzee_bonus = 0
    allow_yahtzee_bonus = False
    for name in history:               #      
        scored = history[name][0]      # 
        dice = history[name][1]        # 
        # is upper section?            # 
        if name in ("Ones", "Twos", "Threes",
                    "Fours", "Fives", "Sixes"):
            sum_upper_section += scored
        else:
            sum_lower_section += scored
        # is Yahtzee?                  # 
        if len(set(dice)) == 1: 
            # enable Yahtzee bonus ?
            if scored == 50 and not allow_yahtzee_bonus:
                allow_yahtzee_bonus = True
            elif allow_yahtzee_bonus:
                yahtzee_bonus += 100
    if sum_upper_section >= 63:        # 
        upper_section_bonus = 35
    return sum_upper_section, upper_section_bonus, sum_lower_section, yahtzee_bonus

# main function

def game():
    for game_round in range(1,14):                    
        dicelist = []                                     
        for _ in range(5):    
            dicelist.append(random.randint(1,6))  
        print(f"-------- round {game_round} of 13--------------")  
        for throw in (1,2,3):                         
            text= "throw: {} of 3".format(throw)      
            print(text)
            print(" a  b  c  d  e")
            print(dicelist)
            if throw < 3:
                text = "Type letter(s) of dice to keep and press ENTER >>>"
                command = input(text).lower()              
                for i, char in enumerate("abcde") :         
                    if char not in command:                
                        dicelist[i] = random.randint(1,6)   

        print(f"------- end of round {game_round}------")
        # calculate playable options    # 
        unplayed = [name for name in all_options if name not in history]
        while True:                     #  
            # create menu, starting with number 1 for "Ones"
            for name in unplayed:     
                print(list(all_options).index(name)+1, name) #  
            command = input("enter number of option to play >>>")
            try:                         # 
                number = int(command)    # 
            except ValueError:           # 
                print("Please enter only a NUMBER")
                continue                 # 
            if not (0 < number < 14):    # 
                print("Number out of allowed range, try again")
                continue                 # 
            #try:
            option = list(all_options)[number-1] #  
            #except IndexError:
            #    print("Please enter only a listed number")
            #    continue
            if option not in unplayed:   # 
                print(f"You already played {option}, try again")
                continue                 # 
            print("You play: ", option)
            break                        # 
        # option should be correct now
        #points = all_options[option]     #  
        points = calculate_score(dicelist, option)
        print(f"you get {points} points!")
        if game_round == 13:                       
            text = "press ENTER to see the history of dice throws >>>"
        else:
            text = "press ENTER to start the next game round >>>"
        input(text)                            
        history[option] = (points, dicelist.copy())  # 
    print("Game Over")  
    print("--- history of your dice throws: ---")
    print("game round,  played_option, points, dicelist")
    sum_upper, upper_bonus, sum_lower, yat_bonus = calculate_final_scores(history)
    print("option,          score (of max.)    dice")
    for name in all_options:
        print("{:<20}   {:>2} (of {:>2})   {:<20}".format(
            name,
            history[name][0],
            all_options[name],
            str(history[name][1])
        ))
        if name == "Sixes":
            print("-------------------------")
            print("sum upper section:", sum_upper)
            print("upper section bonus:", upper_bonus)
    print("--------------------")
    print("sum lower section", sum_lower)
    print("Yahtzee bonus:", yat_bonus)
    print("==========================")
    print("final score:",
           sum_upper+upper_bonus+sum_lower+yat_bonus)
    
            


if __name__ == "__main__":
    game()
