PA1: Chutes and Ladders
You have probably played this one on a rainy afternoon. Roll the die, move your piece, and hope. Land at the bottom of a ladder and you shoot up the board; land at the top of a chute and you slide all the way back down, usually right when you were winning. First one to the last square wins.
In this PA, you are the referee. Your code builds the board, moves a player one roll at a time, and replays an entire game from a list of rolls, then announces the turn they won. No dice, no arguing about whether that was really a 6: every square, ladder, and chute lives in one Python list, and the list does not lie.
This is one of the two PA1 options; do this one or Robot Vacuum, not both.
What you'll practice
- Using a
forloop over arangeto build a list, and index assignment to change it. - Looping over the indexes of two lists at once, when
starts[i]goes withends[i]. - Writing a function with several cases, and checking them in the right order.
- Remembering what happened across the turns of a loop, and returning two results as a tuple.
Get set up
Download chutes.py and save it in the Week06 folder inside ~/CS149.
That one file is the whole PA.
Open it and look around.
All three functions are already there, each with a docstring that says exactly what it takes and what it returns, and each body is just pass.
Your job is to replace those pass lines with real code.
Read each docstring all the way through, example included, before you start that function.
The main block at the bottom is written for you; leave it alone, and it will play a whole game for you at the end.
Put your name and today's date in place of YOUR NAME and THE DATE at the top of the file.
Gradescope checks.
Submit early to Gradescope
If you don't submit to Gradescope and get a nonzero score, you will receive a reflection grade of no higher than 15/30 points.
How the board works
Here is the trick that makes the whole PA simple: the track is a list, and the index is the square number.
Square 0 is the start, just off the board, and the last index is the finish.
So a track that finishes at square 10 has 11 items, and track[10] is the finish.
The value stored at each square is where that square sends you. A plain square sends you to itself. A ladder sends you up, and a chute sends you down. Here is a little 10-square track with a ladder from 2 to 7 and a chute from 9 to 4:
| Square | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Sends you to | 0 | 1 | 7 | 3 | 4 | 5 | 6 | 7 | 8 | 4 | 10 |
Notice what that buys you: to find where a player ends up, you never have to ask "is this a ladder? a chute? neither?"
track[square] is the answer every single time.
Quick check: which squares on this track send you down?
Only square 9, which sends you to 4. A square sends you down when the value stored there is smaller than the square number itself.
Ladders and chutes never chain
No ladder ever ends on a chute, and no chute ever ends on a ladder, so each turn looks up exactly one square.
Part A: build the board with make_track
make_track(size, starts, ends) builds a track like the one above.
size is the finish square, and the two lists say where each ladder or chute begins and ends: starts[0] goes with ends[0], starts[1] with ends[1], and so on.
Predict it: what does make_track(6, [5], [1]) return?
[0, 1, 2, 3, 4, 1, 6].
Seven items for squares 0 to 6, all plain except square 5, which is a nasty chute right before the finish.
Check it in the Shell once your function works.
Two loops, one after the other
First build the plain track, where every square sends you to itself. Then fix up the squares that have a ladder or a chute. The second loop is not over the track; think about which list it should go through, and why it needs the indexes rather than the values.
Part B: take a turn with move
move(track, position, roll) takes one turn and returns the square the player ends on.
There are three rules, and the order you check them in matters:
- A player who is already on the finish stays there, whatever they roll. Game over, you won, relax.
- A roll that would go past the finish bounces back by the squares left over. On the track above, a player on square 8 who rolls 4 counts 9, 10, then back to 9 and 8, and ends on 8. (Yes, it is as frustrating as it sounds.)
- Wherever the player lands, they follow that square, up a ladder or down a chute.
Rules 2 and 3 can both happen on one turn, which is the cruelest move in the game. From square 6, a roll of 5 counts to the finish, bounces back to 9, then slides down the chute to 4.
>>> track = make_track(10, [2, 9], [7, 4])
>>> move(track, 0, 2)
7
>>> move(track, 8, 4)
8
>>> move(track, 6, 5)
4
>>> move(track, 10, 3)
10
Your turn: on this track, you are on square 7 and roll a 6. Where do you end up?
Square 7. You count 8, 9, 10, then bounce back 9, 8, 7, and square 7 is a plain square. A big roll near the end can leave you exactly where you started.
The bounce is subtraction, twice
Work out how far past the finish the roll would go. Then go back from the finish by that much. Try it on paper for a player on 8 who rolls 4 before you write the line of code.
Part C: play a whole game with play
play(track, rolls) plays a one-player game.
The player starts on square 0 and takes one turn for each roll, in order, using your move.
It returns a tuple of two things:
- a new list of the square the player is on after each turn, and
- the turn number, counting from 1, on which the player first reached the finish, or
-1if they never did.
Every roll is used, even after the player reaches the finish.
Rule 1 of move keeps them parked there, so the list just repeats the finish.
Here is a game on the track above with the rolls [2, 1, 2, 1, 3]:
| Turn | Roll | From | Lands on | Ends on | Finished? |
|---|---|---|---|---|---|
| 1 | 2 | 0 | 2 | 7 (ladder) | |
| 2 | 1 | 7 | 8 | 8 | |
| 3 | 2 | 8 | 10 | 10 | yes, on turn 3 |
| 4 | 1 | 10 | — | 10 | |
| 5 | 3 | 10 | — | 10 |
Trace it: what does play(track, [2, 3, 4, 1, 5]) return?
([7, 10, 10, 10, 10], 2).
The ladder on square 2 carries you to 7, and a 3 from there lands exactly on 10, so you win on turn 2 and sit on the finish for the rest of the game.
The first time, not the last
The player is on the finish on turns 3, 4, and 5, but the answer is 3. You cannot stop the loop early, so you need a way to remember that you have already found the turn, and to leave it alone after that.
Test it before you submit
Press Run.
Until play works, the main block stops with a TypeError, and that is expected, not a sign you broke something.
Your functions are still defined, so you can call each one in the Shell as you finish it, using the examples on this page and in the docstrings.
Then try to break your own code: a track with no ladders or chutes, a game with no rolls, a roll that lands exactly on the finish.
When all three work, the main block plays a full game on a 30-square track, and you get to watch it:
Turn 1: rolled 3, now on square 12
Turn 2: rolled 5, now on square 5
Turn 3: rolled 6, now on square 11
Turn 4: rolled 4, now on square 15
Turn 5: rolled 2, now on square 5
Turn 6: rolled 6, now on square 11
Turn 7: rolled 5, now on square 16
Turn 8: rolled 3, now on square 19
Turn 9: rolled 6, now on square 25
Turn 10: rolled 6, now on square 29
Turn 11: rolled 1, now on square 30
Turn 12: rolled 4, now on square 30
Reached square 30 on turn 11
Ouch: turns 2 and 5 both hit the chute on 17. Before you submit, work out on paper where turns 1, 2, and 10 should end, and check that your output agrees.
Rules
Only what we have covered
Gradescope rejects a submission that uses any of these:
- a
whileloop (Week 9) inornot inas a test, likeif square in starts:(Week 8); aforloop'sinis fine- slicing, like
track[2:5](Week 10) - a list comprehension (Week 11)
copy(),list(),count(),index(),extend(),sum(),max(), ormin()
Each of these would do a loop's job for you, and writing the loop yourself is what this PA is for.
Do not change the lists you are given
None of the functions may change a list passed into it: make_track leaves starts and ends alone, and play leaves track and rolls alone.
Gradescope checks.
Submission
Submit chutes.py to the Gradescope assignment PA1a Chutes and Ladders, then commit and push your Week06 folder (how).
Gradescope also checks that your name and the date are in the docstring, and that ruff check is clean.
Where the game comes from
Chutes and Ladders is the American name for Snakes and Ladders, a board game from India that is centuries old. The original was a lesson about luck and choices; yours is a lesson about lists.