Ren'Py Tutorial Newbie introduction to Lists, Dictionaries and Classes in RenPy

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,559
2,176
Newbie introduction to Lists, Dictionaries and Classes in RenPy

And by newbie, I mean me obviously.
This can be considered Chapter 2 continuation of Newbie guide to basic variables in RenPy.

This is not a short guide and even now, there is still a lot potentially missing.
I take some liberties for the sake of keeping things simple (Yes, I know Character() is a function not a class, but it's easier to explain the familiar than be 100% accurate).


I'm going to be mixing RenPy and python statements here without specifically using the RenPy statements $ or python:. It's only to make it easier to read here at the beginning. As I need to introduce the full code, I will. The actual syntax in a RenPy game will vary, depending on how you use each statement.


Lists. See :

Just arrays in my mind. Collection of other stuff.

I'm going to do into a lot of detail with lists here, but only because most of it is true for the other stuff I will talk about later.

Python:
mylist = ["apple", "banana", "cherry"]

It's the square brackets that make python recognise a list. [ ] .
Each part of the list can be referenced by a number. Those numbers start at ZERO. It's known as it's index number.

So mylist[0] is "apple"

If you try to access an item beyond the end of the list, you get an "index out of bounds" error.

So mylist[3] wouldn't work with this data.

There's also special case where index number -1 (minus one) lets you access the last item in the last. (-2, -3, also work going backwards - though why bother?).

So mylist[-1] would be "cherry"

The contents of a List can be any other data type. So far I've only used strings.

But this would be fine too...
Python:
mylist = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]

Which is a list of integer numbers.


You can also get part of a list by using ranges of index numbers.
A range is specified as [x:y], where x is the start index and y is the end index. The "end index" item is NOT part of the returned values, so I find it easier to think of it as "end index + 1".

So...
mylist[0:4] is [2, 3, 5, 7].
mylist[3:4] is [7].
mylist[3:8] is [7, 11, 13, 17, 19]

Reminder: mylist[8] is 23.

You can also do open ended ranges...
mylist[:5] is [2, 3, 5, 7, 11].
mylist[5:] is [13, 17, 19, 23, 29, 31, 37]


You can also mix any other data types too.

Python:
mylist = [2, "banana", "cherry", 3.14159265, 11, "42", 17, 19, 23, 29, 31, 37]

# [0] = 2           (integer aka int)
# [1] = "banana"    (string aka str)
# [2] = "cherry"    (str)
# [3] = pi          (floating point number aka float)
# [4] = 11          (int)
# [5] = "42"        (str) !!!
# etc, etc.

Though programming tends to work towards fixed, predictable structures... so something as random as this is very unlikely (at least in something as simple as a RenPy game).
That said, it could be a persons name, age, phone number, height, etc.

Python:
list_bob = ["Bob", "Parker", 35, "1-800-fuc-kyou", 1.77]
list_alice = ["Alice", "Taylor", 28, "1-212-555-1234", 1.57]
list_john = ["John", "Doe", 65, "", 1.9]              # John doesn't have a phone number.


You can also have lists of lists.

Python:
list_people = [list_bob, list_alice, list_john]

# list_people[0]    is ["Bob", "Parker", 35, "1-800-fuc-kyou", 1.77]
# list_people[0][0] is "Bob"
# list_people[2][1] is "Doe"
# list_people[1][4] is 1.57

Though RenPy (maybe python too) stores strings as Unicode, so when you display things like "Bob", it actually shows as u'Bob'.

Much more common though is to just go straight to the creation of list_people, without creating the containing lists separately.

Python:
list_people = [ ["Bob", "Parker", 35, "1-800-fuc-kyou", 1.77], ["Alice", "Taylor", 28, "1-212-555-1234", 1.57], ["John", "Doe", 65, "", 1.9] ]

Though something that unwieldy can be made a bit easier to read by spreading it across multiple lines....
Python:
list_people = [
            ["Bob", "Parker", 35, "1-800-fuc-kyou", 1.77],
            ["Alice", "Taylor", 28, "1-212-555-1234", 1.57],
            ["John", "Doe", 65, "", 1.9]
        ]

Again, in theory, each of those lists within the main list could be different lengths and have different data types in there. But it just wouldn't make sense for most programming needs with a simple RenPy game.

Beyond that, you can have lists within lists within lists within lists within lists.
Things are only limited by your imagination. Though keeping it simple will keep you sane.


