From 56a106ce10e51b0ab14919d9bf13f695c30fb3b6 Mon Sep 17 00:00:00 2001 From: Imfuyuwei <50607209+Imfuyuwei@users.noreply.github.com> Date: Mon, 13 May 2019 20:59:33 -0700 Subject: [PATCH] Add files via upload --- 00.py | 49 +++++++++ 01.py | 122 ++++++++++++++++++++++ 02.py | 48 +++++++++ 03.py | 130 +++++++++++++++++++++++ 04.py | 128 +++++++++++++++++++++++ 05.py | 239 ++++++++++++++++++++++++++++++++++++++++++ 06.py | 118 +++++++++++++++++++++ 07.py | 55 ++++++++++ 08.py | 92 +++++++++++++++++ 09.py | 91 ++++++++++++++++ 10.py | 114 ++++++++++++++++++++ 11.py | 98 ++++++++++++++++++ 12.py | 35 +++++++ check_strategy.py | 55 ++++++++++ play_utils.py | 258 ++++++++++++++++++++++++++++++++++++++++++++++ 15 files changed, 1632 insertions(+) create mode 100644 00.py create mode 100644 01.py create mode 100644 02.py create mode 100644 03.py create mode 100644 04.py create mode 100644 05.py create mode 100644 06.py create mode 100644 07.py create mode 100644 08.py create mode 100644 09.py create mode 100644 10.py create mode 100644 11.py create mode 100644 12.py create mode 100644 check_strategy.py create mode 100644 play_utils.py diff --git a/00.py b/00.py new file mode 100644 index 0000000..a313e0d --- /dev/null +++ b/00.py @@ -0,0 +1,49 @@ +test = { + 'name': 'Question 0', + 'points': 0, + 'suites': [ + { + 'cases': [ + { + 'code': r""" + >>> test_dice = make_test_dice(4, 1, 2) + >>> test_dice() + 4 + >>> test_dice() # Second call + 1 + >>> test_dice() # Third call + 2 + >>> test_dice() # Fourth call + 4 + """, + 'hidden': False, + 'locked': False + } + ], + 'scored': True, + 'setup': r""" + >>> from hog import * + """, + 'teardown': '', + 'type': 'doctest' + }, + { + 'cases': [ + { + 'answer': 'six_sided()', + 'choices': [ + 'make_test_dice(6)', + 'make_fair_dice(6)', + 'six_sided', + 'six_sided()' + ], + 'hidden': False, + 'locked': False, + 'question': 'Which of the following is the correct way to "roll" a fair, six-sided die?' + } + ], + 'scored': False, + 'type': 'concept' + } + ] +} diff --git a/01.py b/01.py new file mode 100644 index 0000000..9195eed --- /dev/null +++ b/01.py @@ -0,0 +1,122 @@ +test = { + 'name': 'Question 1', + 'points': 2, + 'suites': [ + { + 'cases': [ + { + 'code': r""" + >>> roll_dice(2, make_test_dice(4, 6, 1)) + 10 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> roll_dice(3, make_test_dice(4, 6, 1)) + 1 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> roll_dice(4, make_test_dice(2, 2, 3)) + 9 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> roll_dice(4, make_test_dice(1, 2, 3)) + 1 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> counted_dice = make_test_dice(4, 1, 2, 6) + >>> roll_dice(3, counted_dice) + 1 + >>> roll_dice(1, counted_dice) # Make sure you call dice exactly num_rolls times! + 6 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> roll_dice(9, make_test_dice(6)) + 54 + >>> roll_dice(7, make_test_dice(2, 2, 2, 2, 2, 2, 1)) + 1 + """, + 'hidden': False, + 'locked': False + } + ], + 'scored': True, + 'setup': r""" + >>> from hog import * + """, + 'teardown': '', + 'type': 'doctest' + }, + { + 'cases': [ + { + 'code': r""" + >>> roll_dice(5, make_test_dice(4, 2, 3, 3, 4, 1)) + 16 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> roll_dice(2, make_test_dice(1)) + 1 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> dice = make_test_dice(5, 4, 3, 2, 1) + >>> roll_dice(1, dice) # Roll 1 (5) + 5 + >>> roll_dice(4, dice) # Reset (4, 3, 2, 1) + 1 + >>> roll_dice(2, dice) # Roll 2 (5, 4) + 9 + >>> roll_dice(3, dice) # Reset (3, 2, 1) + 1 + >>> roll_dice(3, dice) # Roll 3 (5, 4, 3) + 12 + >>> roll_dice(2, dice) # Reset (2, 1) + 1 + >>> roll_dice(4, dice) # Roll 4 (5, 4, 3, 2) + 14 + >>> roll_dice(1, dice) # Reset (1) + 1 + >>> roll_dice(5, dice) # Roll 5 (5, 4, 3, 2, 1) + 1 + >>> roll_dice(10, dice) # Roll 10 (5, 4, 3, 2, 1, 5, 4, 3, 2, 1) + 1 + """, + 'hidden': False, + 'locked': False + } + ], + 'scored': True, + 'setup': r""" + >>> from hog import * + """, + 'teardown': '', + 'type': 'doctest' + } + ] +} diff --git a/02.py b/02.py new file mode 100644 index 0000000..47b0f63 --- /dev/null +++ b/02.py @@ -0,0 +1,48 @@ +test = { + 'name': 'Question 2', + 'points': 1, + 'suites': [ + { + 'cases': [ + { + 'code': r""" + >>> free_bacon(35) + 4 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> free_bacon(71) + 8 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> free_bacon(7) + 9 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> free_bacon(0) + 2 + """, + 'hidden': False, + 'locked': False + } + ], + 'scored': True, + 'setup': r""" + >>> from hog import * + """, + 'teardown': '', + 'type': 'doctest' + } + ] +} diff --git a/03.py b/03.py new file mode 100644 index 0000000..ce0f9ed --- /dev/null +++ b/03.py @@ -0,0 +1,130 @@ +test = { + 'name': 'Question 3', + 'points': 1, + 'suites': [ + { + 'cases': [ + { + 'code': r""" + >>> take_turn(2, 0, make_test_dice(4, 6, 1)) + 10 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> take_turn(3, 0, make_test_dice(4, 6, 1)) + 1 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> take_turn(0, 35) + 4 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> take_turn(0, 71) + 8 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> take_turn(0, 7) + 9 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> take_turn(0, 0) + 2 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> take_turn(2, 0, make_test_dice(6)) + 12 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> take_turn(9, 0, make_test_dice(4)) + 36 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> take_turn(8, 0, make_test_dice(4)) + 32 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> take_turn(7, 0, make_test_dice(4)) + 28 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> take_turn(6, 0, make_test_dice(4)) + 24 + """, + 'hidden': False, + 'locked': False + } + ], + 'scored': True, + 'setup': r""" + >>> from hog import * + """, + 'teardown': '', + 'type': 'doctest' + }, + { + 'cases': [ + { + 'code': r""" + >>> hog.take_turn(5, 0) # Make sure you call roll_dice! + Called roll dice! + 9002 + """, + 'hidden': False, + 'locked': False + } + ], + 'scored': True, + 'setup': r""" + >>> import hog + >>> def roll_dice(num_rolls, dice): + ... print("Called roll dice!") + ... return 9002 + ... + >>> hog.roll_dice, old_roll_dice = roll_dice, hog.roll_dice + """, + 'teardown': r""" + >>> hog.roll_dice = old_roll_dice + """, + 'type': 'doctest' + } + ] +} diff --git a/04.py b/04.py new file mode 100644 index 0000000..381c5d9 --- /dev/null +++ b/04.py @@ -0,0 +1,128 @@ +test = { + 'name': 'Question 4', + 'points': 1, + 'suites': [ + { + 'cases': [ + { + 'code': r""" + >>> is_swap(19, 91) + False + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> is_swap(20, 40) + True + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> is_swap(40, 20) + True + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> is_swap(41, 14) + False + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> is_swap(13, 32) + False + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> is_swap(1, 0) + False + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> is_swap(0, 1) + False + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> is_swap(3, 0) + False + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> is_swap(0, 3) + False + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> is_swap(10, 1) + False + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> is_swap(1, 10) + False + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> is_swap(34, 17) + True + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> is_swap(15, 30) + True + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> is_swap(1, 30) + False + """, + 'hidden': False, + 'locked': False + } + ], + 'scored': True, + 'setup': r""" + >>> from hog import * + """, + 'teardown': '', + 'type': 'doctest' + } + ] +} diff --git a/05.py b/05.py new file mode 100644 index 0000000..ff222a8 --- /dev/null +++ b/05.py @@ -0,0 +1,239 @@ +test = { + 'name': 'Question 5', + 'points': 3, + 'suites': [ + { + 'cases': [ + { + 'answer': 'While score0 and score1 are both less than goal', + 'choices': [ + 'While score0 and score1 are both less than goal', + 'While at least one of score0 or score1 is less than goal', + 'While score0 is less than goal', + 'While score1 is less than goal' + ], + 'hidden': False, + 'locked': False, + 'question': r""" + The variables score0 and score1 are the scores for Player 0 + and Player 1, respectively. Under what conditions should the + game continue? + """ + }, + { + 'answer': 'A function that returns the number of dice a player will roll', + 'choices': [ + 'The number of dice a player will roll', + 'A function that returns the number of dice a player will roll', + "A player's desired turn outcome" + ], + 'hidden': False, + 'locked': False, + 'question': 'What is a strategy in the context of this game?' + }, + { + 'answer': 'strategy1(score1, score0)', + 'choices': [ + 'strategy1(score1, score0)', + 'strategy1(score0, score1)', + 'strategy1(score1)', + 'strategy1(score0)' + ], + 'hidden': False, + 'locked': False, + 'question': r""" + If strategy1 is Player 1's strategy function, score0 is + Player 0's current score, and score1 is Player 1's current + score, then which of the following demonstrates correct + usage of strategy1? + """ + } + ], + 'scored': False, + 'type': 'concept' + }, + { + 'cases': [ + { + 'code': r""" + >>> # + >>> # Play function stops at goal + >>> s0, s1 = hog.play(always(5), always(3), score0=91, score1=10, dice=always_three) + >>> s0 + 106 + >>> s1 + 10 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> # + >>> # Goal score is not hardwired + >>> s0, s1 = hog.play(always(5), always(5), goal=10, dice=always_three) + >>> s0 + 15 + >>> s1 + 0 + """, + 'hidden': False, + 'locked': False + } + ], + 'scored': True, + 'setup': r""" + >>> import hog + >>> always_three = hog.make_test_dice(3) + >>> always = hog.always_roll + """, + 'teardown': '', + 'type': 'doctest' + }, + { + 'cases': [ + { + 'code': r""" + >>> # + >>> # Use strategies + >>> # We recommend working this out turn-by-turn on a piece of paper. + >>> strat0 = lambda score, opponent: opponent % 10 + >>> strat1 = lambda score, opponent: score // 10 + >>> s0, s1 = hog.play(strat0, strat1, score0=41, score1=80, dice=always_three) + >>> s0 + 51 + >>> s1 + 104 + """, + 'hidden': False, + 'locked': False + } + ], + 'scored': True, + 'setup': r""" + >>> import hog + >>> always_three = hog.make_test_dice(3) + """, + 'teardown': '', + 'type': 'doctest' + }, + { + 'cases': [ + { + 'code': r""" + >>> # + >>> # Goal edge case + >>> s0, s1 = hog.play(always(4), always(3), score0=88, score1=20, dice=always_three) + >>> s0 + 20 + >>> s1 + 100 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> # + >>> # Player 1 win + >>> s0, s1 = hog.play(always(4), always(4), score0=87, score1=88, dice=always_three) + >>> s0 + 99 + >>> s1 + 100 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> # Check strategies are actually used correctly + >>> strat0 = lambda score, opponent: opponent % 10 + >>> strat1 = lambda score, opponent: opponent // 10 + >>> s0, s1 = hog.play(strat0, strat1, score0=40, score1=92, dice=always_three) + >>> s0 + 101 + >>> s1 + 73 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> # + >>> # Swine swap applies during Player 1 turn + >>> s0, s1 = hog.play(always(4), always(4), score0=42, score1=96, dice=always_three) + >>> s0 + 108 + >>> s1 + 54 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> # + >>> # Free bacon refers to correct opponent score + >>> s0, s1 = hog.play(always(0), always(0), score0=11, score1=99, dice=always_three) + >>> s0 + 13 + >>> s1 + 103 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> # + >>> # Handle multiple turns with many swaps + >>> s0, s1 = hog.play(always(0), always(2), goal=15, dice=always_three) + >>> s0 + 16 + >>> s1 + 2 + """, + 'hidden': False, + 'locked': False + } + ], + 'scored': True, + 'setup': r""" + >>> import hog + >>> always_three = hog.make_test_dice(3) + >>> always = hog.always_roll + """, + 'teardown': '', + 'type': 'doctest' + }, + { + 'cases': [ + { + 'code': r""" + >>> tests.play_utils.check_play_function(hog) + """, + 'hidden': False, + 'locked': False + } + ], + 'scored': True, + 'setup': r""" + >>> # Fuzz Testing + >>> # Plays a lot of random games, and calculates a secret value. + >>> # Failing this test means something is wrong, but you should + >>> # look at other tests to see where the problem might be. + >>> # Hint: make sure you're only calling take_turn once per turn! + >>> # + >>> import hog, importlib + >>> importlib.reload(hog) + >>> import tests.play_utils + """, + 'teardown': r""" + + """, + 'type': 'doctest' + } + ] +} diff --git a/06.py b/06.py new file mode 100644 index 0000000..39a3815 --- /dev/null +++ b/06.py @@ -0,0 +1,118 @@ +test = { + 'name': 'Question 6', + 'points': 2, + 'suites': [ + { + 'cases': [ + { + 'code': r""" + >>> # + >>> def echo(s0, s1): + ... print(s0, s1) + ... return echo + >>> s0, s1 = play(always_roll(1), always_roll(1), dice=make_test_dice(3), goal=4, say=echo) + 3 0 + 3 3 + 3 6 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> # + >>> def echo(s0, s1): + ... print(s0, s1) + ... return echo + >>> s0, s1 = play(always_roll(0), always_roll(0), goal=4, say=echo) + 2 0 + 4 2 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> # + >>> # Ensure that say is properly updated within the body of play. + >>> def total(s0, s1): + ... print(s0 + s1) + ... return echo + >>> def echo(s0, s1): + ... print(s0, s1) + ... return total + >>> s0, s1 = play(always_roll(1), always_roll(1), dice=make_test_dice(2, 3), goal=5, say=echo) + 2 0 + 5 + 4 3 + 10 + """, + 'hidden': False, + 'locked': False + } + ], + 'scored': True, + 'setup': r""" + >>> from hog import play, always_roll + >>> from dice import make_test_dice + """, + 'teardown': '', + 'type': 'doctest' + }, + { + 'cases': [ + { + 'code': r""" + >>> # + >>> def echo_0(s0, s1): + ... print('*', s0) + ... return echo_0 + >>> def echo_1(s0, s1): + ... print('**', s1) + ... return echo_1 + >>> s0, s1 = play(always_roll(0), always_roll(0), goal=1, say=both(echo_0, echo_1)) + * 2 + ** 0 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> # + >>> s0, s1 = play(always_roll(3), always_roll(3), dice=make_test_dice(1, 2, 3, 3), goal=8, say=both(say_scores, announce_lead_changes())) + Player 0 now has 1 and Player 1 now has 0 + Player 0 takes the lead by 1 + Player 0 now has 1 and Player 1 now has 1 + Player 0 now has 2 and Player 1 now has 1 + Player 0 takes the lead by 1 + Player 0 now has 2 and Player 1 now has 9 + Player 1 takes the lead by 7 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> # + >>> s0, s1 = play(always_roll(0), always_roll(0), goal=8, say=both(say_scores, announce_lead_changes())) + Player 0 now has 2 and Player 1 now has 0 + Player 0 takes the lead by 2 + Player 0 now has 4 and Player 1 now has 2 + Player 0 now has 2 and Player 1 now has 8 + Player 1 takes the lead by 6 + """, + 'hidden': False, + 'locked': False + } + ], + 'scored': True, + 'setup': r""" + >>> from hog import play, always_roll, both, announce_lead_changes, say_scores + >>> from dice import make_test_dice + """, + 'teardown': '', + 'type': 'doctest' + } + ] +} diff --git a/07.py b/07.py new file mode 100644 index 0000000..57af40e --- /dev/null +++ b/07.py @@ -0,0 +1,55 @@ +test = { + 'name': 'Question 7', + 'points': 2, + 'suites': [ + { + 'cases': [ + { + 'code': r""" + >>> f0 = announce_highest(1) # Only announce Player 1 score gains + >>> f1 = f0(11, 0) + >>> f2 = f1(11, 1) + 1 point! That's the biggest gain yet for Player 1 + >>> f3 = f2(20, 1) + >>> f4 = f3(5, 20) # Player 1 gets 4 points, then Swine Swap applies + 19 points! That's the biggest gain yet for Player 1 + >>> f5 = f4(20, 40) # Player 0 gets 35 points, then Swine Swap applies + 20 points! That's the biggest gain yet for Player 1 + >>> f6 = f5(20, 55) # Player 1 gets 15 points; not enough for a new high + >>> f7 = f6(21, 55) + >>> f8 = f7(21, 75) + >>> f9 = f8(75, 25) # Swap! + >>> f10 = f9(50, 75) # Swap! + 50 points! That's the biggest gain yet for Player 1 + >>> # The following function call checks if the behavior of f1 changes, + >>> # perhaps due to a side effect other than printing. The only side + >>> # effect of a commentary function should be to print. + >>> f2_again = f1(11, 1) + 1 point! That's the biggest gain yet for Player 1 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> # + >>> announce_both = both(announce_highest(0), announce_highest(1)) + >>> s0, s1 = play(always_roll(0), always_roll(0), goal=10, say=announce_both) + 2 points! That's the biggest gain yet for Player 0 + 2 points! That's the biggest gain yet for Player 1 + 6 points! That's the biggest gain yet for Player 1 + 10 points! That's the biggest gain yet for Player 0 + """, + 'hidden': False, + 'locked': False + } + ], + 'scored': True, + 'setup': r""" + >>> from hog import play, always_roll, announce_highest, both + """, + 'teardown': '', + 'type': 'doctest' + } + ] +} diff --git a/08.py b/08.py new file mode 100644 index 0000000..d8b4763 --- /dev/null +++ b/08.py @@ -0,0 +1,92 @@ +test = { + 'name': 'Question 8', + 'points': 2, + 'suites': [ + { + 'cases': [ + { + 'answer': 'It both takes in a function as an argument and returns a function', + 'choices': [ + 'It takes in a function as an argument', + 'It returns a function', + 'It both takes in a function as an argument and returns a function', + 'It uses the *args keyword' + ], + 'hidden': False, + 'locked': False, + 'question': 'What makes make_averaged a higher order function?' + }, + { + 'answer': 'An arbitrary amount, which is why we need to use *args to call it', + 'choices': [ + 'None', + 'Two', + 'An arbitrary amount, which is why we need to use *args to call it' + ], + 'hidden': False, + 'locked': False, + 'question': 'How many arguments does the function passed into make_averaged take?' + } + ], + 'scored': False, + 'type': 'concept' + }, + { + 'cases': [ + { + 'code': r""" + >>> dice = make_test_dice(3, 1, 5, 6) + >>> averaged_dice = make_averaged(dice, 1000) + >>> # Average of calling dice 1000 times + >>> averaged_dice() + 3.75 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> dice = make_test_dice(3, 1, 5, 6) + >>> averaged_roll_dice = make_averaged(roll_dice, 1000) + >>> # Average of calling roll_dice 1000 times + >>> # Enter a float (e.g. 1.0) instead of an integer + >>> averaged_roll_dice(2, dice) + 6.0 + """, + 'hidden': False, + 'locked': False + } + ], + 'scored': True, + 'setup': r""" + >>> from hog import * + """, + 'teardown': '', + 'type': 'doctest' + }, + { + 'cases': [ + { + 'code': r""" + >>> hundred_range = range(1, 100) + >>> hundred_dice = make_test_dice(*hundred_range) + >>> averaged_hundred_dice = make_averaged(hundred_dice, 5*len(hundred_range)) + >>> correct_average = sum(range(1, 100)) / len(hundred_range) + >>> averaged_hundred_dice() + 50.0 + >>> averaged_hundred_dice() + 50.0 + """, + 'hidden': False, + 'locked': False + } + ], + 'scored': True, + 'setup': r""" + >>> from hog import * + """, + 'teardown': '', + 'type': 'doctest' + } + ] +} diff --git a/09.py b/09.py new file mode 100644 index 0000000..fe7133f --- /dev/null +++ b/09.py @@ -0,0 +1,91 @@ +test = { + 'name': 'Question 9', + 'points': 1, + 'suites': [ + { + 'cases': [ + { + 'answer': 'The lowest num_rolls', + 'choices': [ + 'The lowest num_rolls', + 'The highest num_rolls', + 'A random num_rolls' + ], + 'hidden': False, + 'locked': False, + 'question': r""" + If multiple num_rolls are tied for the highest scoring + average, which should you return? + """ + } + ], + 'scored': False, + 'type': 'concept' + }, + { + 'cases': [ + { + 'code': r""" + >>> dice = make_test_dice(3) # dice always returns 3 + >>> max_scoring_num_rolls(dice, num_samples=1000) + 10 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> dice = make_test_dice(1, 2, 2, 2, 2, 2, 2, 2) + >>> max_scoring_num_rolls(dice, num_samples=1000) + 4 + """, + 'hidden': False, + 'locked': False + } + ], + 'scored': True, + 'setup': r""" + >>> from hog import * + """, + 'teardown': '', + 'type': 'doctest' + }, + { + 'cases': [ + { + 'code': r""" + >>> dice = make_test_dice(2) # dice always rolls 2 + >>> max_scoring_num_rolls(dice, num_samples=1000) + 10 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> dice = make_test_dice(1, 2) # dice alternates 1 and 2 + >>> max_scoring_num_rolls(dice, num_samples=1000) + 1 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> dice = make_test_dice(1, 2, 3, 4, 5) # dice sweeps from 1 through 5 + >>> max_scoring_num_rolls(dice, num_samples=1000) + 3 + """, + 'hidden': False, + 'locked': False + } + ], + 'scored': True, + 'setup': r""" + >>> from hog import * + """, + 'teardown': '', + 'type': 'doctest' + } + ] +} diff --git a/10.py b/10.py new file mode 100644 index 0000000..bad4046 --- /dev/null +++ b/10.py @@ -0,0 +1,114 @@ +test = { + 'name': 'Question 10', + 'points': 1, + 'suites': [ + { + 'cases': [ + { + 'code': r""" + >>> bacon_strategy(0, 0, margin=8, num_rolls=5) + 5 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> bacon_strategy(70, 50, margin=8, num_rolls=5) + 5 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> bacon_strategy(50, 70, margin=8, num_rolls=5) + 0 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> bacon_strategy(32, 4, margin=5, num_rolls=4) + 0 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> bacon_strategy(20, 18, margin=9, num_rolls=4) + 0 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> from tests.check_strategy import check_strategy + >>> check_strategy(bacon_strategy) + """, + 'hidden': False, + 'locked': False + } + ], + 'scored': True, + 'setup': r""" + >>> from hog import * + """, + 'teardown': '', + 'type': 'doctest' + }, + { + 'cases': [ + { + 'code': r""" + >>> bacon_strategy(20, 4, margin=3, num_rolls=4) + 0 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> bacon_strategy(20, 23, margin=5, num_rolls=0) + 0 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> bacon_strategy(20, 24, margin=7, num_rolls=5) + 5 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> bacon_strategy(20, 25, margin=7, num_rolls=5) + 5 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> bacon_strategy(20, 26, margin=11, num_rolls=6) + 6 + """, + 'hidden': False, + 'locked': False + } + ], + 'scored': True, + 'setup': r""" + >>> from hog import * + """, + 'teardown': '', + 'type': 'doctest' + } + ] +} diff --git a/11.py b/11.py new file mode 100644 index 0000000..85f057c --- /dev/null +++ b/11.py @@ -0,0 +1,98 @@ +test = { + 'name': 'Question 11', + 'points': 2, + 'suites': [ + { + 'cases': [ + { + 'code': r""" + >>> swap_strategy(12, 60, 8, 6) + 0 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> swap_strategy(30, 54, 8, 6) + 6 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> swap_strategy(7, 24, 8, 6) + 6 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> swap_strategy(7, 28, 8, 6) + 0 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> from tests.check_strategy import check_strategy + >>> check_strategy(swap_strategy) + """, + 'hidden': False, + 'locked': False + } + ], + 'scored': True, + 'setup': r""" + >>> from hog import * + """, + 'teardown': '', + 'type': 'doctest' + }, + { + 'cases': [ + { + 'code': r""" + >>> swap_strategy(10, 28, 8, 6) # beneficial + 0 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> swap_strategy(9, 1, 8, 6) + 6 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> swap_strategy(44, 24, 8, 6) + 6 + """, + 'hidden': False, + 'locked': False + }, + { + 'code': r""" + >>> swap_strategy(37, 24, 8, 6) + 6 + """, + 'hidden': False, + 'locked': False + } + ], + 'scored': True, + 'setup': r""" + >>> from hog import * + """, + 'teardown': '', + 'type': 'doctest' + } + ] +} diff --git a/12.py b/12.py new file mode 100644 index 0000000..1866318 --- /dev/null +++ b/12.py @@ -0,0 +1,35 @@ +test = { + 'name': 'Question 12', + 'points': 0, + 'suites': [ + { + 'cases': [ + { + 'code': r""" + >>> check_strategy(hog.final_strategy) + """, + 'hidden': False, + 'locked': False + } + ], + 'scored': False, + 'setup': r""" + >>> import hog + >>> def check_strategy(strat): + ... for score in range(100): + ... for opp in range(100): + ... num_rolls = strat(score, opp) + ... if not isinstance(num_rolls, int): + ... raise ValueError("final_strategy({0}, {1}) returned {2}, not an int.".format(score, opp, num_rolls)) + >>> def max_scoring_num_rolls(dice=lambda: 1): + ... raise RuntimeError("Your final strategy should not call max_scoring_num_rolls.") + >>> old_max_scoring_num_rolls = hog.max_scoring_num_rolls + >>> hog.max_scoring_num_rolls = max_scoring_num_rolls + """, + 'teardown': r""" + >>> hog.max_scoring_num_rolls = old_max_scoring_num_rolls + """, + 'type': 'doctest' + } + ] +} diff --git a/check_strategy.py b/check_strategy.py new file mode 100644 index 0000000..cf78d88 --- /dev/null +++ b/check_strategy.py @@ -0,0 +1,55 @@ +from hog import GOAL_SCORE + +def check_strategy_roll(score, opponent_score, num_rolls): + """Raises an error with a helpful message if NUM_ROLLS is an invalid + strategy output. All strategy outputs must be integers from 0 to 10. + + >>> check_strategy_roll(10, 20, num_rolls=100) + Traceback (most recent call last): + ... + AssertionError: strategy(10, 20) returned 100 (invalid number of rolls) + + >>> check_strategy_roll(20, 10, num_rolls=0.1) + Traceback (most recent call last): + ... + AssertionError: strategy(20, 10) returned 0.1 (not an integer) + + >>> check_strategy_roll(0, 0, num_rolls=None) + Traceback (most recent call last): + ... + AssertionError: strategy(0, 0) returned None (not an integer) + """ + msg = 'strategy({}, {}) returned {}'.format( + score, opponent_score, num_rolls) + assert type(num_rolls) == int, msg + ' (not an integer)' + assert 0 <= num_rolls <= 10, msg + ' (invalid number of rolls)' + + +def check_strategy(strategy, goal=GOAL_SCORE): + """Checks the strategy with all valid inputs and verifies that the strategy + returns a valid output. Use `check_strategy_roll` to raise an error with a + helpful message if the strategy returns an invalid output. + + >>> def fail_15_20(score, opponent_score): + ... if score != 15 or opponent_score != 20: + ... return 5 + ... + >>> check_strategy(fail_15_20) + Traceback (most recent call last): + ... + AssertionError: strategy(15, 20) returned None (not an integer) + >>> def fail_102_115(score, opponent_score): + ... if score == 102 and opponent_score == 115: + ... return 100 + ... return 5 + ... + >>> check_strategy(fail_102_115) + >>> fail_102_115 == check_strategy(fail_102_115, 120) + Traceback (most recent call last): + ... + AssertionError: strategy(102, 115) returned 100 (invalid number of rolls) + """ + for score in range(goal): + for opponent_score in range(goal): + num_rolls = strategy(score, opponent_score) + check_strategy_roll(score, opponent_score, num_rolls) diff --git a/play_utils.py b/play_utils.py new file mode 100644 index 0000000..9a2a034 --- /dev/null +++ b/play_utils.py @@ -0,0 +1,258 @@ +import random + +from hashlib import md5 + +TRACE_SOL = 'tests/play.sol' +TEST_SEED = 1337 +NUM_TESTS = 1000 + +def hash(val): + return int(md5(str(val).encode()).hexdigest(), base=16) & 0xffffffff + +def make_random_strat(): + """Makes a random pure strategy.""" + seed = random.randrange(0, 2 ** 31) + + def random_strat(score, opponent_score): + # Save the state of the random generator, so strategy calls don't + # impact dice rolls. + state = random.getstate() + random.seed(hash((score, opponent_score, seed))) + roll = random.randrange(0, 11) + random.setstate(state) + return roll + return random_strat + + +class GameTurn(object): + def __init__(self, score, opponent_score, who, num_rolls): + if who == 0: + self.score0, self.score1 = score, opponent_score + else: + self.score0, self.score1 = opponent_score, score + self.who = who + self.num_rolls = num_rolls + self.rolls = [] + self.dice_sides = 6 + self.score0_final, self.score1_final = None, None + + def is_over(self): + """Returns True iff this GameTurn should be over.""" + return len(self.rolls) >= self.num_rolls + + def is_successor(self, other): + """Returns True if another GameTurn is a plausible successor of this + GameTurn. Used for preventing multiple calls to a strategy function + from messing up the tracer (to a reasonable degree).""" + # In case students call a strategy multiple times per turn. + if self.who == other.who: + return False + # In case students call both strategies regardless of whose turn it is + if self.score0 == other.score0 and self.score1 == other.score1 or \ + not self.is_over(): + return False + # In case students call a strategy after the game should be over + if max(other.score0, other.score1) >= 100: + return False + return True + + def set_successor(self, other): + """Sets another GameTurn as the successor of this GameTurn.""" + self.score0_final , self.score1_final = other.score0, other.score1 + + def is_correct(self, sol_hash): + """Returns True if the hash of this GameTurn matches the solution + hash.""" + return hash(self) == sol_hash + + @property + def turn_summary(self): + """Returns a string containing a description of how who rolled how many + dice this turn.""" + if self.num_rolls == 0: + return 'Player {0} rolls 0 dice:'.format(self.who) + elif self.num_rolls == 1: + return 'Player {0} rolls {1} {2}-sided die:'.format( + self.who, + self.num_rolls, + 'six' if self.dice_sides == 6 else 'four') + else: + return 'Player {0} rolls {1} {2}-sided dice:'.format( + self.who, + self.num_rolls, + 'six' if self.dice_sides == 6 else 'four') + + @property + def turn_rolls(self): + """Returns a string containing the dice values rolled this turn.""" + return str(self.rolls)[1:-1] + + @property + def dice_summary(self): + """Returns a string containing a summary of the dice values rolled this + turn.""" + if len(self.rolls) == 0: + return '' + return 'Dice sum: {0} {1}'.format( + sum(self.rolls), + '(rolled ones)' if 1 in self.rolls else '') + + def __repr__(self): + return str((self.score0, self.score1, self.score0_final, + self.score1_final, self.who, self.num_rolls, self.dice_sides)) + + +def make_traced(s0, s1, six_sided, four_sided): + """Given the strategy functions of player 0 and player 1, and six-sided and + four-sided dice, returns traced versions of the function to be used for the + game, as well as a function to retrieve the trace. """ + trace = [] # List of GameTurns + + def make_traced_strategy(strat, player): + def traced_strategy(score, opponent_score): + num_rolls = strat(score, opponent_score) + state = GameTurn(score, opponent_score, player, num_rolls) + + if not trace: + trace.append(state) + elif trace[-1].is_successor(state): + trace[-1].set_successor(state) + trace.append(state) + return num_rolls + return traced_strategy + + def make_traced_dice(dice, dice_sides): + def traced_dice(): + roll = dice() + if trace: + trace[-1].dice_sides = dice_sides + trace[-1].rolls.append(roll) + return roll + return traced_dice + + def get_trace(score0, score1): + """Given the final score outcome of the game, returns the trace of the + game.""" + trace[-1].score0_final = score0 + trace[-1].score1_final = score1 + return trace + + return make_traced_strategy(s0, 0), \ + make_traced_strategy(s1, 1), \ + make_traced_dice(six_sided, 6), \ + make_traced_dice(four_sided, 4), \ + get_trace + + +def play_traced(hog, strat0, strat1): + """Returns the trace of a hog game, given the HOG module, as well as the + player 0 and 1 strategies for the game.""" + four_sided, six_sided = hog.four_sided, hog.six_sided + strat0, strat1, traced_six_sided, traced_four_sided, get_trace = \ + make_traced(strat0, strat1, six_sided, four_sided) + + hog.four_sided = traced_four_sided + hog.six_sided = traced_six_sided + score0, score1 = hog.play(strat0, strat1) + trace = get_trace(score0, score1) + + hog.four_sided = four_sided + hog.six_sided = six_sided + return trace + + +def check_play_function(hog): + """Checks the `play` function of a student's HOG module by running multiple + seeded games, and comparing the results.""" + random.seed(TEST_SEED) + sol_traces = load_traces_from_file(TRACE_SOL) + for i in range(NUM_TESTS): + strat0, strat1 = make_random_strat(), make_random_strat() + trace = play_traced(hog, strat0, strat1) + incorrect = compare_trace(trace, sol_traces[i]) + if incorrect != -1: + print('Incorrect result after playing {0} game(s):'.format(i + 1)) + print_trace(trace, incorrect) + print('Incorrect implementation of game at turn {0}.'.format(incorrect)) + print('Please read over the trace to find your error.') + print("\nIf you're having trouble, try looking up the error ID on Piazza,") + print('or making a post with this full trace output.') + print('(error_id: {0})'.format( + hash((trace[incorrect], incorrect, i)))) + break + + +def make_solution_traces(hog): + """Given a reference HOG solution module, returns the hashed solution + trace.""" + random.seed(TEST_SEED) + sol_traces = [] + for i in range(NUM_TESTS): + strat0, strat1 = make_random_strat(), make_random_strat() + trace = play_traced(hog, strat0, strat1) + sol_traces.append([hash(state) for state in trace]) + return sol_traces + + +def compare_trace(trace, sol): + """Compares TRACE with the SOLUTION trace, and returns the turn number + where the two traces differ, or -1 if the traces are the same. + """ + i = 0 + while i < min(len(trace), len(sol)): + state, sol_state = trace[i], sol[i] + if not state.is_correct(sol_state): + return i + i += 1 + if len(trace) != len(sol): + return len(trace) + return -1 + + +def print_trace(trace, incorrect=None): + """Prints out the student trace.""" + print('-'*64) + print('{0:>10}{1:>8}{2:>8} {3}'.format( + '', + 'score0', + 'score1', + 'Turn Summary')) + print('-'*64) + for i, turn in enumerate(trace): + if incorrect is not None and i != incorrect: + continue + s0_change = turn.score0_final - turn.score0 + s1_change = turn.score1_final - turn.score1 + print('{0:<10}{1:8}{2:8} {3}'.format( + 'Turn {0}:'.format(i), + turn.score0, + turn.score1, + turn.turn_summary)) + print('{0:<10}{1:>8}{2:>8} {3}'.format( + '', + '' if s0_change == 0 else '{0:+}'.format(s0_change), + '' if s1_change == 0 else '{0:+}'.format(s1_change), + turn.turn_rolls)) + print('{0:<10}{1:8}{2:8} {3}'.format( + '', + turn.score0_final, + turn.score1_final, + turn.dice_summary)) + print('-'*64) + print('{0:<15}{1:3}{2:8}'.format( + 'Final Score:', + turn.score0_final, + turn.score1_final)) + print('-'*64) + + +def load_traces_from_file(path): + """Given a file specified by a PATH, returns a trace.""" + with open(path) as f: + return eval(f.read()) + +def write_traces_to_file(path, traces): + """Given a target file specified by a PATH, and a solution trace, writes + the trace to the file.""" + with open(path, 'w') as f: + f.write(str(traces))