Trying to turn nest of arrays into something more manageable

Discuss how to use the Ren'Py engine to create visual novels and story-based games. New releases are announced in this section.
Forum rules
This is the right place for Ren'Py help. Please ask one question per thread, use a descriptive subject like 'NotFound error in option.rpy' , and include all the relevant information - especially any relevant code and traceback messages. Use the code tag to format scripts.
Post Reply
Message
Author
Wap
Newbie
Posts: 12
Joined: Wed Jul 13, 2016 6:11 pm
Contact:

Trying to turn nest of arrays into something more manageable

#1 Post by Wap »

Hi there.

I'm trying to make a game where there's a town with a bunch of residents, and your interactions with them are determined by traits each is assigned, rather than any pre-scripted dialogue specifically for each individual character. I realized going in that I might need to learn a bit of python, and I have been doing so, but there's a really basic coding mechanic that I haven't come across yet. I don't know the correct terminology for it, so I'm just going to have to explain it longhand.

I'd previously had this working by defining a Person class with python and just declaring each as a variable, eg:

Code: Select all

guy01 = Person('Gary','Smith',44,'Blacksmith',['short-tempered','single father','tough','honest'])
gal01 = Person('Jane','Jones',27,'Baker',['patient','funny'])
(That was a massively simplified, the actual character definitions were way more complicated the way I had it.)

Now, this was fine, but I realized as I worked on it that it would be much easier if I could have everything read from a tab-delimited text file that I export from an Excel spreadsheet. So I found out how to do this, I'll include the code here just for clarity, or if anyone might find it useful:

Code: Select all

    def parse_csv(filename):
        f = file(config.gamedir + "/data/" + filename)
        rv = [ ]

        for l in f:
             l = l[:-1]
             rv.append(l.split('\t'))

        f.close()
        return rv
The problem is this sticks everything into a bunch of nested arrays, like it would look something like [['Gary','Smith',44,'Blacksmith',['short-tempered','single father','tough','honest']],['Jane','Jones',27,'Baker',['patient','funny']] (etc etc)]. Where before each character was defined as a variable (guy01, gal01, etc) that you could name and reference and move around quite easily, this method just has it as a sort of nameless object inside an array. I don't want that.

In other programming languages I've fiddled with - probably Flash or JavaScript or both, they sort of blur together for me - you could take data and stick it into a variable name dynamically, sorta like like:

_root["person "+i] = Person([whatever array is necessary])

And as part of a loop where i is each successive pass, you'd end up with a bunch of people objects named person1, person2, person3, etc. I want to be able to do that.

Does this make sense to anyone? I feel like I've written a lot to ask a simple question and may have nevertheless failed to communicate it properly. Hope it's clear enough. Lemme know if it isn't.

User avatar
Pando
Regular
Posts: 29
Joined: Wed Oct 08, 2014 7:57 am
Projects: Crossfire
Organization: Agape Studios
IRC Nick: Pando
Github: Scylardor
Contact:

Re: Trying to turn nest of arrays into something more manage

#2 Post by Pando »

Despite Python being a dynamic language, I don't think you can really name a variable after a string value (basically what you want) by design.
There is a way to do it, but it is discouraged.
You could use a dictionary instead.

As for what you're trying to do... Some comments directly on the code:

To open files it is considered best practice to use the RAII Python idiom "with" when dealing with files, so you can't accidentally forget to close the file handle.
Plus, it should be of no importance for such trivial task, but in principle, you should close the file as soon as you're done with it, instead of doing all the computation and closing it only at the end. :)

Code: Select all

lines = []
with open(config.gamedir + "/data/" + filename, 'rb') as f:
    lines = f.readlines()
# The file is now closed
# Awesome algorithm using lines here
also, although parsing csv is pretty straightforward, there's the csv standard package that can do all the parsing for you and more:

Code: Select all

import csv
with open('some.csv', 'rb') as f:
    reader = csv.reader(f)
    for row in reader:
        print row
In the following I would assume you can use a dictionary named "characters" by example...
Not sure what the Person class is really for, but in the event it would just be a data holder, you could completely replace it by the dictionary:

Code: Select all

characters = {}
l = [['Gary','Smith',44,'Blacksmith',['short-tempered','single father','tough','honest']],['Jane','Jones',27,'Baker',['patient','funny']]]
for char_info in l:
    first_name, last_name, age, job, traits = char_info
    characters[first_name+ '_' + last_name] = {
       'name': first_name + ' ' + last_name,
       'age': age,
       'job': job,
       'traits': traits
    }
characters
{'Gary_Smith': {
  'age': 44,
  'job': 'Blacksmith',
  'name': 'Gary Smith',
  'traits': ['short-tempered', 'single father', 'tough', 'honest']},
 'Jane_Jones': {
  'age': 27,
  'job': 'Baker',
  'name': 'Jane Jones',
  'traits': ['patient', 'funny']}}
