
##########################
# Command Line Interface #
##########################

# Note: Functions in this section do not need to be changed.  They use features
#       of Python not yet covered in the course.

from hog import roll_dice
from dice import *
import random

def run(*args):
    """Read in the command-line argument and calls corresponding functions.

    This function uses Python syntax/techniques not yet covered in this course.
    """
    import argparse
    parser = argparse.ArgumentParser(description="Play Hog")
    parser.add_argument('--final', action='store_true',
                        help='Display the final_strategy win rate against always_roll(5)')
    parser.add_argument('--run_experiments', '-r', action='store_true',
                        help='Runs strategy experiments')
    args = parser.parse_args()

    if args.run_experiments:
        run_experiments()
    elif args.final:
        #from hog_eval import final_win_rate
        win_rate = final_win_rate()
        print('Your final_strategy win rate is')
        print('    ', win_rate)
        print('(or {}%)'.format(round(win_rate * 100, 2)))

#######################
# Phase 2: Strategies #
#######################

from hog import *
from dice import *

def always_roll(n):
    """Return a strategy that always rolls N dice.

    A strategy is a function that takes two total scores as arguments
    (the current player's score, and the opponent's score), and returns a
    number of dice that the current player will roll this turn.

    >>> strategy = always_roll(5)
    >>> strategy(0, 0)
    5
    >>> strategy(99, 99)
    5
    """
    def strategy(score, opponent_score):
        return n
    return strategy

# Experiments

def make_averaged(fn, num_samples=1000):
    """Return a function that returns the average_value of FN when called.

    To implement this function, you will have to use *args syntax, a new Python
    feature introduced in this project.  See the project description.

    >>> dice = make_test_dice(3, 1, 5, 6)
    >>> averaged_dice = make_averaged(dice, 1000)
    >>> averaged_dice()
    3.75
    >>> make_averaged(roll_dice, 1000)(2, dice)
    6.0

    In this last example, two different turn scenarios are averaged.
    - In the first, the player rolls a 3 then a 1, receiving a score of 1.
    - In the other, the player rolls a 5 and 6ma, scoring 11.
    Thus, the average value is 6.0.
    """
    # BEGIN Question 6
    def helper(*args):
        total = 0
        for it in range(num_samples):
            total += fn(*args)

        return total / 1000

    return helper
    # END Question 6

def max_scoring_num_rolls(dice=six_sided, num_samples=1000):
    """Return the number of dice (1 to 10) that gives the highest average turn
    score by calling roll_dice with the provided DICE over NUM_SAMPLES times.
    Assume that dice always return positive outcomes.

    >>> dice = make_test_dice(3)
    >>> max_scoring_num_rolls(dice)
    10
    """
    # BEGIN Question 7
    average = make_averaged(roll_dice, num_samples)
    results = [0] * 10
    for it in range(10):
        results[it] = average(it+1, dice)

    return results.index(max(results))+1
    # END Question 7

def winner(strategy0, strategy1):
    """Return 0 if strategy0 wins against strategy1, and 1 otherwise."""
    score0, score1 = play(strategy0, strategy1)
    if score0 > score1:
        return 0
    else:
        return 1

def average_win_rate(strategy, baseline=always_roll(5)):
    """Return the average win rate (0 to 1) of STRATEGY against BASELINE."""
    win_rate_as_player_0 = 1 - make_averaged(winner)(strategy, baseline)
    win_rate_as_player_1 = make_averaged(winner)(baseline, strategy)
    return (win_rate_as_player_0 + win_rate_as_player_1) / 2 # Average results

def run_experiments():
    """Run a series of strategy experiments and report results."""
    score = 0 #random.randint(0,100)
    opponent_score = 0 #randint(0,100)

    if True: # Change to False when done finding max_scoring_num_rolls
        six_sided_max = max_scoring_num_rolls(six_sided)
        print('Max scoring num rolls for six-sided dice:', six_sided_max)
        four_sided_max = max_scoring_num_rolls(four_sided)
        print('Max scoring num rolls for four-sided dice:', four_sided_max)

    if True: # Change to True to test always_roll(8)
        print('always_roll(5) win rate:', average_win_rate(always_roll(5)))

    if True: # Change to True to test bacon_strategy
        print('bacon_strategy win rate:', average_win_rate(bacon_strategy(score, opponent_score)))

    if True: # Change to True to test swap_strategy
        print('swap_strategy win rate:', average_win_rate(swap_strategy(score, opponent_score)))

    if True:
        wr = average_win_rate(final_strategy(score, opponent_score))

        print('final_strategy win rate:', wr)

    "*** You may add additional experiments as you wish ***"

# Strategies

def bacon_strategy(score, opponent_score, margin=8, num_rolls=5):
    """This strategy rolls 0 dice if that gives at least MARGIN points,
    and rolls NUM_ROLLS otherwise.
    """
    # BEGIN Question 8
    def strategy(score, opponent_score):
        if free_bacon(opponent_score) >= margin:
            return 0
        else:
            return num_rolls

    return strategy
    # END Question 8

def swap_strategy(score, opponent_score, margin=8, num_rolls=5):
    """This strategy rolls 0 dice when it results in a beneficial swap and
    rolls NUM_ROLLS if rolling 0 dice results in a harmful swap. It also
    rolls 0 dice if that gives at least MARGIN points and rolls NUM_ROLLS
    otherwise.
    """
    # BEGIN Question 9
    def strategy(score, opponent_score):
        post_bacon = score + free_bacon(opponent_score)
        if opponent_score > post_bacon and is_swap(post_bacon, opponent_score):
            return 0
        else:
            return bacon_strategy(score, opponent_score, margin, num_rolls)(score, opponent_score)

    return strategy
    # END Question 9


def final_strategy(score, opponent_score):
    """Write a brief description of your final strategy.

    The final strategy alternates between conservative an aggressive, based on
    score differential.

    If I am winning by a large margin (30 points), default to free_bacon if it
    does not cause a swap.

    If I am winning by a smaller margin, use more regular rolls if appropriate.

    If I am losing, seek to invoke swap.

    The number of dice used is based on the score differential.
    """
    # BEGIN Question 10
    def strategy(score, opponent_score):
        losing =        score <= opponent_score
        post_bacon =    score + free_bacon(opponent_score)
        dice =          select_dice(score, opponent_score)
        will_be_four =  select_dice(post_bacon, opponent_score) == four_sided
        swap =          is_swap(post_bacon, opponent_score)

        if post_bacon >= 100 and not swap:
            return 0


        if dice == four_sided:
            num_rolls = 4
        elif dice == six_sided:
            num_rolls =  6

        if not losing and score - opponent_score > 15:
            num_rolls -= 2
        elif losing and opponent_score - score > 15:
            num_rolls += 1

        if not losing:
            if swap:
                return num_rolls
            elif will_be_four:
                return 0
            else:
                return  bacon_strategy(score, opponent_score, 8, num_rolls)(score, opponent_score)
        else:
            if (swap and not post_bacon > opponent_score) or will_be_four:
                return 0
            else:
                return bacon_strategy(score, opponent_score, 8, num_rolls)(score, opponent_score)


    return strategy
    # END Question 10

def final_win_rate():
    wr = 0
    for x in range(0,1000):
        wr += average_win_rate(final_strategy(0,0))

    return wr / x

if __name__=="__main__": run()
