This assignment has been closed on November 19, 2024.
You must be authenticated to submit your files

Tutorial 6: Digital Pets

S. Mengel

Setup: before you start

Create a new empty project in Spyder, and call it “Tutorial_6”.

Now create a new program file, trobble.py. All the classes and functions you write have to be added to this file.

Context and Objectives

Since the time when we were young is the greatest of all times, we will today relive the golden age when the CSE101 staff members were young. Some of us have fond memories of digital pets that were fashionable at that point, see e.g. these. You might not know the principle (after all, you were not even born then), so let us give a quick introduction: digital pets were small roughly egg-shaped toys that simulated real pets with a small computer chip and a tiny black and white screen. One had to “feed” them regularly, clean up behind them, and generally take care of their well-being.

In this tutorial, we will create a simplistic version of this by creating sweet little pets that we call trobbles.

Skills we will practice today: writing simple classes, creating objects.

Exercises

Exercise 1: A new pet is born

We will create several trobbles later on, each of them with their own properties. Consequently, it makes sense to represent each trobble as an object of a common class which will be called Trobble.

In this exercise, we will define the Trobble class, to prepare the basic functionalities of our Trobble objects.

Create a new class Trobble in your trobble.py file. (Note: the class name Trobble starts with an upper-case letter, but it is defined in module trobble from the file trobble.py, whose names start with a lower-case letter!)

Your class should begin as follows. Note how the docstring describes the data contained in each Trobble object.

class Trobble:
    """Trobbles: simplified digital pets.

    Data Attributes:
    name -- the Trobble's name.
    sex -- 'male' or 'female'.
    age -- a non-negative integer
    health -- an integer between 0 (dead) and 10 (full health) inclusive
    hunger -- a non-negative integer (0 is not hungry)
    """

Add an __init__() method to the Trobble class. This method has three parameters: self (the object itself), name, and sex. It initializes each new Trobble object’s data attributes with the given values:

  • name is a string containing the name of the Trobble instance, to be initialized by the parameter name,
  • sex is one of the two strings 'male' or 'female'. (Note that this binary perspective on sexes is a gross oversimplification that we make here to keep things manageable.)

Additionally, there are a few more data attributes that you should initialize as follows, in the __init__ method:

  • health encodes the health of the Trobble instance as an integer between 0 (the instance is dead!) and 10. The initial value for health is 10.
  • age is the age of the instance, starting at 0.
  • hunger encodes how hungry the instance is, starting from (and initialized to) 0 and unbounded.

Test your Trobble class. For example, you should see the following behaviour in the console

In [1]: t1 = Trobble('Dave', 'male')

In [2]: t1.name
Out[2]: 'Dave'

In [3]: t1.sex
Out[3]: 'male'

In [4]: t1.age
Out[4]: 0

In [5]: t1.health
Out[5]: 10

In [6]: t1.hunger
Out[6]: 0

Upload your file trobble.py:

Upload form is only available when connected

Exercise 2: Showing what we got

As we have seen in the tests for the last exercise, checking one data attribute after the other to see the status of a Trobble is not very convenient. To simplify this, we will now add a __str__() method that produces a string representing the instance.

Add a __str__() method to your Trobble class. It should start as follows:

    def __str__(self):
        pass  # remove this line and replace with your own code

The strings that your __str__ returns should be in the form '_name_: _sex_, health _health_, hunger _hunger_, age _age_', where _name_, _sex_, _health_, _hunger_, and _age_ are the values of the data attributes with the same name in the active Trobble object.

Test your __str__() method. For example, in the ipython console you should have

In [7]: t1 = Trobble('Dave', 'male')

In [8]: print(t1)
Dave: male, health 10, hunger 0, age 0

In [9]: t1.sex = 'female'

In [10]: print(t1)
Dave: female, health 10, hunger 0, age 0

In [11]: t1.name = 'Eve'

In [12]: print(t1)
Eve: female, health 10, hunger 0, age 0

Upload your file trobble.py:

Upload form is only available when connected

Exercise 3: Growing older

While we can now encode Trobbles as objects, so far they are rather boring because they are just static and do not change unless we change their attributes manually. In this exercise we will make them change over time. But instead of using real time (as is the case for the toys we are trying to emulate), we will work with turns.

To this end, add a method next_turn() to the class Trobble, which should start as follows:

    def next_turn(self):
        """End the turn for the instance and recompute the attribute values
        for the next turn.
        """

When next_turn() is called, we first check if the Trobble is still alive, i.e., if its health is positive. If not, we change nothing. Otherwise,

  • the age of a Trobble instance increases by 1,
  • the hunger increases by the (new) age of the instance (for reasons no one really understands Trobbles have to eat more once they get older),
  • afterwards, for every full 20 hunger the health decreases by 1, e.g. it decreases by 1 for 20 hunger, by 2 for 40, not at all for 19, and by 1 for 39.