That would be if you'd want to use "first name_last name" as an unique key to identify a character, but it should not be too hard to use "guy01", "gal01" as you intended instead.
Of course this solution becomes tightly coupled with the format of your csv file :wink: (if you finally put first name and last name at the end, you'll have to adapt the code too)

If that's a problem, you could solve it using the DictReader of the csv package, for example.
Image

User avatar
nyaatrap
Crawling Chaos
Posts: 1824
Joined: Mon Feb 13, 2012 5:37 am
Location: Kimashi Tower, Japan
Contact:

Re: Trying to turn nest of arrays into something more manage

#3 Post by nyaatrap »

Why not directly make instances by one function?

Code: Select all

def make_person_instances_from_tsv(filename):
   f = renpy.file(filename) #Use renpy.file for archive files
      for l in f:
          l = l.decode("utf-8")
          a = l.rstrip().split("\t")
          setattr(store, a[0], Person(*a)) #"bob 10" becomes bob=Person("bob", "10")
   f.close()

User avatar
Pando
Regular
Posts: 29
Joined: Wed Oct 08, 2014 7:57 am
Projects: Crossfire
Organization: Agape Studios
IRC Nick: Pando
Github: Scylardor
Contact:

Re: Trying to turn nest of arrays into something more manage

#4 Post by Pando »

I didn't know this store variable magic :(
Using renpy.file is a good idea too.

It seems indeed a superior solution !
Image

Wap
Newbie
Posts: 12
Joined: Wed Jul 13, 2016 6:11 pm
Contact:

Re: Trying to turn nest of arrays into something more manage

#5 Post by Wap »

Thanks both of you. I'll opt for the nyaatrap method since it gets both of your votes, but I would like to thank you regardless Pando, you've given me information I may still use.

However I have a problem, and I suspect it's just me overlooking something simple. When I use this:

Code: Select all

    def importPeople(filename):
        f = renpy.file(filename) # Use renpy.file for archive files
        #f = filename
        for l in f:
            l = l.decode("utf-8")
            a = l.rstrip().split("\t")
            setattr(store, a[0], Person(*a)) # "bob 10" becomes bob=Person("bob", "10")
        f.close()

    importPeople(config.gamedir + "/data/people.txt")
I receive this error:

Code: Select all

I'm sorry, but an uncaught exception occurred.

While running game code:
  File "game/script.rpy", line 14, in script
    init python:
  File "game/script.rpy", line 267, in <module>
    importPeople(config.gamedir + "/data/people.txt")
  File "game/script.rpy", line 259, in importPeople
    f = renpy.file(filename) # Use renpy.file for archive files
Exception: Backslash in filename, use '/' instead: u'C:\\Games\\Anime\\RENPY\\[my games]\\Lab/game/data/people.txt'

-- Full Traceback ------------------------------------------------------------

Full traceback:
  File "game/script.rpy", line 14, in script
    init python:
  File "C:\Games\Anime\RENPY\renpy-6.99.10-sdk\renpy\ast.py", line 806, in execute
    renpy.python.py_exec_bytecode(self.code.bytecode, self.hide, store=self.store)
  File "C:\Games\Anime\RENPY\renpy-6.99.10-sdk\renpy\python.py", line 1577, in py_exec_bytecode
    exec bytecode in globals, locals
  File "game/script.rpy", line 267, in <module>
    importPeople(config.gamedir + "/data/people.txt")
  File "game/script.rpy", line 259, in importPeople
    f = renpy.file(filename) # Use renpy.file for archive files
  File "C:\Games\Anime\RENPY\renpy-6.99.10-sdk\renpy\exports.py", line 1937, in file
    return renpy.loader.load(fn)
  File "C:\Games\Anime\RENPY\renpy-6.99.10-sdk\renpy\loader.py", line 526, in load
    raise Exception("Backslash in filename, use '/' instead: %r" % name)
Exception: Backslash in filename, use '/' instead: u'C:\\Games\\Anime\\RENPY\\[my games]\\Lab/game/data/people.txt'

Windows-7-6.1.7601-SP1
Ren'Py 6.99.10.1227
Lab 0.0
Now, I only used '/' - the \\s appear to come from config, which I didn't set. How do I sort this?

User avatar
Pando
Regular
Posts: 29
Joined: Wed Oct 08, 2014 7:57 am
Projects: Crossfire
Organization: Agape Studios
IRC Nick: Pando
Github: Scylardor
Contact:

Re: Trying to turn nest of arrays into something more manage

#6 Post by Pando »

Does simply replacing the backslashes by slashes work ?

Code: Select all

importPeople(config.gamedir.replace('\\', '/') + "/data/people.txt")
Perhaps using the os module would be cleaner, since it should clean the file delimiters for you (I think...):

Code: Select all

import os
importPeople(os.path.join(config.gamedir, "data", "people.txt"))
Image

Wap
Newbie
Posts: 12
Joined: Wed Jul 13, 2016 6:11 pm
Contact:

Re: Trying to turn nest of arrays into something more manage

#7 Post by Wap »

Wow, at this point I know enough that I should have thought of that. Doy!

I ended up removing the asterisk from the (*a) so that each character's data (apparently) gets passed as an array rather than a sequence of arguments. This way I can add or remove collumns from the xls without having to worry about RenPy giving errors about the wrong number of arguments.

Thanks to both of you for this help. With this out of the way, the thing will actually start resembling something that, in a dim light, from the right angle, if you squint a little, resembles a game somewhat.

Post Reply

Who is online

Users browsing this forum: Bing [Bot]