Working with lists.

You can figure out how long a specific list is by using the len() function...

len(list_people) is 3.
len(list_people[0]) is 5.


You can check if a given value is part of this list using if ... in ...

Python:
default mylist = ["apple", "banana", "cherry"]

label start:

    if "apple" in mylist:
        "There is an apple in the list."
    else:
        "Sorry, no apples today."

    return

You can add to the end of a list using .append()...

Python:
mylist = ["apple", "banana", "cherry"]
mylist.append("orange")

# mylist is now ["apple", "banana", "cherry", "orange"]

You can add into the middle of a list using .insert()...

Python:
mylist = ["apple", "banana", "cherry"]
mylist.insert(2, "orange")

# mylist is now ["apple", "banana", "orange", "cherry"]


You can remove something from a list by value using .remove()...

Python:
mylist = ["apple", "banana", "cherry"]
mylist.remove("banana")

# mylist is now ["apple", "cherry"]

If there is more than one "banana", it will remove the first...
Python:
thislist = ["apple", "banana", "cherry", "orange", "banana"]
thislist.remove("banana")

# mylist is now ["apple", "cherry", "orange", "banana"]


You can also remove something from a list by it's index number using .pop()...

Python:
mylist = ["apple", "banana", "cherry"]
mylist.pop(1)

# mylist is now ["apple", "cherry"]

One other aspect of .pop() is that the data that is removed is passed back to the invoking code. It might be that .remove() does the same, I haven't tested it.

Python:
mylist = ["apple", "banana", "cherry"]
removed_item = mylist.pop(1)

# mylist is now ["apple", "cherry"]
# removed_item is now "banana"

If you don't give it an index number, it will just remove the last item in the list...

Python:
mylist = ["apple", "banana", "cherry"]
mylist.pop()

# mylist is now ["apple", "banana"]


Finally, you can empty the list completely using .clear().

Python:
mylist = ["apple", "banana", "cherry"]
mylist.clear()

# mylist is now []

# Though honestly... this will work just as well, and is quicker to just type...
mylist = []


Looping through lists.

You can loop through a list's values using for:...
for: is a python command. There is no RenPy equivalent, except when writing screen code.
But since you can put python code into RenPy code, you can still do this...


Python:
mylist = ["apple", "banana", "cherry"]
for x in mylist:
    renpy.say(None, x)

# Will show...
# "apple"
# "banana"
# "cherry"

list_people = [
            ["Bob", "Parker", 35, "1-800-fuc-kyou", 1.77],
            ["Alice", "Taylor", 28, "1-212-555-1234", 1.57],
            ["John", "Doe", 65, "", 1.9]
        ]
for x in list_people:
    renpy.say(None, x)

# Will show...
# ["Bob", "Parker", 35, "1-800-fuc-kyou", 1.77]
# ["Alice", "Taylor", 28, "1-212-555-1234", 1.57]
# ["John", "Doe", 65, "", 1.9]

Why "x" ? ... Why not?
It can be any variable name that is unique.
Though in RenPy games, where characters are known as a for Character("Anne") and e for Character("Eileen")... don't use a or e. I guess all I'm saying is that if "x" is used for something specific, don't use "x".

I'm using renpy.say() here to have the RenPy game display something as dialogue. None is used because no specific character is speaking.


Here's a more complex example... though seriously, don't ever do this...

Python:
list_people = [
            ["Bob", "Parker", 35, "1-800-fuc-kyou", 1.77],
            ["Alice", "Taylor", 28, "1-212-555-1234", 1.57],
            ["John", "Doe", 65, "", 1.9]
        ]
for x in list_people:
    for y in x
        renpy.say(None, y)

# Will show...
# ... well, everything... one value at a time. It's slow and I'm not typing all that shit here.


You can also loop through a list's indexes using for ... in range(): in combination with len()...

Python:
list_people = [
            ["Bob", "Parker", 35, "1-800-fuc-kyou", 1.77],
            ["Alice", "Taylor", 28, "1-212-555-1234", 1.57],
            ["John", "Doe", 65, "", 1.9]
        ]

for i in range(len(list_people)):
    renpy.say(None, list_people[i])

If I'm honest, I can't really see why you'd want to. But if the index numbers matter to you more than the data... I guess this is your solution.


A note of *** WARNING ***

mylist2 = mylist DOES NOT CREATE A NEW LIST called mylist2.