Remember that the health of a Trobble can never be negative, so whenever the rules above would give a negative health, we set the health to 0 instead.

Test your next_turn() method. In the ipython console you might have

In [13]: t1 = Trobble('Dave', 'male')

In [14]: print(t1)
Dave: male, health 10, hunger 0, age 0

In [15]: for i in range(13):
    ...:     print(f'Turn {i} -> {t1}')
    ...:     t1.next_turn()
    ...:
Turn 0 -> Dave: male, health 10, hunger 0, age 0
Turn 1 -> Dave: male, health 10, hunger 1, age 1
Turn 2 -> Dave: male, health 10, hunger 3, age 2
Turn 3 -> Dave: male, health 10, hunger 6, age 3
Turn 4 -> Dave: male, health 10, hunger 10, age 4
Turn 5 -> Dave: male, health 10, hunger 15, age 5
Turn 6 -> Dave: male, health 9, hunger 21, age 6
Turn 7 -> Dave: male, health 8, hunger 28, age 7
Turn 8 -> Dave: male, health 7, hunger 36, age 8
Turn 9 -> Dave: male, health 5, hunger 45, age 9
Turn 10 -> Dave: male, health 3, hunger 55, age 10
Turn 11 -> Dave: male, health 0, hunger 66, age 11
Turn 12 -> Dave: male, health 0, hunger 66, age 11

Notice that Dave starts losing health from Turn 6, and (sadly) dies on Turn 11.

Upload your file trobble.py:

Upload form is only available when connected

Exercise 4: Caring for your Trobble

Our Trobbles now change over time, but they only get hungry, then sick, and ultimately they die. While this may be realistic, it is perhaps a little depressing for a pet. So let’s make them live longer now.

Exercise 4a: Feeding

As a start, add a new method feed() that decreases the hunger by 25, but never below 0. Your method should start with

    def feed(self):
        """Feed the Trobble instance to decrease the hunger by 25
        with a minimum value of 0.
        """

Exercise 4b: Curing

Afterwards, to cure sick Trobbles, add a method cure() that increases the health by 5 up to the maximum of 10. The method should start with

    def cure(self):
        """Increase the health of the instance by 5 up to the maximum of 10.
        """

Exercise 4c: Having fun

To keep healthy, having fun is also important. Thus, write a method party() which increases the health by 2 up to the maximum of 10. However, partying will make you hungry, so party() will increase the hunger by 4.

    def party(self):
        """Increase the health of the instance by 2 up to the maximum of 10 
	and increase the hunger by 4.
        """

Exercise 4d: Checking

Now our pets can live longer. So let’s add a method to verify if they are still alive.

Add a method is_alive that returns True if the health of the instance is positive, and False otherwise. Your method should begin with

    def is_alive(self):
        """Return True if the health of the instance is positive,
        otherwise False.
        """

Test the methods you have added in this exercise. In the ipython console you might have

In [16]: t1 = Trobble('Dave', 'male')

In [17]: print(t1)
Dave: male, health 10, hunger 0, age 0

In [18]: for i in range(7):
     ...:     t1.next_turn()
     ...:     

In [19]: print(t1)
Dave: male, health 8, hunger 28, age 7

In [20]: t1.feed()

In [21]: print(t1)
Dave: male, health 8, hunger 3, age 7

In [22]: t1.cure()

In [23]: print(t1)
Dave: male, health 10, hunger 3, age 7

In [24]: t1.party()

In [25]: print(t1)
Dave: male, health 10, hunger 7, age 7


In [26]: for i in range(20):
     ...:     t1.next_turn()
     ...:

In [27]: print(t1)
Dave: male, health 0, hunger 84, age 14

In [28]: t1.is_alive()
Out[28]: False

Upload your file trobble.py:

Upload form is only available when connected

Exercise 5: Playing with Trobbles

After all this hard work, we deserve a break now to play with the pets we have created. To this end, copy the following code to your file trobble.py, separately from the class definition (that is, make sure that it is not part of the indented code block defining the class Trobble).

def get_name():
    return input('Please give your new Trobble a name: ')

def get_sex():
    sex = None
    while sex is None:
        prompt = 'Is your new Trobble male or female? Type "m" or "f" to choose: '
        choice = input(prompt)
        if choice == 'm':
            sex = 'male'
        elif choice == 'f':
            sex = 'female'
    return sex

def get_action(actions):
    while True:
        prompt = f"Type one of {', '.join(actions.keys())} to perform the action, or stop to quit the game: "
        action_string = input(prompt)
        if action_string == 'stop':
            print('Thanks for having played with Trobbles!')
            return
        if action_string not in actions:
            print('Unknown action!')
        else:
            return actions[action_string]
        
