28 June, 2006

Rethinking Real-World Object Models in Simulations

    Over the past quarter century I have routinely pondered back to the time of my childhood and early experiences with interactive fiction text-based adventure games.  Chances are that if you were involved with computers  at any point prior to the mid-eighties, you have experienced what I speak of.  If not, maybe you experienced multi-user dungeons (MUDs) in your college or bulletin board (BBS) years.


    In many cases these ‘games’ were written in the low level language du jour, such as ‘C’, or in a custom language like ‘Inform’ (in the case of Infocom games) which was also written in ‘C’.   There were languages which would’ve been better suited for these games/simulations, object oriented programming (OOP) languages such as Smalltalk, or Objective-C, but they were generally not utilised as such.  


    Recently in my workplace I have been developing full time (for the past couple of years) in Python, a wonderful high level language written by Guido van Rossum which is a proper OOP language.  As such, the idea of writing a simulation (not a ‘game’ per se) which would allow me to focus on not only how one would model the real world (to a certain depth), but as an exercise in python objects.  


    While the code is still an ongoing side work of mine, I am posting the parent class (SimObject) and it’s associated child classes (Place, Person and ExitObject) for review by those who are interested.  Please note that due to certain conflicts with word press, proper pythonic indentation (required for execution) is not established as such.  



#####################################################

#                            S  I  M  O  B  J  E  C  T       C   L   A   S   S                          #

#####################################################


class SimObject:

    object_master_list=[]

    def __init__(self,

                object_id = 0,

                object_sku = 0,

                short_description = 'Short Description',

                long_description = 'Long Description',

                name = 'Name',

                weight = 1.0,

                height = 1.0,

                width  = 1.0,

                depth  = 1.0,

                visible  = True,

                closed   = False,

                contents = [],

                stack    = []):

        self._object_id         = object_id

        self._object_sku        = object_sku

        self._short_description = short_description

        self._long_description  = long_description

        self._name              = name

        self._weight            = weight

        self._height            = height

        self._width             = width

        self._depth             = depth

        self._visible           = visible

        self._closed            = closed

        self._contents          = []

        self._stack             = []

        self.object_master_list.append(self)

        


    def getObjectById(self,object_id):

        for sim_objects in self.object_master_list:

            if sim_objects.getObjectId() == object_id:

                return sim_objects

                

    def addContents(self,item):

        try:

            self._contents.append(item)

            return True

        except:

            return False


    def addToStack(self,item):

        try:

            self._stack.append(item)

            return True

        except:

            return False

            

    def isVisible(self):

        return self._visible

        

    def getContents(self):

        return self._contents       


    def getShortDescription(self):

        return self._short_description


    def getLongDescription(self):

        return self._long_description


    def getSize(self):

        return "Object Details ... Weight: %.2f, Height: %.2f, Width: %.2f, Depth: %.2f" % (self._weight, self._height, self._width, self._depth)


    def getHeight(self):

        return self._height


    def getWeight(self):

        return self._weight


    def getName(self):

        return self._name


    def getObjectId(self):

        return self._object_id


    def getDescription(self):

        output = ''

        vowels = ['a','e','i','o','u']

        if isinstance(self,Place):

            short_desc = self.getShortDescription()

            output += short_desc + "\n"

            underline = ''

            for i in str(short_desc):

                underline += '-'

            output += underline + "\n"

            output += self.getLongDescription() + '\n'

            for thing in self.getContents():

                if isinstance(thing,ExitObject):

                    if thing.isVisible():

                        output += "There is "

                        if thing.getName()[0].lower() in vowels:

                            output += 'an '

                        else:

                            output += 'a '

                        output += thing.getName() + ' '

                        output += 'here.\n'

                    else:

                        ### Don't short exits if they are just cardinal directions (_visible=False)

                        pass


            itemlist = []

            peoplelist = []

            for thing in self.getContents():

                if isinstance(thing,SimObject) and not isinstance(thing,ExitObject) and not isinstance(thing,Person):

                    itemlist.append(thing.getName()) 

                elif isinstance(thing,Person):

                    peoplelist.append(thing.getName())

            if len(itemlist) == 1:

                output += "There is "

                if itemlist[0][0].lower() in vowels:

                    output += 'an '

                else:

                    output += 'a '

                output += itemlist[0] + ' '

                output += 'here.\n'

            if len(itemlist) >= 2:                                    

                total_items = len(itemlist)

                item_counter = 1

                output += "There are "

                for items in itemlist:

                    if items[0].lower() in vowels:

                        output += 'an '

                    else:

                        output += 'a '  

                    if item_counter <= total_items-2:

                        output += str(items)+', '

                    elif item_counter == total_items-1:

                        output += str(items)+' and '

                    else:

                        output += items + " "

                    item_counter += 1

                output += "here.\n"

                output += "\n"

            if len(peoplelist) == 1:

                output += str(peoplelist[0]) + " is here.\n" 

            elif len(peoplelist) > 1:

                item_counter = 1

                total_items = len(peoplelist)

                for items in peoplelist:

                    if item_counter <= total_items-2:

                        output += str(items) + ', '

                    elif item_counter == total_items-1:

                        output += str(items) + ' and '

                    else:

                        output += str(items)

                    item_counter += 1

                output += "are here.\n"  

        elif isinstance(self,Person):

            output += self.getName()

            output += "is standing before you!\n"

        elif isinstance(self,ExitObject):

            if self.isVisible():

                output += str(self.getShortDescription()) + '\n'

            else:

                output += "You see nothing out of the ordinary here.\n"

        else:

            output += "unsure of type!\n"

        output += "\n"

        return output

        

