**Project 1: automating a dice game** **Reed CSCI 121 Spring 2025** *submit by 2/28 at 11:59pm* In this project you will make a program that plays a dice game called **Roll100**. We'll begin by with an interactive program that allows two players to play the game against each other. From there, we will build tools that allow us to explore different strategies for playing the game by considering computer players written as Python code. The first tool will allow us to pit two computer players against each other. The second tool we'll use to figure out how well different computer players play the game. Finally, you'll write your own strategy code (or several strategy codes, if you like). A strategy will be described using a Python function, one that takes one turn of the game. You can pit this strategy function against some test players that we've written. You'll also have a chance to pit your strategy function against your classmates'. Have fun! (#) Setting up. Download the starting code using one of the links listed just below. It will download as a "ZIP package" and this will unpack as a folder (usually by just double-clicking it) on your computer's filesystem. You'll want to choose the one that matches your installed Python system. My Mac's system, for example, is running Python version 3.12. I see this version when I start up the Python interpreter: ~~~none % python3 Python 3.12.6 (v3.12.6:a4a2d2b0d85, Sep 6 2024, 16:08:03) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> ~~~ If you are running version 3.12 like I am, then you will want to download the package `project1-v312.zip`. If instead you are running the latest version 3.13, you'll want the one with `v313` in the name. If you have one of the older ones, version 3.9, or 3.10, or, 3.11, download the ZIP file for that one instead. * [project1-v313.zip](project1-v313.zip) * [project1-v312.zip](project1-v312.zip) * [project1-v311.zip](project1-v311.zip) * [project1-v310.zip](project1-v310.zip) * [project1-v39.zip](project1-v39.zip) Several versions of the project are necessary because we are providing a pre-compiled collection of game strategies you can test. The compiled code is version-specific and is kept in the file named `pr1testing.pyc`. We'd like their exact code to be a mystery to you. The main code you will be editing for the assignment is in the file named `pr1.py`. You will also, when done, have created a file named `myStrategy.py`. This will contain the code for your game strategy that you will have developed. (#) The Game The game of **Roll100** is a dice game between two players. The game begins with both players having a score of zero, and they each take turns rolling dice and (possibly) increasing their score. The dice used are 6-sided, but they differ from regular ones: their sides are *labeled with the numbers 0 through 5, rather than from 1 to 6*. A player's goal is to get the higher score, though ties can happen. The game begins with the first player choosing how many dice he'd like to roll in his turn. He can choose any number of dice: 0, 1, 2, or more. He then rolls that number of dice and adds their sum to his score. Next, player two takes her turn. She also chooses a number of dice she would like to roll, rolls them, and adds their sum to her score. The game continues this way, with the two players taking these alternating turns. The game ends if, as a result of a dice roll, a player's score goes over 100. In that case the other player wins. There is another way the game ends: on his/her turn a player is allowed to choose to roll 0 dice, in which case that player is choosing to *pass*. In the case that a player passes their turn, the other player *gets one more turn to roll* the dice and add that roll's total to their score. After that last roll, the player with the highest score not higher than 100 is declared the winner. They tie if they are at the same score. (#) The Assignment Below I outline a series of steps I'd like you to follow in order to successfully complete this assignment. They are designed so as to introduce you to game play, and to help you devise strategies for playing the game. We are providing code that you will work with. Also, you will need to write code that works with ours, as well as others' code. The steps below are also designed to help you figure out these kinds of logistics. Before you scan through these steps, make sure that you've downloaded the project folder which contains the code that you'll be working with. The first steps have you running this code. (##) Step 1: experiment. You should first work to understand the game. The `play` function written in `pr1.py` conducts a game between two human players, both typing their moves into prompts in the console. Start up the Python interactive interpreter by loading this Python file and then entering the expression `play()`, similar to the commands I used on my computer below: ~~~none python3 -i pr1.py >>> play() ~~~ This will start the two-player game, immediately asking for the two players' names, and then letting them play the game. Play several games with a friend (or just against yourself) to get a feel for **Roll100**. You should then look at the code for the `play` function and try to understand how it works. (It is purposefully not documented very well.) (##) Step 2: inspect a sample strategy. Our goal is to program strategies so that they can play the **Roll100** game against each other, rather than just having humans play the game. The source code `pr1.py` contains the definition of a function named `sample1`. This is the code for a sample **Roll100** strategy. This strategy function, like all the strategies we will write, takes three parameters. The parameters represent the current state of the game just before a player is about to take a turn. The strategy uses this information to determine its automated player's dice roll choice. The first parameter `myscore` gives the strategy's current score. The second parameter `theirscore` gives the opponent's score. The third paramter `last` is a boolean indicating whether or not the opponent passed on their last turn. If `last` is `True` then the strategy's player is about to take its last turn to roll dice in the game. The `sample1` function uses this info to return an integer value. That integer represents its next move, that is, how many dice it wishes to roll on this turn. If it returns 0, that means that it is choosing to pass on this turn. You should look at this `sample1` code. It is, in fact, a very simple strategy--- it passes if it's currently ahead, and otherwise it rolls 12 dice. Unfortunately, we can't actually watch it play a game given the code as it currently is in the file. We will do that in the next step. You'll also see the start of a definition for a second sample strategy named `sample2`. We'll ask you to write that code soon, too. (##) Step 3. autoplayLoud. This step is the first coding part of the assignment. Your job is to write a function called `autoplayLoud` that conducts games between two computer strategies instead of two people. It should work very similarly to `play`, but it will not prompt for any human input. It will instead use two automated players to make dice roll choices, one acting as Player 1 and the other acting as Player 2. That is, rather than pitting human players against each other with the `play` function, `autoplayLoud` lets us watch the gameplay of automated strategies we invent. To do this, you'll write `autoplayLoud` so that it is fed two strategies as parameters---formal parameters named `strat1` and `strat2`. These will each be a strategy function, one that takes three parameters (just like how `sample1` was written) and returns a number of dice to roll. This makes `autoplayLoud` a great example of how functions can be passed as parameters to other functions. We feed it two strategy functions, whatever ones we want to have comptete. Our code inside `autoplayLoud` can call them with `strat1(...)` or `strat2(...)` to see what they each want to do in their turn's situation. For example, if we evaluate ~~~none >>> autoplayLoud(sample1,sample1) ~~~ then we expect to see a transcript that results from a game of **Roll100** where Player 1 follows the strategy coded in `sample1` and where Player 2 does too. You get to watch how `sample1` fares against itself. Here is what that printed output might look like: ~~~none Player 1: 0 Player 2: 0 It is Player 1's turn. 12 dice chosen. Dice rolled: 3 4 5 2 3 2 1 0 1 5 2 4 Total for this turn: 32 Player 1: 32 Player 2: 0 It is Player 2's turn. 12 dice chosen. Dice rolled: 4 2 4 2 4 1 3 1 4 0 1 5 Total for this turn: 31 Player 1: 32 Player 2: 31 It is Player 1's turn. 0 dice chosen. Dice rolled: Total for this turn: 0 Player 1: 32 Player 2: 31 It is Player 2's turn. 12 dice chosen. Dice rolled: 0 3 4 1 1 0 4 2 5 0 2 2 Total for this turn: 24 Player 1: 32 Player 2: 55 Player 2 wins. ~~~ Write the code for `autoplayLoud` by adapting the code for `play`. Have it mimic exactly the formatting that you see above. (##) Step 4: autoplay. Having the transcript of the game printed to the terminal is great when we're running one game at a time, but when we really want to know which strategy is better, we'll want to take a sample of thousands of games. To do this, create `autoplay`, a function that does roughly the same thing as `autoplayLoud`. The difference is that instead of printing the transcript of the game to the terminal, the simulation is done "quietly". To communicate the winner to any testing code we write, the `autoplay` function needs to `return` a value that indicates who won that play of the game. Have it return the value 1 if the first player (the strategy listed in the first argument) wins. It should return 2 if instead the second player wins. Return a 3 when it's a tie. (##) Step 5: manyGames. Let's now write code to compare strategies. Any single game is going to include a lot of luck, and possibly a big advantage for one side based on who goes first. Write a function `manyGames` to give us a much more reliable way to compare strategies. It should take two strategies (like `autoplayLoud` and `autoplay`) as parameters along with a third integer parameter `n`. It should then run the `autoplay` function `n` times. For half of those it should have the first strategy roll first, acting as Player 1 (or nearly half when `n` is odd). It then lets the second strategy roll first for the remaining half. It keeps track of wins and ties, and prints a summary at the end. This summary should look like what's below: ~~~none Player 1 wins: 496 Player 2 wins: 503 Ties: 1 ~~~ In our experience with this project, when you write `manyGames` you might introduce bugs, but the code could still run to completion. To give you some confidence, run `manyGames(sample1, sample1, 1000)`. The output just above resulted from doing this with our solution. Though there is randomness here, your function's output should be similar to what you see above (nearly equal performance by both players, only a few ties) most of the time. (##) Step 6: write a second strategy. Now we ask you to write a strategy. It should behave as follows: *If its current score is no more than 50, it should roll 30 dice. If its current score is between 51 and 80 inclusive, it should roll 10 dice. When its score is above 80, it should pass.* Write this code in the function definition named `sample2`. Experiment to see how it compares to `sample1`. You should find that in large samples, switching which strategy goes first, it consistently beats `sample1`. If you don't, you have an error somewhere. (##) Step 7: develop your myStrategy. Now it's time for the open-ended part of the project where you work to develop your own strategy. How it plays is completely up to you. It can work any way you want, but your goal is to make the strategy as good as possible. Be creative. Try lots of things. Tweak the strategy a lot---changing a couple little details can have a big effect. To complete this step of the assignment, we only want you to submit one strategy. Develop several strategies if you like, but we ask you to choose one that you officially submit. That one you choose, call it `myStrategy`. You can keep the other strategies, named differently, in your `pr1.py` file. We will be happy to take a look at that other work and see all the things you tried. Note that, because of the luck factor, the difference between a decent strategy and a really good one could be appear as a small effect on the win percentage. Say one strategy *A* has a 60% win rate against a given opponent *X*, while another *B* has a 58% win rate against the same opponent *X*. Then *A* might win very decisively when pitted against *B*. You should try writing several strategies that take substantially different approaches and then see which one performs best. But, again, in the end, you should have one strategy that you are willing to stand behind, written in the `myStrategy` function of the program. Be sure to document it, providing comments in the code that explain what it is doing and (if not self-explanatory) why what it does is a reasonable thing to do. (#) Playing others' We've provided a file `pr1testing.pyc` to help you develop your strategy. It defines a `pr1testing` module that contains eleven strategies of varying quality for you to test yours against. These happen to be a collection of ones that other Reed students have developed over the years. You are not allowed to inspect the contents of this file. (It will just look like gibberish anyway, but trying to decipher the gibberish is forbidden.) There is an `import` command for this testing file already in the `pr1.py` file. You can run a strategy by referring to, for example, `pr1testing.test6` (for the sixth of the eleven test strategies). That means that you could run ~~~none >>> manyGames(myStrategy, pr1testing.test8, 10000) ~~~ to see a sample of 10000 games between your strategy and the eighth test strategy. There is also a function that will run consecutive tests against all eleven of the test strategies. If you enter ~~~none >>> pr1testing.testStrat(myStrategy, 10000), ~~~ you will see the result of eleven matches---a match playing 10000 games of **Roll100** using `myStrategy`---each match against each of the eleven test strategies. You can of course also watch games between your strategy and the test strategies (or two of your strategies, two of the test strategies, etc.) using the `autoplayLoud` function. You should try to get your strategy to beat as many of the test strategies as possible. When you submit your `myStrategy`, we will run it against these as part of our evaluation of your work. We'll also, all in good fun, hold a tournament between the strategies submitted by the class. The tournament will pit people's submitted `myStrategy` functions against each other in one-on-one matches. Each match will involve large runs of `manyGames` and we'll pick winners based on the aggregate results of a match. This tournament has been a lot of fun to watch in the past, and is meant to be low pressure: the outcome of the tournament will not affect your grade. (Though there will be prizes for the top tournament winners...) We have a few technical rules restricting how your strategies can behave. Your function must compute the next turn *from scratch* each time it is called. (For example, you cannot write to a file to save computation from previous calls.) Similarly, you cannot compute moves in some other way and attempt to write out all possibilities in your function definition. The strategy must be pretty fast. Running `pr1testing.testStrat(myStrategy, 10000)` should take no more than 30 seconds. How fast things actually take obviously depends on the particular computer they are run on. This time limit, in our experience, is not a hindrance to most things you might reasonably do. If you're worried about your strategy taking too long, talk to us.