[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