Object Oriented Programming & Rollback Module

A place for Ren'Py tutorials and reusable Ren'Py code.
Forum rules
Do not post questions here!

This forum is for example code you want to show other people. Ren'Py questions should be asked in the Ren'Py Questions and Announcements forum.
Post Reply
Message
Author
sqmath
Newbie
Posts: 18
Joined: Fri Jun 14, 2013 11:03 am
Contact:

Object Oriented Programming & Rollback Module

#1 Post by sqmath »

Hi everyone,
I asked a question in the forum a couple of weeks ago, but no one could answer my question exactly, so I began developing a module that solved the problem.
What is the problem? I want to used Object Oriented Programming with Ren'py, so that Ren'py automatically saves every attribute of both classes and instances and, if possible, support rollback that way.
It can be saved. It can be rollbacked (at least in the more or less simplest examples; I couldn't test everything because I don't know how rollback works and I don't have the time nor the objective to do it).
So, here it is. I leave the module to you; just follow the instructions at the beggining of the code.

Code: Select all

__author__ = "Alvaro Parafita (parafita.alvaro@gmail.com)"

"""
Creates a base class to ensure that all attributes of a user-defined
class are in a dictionary so that ren'py can store them accordingly.
It also enables rollback with complex variables

Usage:
* Copy this module as 'oop.py' in your game folder
* Copy this snippet in any .rpy script file:

init -999:
    $ from oop import *

* Make all of your classes inherit from OOP
* When you need to use lists, tuples, dicts or sets use the ones defined in oop.py
  Those are created calling List(), Tuple(), Dict(), Set() (passing as argument the values you want to add; check the module for more info on this)
  These classes have all methods that list, tuple, dict or set use.

Caution:
* Don't create or delete classes during the game. 
  They must be defined in a init block.
* This only saves lists, tuples, dicts and sets. For any other structure, implement it yourself inheriting from OOP.
* Be careful with rollback. It's supposed to work but it could to some weird behaviour (I can't test every single case).
* This solution is meant to be functional, not optimal. 
  I didn't have more time to optimize anything (specially the structure definitions),
  so it's sure to be potentially more optimal. Feel free to edit the code according to your needs.
"""

import renpy.store as store


ins_ids_expression = "__oop_ins_ids_%s" #%class name
cls_attr_expression = "__oop_cls_attr_%s_%s" #%(class_name, attr_name)
ins_attr_expression = "__oop_ins_attr_%s_%d_%s" #%(class_name, ins_ids, attr_name)


class OOPType(type):

    def __new__(cls, name, parents, dct, *args, **kwargs):
        store.__setattr__(ins_ids_expression%name, 0)

        return super(OOPType, cls).__new__(cls, name, parents, dct)


    def __getattr__(cls, name):
        return store.__getattribute__(cls_attr_expression%(cls.__name__, name))


    def __setattr__(cls, name, value):
        store.__setattr__(cls_attr_expression%(cls.__name__, name), value)


    def __delattr__(cls, name):
        store.__delattr__(cls_attr_expression%(cls.__name__, name))


class OOP(object):

    __metaclass__ = OOPType


    def __new__(cls, *args, **kwargs):
        ins = super(OOP, cls).__new__(cls)

        name = cls.__name__
        ids = store.__getattribute__(ins_ids_expression%name)
        store.__setattr__(ins_ids_expression%name, ids + 1)

        ins.__referrer__ = (name, ids)
        ins.__oopattrs__ = set() #It can be a normal set cause it will only store strings

        return ins


    def __getattr__(self, name):
        cls, ids = self.__referrer__

        try:
            return store.__getattribute__(ins_attr_expression%(cls, ids, name))
        except:
            raise AttributeError(name)


    def __setattr__(self, name, value):
        if name == "__referrer__":
            super(OOP, self).__setattr__(name, value)
        else:
            cls, ids = self.__referrer__
            store.__setattr__(ins_attr_expression%(cls, ids, name), value)
            if name != '__oopattrs__':
                #It's a set, so we can't add it more than once
                self.__oopattrs__.add(name)


    def __delattr__(self, name):
        cls, ids = self.__referrer__
        store.__delattr__(ins_attr_expression%(cls, ids, name))
        self.__oopattrs__.remove(name)


    def __del__(self):
        cls, ids = self.__referrer__

        if '__oopattrs__' not in store.__dict__:
            #We're exiting the program, not deleting conciously the object
            #Nothing must be done
            return

        for name in list(self.__getattr__('__oopattrs__')):
            self.__delattr__(name)

        last_ids = store.__getattribute__(ins_ids_expression%cls) 

        if (last_ids-1) == ids:
            store.__setattr__(ins_ids_expression%cls, last_ids-1)


_ooplist_items_expression = "item%d" #%index

class List(OOP):

    def __init__(self, original=[]):
        self._ooplist_num_items = 0
        
        for x in original:
            self.append(x)


    def __len__(self):
        return self._ooplist_num_items


    def __getitem__(self, key):
        if type(key).__name__ == 'slice':
            start, stop, step = key.start, key.stop, key.step

            start = start if start else 0
            stop = stop if stop else len(self)
            step = step if step else 1

            return List([ self[i] for i in range(start, stop, step) ])

        else:
            if key<0:
                key = len(self) - key
            try:
                return self.__getattr__(_ooplist_items_expression%key)
            except:
                raise IndexError(key)


    def __setitem__(self, key, value):
        if type(key).__name__ == 'slice':
            if key.step:
                raise Error("No step allowed here!")

            start, stop = key.start, key.stop

            start = start if start else 0
            stop = stop if stop else len(self)

            for i in range(stop - start):
                self.pop(start)

            try:
                #Object is iterable?
                for x in value.__reversed__():
                    #We get them reversed because the inserting would be reversed otherwise
                    self.insert(start, x)
            except:
                #Object is not iterable
                self.insert(start, value)

        else:
            if key<0:
                key = len(self) - key

            if key >= len(self):
                for i in range(len(self), key):
                    self.__setattr__(_ooplist_items_expression%i, None)
                self.__setattr__(_ooplist_items_expression%key, value)
                self._ooplist_num_items = key + 1

            else:
                self.__setattr__(_ooplist_items_expression%key, value)


    def __iter__(self):
        return [self[i] for i in range(0, len(self))].__iter__()


    def __reversed__(self):
        return [self[i] for i in range(0, len(self))].__reversed__()


    def append(self, item):
        length = len(self)
        self._ooplist_num_items += 1
        self[length] = item


    def extend(self, l):
        for item in l:
            self.append(item)


    def insert(self, index, item):
        length = len(self)
        self._ooplist_num_items += 1

        for i in range(length-1, index-1, -1):
            self[i+1] = self[i]

        self[index] = item


    def remove(self, item):
        self.pop(self.index(item))


    def pop(self, i=-1):
        i = len(self)-1 if i==-1 else i

        for i in range(i+1, len(self)):
            self[i-1] = self[i]

        self.__delattr__(_ooplist_items_expression%(len(self)-1))
        self._ooplist_num_items -= 1


    def index(self, item):
        for n, x in enumerate(self):
            if x == item:
                return n

        raise Exception("No such item")
        

    def count(self, item):
        n = 0
        for x in self:
            if x == item:
                n += 1

        return n


    def sort(self, cmp=None, key=None, reverse=False):
        values = list(self)
        values.sort(cmp=cmp, key=key, reverse=reverse)
        self[:] = values


    def reverse(self):
        values = list(self)
        values.reverse()
        self[:] = values


    def __str__(self):
        return str(list(self))


    def __repr__(self):
        return repr(list(self))


_ooptuple_items_expression = "item%d" #%index

class Tuple(OOP):

    """
        Tuple class
        Usage: 
            Create a Tuple object calling the class 
            and passing as parameters all objects you want in the tuple.
            If you want to pass a tuple, call it like this: Tuple(*t)
    """

    def __init__(self, *args):
        self._ooptuple_num_items = len(args)
        
        for n, x in enumerate(args):
            self.__setattr__(_ooptuple_items_expression%n, x)


    def __len__(self):
        return self._ooptuple_num_items


    def __getitem__(self, key):
        if type(key).__name__ == 'slice':
            start, stop, step = key.start, key.stop, key.step

            start = start if start else 0
            stop = stop if stop else len(self)
            step = step if step else 1

            return Tuple(*[ self[i] for i in range(start, stop, step) ])

        else:
            if key<0:
                key = len(self) - key
            try:
                return self.__getattr__(_ooptuple_items_expression%key)
            except:
                raise IndexError(key)


    def __iter__(self):
        return (self[i] for i in range(0, len(self))).__iter__()


    def __reversed__(self):
        return (self[i] for i in range(0, len(self))).__reversed__()


    def index(self, item):
        for n, x in enumerate(self):
            if x == item:
                return n

        raise Exception("No such item")
        

    def count(self, item):
        n = 0
        for x in self:
            if x == item:
                n += 1

        return n


    def sort(self, cmp=None, key=None, reverse=False):
        values = list(self)
        values.sort(cmp=cmp, key=key, reverse=reverse)
        return Tuple(*values) #It returns it because Tuples are inmutable


    def reverse(self):
        values = list(self)
        values.reverse()
        return Tuple(*values) #It returns it because Tuples are inmutable


    def __str__(self):
        return str(tuple(self))


    def __repr__(self):
        return repr(tuple(self))


class Dict(OOP):

    def __init__(self, **kwargs):
        for k,v in kwargs.iteritems():
            self[k] = v


    def __getitem__(self, key):
        try:
            return self.__getattr__(key)
        except:
            raise KeyError(key)


    def __setitem__(self, key, value):
        self.__setattr__(key, value)


    def __delitem__(self, key):
        self.__delattr__(key)


    def __iter__(self):
        return list(self.__oopattrs__).__iter__()


    def __len__(self):
        return len(self.__oopattrs__)


    def clear(self):
        for k in self:
            del self[k]


    def copy(self):
        return Dict(**{k:self[k] for k in self})


    def get(self, key, default=None):
        try:
            return self[key]
        except:
            return default


    def has_key(self, key):
        return key in self


    def items(self):
        return Tuple(*[(k,self[k]) for k in self])


    def iteritems(self):
        return self.items().__iter__()


    def iterkeys(self):
        return self.__oopattrs__.__iter__()


    def itervalues(self):
        return [self[k] for k in self].__iter__()


    def keys(self):
        return Tuple(*self.__oopattrs__)


    def pop(self, key, default=None):
        if key in self:
            v = self[key]
            del self[key]
            return v
        else:
            return default


    def popitem(self):
        import random
        return self.pop(self.keys()[random.randint(0, len(self)-1)])


    def setdefault(self, key, default=None):
        if key not in self:
            self[key] = default

        return self[key]


    def update(self, other=None, **kwargs):
        if not other:
            other = []

        if type(other).__name__.lower() != 'dict':
            it = {k: v for (k,v) in other}.iteritems()
        else:
            it = other.iteritems()

        for k,v in it:
            self[k] = v

        for k,v in kwargs.iteritems():
            self[k] = v


    def values(self):
        return Tuple(*[self[k] for k in self])


    def __str__(self):
        return str(dict(self))


    def __repr__(self):
        return repr(dict(self))


class Set(List):
    """
        Set is in fact a List.
        If you want optimization, feel free to work with this class 
        (and change the inheritance to OOP)
    """
    
    def add(self, item):
        if item not in self:
            self.append(item)
Please, feel free to point any mistakes in the code and I'll try to solve them.
Happy renpying :)

User avatar
xela
Lemma-Class Veteran
Posts: 2481
Joined: Sun Sep 18, 2011 10:13 am
Contact:

Re: Object Oriented Programming & Rollback Module

#2 Post by xela »

As you've said, there are limitations but it's really nice bit of programming, well done.
Like what we're doing? Support us at:
Image

Post Reply

Who is online

Users browsing this forum: No registered users