def play():
    name = get_name()
    sex = get_sex()
    trobble = Trobble(name, sex)
    actions = {'feed': trobble.feed, 'cure': trobble.cure}
    while trobble.is_alive():
        print('You have one Trobble named ' + str(trobble))
        action = get_action(actions)
        if action is None:
            return
        action()
        trobble.next_turn()
    print(f'Unfortunately, your Trobble {trobble.name} has died at the age of {trobble.age}')

Exercise 5a: Play

You can play by calling the function play(). Note that you can hit ctrl-C to stop playing at any point. After having played a little with some Trobbles, try to understand what is happening in the code you have just copied. In particular, what is the role of action?

Exercise 5b: Party

Additionally, add the method party() to play() as an action.

Exercise 5c: Celebrate some birthdays!

Each time your Trobble turns 10, 20, 30, … years, congratulate by printing “Happy Birthday {trobble.name}!” and give him some food as a present, thus, decrease the hunger by 5. Note: do not congratulate when your trobble just has been created and is 0 years old!

Test the methods you have added in this exercise. In the ipython console you might have

In [29]: play()

Please give your new Trobble a name: Lily

Is your new Trobble male or female? Type "m" or "f" to choose: f
You have one Trobble named Lily: female, health 10, hunger 0, age 0

Type one of feed, cure, party to perform the action: party
You have one Trobble named Lily: female, health 10, hunger 5, age 1

Type one of feed, cure, party to perform the action: feed
You have one Trobble named Lily: female, health 10, hunger 2, age 2

Type one of feed, cure, party to perform the action: party
You have one Trobble named Lily: female, health 10, hunger 9, age 3

Type one of feed, cure, party to perform the action: party
You have one Trobble named Lily: female, health 10, hunger 17, age 4

Type one of feed, cure, party to perform the action: feed
You have one Trobble named Lily: female, health 10, hunger 5, age 5

Type one of feed, cure, party to perform the action: party
You have one Trobble named Lily: female, health 10, hunger 15, age 6

Type one of feed, cure, party to perform the action: party
You have one Trobble named Lily: female, health 9, hunger 26, age 7

Type one of feed, cure, party to perform the action: feed
You have one Trobble named Lily: female, health 9, hunger 9, age 8

Type one of feed, cure, party to perform the action: feed
You have one Trobble named Lily: female, health 9, hunger 9, age 9

Type one of feed, cure, party to perform the action: party
You have one Trobble named Lily: female, health 9, hunger 23, age 10
Happy Birthday Lily!

Type one of feed, cure, party to perform the action: partey
Unknown action!

Upload your file trobble.py:

Upload form is only available when connected

Exercise 6: Be fruitful, and multiply

Trobbles are social animals, and they feel lonely if they have no other animals of their species around them. But since creating them by hand is rather annoying, why not simply let them procreate? Fortunately, Trobbles are very fertile: whenever a female and a male specimen are left alone, they create a new Trobble assuming that both potential parents are old enough to have offspring, which is starting from the age of 4.

Write a function mate(trobble1, trobble2, name_offspring) that, given two living Trobbles and a name for the potential offspring, checks if the prerequisites are met to create a new Trobble and if so returns a new pet with the name name_offspring. For reasons no one really understands, the offspring will always have the same sex as the first argument trobble1.

Your function should start as follows:

def mate(trobble1, trobble2, name_offspring):
    """Check if the given Trobbles can procreate and if so return a new
    Trobble that has the sex of trobble1 and the name 'name_offspring'.
    Otherwise, return None.
    """

Warning: This function is not a method of the Trobble class, so make sure that it is defined outside the class Trobble!

Test your function mate. For example, in the ipython console you should have

In [30]: t1 = Trobble('Dave', 'male')

In [31]: t2 = Trobble('Eve', 'female')

In [32]: c1 = mate(t1, t2, 'Fred')

In [33]: print(c1)
None

In [34]: t1.age = 10

In [35]: t2.age = 10

In [36]: c1 = mate(t1, t2, 'Fred')

In [37]: print(c1)
Fred: male, health 10, hunger 0, age 0

In [38]: c2 = mate(t2, t1, 'Gerda')

In [39]: print(c2)
Gerda: female, health 10, hunger 0, age 0

In [40]: c3 = mate(t1, t1, 'Jon')

In [41]: print(c3)
None

Upload your file trobble.py:

Upload form is only available when connected

Optional Exercise 7: More Trobbles, more troubles

Since now we can make more Trobbles from some others, we can make the game of Exercise 5 more interesting.

To this end, add a function multi_play() that lets you play a game as in play(), but starts out with two Trobbles, one male and one female. In each round, you can feed or cure one of the current pets individually or use the function mate on two of them to create more pets. How many living pets can you have at the same time?

Try to think of different ways to make the game more interesting. For example, you could:

  1. try changing the basic health/hunger mechanics;
  2. try adding a mood attribute to the trobbles (which might depend on health, hunger, breeding, or anything else);
  3. try including more interactions.