HW 4 (Pig)

Assignment overview

In this assignment, you'll write code that simulates playing the game Pig.

Goals

Get practice using functions (especially with return values) and using conditional statements.

Logistics

This is a partner assignment, which means you should complete all pieces of this assignment with your assigned partner. In particular, any coding must be completed side-by-side in a pair programming style. You are welcome to discuss the assignment with other classmates, course staff or Anna. Make sure to cite any help you receive in the "acknowlegdements" portion of the assignment.

Unless we have worked out a different arrangement together, you are working with the same partner as for HW2.

This assignment is due at 10PM on Friday, April 17.

Setup

Mount the COURSES drive and create a folder called hw4 in your STUWORK folder. Open the new folder in VSCode. If you need a refresher on how to complete these steps, refer back to the in-class lab from the first day of class.

Next, download this starter Python file. Move the file (pig.py) to your new hw4 folder.

For this assignment, you will fill in the missing code in pig.py to implement the game Pig.

Rules of Pig

Pig is a game in which two players take turns rolling dice. There are many variations of the game, but here are the rules we will be playing with:

  • A player rolls two dice at a time.
    • If either die (but not both dice) is a 1, the player's turn ends and they get 0 points.
    • If both dice are the same number (including two 1's), 15 points are added to the player's turn total. In this scenario, the player must re-roll the die as described below (they cannot choose to "hold").
  • If the player's turn hasn't ended by rolling a single 1, they can decide to either re-roll the two dice to have a chance of scoring more points, or they can hold.
    • If the player re-rolls, the same rules as above apply.
    • If the player holds, their turn total is added to their score and it becomes the other player's turn.

There is also an overall cap on how many times a player can roll during their turn (in the code, I call this maxRollsPerTurn). This can be anywhere from two to 10 rolls. This cap means that if the player completes their nth roll (where n is the cap), their turn automatically ends after that roll. (Even if they roll doubles, they do not roll again.)

The game ends when a player has achieved a certain goal score (e.g., 100 points) at the conclusion of their turn.

There is also total number of allowed turns (e.g., 50 or 100). If the players reach the number of allowed turns, (e.g., they each have taken 25 turns or 50 turns), the game ends and the player with the higher score wins. If both players have the same number of points at this time, the game ends in a tie.

The functions in pig.py

I recommend implementing the functions in the order described here. Test each function before moving to the later parts. For instance, to test rollDice, open the Python interpreter (type python3 at the command line), import pig.py (i.e., type import pig), then test the function as pig.rollDice().

rollDice

rollDice simulates rolling two dice. It should return a tuple containing the values of the two dice rolls. You will want to use the random module (imported at the top of the file). In particular, the random.randInt() function will be helpful.

Strategy functions

Strategy functions include alwaysHold, alwaysRoll, human, and holdIfWinning.

  • alwaysHold (completed for you). Always return False to simulate never choosing to re-roll within a single turn.
  • alwaysRoll (completed for you). Always return True to simulate always choosing to re-roll.
  • human asks the user to decide whether they want to hold or re-roll. Return True to re-roll or False to hold.
  • holdIfWinning returns False to hold if the current player is winning (according to oldScore + newScore) and True to roll otherwise.

All of these functions take 4 parameters: newScore (points earned so far in the current player's current turn), oldScore (the current player's score before their current turn began), opponentScore, and goalScore. You'll notice that not all of these functions need to use all parameters. We still need to include them all as parameters for reasons described in the next section.

getScore and turn

getScore is a helper function that accepts a dice roll (a tuple of two values) and returns the score according to the game rules. Recall that you can use the syntax t[0] and t[1] to access the first and second items in a tuple, respectively.

turn simulates one player's turn. The parameters to this function include the current game status (playerScore and opponentScore), details about the game settings (goalScore and maxRollsPerTurn), and a strategy that describes how the turn should proceed.

Note that the final parameter of turn, strategy is itself a function, which is something we haven't seen before. Python treats variables and functions similarly; functions can be passed as parameters to functions and also returned from functions. We won't do this often in CS111, but it's very powerful.

The following code snippets shows an example of how to pass functions as parameters using a simplified version of the functions in this assignment:

def winningStrategy(playerScore, goalScore):
    # don't reroll if we've won, otherwise reroll
    return playerScore < goalScore
def turn(playerScore, goalScore, strategy):
    roll = strategy(playerScore, goalScore)
turn(0, 100, winningStrategy)

The cool thing here is that if I have another function that also takes two parameters, I can pass the name of that function as the parameter instead. This is why we have the unused parameters in some of the strategy functions. (I've included goalScore as a parameter in case you want to write your own strategy function that includes goalScore in its logic.)

Before trying to write code for turn, write pseudocode. Think about how your turn would proceed, if you're playing Pig. Details about when to roll the dice, how to calculate your new score, and whether to roll again will all be handled in this function (or in helper functions that you create and then call from this function). turn should return the value of the points that the player earns during this single turn. (You should handle updating the playerScore variables in the next function, play.)

play

play switches off between initiating turns (i.e., calling turn) for each of the two players. It takes in parameters about the game's settings (goalScore, globalMaxTurns, and maxRollsPerTurn) and about the players (strategy1 and strategy2). You may find it useful to print updates (such as whose turn it is, and what the current scores are) from this function. play should return a value to indicates who wins (Player 1, Player 2, or a tie)

main

main is called when the program is executed from the command line. It should call play and then print who wins based on the return value of play. In main, you can hardcode the two players' strategies, or you can find a way to ask for user input to set the strategies.

You are welcome to write and call additional helper functions, but please keep the names and signatures (i.e., how many parameters they take and what the parameters are called) the same for the existing functions.

Code notes and tips

Testing in the interpreter

As I mentioned above, you should test each function in the interpreter after you complete it. The syntax is a little different when passing function names in the interpreter. For instance, in your code, you call play like this:

play(50, 50, 5, human, alwaysHold)

but in the interpreter you have to call

pig.play(50, 50, 5, pig.human, pig.alwaysHold)

Playing the game

By the end of this assignment, you should be able to run python3 pig.py and actually play against the computer! Here's an example interaction I had while playing the game against the computer: (You don't need to match this interaction exactly, see below for what you need to print and what is optional.)

Player 1: computer; Player 2: human

--- Turn 1 (Player 1's turn) ---
--- Current scores ---
--- Player 1: 0 ; Player 2: 0 ---
Rolled 6 and 3

--- Turn 2 (Player 2's turn) ---
--- Current scores ---
--- Player 1: 9 ; Player 2: 0 ---
Rolled 4 and 4
Rolled 2 and 6
Your score is 23 ( 23 points are new) and your opponent's score is 9
Do you want to roll again or hold? Type h for hold anything else for roll: h

--- Turn 3 (Player 1's turn) ---
--- Current scores ---
--- Player 1: 9 ; Player 2: 23 ---
Rolled 3 and 4
Rolled 3 and 6

--- Turn 4 (Player 2's turn) ---
--- Current scores ---
--- Player 1: 25 ; Player 2: 23 ---
Rolled 4 and 6
Your score is 33 ( 10 points are new) and your opponent's score is 25
Do you want to roll again or hold? Type h for hold anything else for roll: r
Rolled 5 and 3
Your score is 41 ( 18 points are new) and your opponent's score is 25
Do you want to roll again or hold? Type h for hold anything else for roll: r
Rolled 3 and 5
Your score is 49 ( 26 points are new) and your opponent's score is 25
Do you want to roll again or hold? Type h for hold anything else for roll: r
Rolled 6 and 2
Your score is 57 ( 34 points are new) and your opponent's score is 25
Do you want to roll again or hold? Type h for hold anything else for roll: h

Player 1: 25 ; Player 2: 57
Player 2 wins!

What do I need to print?

At a minimum, you should print the current scores and turn number for each turn. You should also print the final scores at the end of the game along with a message of who won.

You can print out the values of the dice rolls, intermediate information that can help the user decide whether to re-roll (e.g., the (34 points are new) part of my example output above), or anything else that makes the interactive game play smoother from the command line.

When I was writing and testing my code, I found it useful to add the line input() at places in the code where I wanted to be able to pause and check the output (e.g., at the end of the computer's turn before my turn began). Then, I could press enter to get more text printed to the screen only when I was ready to read it. If you do this, you should remove the extra input() statements before submtiting your code.

Optional extensions

  • Write your own strategy function.

Wrap up

When you're finished, make sure to complete the usual documentation steps. This includes adding comments, writing function docstrings where they are missing or incomplete, and filling out all pieces of the header.

You should also think about coding style. Have you written everything in a consistent way that is easy to read? Does your code have any unnecessary print statements? (Remove them.) Review the style document on Moodle for the expectations for this assignment.

Assignment submission and misc. notes

Handing in the assignment

You need to hand in pig.py on Gradescope. Only one partner should submit the assignment to Gradescope (but make sure to add your partner to your group after submitting)

Grading

This assignment is worth 40 points, broken up as follows:

  • rollDice function (4 points)
  • human function (4 points)
  • holdIfWinning function (4 points)
  • getScore function (6 points)
  • turn function (6 points): Do you successfully call rollDice and getScore? Do you successfully call the passed-in strategy function? Do you obey the maxRollsPerTurn parameter?
  • play function (6 points): Do you alternate turns between players, print the current scores for each turn, and correctly determine the game's winner?
  • main function (4 points)
  • Style (6 points): header, comments, following the style guidelines from Moodle.

Start early, ask lots of questions, and have fun!

Anna's acknowledgements

This assignment was adapted from assignments used by Anna Rafferty and Layla Oesper. Thanks for sharing!