#####################################################

#                              P   L   A   C   E        C   L   A   S   S                                  #

#####################################################


class Place(SimObject):

    def __init__(self,

                object_id = 0,

                object_sku = 0,

                short_description = 'Short Description',

                long_description = 'Long Description',

                name = 'Name',

                weight = 1.0,

                height = 1.0,

                width  = 1.0,

                depth  = 1.0,

                visible = True,

                contents = []):

        SimObject.__init__(self,

                            object_id=object_id,

                            short_description = short_description,

                            long_description  = long_description,

                            name              = name,

                            weight            = weight,

                            height            = height,

                            width             = width,

                            depth             = depth,

                            visible           = visible,

                            object_sku        = object_sku,

                            contents          = [])



#####################################################

#                          P   E   R   S   O   N          C   L   A   S   S                              #

#####################################################


class Person(SimObject):

    def __init__(self,name="",object_id=0,object_sku=0):

        SimObject.__init__(self,name=name,object_id=object_id,object_sku=object_sku)

        self._hp   = 100

    def isAlive(self):

        if self._hp > 0:

            return True

        else:

            return False

    def getStats(self):

        print "%s has %i hit points remaining!" % (self._name,self._hp)

    def receiveHit(self,damage="0"):

        self._hp = self._hp - damage

        if self._hp <>

            self._hp = 0

    def attack(self,enemy):

        if type(enemy) == type(self):

            attempt_roll = randint(1,10)

            if attempt_roll > 5:

                enemy.receiveHit(attempt_roll)         

            else:

                print "Missed %s!" % str(enemy._name)

        else:

            print "Cannot attack %s" % str(enemy)

    def getSize(self):

        return "Person Details ... Weight: %.1f, Height: %.1f, Width: %.1f, Depth: %.1f" % (self._weight, self._height, self._width, self._depth)

    def move(self,destination):

        #### add test against dimensions.  person WxD must be greater than Exit HxW, and the smallest Person dimension much be

        #### smaller than the smallest Exit dimension

        destination_room = self.getObjectById(destination)

        destination_room.addContents(self)


#####################################################

#                                  E   X   I   T        C   L   A   S   S                                    #

#####################################################


class ExitObject(SimObject):

    def __init__(self,visible=False,name="ExitName",short_description='Exit',

long_description='an Exit',aliases=(), destination=0,object_id=0,object_sku=0, weight=1.0,width=1.0,height=1.0,depth=1.0,closed=False):

        """Aliases are lists of names by which this exit can be referenced"""

        SimObject.__init__(self,visible=visible,name=name,short_description= 

short_description, long_description=long_description,object_id=object_id,

object_sku=object_sku, weight=weight,width=width,height=height,depth=depth,

closed=closed)

        self._aliases = aliases

        self._destination = destination

    def getDestination(self):

        return self._destination

    def getAliases(self):

        return self._aliases