Skip to content

Instantly share code, notes, and snippets.

@r3
Created September 7, 2012 19:03
Show Gist options
  • Save r3/3668650 to your computer and use it in GitHub Desktop.
Save r3/3668650 to your computer and use it in GitHub Desktop.
Classes
import random
from collections import namedtuple
COUNTRIES = ("Norway", "Sweden", "Germany", "Egypt", "South Africa", "Canada",
"China", "Brazil", "Mexico")
# Normally, I'd store key/value pairs as a dictionary for the next two globals,
# but in this case, it wouldn't help since we don't do any lookups.
CONDITIONS = (("Cancer", 30), ("AIDS", 40), ("Common Cold", 3),
("Healthy", 0))
TRAITS = (("Healthy Eater", 10),)
# Creating two named tuples here to represent a condition and a trait.
# The notation is discussed further in the _contract_condition method
# Also see the docs:
# http://docs.python.org/library/collections.html#collections.namedtuple
Condition = namedtuple("Condition", ("name", "decrement"))
Trait = namedtuple("Trait", ("name", "increment"))
class Player():
"""This is a player object that we're defining. It will have variables
associated with it (attributes) as well as functions that will affect the
'instances' of a Player (more on that shortly). These functions are called
methods.
Think of attributes as variables that describe the object that we're
building (in this case, a player). What information to you need to store
to handle the player?
A method is more of a behavior of the object. What does the player need to
do? Does it need to change one of its attributes? Does it need to respond
with certain information?
"""
def __init__(self):
"""This is a special method. You can tell by the __underscore__
surrounded name. This is known as a dunder method. It sounds stupid,
I know. This particular special method is called when we create an
instance of the class. When we build a player. Think of this class
definition as a blueprint for a house. We can't live in a blueprint,
we have to build the house that the blueprint describes! Given that
we have a blueprint, we can create any number of houses. These
houses are instances, and it's what we'll manipulate in our program.
The first parameter, 'self' is conventionally the first argument
of a method that deals with these instances. When a method is called,
Python implicitly passes a reference to the instance that called the
method. For example, if you shut a door in our house instance (think
of shutting the door as a method), the house passes the shut_door
method a reference to the house. The first parameter used will contain
this reference. By strong convention, we use 'self', but that doesn't
make 'self' a keyword or special. We could just as well use 'this'
or 'that' as our first parameter and everything would be peachy.
But don't.
"""
# First, we need to setup some attributes
# Let's start with any conditions that our player might have.
# Since the player can have any number of diseases, we might use
# a list to keep track of them.
self.conditions = []
# Notice that we assign to 'self.variable_name'? This is because we
# want Python to know that variable_name is an attribute of the
# instance that 'self' points at. Remember that 'self' will contain
# a reference to the instance that called the method, so we're
# "attaching" a list to the calling instance called 'conditions'.
# Whenever we wish to affect an instance of a class (usually),
# you'll be making changes to the calling instance object referenced
# by 'self'.
# Next, we want to select a country. This might me construed as a
# behavior. But this isn't a behavior that should necessarily be
# called from outside the class. Only a Player instance should
# be manipulating the country, so we might refer to the method
# that manipulates it as "private." We don't have enforced protection
# of methods like some other languages, but we have another strong
# convention that says any method that starts with an underscore is
# a private one. We'll call one such method here. We could go through
# the process of selecting a country here, but it's good design to
# place such self contained actions in a method to keep down clutter.
self._random_country()
# We'll use another private method to select a trait
self._select_trait()
# Third up on our list is the health. It's just going to be an int.
self.health_points = 100
# Same with age, but notice how I specify the time units?
self.years_old = 1
# Next, let's interact with our user. Remember, this __init__ method
# is only called when we create an instance of the Player class, so
# only once for each player.
print("You have just been born!")
self.name = input("What's your name? ")
# Remember how we called the _random_country method earlier? Well, it
# will have set our self.country variable, so we might as well print
# it out when the user is born; when the Player is instantiated.
print("You're from {}".format(self.country))
def _random_country(self):
"""Remember, this is a private method by convention. It will go about
selecting a random country from the global country list. Using a
global variable like this isn't bad since we won't ever change that
variable. In my code, I'd likely make these class variables, but I
don't want to introduce too much at once.
"""
self.country = random.choice(COUNTRIES)
def _select_trait(self):
"""Now we'll assign a trait to the self.trait attribute. The selection
of the trait differes from selection of a country, but the assignment
to the attribute is the same.
"""
# Print out a list of the traits that the user can choose from.
print("Select one:")
for index, trait in enumerate(TRAITS):
trait_name = trait[0]
trait_increment = trait[1]
print("\t{} - {}".format(index, trait_name))
# The 'enumerate' builtin function used above will provide an
# iterator of index, value pairs:
# lst = ("A", "B", "C")
# for index, val in enumerate(lst):
# print("{} - {}".format(index, val)
# # => 0 - A
# # => 1 - B
# # => 2 - C
# This would be a good place to verify that the input is valid, but
# we'll not worry about that for now and just convert it to an 'int'
selection = int(input(">> "))
# This is called tuple unpacking (destructuring). The 'items' method
# of a dict returns an iterator of tuples. Each tuple will be a two
# item container:
# (key, value)
# We then use tuple unpacking to assign these values to a pair of
# local variables. A more simple example is as follows:
# key, value = (1, 2)
# print(value) # => 2
# On line 122-123, tuple unpacking would have made the code:
# trait_name, trait_increment = trait
# Nicer than using indexing, no?
trait_name, trait_increment = TRAITS[selection]
# Now we'll instantiate a Trait named tuple using the those local vars
# A named tuple is exactly like a normal tuple in Python, but the
# items may be accessed by name. In the case of Trait, we've set the
# items to be called Trait.trait_name and Trait.trait_increment. In
# all other ways, they function like tuples.
self.trait = Trait(trait_name, trait_increment)
def _contract_condition(self):
"""We'll use this private method to contract a condition."""
# First, let's grab a condition from the global tuple of conditions.
# We'll use tuple unpacking to save the items to local vars
condition_name, condition_decrement = random.choice(CONDITIONS)
# Let's check if there's a decrement to the condition. If there's
# not, then we've not contracted a disease (as in the case of Healthy)
if condition_decrement:
# Just like with a Trait, we create a named tuple to hold the
# information concerning the condition.
self.conditions.append(
Condition(condition_name, condition_decrement))
print("You've contracted {}".format(condition_name))
def _lose_health_phase(self):
"""Lose health points for each negative condition that has been
contracted.
"""
for condition in self.conditions:
self.health_points -= condition.decrement
print("You've lost {} health due to your {}".format(
condition.decrement,
condition.name))
def _gain_health_phase(self):
"""Gain health points for the selected positive trait"""
self.health_points += self.trait.increment
print("You've gained {} health due to your {}".format(
self.trait.increment,
self.trait.name))
def take_turn(self):
"""This method doesn't start with an underscore, which means that it
is 'public' and intended to be called by some external process.
Whatever is manipulating your Player instances (we'll see this
shortly). This particular method is going to be run whenever a player
is to take a turn.
"""
print("\nYour health is now {}\n".format(self.health_points))
self._gain_health_phase()
self._lose_health_phase()
self._contract_condition()
def is_alive(self):
"""Simple bool describing the state of the Player (living/dead)"""
return self.health_points > 0
# The following is some boilerplate code that you'll see a lot in Python. Essentially, it determines if
# you're running the module as a script, or if it is being imported by another module. If it is run as
# a script (python script.py), then the '__name__' variable will hold the str "__main__".
# If the module is imported by another, the '__name__' will hold the name of the module
# (ie: script.py is imported -> __name__ == 'script')
if __name__ == '__main__':
# This is where we instantiate a player. We build the house from the blueprints. It's exactly like
# calling a function, but the return value will be a Player instance.
player = Player()
while player.is_alive():
player.take_turn()
input("Press Enter to continue.") # Just a cheap "pause"
print("You've died")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment