[Tutor] Chutes & Ladders

Peter Otten __peter__ at web.de
Sun Dec 22 17:47:34 CET 2013


Keith Winston wrote:

> I've put together my first small program. It's a simulation of the game
> Chutes & Ladders. It plays the game and amasses an array of  ([multicount]
> [gamecount]) size, and then crunches simple stats on the average moves,
> chutes, and ladders for all games in each high-level (multi) pass.
> Hopefully the code is clear.
> 
> I don't think I really thought out the OOP element properly... I was
> thinking of accomodating a multiplayer version in the future, but that
> would require a significant rewrite. Perhaps Games should be a separate
> class, I still don't really have OOP down (I'm learning Python, OOP, and
> Linux simultaneously). There's no interaction between players in the game,
> so there's not really any reason to do a multiplayer version: I was just
> using this to familiarize myself with basic Python and maybe some stats.

You could add a rule that a second player arriving on a field can kick out 
the current occupant -- like in 

http://en.wikipedia.org/wiki/Mensch_ärgere_dich_nicht

> I'd be interested in ALL feedback: aesthetic, functional, design,
> whatever. I would have used numpy but I couldn't get it installed... I
> noticed,
> belatedly,  that the math module has arrays, I didn't look to see if they
> would have made sense to use, I think I remember hearing something about
> them being inefficient or something. Anyway, thanks.

My random remarks:
 
> #Chutes & Ladders Simulation 1.0
> import random
> 
> # Landing on a chute (key) causes one to slide down to the corresponding
> value.
> chutes = {16: 6, 47: 26, 49: 11, 56: 53, 62: 19, 64: 60, 87: 24, 93: 73,
> 95: 75, 98:78}
> 
> # Landing on a ladder (key) causes one to slide up to the corresponding
> value.
> ladders = {1: 38, 4: 14, 9: 31, 21: 42, 28: 84, 36: 44, 51: 67, 71: 91,
> 80:100}
> 
> class Player:
>     """Player class for Chutes & Ladders."""
> 
>     def __init__(self):

Consider providing chutes and ladders as arguments to avoid the implicit 
dependency from global variables.

>         self.reset()
> 
>     def reset(self):
>         self.position = 0
>         self.movecount = 0
>         self.numchutes = 0
>         self.numladders = 0
> 
>     def move(self):
>         """Single move, with chutes, ladders, & out of bounds"""
>         roll = random.randint(1,6)
>         self.movecount += 1
>         self.position += roll
>         if self.position in chutes.keys():
>             self.position = chutes.get(self.position)
>             self.numchutes += 1

.keys() is redundant in Python 3 (and does a lot of extra work in Python 2).
When you already know that key is in somedict `somedict[key]` is more 
efficient than `somedict.get(key)` and has the same result.

I would write the above two lines as

          if self.position in chutes: 
              self.position = chutes[self.position]

>         elif self.position in ladders.keys():
>             self.position = ladders.get(self.position)
>             self.numladders += 1

When you look at the code you see that chutes and ladders are handled the 
same way. You might consider using a single dictionary for both.

>         elif self.position > 100:  # move doesn't count, have to land
> exactly
>             self.position -= roll
> 
>     def game(self):
>         """Single game"""
>         self.reset()
>         while self.position < 100:
>             self.move()
> 
>     def gameset(self, reps):
>         """A set of games, returning associated stats array"""
>         setstats = []
>         for i in range(reps):
>             self.game()
>             stat = [i, self.movecount, self.numchutes, self.numladders]

As you are not planning to append items to `stat` a tuple is more idiomatic 
here than a list. For extra readability try collections.namedtuple which 
allows accessing the data in the tuple as (for example) `stat.numchutes` 
instead of the hard to remember stat[3].

>             setstats.append(stat)
>         return setstats
> 
>     def multisets(self, multi, reps):
>         """A set of game sets, adding another dim to the stats array"""
>         multistats = []
>         for i in range(multi):
>             set1 = p1.gameset(reps)

That should be
              set1 = self.gameset(reps)

>             multistats.append(set1)
>         return multistats

I'd probably remove the multisets() method from the class to keep the 
interface small and replicate the functionality in client code with

games = [p1.gameset(gamecount) for _ in range(multicount)]

I might do the same for gameset() (and have game() return the statistics for 
a single game).

> p1 = Player()
> gamecount = 1000
> multicount = 10
> games = p1.multisets(multicount, gamecount)
> print("Avg moves  Avg chutes  Avg ladders")
> for i in range(multicount):
>     tmulti = games[i]

In Python you can iterate over a list directly. With that in mind...

>     summoves, sumchutes, sumladders = 0, 0, 0
>     for j in range(gamecount):
>         tgset = tmulti[j]
>         summoves += tgset[1]
>         sumchutes += tgset[2]
>         sumladders += tgset[3]

your two loops can be simplified:


for tmulti in games:
    ...
    for tgset in tmulti:
        ...
    ...

>     print(str(summoves/gamecount).rjust(9), \
>           str(sumchutes/gamecount).rjust(12), \
>           str(sumladders/gamecount).rjust(13))

Once you become familiar with format strings you may find

print("{moves:9.2f} {chutes:12.2f} {ladders:13.2f}".format(
    moves=summoves/gamecount,
    chutes=sumchutes/gamecount,
    ladders=sumladders/gamecount))

easier to maintain. You could also factor out the calculation of the average 
into a separate function. Your code would then become (untested)

def mean(items):
    items = list(items)
    return sum(items) / len(items)

print("Avg moves  Avg chutes  Avg ladders")
for tmulti in games:
    print("{moves:9.2f} {chutes:12.2f} {ladders:13.2f}".format(
            moves=mean(tgset[1] for tgset in tmulti),
            chutes=mean(tgset[2] for tgset in tmulti),
            ladders=mean(tgset[3] for tgset in tmulti)
            ))

with the option to replace my naive mean() with a better implementation.

> Thanks for any suggestions or thoughts. I know this is a very simple
> program, but I'm very pleased that, once I began to sort out the basics,
> it fell together pretty readily: I really like Python, though there's a
> lot to learn. FYI, I recently played C & L with a 4 y.o. friend, it is not
> otherwise my game of choice ;-)
 
To sum it up: I like what you have, my hints are all about very minor points 
:)




More information about the Tutor mailing list