pig.py (plain text)


"""The Game of Pig"""

from dice import make_fair_die, make_test_die
from ucb import main, trace, log_current_line, interact

goal = 100  # The goal of pig is always to score 100 points.

# Taking turns

def roll(turn_total, outcome):
    """Performs the roll action, which adds outcome to turn_total, or loses the
    turn on outcome == 1.

    Arguments:
    turn -- number of points accumulated by the player so far during the turn
    outcome -- the outcome of the roll (the number generated by the die)

    Returns three values in order:
    - the number of points the player scores after the roll
      Note: If the turn is not over after this roll, this return value is 0.
            No points are scored until the end of the turn.
    - the player turn point total after the roll
    - a boolean; whether or not the player's turn is over
    
    >>> roll(7, 3)
    (0, 10, False)
    >>> roll(99, 1)
    (1, 0, True)
    """
    "*** YOUR CODE HERE ***"

def hold(turn_total, outcome):
    """Performs the hold action, which adds turn_total to the player's score.

    Arguments:
    turn -- number of points accumulated by the player so far during the turn
    outcome -- the outcome of the roll, ie. the number generated by the die

    Returns three values in order:
    - the number of points the player scores after holding
    - the player turn total after the roll (always 0)
    - a boolean; whether or not the player's turn is over
    
    >>> hold(99, 1)
    (99, 0, True)
    """
    "*** YOUR CODE HERE ***"

def take_turn(plan, dice=make_fair_die(), who='Someone', comments=False):
    """Simulate a single turn and return the points scored for the whole turn.

    Important: The d function should be called once, **and only once**, for
               every action taken!  Testing depends upon this fact.
    
    Arguments:
    plan -- a function that takes the turn total and returns an action function
    dice -- a function that takes no args and returns an integer outcome.
            Note: dice is non-pure!  Call it exactly once per action.
    who -- name of the current player
    comments -- a boolean; whether commentary is enabled
    """
    score_for_turn = 0  # Points scored in the whole turn
    "*** YOUR CODE HERE ***"
    return score_for_turn

def take_turn_test():
    """Test the take_turn function using deterministic test dice."""
    plan = make_roll_until_plan(10)  # plan is a function (see problem 2)
    "*** YOUR CODE HERE ***"
    print(take_turn(plan))  # Not deterministic


# Commentating

def commentate(action, outcome, score_for_turn, turn_total, over, who):
    """Print descriptive comments about a game event.
    
    action -- the action function chosen by the current player
    outcome -- the outcome of the die roll
    score_for_turn -- the points scored in this turn by the current player
    turn_total -- the current turn total
    over -- a boolean that indicates whether the turn is over
    who -- the name of the current player 
    """
    print(draw_number(outcome))
    print(who, describe_action(action))
    if over:
        print(who, 'scored', score_for_turn, 'point(s) on this turn.')
    else:
        print(who, 'now has a turn total of', turn_total, 'point(s).')

def describe_action(action):
    """Generate a string that describes an action.

    action -- a function, which should be either hold or roll    

    If action is neither the hold nor roll function, the description should
    announce that cheating has occurred.

    >>> describe_action(roll)
    'chose to roll.'
    >>> describe_action(hold)
    'decided to hold.'
    >>> describe_action(commentate)
    'took an illegal action!'
    """
    "*** YOUR CODE HERE ***"
    return 'did something...'
 
def draw_number(n, dot='*'):
    """Return an ascii art representation of rolling the number n.

    >>> print(draw_number(5))
     -------
    | *   * |
    |   *   |
    | *   * |
     -------
    """
    "*** YOUR CODE HERE ***"
    return ''

def draw_die(c, f, b, s, dot):
    """Return an ascii art representation of a die.

    c, f, b, & s are boolean arguments. This function returns a multi-line
    string of the following form, where the letters in the diagram are either
    filled if the corresponding argument is true, or empty if it is false.
    
     -------
    | b   f |
    | s c s |
    | f   b |
     -------    

    Note: The sides with 2 and 3 dots have 2 possible depictions due to
          rotation. Either representation is acceptable. 

    Note: This function uses Python syntax not yet covered in the course.
    
    c, f, b, s -- booleans; whether to place dots in corresponding positions
    dot        -- A length-one string to use for a dot
    """
    border = ' -------'
    def draw(b): 
        return dot if b else ' '
    c, f, b, s = map(draw, [c, f, b, s])
    top =    ' '.join(['|', b, ' ', f, '|'])
    middle = ' '.join(['|', s, c,   s, '|'])
    bottom = ' '.join(['|', f, ' ', b, '|'])
    return '\n'.join([border, top, middle, bottom, border])


# Game simulator