What it does is create a pointer (I believe it's called a reference) to the first list.

This isn't just true of lists, but (I believe) most objects within python.

Python:
mylist = ["apple", "banana", "cherry"]
mylist2 = mylist

mylist.append("orange")

# mylist is now ["apple", "banana", "cherry", "orange"]
# mylist2 is also ["apple", "banana", "cherry", "orange"] !!!

So be careful anytime you're typing anything that looks like it could be object2 = object.

The solution is either .copy() or list(). Both work to create a new list, completely independent of the first.

Python:
mylist = ["apple", "banana", "cherry"]
mylist2 = mylist.copy()
mylist3 = list(mylist)

mylist.append("orange")

# mylist is now ["apple", "banana", "cherry", "orange"]
# mylist2 is still ["apple", "banana", "cherry"]
# mylist3 is also ["apple", "banana", "cherry"]

Everything else...

Details of what else can be done with lists can be read here...


Like .sort() or .count().


Dictionaries. See :

Dictionaries are just a variation on a theme. They are stored as key and data pairs.

So something like:

Python:
mydict = {
        "apple": "red",
        "banana": "yellow",
        "cherry": "red"
    }

It's the curly brackets and that extra colon that make python recognise a dictionary. { } .
Each data element of the dict can be referenced by it's key, by putting the key within square brackets.

Python:
temp = mydict["banana"]

# temp would be "yellow"

Which feels kinda limiting... with only one bit of data per entry.

Except, in the same way you can have lists within lists... You can have lists within dictionaries too (or even dictionaries within dictionaries).

Maybe something like:

Python:
dict_people = {
            "Bob": ["Parker", 35, "1-800-fuc-kyou", 1.77],
            "Alice": ["Taylor", 28, "1-212-555-1234", 1.57],
            "John": ["Doe", 65, "", 1.9]
        }

Where the person's first name is used as a key to reference the other information held about them.

Which has the downside that you can only have one person called "Bob" or one person called "Alice". But this is only a poor example, so don't sweat it. I trust you to pick better values for keys.

With this dict/list combo, you would access stuff as a combination of both the way you access dictionaries and lists.

Python:
dict_people = {
            "Bob": ["Parker", 35, "1-800-fuc-kyou", 1.77],
            "Alice": ["Taylor", 28, "1-212-555-1234", 1.57],
            "John": ["Doe", 65, "", 1.9]
        }

v_temp_age = dict_people["Alice"][1]

# my v_temp_age variable would be 28.

Working with dictionaries.

You can check if a given value is a key of this dict using if ... in ...

Python:
mydict = {
        "apple": "red",
        "banana": "yellow",
        "cherry": "red"
    }

label start:

    if "apple" in mydict:
        "Yes, there is an apple."
    else:
        "Sorry, no apples today."

    return

You can also check if a given value matches the data of this dict using if ... in ... too

Python:
mydict = {
        "apple": "red",
        "banana": "yellow",
        "cherry": "red"
    }

label start:

    if "red" in mydict.values():
        "Yes, there is a red fruit."
    else:
        "Sorry, no red fruit today."

    return


To use our more complicated "people info" example...

Python:
dict_people = {
            "Bob": ["Parker", 35, "1-800-fuc-kyou", 1.77],
            "Alice": ["Taylor", 28, "1-212-555-1234", 1.57],
            "John": ["Doe", 65, "", 1.9]
        }

label start:

    if "Parker" in dict_people.values()[0]:
        "Yes, we have someone called Parker."
    else:
        "Sorry, nobody from the Parker family here today."

    return

In this example, we've needed to use a mix of both our dict() and list() references. Specifically, we've used .values() to access the actual data rather than the keys and [0] to access the first element of the list, which stores the surname.


The simplest way to add an item to a dictionary is to just update the dictionary using a new key.

Python:
mydict = {
        "apple": "red",
        "banana": "yellow",
        "cherry": "red"
    }

mydict["orange"] = "orange"

# -or-

dict_people = {
            "Bob": ["Parker", 35, "1-800-fuc-kyou", 1.77],
            "Alice": ["Taylor", 28, "1-212-555-1234", 1.57],
            "John": ["Doe", 65, "", 1.9]
        }

dict_people["Helen"] = ["Johnson", 30, "1-212-555-0000", 1.52]

The key for dictionary can be any simple object. It just has to be possible to "hash" it, which I've no clue about. From my perspective it just means that the key should be a string, an integer, a float or even a boolean (True/False). You can't use a list() as a key. I did try using an image as a key and it did technically work, except I couldn't figure out how make practical use of it. So far, in any game I've seen, I've only ever seen strings used as keys.


Updating a value is done in the same way, expect by using a key name that already exists.

Python:
mydict = {
        "apple": "red",
        "banana": "yellow",
        "cherry": "red"
    }

mydict["apple"] = "bright red"

Though there is a dictionary method called .update, that can be used like this:

Python:
mydict = {
        "apple": "red",
        "banana": "yellow",
        "cherry": "red"
    }

mydict.update({"apple"}: "bright red")

Though that kinda feels like the hard way to do it.


Next, there's removing an entry from the dictionary using .pop()

Python:
dict_people = {
            "Bob": ["Parker", 35, "1-800-fuc-kyou", 1.77],
            "Alice": ["Taylor", 28, "1-212-555-1234", 1.57],
            "John": ["Doe", 65, "", 1.9]
        }

dict_people.pop("Bob"]

# removes Bob's data from the dictionary.

Again, like with lists, .pop() passes the removed data back to the invoking code. In the case of a dictionary though, only the data is passed back and not the actual key. So when we do dict_people.pop("Bob"], it returns ["Parker", 35, "1-800-fuc-kyou", 1.77].


Finally, like lists... dictionaries can be emptied by using .clear()

Python:
mydict = {
        "apple": "red",
        "banana": "yellow",
        "cherry": "red"
    }

mydict.clear()

# mydict is now {}

# Though again, I think it's just easier to type:
mydict = {}

Looping through dictionaries.

Looping around a dictionary comes in three flavors. You can either loop around the key values or you can loop around the data values or both.

Python:
for x in dict_people: #keys
    renpy.say(None, "[x]"]

# would show a list of the dictionary keys. "Bob", "Alice", "John".

for x in dict_people.keys(): #keys
    renpy.say(None, "[x]"]

# is another way to do the same thing. (a loop through the keys).

for x in dict_people.values(): #data
    renpy.say(None, "[x]"]

# Would show ["Parker", 35, "1-800-fuc-kyou", 1.77] then ["Taylor", 28, "1-212-555-1234", 1.57] then ["Doe", 65, "", 1.9]

for x, y in dict_people.items(): #both
    renpy.say(None, "Key : [x] has data value [y]"]

# hopefully things make enough sense by now to figure out for yourself what the output would look like.

Everything else...

Details of what else can be done with lists can be read here...


Like .copy() or .setdefault().


Sets and Tuples. See :

I've never had cause to really look into Tuples or Sets. They are the other two types of array in addition to lists and dictionaries.

From what little I've read, tuples are just lists that can't be changed without recreating them. They use parentheses ( ) instead of square brackets.

Sets seem to be lists that can't be accessed using either an index or a key. From what little I can see, you can only loop around the values in set. Though there does seem to be a lot of extra functions for comparing two sets and some other shit.

I'm sure there's a good reason for using either. I just haven't a clue what it might be.

... and no, I really don't need to be told their usage. Feel free to write your own guide - I may even read it.


Classes. See :

For me, Classes are just a variation on lists. At least, to the level I use them at the moment.

Classes are a template used to create variables. Though I suppose all data is an "object" rather than variable. I use the two names interchangeably.

You're already using the string and integer classes without even realising it (although they are treated someone differently when you code python, so it's understandable... they are sort of implied rather than explicit).

