""" The Game of Pig Name: Login: TA: Section: """ 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_total -- 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 player's turn score 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 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_total -- 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 player's turn score 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(tactic, dice=make_fair_die(), who='Someone', comments=False): """Simulate a single turn and return the points scored for the whole turn. Important: The dice function should be called once, **and only once**, for every action taken! Testing depends upon this fact. Arguments: tactic -- 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 """ turn_score = 0 # Points scored in the whole turn "*** YOUR CODE HERE ***" return turn_score def take_turn_test(): """Test the take_turn function using test dice.""" tactic = make_roll_until_tactic(10) # tactic is a higher-order # function (see problem 2) print('----- Testing take_turn() with deterministic test dice -----') "*** YOUR CODE HERE ***" print('\nTests passed') # Will only print if all assert statements pass print('\n----- Value of take_turn() with non-deterministic test dice -----') print(take_turn(tactic)) # Not deterministic # Commentating def commentate(action, outcome, turn_score, 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 turn_score -- 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 """ if action is roll: print(draw_number(outcome)) else: print() print(who, describe_action(action)) if over: print(who, 'scored', turn_score, '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. If a number has multiple possible representations (such as 2 and 3), any valid representation is acceptable. >>> 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_tactic(turn_goal=20): """Return a tactic to roll until turn total is at least turn_goal.""" def tactic(turn_total): if turn_total >= turn_goal: return hold else: return roll return tactic def make_roll_until_strategy(turn_goal): """Return a strategy that returns a tactic that will roll until it reaches turn_goal. A strategy is a function that takes two game scores as arguments (the player's score, and the opponent's score), and returns a tactic (which is a function from turn totals to actions). Note: The player's turn_total need not exceed the turn_goal in order to hold. If the player's turn_total is equal to the turn_goal, then your tactic should hold. >>> strategy = make_roll_until_strategy(15) >>> tactic = strategy(0, 0) >>> tactic(14) == roll True >>> tactic(15) == hold True >>> tactic(16) == hold True """ "*** YOUR CODE HERE ***" # Experiments (Phase 2) def make_average(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 = make_average(die) >>> avg_die() 4.0 >>> avg_turn = make_average(take_turn) >>> avg_turn(make_roll_until_tactic(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 - make_average(play)(strategy, baseline) as_second = make_average(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 tactic. 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 final_strategy_test(win_rate): """Tests how many times final_strategy returns higher than win_rate. This function runs compare_strategies 100 times and counts how many times final_strategy wins less than the desired win_rate, and how many times it wins over the desired win_rate. It also computes the average win rate. win_rate -- the desired win rate """ above = below = total = 0 for i in range(100): score = compare_strategies(final_strategy) total += score if score < win_rate: below += 1 else: above += 1 print('----- Testing final_strategy -----') print('Number of times above', win_rate, ':', above) print('Number of times below', win_rate, ':', below) print('Average win rate:', total / 100) if above > below: print('Good job!') ################################################################# ## You don't need to look at this unless you really want to ## ################################################################# def interactive_strategy(score, opponent_score): """Prints total game scores and returns an interactive tactic. Note: this function uses Python syntax not yet covered in the course. """ print('You have', score, 'and they have', opponent_score, 'total score') def tactic(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 tactic def setup_args(): """Reads in the command-line argument, and chooses an appropriate action. Note: this function uses Python syntax/techniques not yet covered in this course. You do not need to understand how this works. """ import argparse fns = (('take_turn_test', take_turn_test), ('play', lambda : play(interactive_strategy, make_roll_until_strategy(20))), ('run_strat_exps', run_strategy_experiments), ('final_strat_test', lambda : final_strategy_test(0.6))) description = "Run your project code in a specific manner. For \ instance, to run the take_turn_test code, do: 'python3 pig.py \ --take_turn_test'. Defaults to {0} if no argument is given.".format(fns[0][0]) parser = argparse.ArgumentParser(description=description) parser.add_argument('--take_turn_test', '-t', action='store_true', dest='take_turn_test') parser.add_argument('--play', '-p', action='store_true', dest='play') parser.add_argument('--run_strat_exps', '-r', action='store_true', dest='run_strat_exps') parser.add_argument('--final_strat_test', '-f', action='store_true', dest='final_strat_test') parser.add_argument('--all_tests', '-a', action='store_true', dest='all_tests') args = parser.parse_args() if args.all_tests: return (take_turn_test, run_strategy_experiments, lambda : final_strategy_test(0.6)) return_fns = [] for name, fn in fns: if getattr(args, name): return_fns.append(fn) return return_fns @main def run(*args): fns = setup_args() for fn in fns: fn()