def play(strategy, opponent_strategy):
    """Simulate a game and return 0 if the first player wins and 1 otherwise.
    
    strategy -- The strategy function for the first player (who plays first)
    opponent_strategy -- The strategy function for the second player
    """
    who = 0 # Which player is about to take a turn, 0 (first) or 1 (second)
    "*** YOUR CODE HERE ***"
    return who

def other(who):
    """Return the other player, for players numbered 0 and 1.
    
    >>> other(0)
    1
    >>> other(1)
    0
    """
    return (who + 1) % 2


# Basic Strategies

def make_roll_until_plan(turn_goal=20):
    """Return a plan to roll until turn total is at least turn_goal."""
    def plan(turn):
        if turn >= turn_goal:
            return hold
        else:
            return roll
    return plan

def make_roll_until_strategy(turn_goal):
    """Return a strategy to always adopt a plan to roll until turn_goal.
    
    A strategy is a function that takes two game scores as arguments and
    returns a plan (which is a function from turn totals to actions).
    """
    "*** YOUR CODE HERE ***"

def make_roll_until_strategy_test():
    """Test that make_roll_until_strategy gives a strategy that returns correct
    roll-until plans."""
    strategy = make_roll_until_strategy(15)    
    plan = strategy(0, 0)
    assert plan(14) == roll, 'Should have returned roll'
    assert plan(15) == hold, 'Should have returned hold'
    assert plan(16) == hold, 'Should have returned hold'


# Experiments (Phase 2)

def average_value(fn, num_samples):
    """Compute the average value returned by fn over num_samples trials.
    
    >>> d = make_test_die(1, 3, 5, 7)
    >>> average_value(d, 100)
    4.0
    """
    "*** YOUR CODE HERE ***"

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

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

    >>> die = make_test_die(3, 1, 5, 7)
    >>> avg_die = averaged(die)
    >>> avg_die()
    4.0
    >>> avg_turn = averaged(take_turn)
    >>> avg_turn(make_roll_until_plan(4), die, 'The player', False)
    3.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 (then holds on the 7), scoring 5.
    Thus, the average value is 3.0

    Note: If this last test is called with comments=True in take_turn, the
    doctests will fail because of the extra output.
    """
    "*** YOUR CODE HERE ***"

def compare_strategies(strategy, baseline=make_roll_until_strategy(20)):
    """Return the average win rate (out of 1) of strategy against baseline."""
    as_first = 1 - averaged(play)(strategy, baseline)
    as_second = averaged(play)(baseline, strategy)
    return (as_first + as_second) / 2  # Average the two results

def eval_strategy_range(make_strategy, lower_bound, upper_bound):
    """Return the best integer argument value for make_strategy to use against
    the roll-until-20 baseline, between lower_bound and upper_bound (inclusive).

    make_strategy -- A one-argument function that returns a strategy.
    lower_bound -- lower bound of the evaluation range
    upper_bound -- upper bound of the evaluation range
    """
    best_value, best_win_rate = 0, 0
    value = lower_bound
    while value <= upper_bound:
        strategy = make_strategy(value)
        win_rate = compare_strategies(strategy)
        print(value, 'win rate against the baseline:', win_rate) 
        if win_rate > best_win_rate:
            best_win_rate, best_value = win_rate, value
        value += 1
    return best_value

def run_strategy_experiments():
    """Run a series of strategy experiments and report results."""
    "*** YOUR CODE HERE ***"

def make_die_specific_strategy(four_side_goal, six_side_goal=20):
    """Return a strategy that returns a die-specific roll-until plan.
    
    four_side_goal -- the roll-until goal whenever the turn uses a 4-sided die
    six_side_goal -- the roll-until goal whenever the turn uses a 6-sided die

    """
    "*** YOUR CODE HERE ***"

def make_pride_strategy(margin, turn_goal=20):
    """Return a strategy that wants to finish a turn winning by at least margin.

    margin -- the size of the lead that the player requires
    turn_goal -- the minimum roll-until turn goal, even when winning
    """
    "*** YOUR CODE HERE ***"

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

    *** YOUR DESCRIPTION HERE ***
    """
    "*** YOUR CODE HERE ***"

def interactive_strategy(score, opponent_score):
    """Prints total game scores and returns an interactive plan.
    
    Note: this function uses Python syntax not yet covered in the course.
    """
    print('You have', score, 'and they have', opponent_score, 'total score')
    def plan(turn):
        if turn > 0:
            print('You now have a turn total of', turn, 'points')
        while True:
            response = input('(R)oll or (H)old?')
            if response.lower()[0] == 'r':
                return roll
            elif response.lower()[0] == 'h':
                return hold
            print('Huh?')
    return plan

@main
def run():
    take_turn_test()

    # Uncomment the next line to play an interactive game
    # play(interactive_strategy, make_roll_until_strategy(20))

    # Uncomment the next line to test make_roll_until_strategy
    # make_roll_until_strategy_test()

    run_strategy_experiments()