But Character() is a class* in RenPy. So is Image().
* don't shoot me.


Creating a class.

Classes have two components. "properties" and "methods". Properties are the data. Methods are the code that is runs when you tell it to.

I would use "function" and "method" interchangeable. But that maybe just shows how much I really don't understand what I'm talking about.


There is a special function/method name called __init__ that runs when a new object is created. It can be used to do anything that normal code will do. But mainly it's used to add a list of the properties available to this class. In a lot of ways, it's very similar to the keys of a dictionary.

Taking it step by step...

Python:
init python:

    class Person():
        def __init__ (self, fname, sname, age, phone, height):
        # some other shit

define bob_object = Person("Bob", "Parker", 35, "1-800-fuc-kyou", 1.77)

So matching stuff up.

"self" is the first parameter and is the name of the new variable (in this case bob_object).

After that, all the values within the Person() brackets are passed in sequence as the rest of the parameters.

So...

fname is "Bob", sname is "Parker", age is 35, etc.

The convention is that class names should have each word within it Capitalized and to not use any separators except those capital letters. In this case class Person(). But taking it to an extreme class WeatherReportsAtNight().
Not everyone follows that convention, especially RenPy devs who've barely read the documentation (like me).

But at the moment, these are only the values passed to the function. Now we need to do something with them.

That would look like:

Python:
init python:

    class Person():
        def __init__ (self, fname, sname, age, phone, height):
            self.forename = fname
            self.surname = sname
            self.age = age
            self.phone = phone
            self.height = height

define bob_object = Person("Bob", "Parker", 35, "1-800-fuc-kyou", 1.77)

You may notice I've deliberately used different identifiers for forename and surname.
fname is the name of the parameter passed to the function, whereas forename is the name of the class's property. Same with sname and surname.

Most people would use the same name for both parameter and property though. After all, why make life more difficult for yourself by using two different names (like I have).

Likewise self is just a name. In theory it could be anything, but everyone uses self so it makes sense to do the same.

Here's what it would look like if I didn't use self. It's still fine, but don't do it.

Python:
init python:

    class Person():
        def __init__ (wibble, forename, surname, age, phone, height):
            wibble.forename = forename
            wibble.surname = surname
            wibble.age = age
            wibble.phone = phone
            wibble.height = height

define bob_object = Person("Bob", "Parker", 35, "1-800-fuc-kyou", 1.77)

Everything after the def: line is just python code. So if you wanted to invoke another function or do something clever... sure... why not.

But for __init__ maybe keep it simple and just use it to set up the variables and nothing else.

The data types are inherited from the parameters that are passed to the function.

So since "Bob" is a string, bob_object.forename is also a string. And since 35 is an integer number, bob_object.age is an integer number.

Finally (at least for __init__), functions allow for default values to be assumed if no other value is passed to the function.

So...

Python:
init python:

    class Person():
        def __init__ (self, forename, surname="Smith", age=18, phone="", height=1.75):
            self.forename = forename
            self.surname = surname
            self.age = age
            self.phone = phone
            self.height = height

I don't think it makes much sense to assume a first name. To be honest, it doesn't make much sense to assume a family name either, but "Smith" is a good catch all.

By adding those default values, you can now do stuff like:

Python:
define bob_object = Person("Bob")

Which would create bob_object as Bob Smith, aged 18 with no phone number and of average height.

If you wanted to create his older sister Zoe, but using all those other defaults... then...

Python:
define zoe_object = Person("Zoe", age=22)

Which would create zoe_object as Zoe Smith, aged 22 with no phone number and of the same average height as this version of Bob.

Even though age isn't the next parameter after forename, you can place it out of sequence by naming it explicitly.

In fact, you can put everything out of sequence if you are that sort of warped person...

Python:
define alice_object = Person(age=28, height=1.57, forename="Alice", Surname="Winters", phone="1-212-555-1234")

Accessing the values within a class.

Each time you use a class to create a new variable, all those def: statements will carried forward to the new variable.

Since we know that self is just another name for that variable, and the initialization created properties called things like self.forename or self.age - those carry forward too.

So default john = Person("John", "Barker", 22) will let you access stuff as:

john.forename, john.surname, john.age or any of the properties regardless of whether you set the value explicitly or let it use the default values (if you've set those).


Classes based upon other classes (inheritance).

You can also create new classes based upon other classes. If you don't override anything, all the functions will be copied from the old class to the new class. Since one of those functions is __init__ that also means the same properties too.

All you need to do is put the name of the parent class in the brackets of the the class () statement.

But the point to inheritance is to override things. Maybe you want to add a photo the to Person() class and call it Person2().... Yeah, I know... I've zero imagination.

You'd code something like:

Python:
    class Person2(Person):
        def __init__ (self, forename, surname="Smith", age=18, phone="", height=1.75, photo=None):
            super().__init__(forename, surname, age, phone, height)
            self.photo = photo
Honestly, the idea of inheritance is well beyond "newbie" level - so if you really care about that sort of stuff... go read...



define -vs- default.

So far, I've done all my examples as define.
Simple rule of thumb is if the values are not going to change while the game is running, use define. If they are going to change, use default.

Or put another way, stuff created with default is saved as part of the save game file, stuff created with define is not.

You might use define for a list of possible achievements. Then use default to store which ones have been unlocked.

Or use define for a list of items that can be purchased and default to store the inventory of those items.

Think carefully about this sort of stuff. Data saved is also loaded back into the game. If you have 10 achievements stored in a way that will be saved, then add another 5 in chapter 2 of your game.... those 10 will still be loaded by existing players and those extra 5 will be lost (unless you make other arrangements). Likewise, my example of a shop and inventory. Obviously you want the inventory to be saved by the player, but what if you change the prices in the shop and you didn't use define? Sometimes it's better to split something into two variables rather than risk permanently saving something which you don't expect to change (while the game is running).


Custom functions.

The convention is that function names be all lowercase, with underscores used to separate words for readability. Like def set_age():.

Again, not everyone follows the conventions. So you might see code that only uses mixedCase. Like def setAge():.

Generally speaking... pick a style of naming and stick to it. But the underscores are the recommended style.
See :

Function names that begin with double underscore ( __ ) are reserved internal python names. Don't create any yourself, unless they are already in the python documentation. If you add one in error and a future python release happens to add something using that exact same name... things will go... badly.

The same should be consider true for RenPy too which uses a single underscore for it's internal stuff.


So we've used one "method", called __init__, but we can create as many new ones as we like (or none).

Python:
init python:

    class Person():
        def __init__ (self, forename, surname, age, phone, height):
            self.forename = forename
            self.surname = surname
            self.age = age
            self.phone = phone
            self.height = height

        def set_age(self, newage):
            self.age = newage

default bob = Person("Bob", "Parker", 35, "1-800-fuc-kyou", 1.77)

label start:

    $ bob.set_age(42)
    "Bob is now [bob.age]."

    "*** THE END ***"
    return

Okay. That looks pretty pointless when we can just as easily use:
$ bob.age = 42


But perhaps you want to add some validation, to avoid unrealistic ages.

Python:
init python:

    class Person():
        def __init__ (self, forename, surname, age, phone, height):
            self.forename = forename
            self.surname = surname
            self.age = age
            self.phone = phone
            self.height = height

        def set_age(self, newage=18):    # if no age is specified, use 18.
            if newage < 18:
                self.age = 18
            elif newage > 99:
                self.age = 99
            else:
                self.age = int(newage)     # force an integer, just in case some idiot passed a floating point number as an age.

default bob = Person("Bob", "Parker", 35, "1-800-fuc-kyou", 1.77)

label start:

    $ bob.set_age(6)
    "Bob is now [bob.age]."     # Bob is actually 18, not 6.

    "*** THE END ***"
    return

Some functions are used to return a value back to the calling code.

Python:
init python:

    class Person():
        def __init__ (self, forename, surname, age, phone, height):
            self.forename = forename
            self.surname = surname
            self.age = age
            self.phone = phone
            self.height = height

        def set_age(self, newage=18):    # if no age is specified, use 18.
            if newage < 18:
                self.age = 18
            elif newage > 99:
                self.age = 99
            else:
                self.age = newage

        def is_old(self):
            if self.age >= 50:
                return True
            else:
                return False


default bob = Person("Bob", "Parker", 35, "1-800-fuc-kyou", 1.77)

label start:

    if bob.is_old():
        "Bob is old."
    else:
        "Bob is young."

    "*** THE END ***"
    return

But programmers are always looking for shortcuts, and there's one here...

Python:
        def is_old(self):
            return self.age >= 50

It will still return True or False, just using slightly less code to do it.

Beyond that, there's only your imagination (and learning from experience) to decide what other things work well as functions.


Other special functions.

There are some other special function names I've come across.

__str__ can be used whenever the code expects a string.

So for example, the Character() class* has a __str__ function that I presume looks something like:
* please don't shoot me.

Python:
class Character():
    def __init__ (self, name, magic, more_magic, some_shit_i_dont_understand):
        # some clever shit.

    def __str__ (self):
        return self.name

define e = Character("Eileen", color="#04B486")

label start:

    "Hello, my name is [e]."
    "*** THE END ***"
    return

So even though e is a character object, when printed - it merely shows "Eileen", a string.

In our Person() example above, we could perhaps use it to return the character's full name.

Python:
init python:

    class Person():
        def __init__ (self, forename, surname, age, phone, height):
            self.forename = forename
            self.surname = surname
            self.age = age
            self.phone = phone
            self.height = height

        def __str__ (self):
            return self.forename + " " + self.surname


__eq__ can be used whenever one instance of the class is compared with another.

Honestly, I'm really unsure on this one.

But using our Person() example again, I can imagine something like:

Python:
        def __eq__ (self, other):
            if isinstance(other, Person):
                return self.forename == other.forename and self.surname == other.surname
            else:
                return False

What this is doing is returning False if the other variable being compared isn't actually a Person() class.

But as long as it is a Person() then it compares the forename and the surname and if they match, it returns True. If they don't match, it returns False.

You'd use it something like:

Python:
define bob_object = Person("Bob", "Parker", 35, "1-800-fuc-kyou", 1.77)
define zoe_object = Person("Bob", "Parker", 42, "1-212-555-0001", 1.75)

label start:

    if bob_object == zoe_object:
        "Bob has the same name as Zoe."
    else:
        "Bob and Zoe are different."

In this case, even though not all the data is the same, because both names match, that is enough for the code to say the objects match.

Overriding __eq__ is probably a bad idea in most cases. But since I saw it recently, I'm including it here under my list of special known functions for classes.

I've somewhat glossed over isinstance(). If you want to see what that's all about, check this out:



Practically everything is a class.

So way back near the start, I pointed out that lists can contain lists and dictionaries can contain lists.

But a list is a class and so is a dictionary. And so is a string, an integer, a float and a boolean.

So in the same way we have lists made up from strings and integers as well as lists made up from strings, integers and other lists... we can pretty much mix things up as much as we like.

We could have a list of people. Using a list which contains a Class.

Python:
init python:

    class Person():  # yes, yes, I keep forgetting to use the default values.
        def __init__ (self, forename, surname, age, phone, height):
            self.forename = forename
            self.surname = surname
            self.age = age
            self.phone = phone
            self.height = height

default people = [
            Person("Bob","Parker", 35, "1-800-fuc-kyou", 1.77),
            Person("Zoe", "Smith", 22, "1-212-555-0000", 1.75),
            Person("Alice", "Taylor", 28, "1-212-555-1234", 1.57),
            Person("John", "Doe", 65, "", 1.9)
        ]
In this case, people[0].surname would be "Parker".

Everything beyond this point is just a variation on that theme.

Dictionaries using classes.
Classes using other classes.
Classes using lists.
... and anything else you can keep straight in your head.

I recently saw a piece of code intended to be used as a phone text message system for a RenPy game.

That looked a bit like this:
(I've edited it to remove the other functions)

Python:
    class Contact:
        def __init__(self, name, profilePicture, newMessages=False, locked=True):
            self.name = name
            self.profilePicture = im.Scale(profilePicture, 60, 60)
            self.newMessages = newMessages
            self.locked = locked
            self.messages = []

    class Message:
        def __init__(self, contact, msg):
            self.contact = contact
            self.msg = msg
            self.replies = []
            self.reply = False

    class ImageMessage(Message):
        def __init__(self, contact, image):
            self.contact = contact
            self.image = image
            self.replies = []
            self.reply = False

In this case, each character within the game was given it's own default charname = Contact( ... blah, blah...).
Then functions were used to add messages (using the class Message() or class ImageMessage to the list of messages stored for that person.
In addition, as list() was used to store a list of possible replies to each incoming message until the player picked on. At which point the chosen reply was store in self.message.reply and self.message.replies was set back to being an empty list.

Whilst the meat of the code is missing, I hope it shows the potential for classes which store data using other classes and lists (and dictionaries, strings, integers, etc, etc.).

Anyway. that's it. Well done if you reaches this far. Please accept a sticker from the nurse on the way out.
 
Last edited:

GOHO_FA

New Member
Sep 27, 2020
9
1
Python:
Python:
init python:



class Person(): # yes, yes, I keep forgetting to use the default values.

def __init__ (self, forename, surname, age, phone, height):

self.forename = forename

self.surname = surname

self.age = age

self.phone = phone

self.height = height



default people = [

Person("Bob","Parker", 35, "1-800-fuc-kyou", 1.77),

Person("Zoe", "Smith", 22, "1-212-555-0000", 1.75),

Person("Alice", "Taylor", 28, "1-212-555-1234", 1.57),

Person("John", "Doe", 65, "", 1.9)

]

In this case, people[0].surname would be "Parker".

******

I need learn to some tips;

If i want to accest to "Parker" by using index number or merge with a String


I mean like people[0][1]

or

$ strChanger = "Parker"

people[0].strChanger




Its posible??


Thank you..
 

pepplez

Member
Jun 7, 2020
247
403
They're all objects within classes, I knew it. Thank you very much for this post. Thank's to my headache I tend to return to basic:
10 print "Yay!"
20 goto 10
run
 

RandomAccessMammary

New Member
Feb 21, 2021
2
1
Helpful addendum: A set in python is kind of like a list but it automatically filters out duplicates. In addition, you can also use set operations (think of Venn Diagrams) to manipulate them.

You use `setA.add(element)` to add something new into a set, and `setA.discard(element)` to remove it.
`setA.intersection(setB)` will return a new set that contains elements that are in both setA and setB
`setA - setB` will return a new set that contains everything in A, but with everything in setB removed.

There's a few other operations that can be done on them; they're very powerful.
 
  • Like
Reactions: 79flavors

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,151
14,835
Helpful addendum: A set in python is kind of like a list but it automatically filters out duplicates.
Yes but no. set are unordered, therefore you can't strictly consider them as equal to list.
They are a collection of entries that you'll know as being duplication free, but not a list of entries, what make their use radically different.
 
  • Like
Reactions: 79flavors

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,559
2,176
Helpful addendum: A set in python is kind of like a list but it automatically filters out duplicates.

The lack of python's within the initial guide is purely a reflection of my inexperience. At the time, I couldn't see a use case for set types (I still can't). So rather than explain something I didn't really understand, I just omitted them completely.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,151
14,835
At the time, I couldn't see a use case for set types (I still can't).
For games there's only one that cross my mind, but it apply to a really limited set of games ; free roaming with repeatable content. sets can serve as counter for an "end of update" feature.
You use them to store an Unique ID corresponding to the scene, and count how many entries there's in order to know if the player have seen all the available content or not.

Python:
define sarahScenesNumber = 7
define annieScenesNumber = 8
define gameScenesNumber = 15

default sarahSeenScenes = set( [] )
default annieSeenScenes = set( [] )
default gameSeenScenes = set( [] )

label whatever:
    [...]
    call sarahEndOfContent( "secondKiss" )

label sarahEndOfContent( UID ):
    $ sarahSeenScenes.add( UID )
    $ gameSeenScenes.add( "sarah_"+UID )

    if len( sarahSeenScenes ) == sarahScenesNumber:
        if len( gameSeenScenes ) == gameScenesNumber:
            "You've seen all content present so far."
        else:    
            "You've seen all Sarah content present so far, but there's still other scenes to discover."
    return
All the other use I can think about right now are in fact misuse of set.
 

RandomAccessMammary

New Member
Feb 21, 2021
2
1
Yes but no. set are unordered, therefore you can't strictly consider them as equal to list.
They are a collection of entries that you'll know as being duplication free, but not a list of entries, what make their use radically different.
I meant "kinda like a list" in the most abstract newbie-friendly definition possible, though I will say if anyone needs to turn a set into a list they can just call `list(set_obj)` or `sorted(set_obj)`. Keeping in mind the right datastructure for the job is always important for writing maintainable code.

The lack of python's within the initial guide is purely a reflection of my inexperience. At the time, I couldn't see a use case for set types (I still can't). So rather than explain something I didn't really understand, I just omitted them completely.
Example use of a set: Marking squicky fetish content as ok and printing it in alphabetical order.
Python:
default ok_content = set()
label ask_kinks:
    "This game may have some objectionable content. Please select what you're okay with."

label select_kinks:
    menu:
        "BDSM":
            $ok_content.add("bdsm")
            "Enabled bdsm content"
        "Futanari":
            $ok_content.add("futanari")
            "Enabled futanari content"
        "Furry":
            $ok_content.add("furry")
            "Enabled furry content"
        "NTR":
            $ok_content.add("ntr")
            "Enabled NTR content"
        "I'm done adding kinks":
            jump confirm_kinks
    jump select_kinks

label confirm_kinks:
    $renpy.say(None, "The kinks you selected are {}".format(", ".join(list(ok_content))))
    if "bdsm" in ok_content:
        "Pretend there's a bdsm scene here"
    if "futanari" in ok_content:
        "Pretend there's futa content here"
    if "furry" in ok_content:
        "Pretend there's furry content here"
    if "ntr" in ok_content:
        "Pretend there's NTR content here"