Previous Section : Semantic Needs previous section next section Next Section : Links

"Star-scripting across the Universe..."

Scripting the Universe...

At this point, I don't intend to make another script language (check [2] for a good book about compilers) but to choose a proper one. After some research, I'm inclined to Python (want to learn Python in 5 minutes?). Let me state my reasons:

A stable scripting language, good for steering with kernels in C, C++, C**, Java or even Delphi. (ok! I invented C**, it does not exist but sounds good!and yet another joke?) Btw, these are my favorite languages, so this document may be biased toward them.
Adequate data types. Python deals with tuples, lists and associative arrays.
It supports object oriented programming (however, all class members are public. This is not too serious, since information hiding can be implemented at kernel level).
Extensive Function Library. Python comes with an impressive number of libraries to handle many complex tasks, like threading, Internet, multimedia, cryptographic services...
Freely distributed and comes with good documentation (check Links section).
And now for something completely different... I love Monty Python!

section at work Some initial thoughts:

Stability: The script should not be allowed to access kernel internal variables directly, in order to avoid crashes due to bad written scripts. All access must be done via library function calls. This increases stability and detachment of kernel and configuration coding.
Speed: Since kernel code is a lot faster than script code, all complex and repetitive tasks (e.g., animations, AI functions) should be implemented in the kernel and called by pre-defined function libraries.
Speed Revisited: Script code must always be associated with event handling. It should not have a main() method, just event handlers. Script codes should run just on a small percentage of time.
About the code: I'm new on Python, so if you are reading this and found a better way to do some stuff, tell me. I'm using a little Delphi+Python demo program [193k] to run these examples (you'll need python15.dll).

States

How can we handle states in Python?

1) Define a dictionary (i.e., an associative array) relating states and proper initialization methods (there should always be one special state named "_default") :

initState = { 'patrol'  : initPatrol,
              'fight'   : initFight,
              'hide'    : initHide,
              'heal'    : initHeal,
              'escape'  : initEscape,
              '
_default': _init }

E.g., to execute the method related with state 'patrol', just do:

initState['patrol'](...)

2) If there are methods depending on specific states, define a dictionary for them:

#in this case, just patrol has a different method for 'touch' event

touchState = { 'patrol'  : attack,
              '
_default': _touch }

3) A "jump state" is treated like:

def jump( self, newState = '_default' ):
  if self.initState.has_key(newState):    # if newState is defined,
     self._state = newState               #    change state, and
     self.initState[self._state](self)    #    call proper init function

E.g., to jump to Patrol state, just do:

jump('patrol')

4) When method touch(...) is called, it is treate like:

def touch(self, ...):
  if self.touchState.has_key(self._state):
 # if _state has defined method,
      self.touchState[self._state](self, ...)   # call it!
   else:
      self.touchState['_default'](self, ...)    # if not, execute default 

So, when you call touch method, it may execute differently depending on the actual object state. Like in this little code:

x = StateObj()     # define new object

x.touch(...)       # executes _touch() method
x.jump('patrol')   # changes state and executes initPatrol method
x.touch(...)       # executes attack() method  

Here it is a complete sample code if you want to try it in Python.

Events

Events are special occurrences that trigger given methods. There must have an event queue, in order to keep all incoming events, and an event table to keep all events that the object knows how to handle. Events are static, they cannot be added or deleted at runtime, they must be defined in the class definition.

evtTable = {}              # define event table as a python dictionary
evtQueue = []              # define event queue as a python list

def newEvent(self, evtName, *args):   # keep incoming event for processing
  if self.evtTable.has_key(evtName):  # if event exists in evtTable,

      evtTuple = (evtName, args)      #    make event structure, and
      self.evtQueue.append(evtTuple)  #   insert it at the queue end

Notice that an handling method may have variable arguments, so Python constructs a tuple, where the 1st value is the event name, and the 2nd value is a tuple with the optional arguments.

To include new events in subclasses, just add them in the class initialization

class newEventObj(EventObj):
   "Expanding EventObj class"


   def newMethod(self, ...):
     ...


   def __init__(self):

           # superclass init method. This call reuses all important
           #  data structures of superclass, namely, evtQueue
      self.__class__.__bases__[0].__init__(self)  

           # add new event to this subclass
      self.evtTable['newEvent'] = (self.newMethod, nArguments)

How event methods are called? The kernel is responsible for event handling, it may insert, delete and call handler methods at runtime. However, it is possible to force the next event to be processed using code like this:

def hasEvents(self):           # Returns true if queue is not empty
   return self.evtQueue != []

def forceEvent(self):         # Force next event to be processed
   evt = self.evtQueue[0]     #  get next event, and
   self.evtQueue.remove(evt)  #  remove it from queue
                             
     # execute method with specific sequence
   apply( self.evtTable[evt[0]][0], evt[1] )

E.g., to process all awaiting events of object x:

while x.hasEvents():
   x.forceEvent()

Check the complete sample code if you want to try it in Python.

Tags

Each object carries 0+ tags, containing arbitrary information about the object's actual configuration (e.g., a cursed object always have a 'curse' tag). Herein, a tag is a string, and each object have a list of tags. Something like:

class TagObj:
   "class presenting tag related methods"


def __init__(self):
   self.tagList = []

The methods presented in the last section are defined in the following way:

def tagInsert(self, tag):
   if self.tagList.count(tag) == 0: # if doesn't exists,
      self.tagList.append(tag) # insert it!

def tagRemove(self, tag):
   if self.tagList.count(tag) > 0: # if exists,
      self.tagList.remove(tag) # remove it!
#
# Returns true (i.e., one) if object has tag
#
def tagHas(self, tag):
   return self.tagList.count(tag)
#
# Returns tag list
#
def tagCollection(self):
   return self.tagList
#
# Returns all objects from objList with same tags as object
#
def tagSimilar(self, objList):
   newList = []
   for obj in objList:
      missedTag = 0
      for tag in obj.tagList:
         if self.tagList.count(tag) == 0:
            missedTag = 1
            break
      if missedTag == 0 and obj.tagList != []:
         newList.append(obj)
   return newList
#
# Returns all objects from objList with at least one tag as object
#
def tagRelated(self, objList):
   newList = []
   for obj in objList:
      hasTag = 0
      for tag in obj.tagList:
         if self.tagList.count(tag) > 0:
            hasTag = 1
            break
      if hasTag == 1:
         newList.append(obj)
   return newList
#
# Returns all tags that object has, that are related to
# at least one other object from objList
#
def tagListAg(self, objList):
   newList = []
   for obj in objList:
      for tag in obj.tagList:
         if self.tagList.count(tag) > 0 and \
            newList.count(tag) == 0:
            newList.append(tag)
   return newList

Check the complete sample code if you want to try it in Python.

Deamons

A Deamon links timers with methods and objects. For each timer there is one method, that is applied to all linked objects, when the timer is triggered. The main structures for Deamons are the list of objects and the list of timers and associated methods.

class DeamonObj:
   "Deamon class"


def dmnSet(self, timer, interval, method):
      # include info as tuple into timer list
   self.timerList.append((timer,interval,method))
      # call kernel function that sets 'timer' to
      # execute 'method(self)' at specific 'interval' time
   ...


def __init__(self):
    self.timerList = []
    self.linkedObjs = []

All methods dealing with time management are part of the kernel. So, it only remains object link and split, timer initialization and forcing an timer execution.

#
# Applies timer's method to all linked objects
#
def dmnTrigger(self, timer):
   found = 0
   for tmr in self.timerList:         # find timer
      if timer == tmr[0]:
        found = 1
        break
   if found == 1:
      for obj in self.linkedObjs:
         tmr[2](obj)                   # execute method
#
# Links an object to Deamon
#
def dmnLink(self, obj):
    self.linkedObjs.append(obj)
#
# Removes an object from Deamon
#
def dmnSplit(self, obj):
    self.linkedObjs.remove(obj)
#
# Shows what objects are linked to Deamon
#
def dmnList(self, obj):
    return self.linkedObjs

Check the complete sample code if you want to try it in Python.

Messages

Messages is a subclass of Event. When a new message is sended to an object, this object keeps that message in its event list, until it is processed.

import rotor              # import encrypt tools

from EventObj import *
from TagObj import *

# I didn't define msgBroadcast and msgSendTag, because I don't
# have yet a total obj list

class MsgObj(EventObj, TagObj):
   "message handling class"

...
   def __init__(self):
         # execution of super init methods
      EventObj.__init__(self)
      TagObj.__init__(self)
         # insert newMsg event into event list
      self.evtTable['evtMessage'] = (self.msgReceive, 2)
         # filter lists
      self.filterList = []
      self.filterTagList = []

There are filters, that is, ways to refuse certain messages for certain objects or sets of objects:

# Refuse messages from object 'obj'

def msgFilter(self, obj):
   self.filterList.append(obj)

# Refuse messages from objects with 'tag'

def msgFilterTag(self, tag):
   self.filterTagList.append(tag)

Next we have the send and receive message methods:

# Send event message to specific object

def msgSend(self, obj, msg):
   try: obj.newEvent('evtMessage', self, msg)
   except: pass

# Process message event. Only does anything if ok is 1

def msgReceive(self, obj, msg):
   ok = 0
   if not obj in self.filterList:
      ok = 1
      for tag in self.filterTagList:
         if obj.tagHas(tag):
            ok = 0
            break
      if ok == 1:       # then do whatever you need to do!
         ...

And also some encrypt tools. I use a Python library called rotor, that implements the Nazi Enigma coding machine cracked by the great Alan Turing, who, because of that, is one of the main responsibles of the Nazi defeat in the England Channel War.

# Encrypt message using a numeric key

def msgEncrypt(self, msg, numKey):
   rt = rotor.newrotor('key', numKey)
   return rt.encrypt(msg)

# Decrypt message using a numeric key

def msgDecrypt(self, msg, numKey):
   rt = rotor.newrotor('key', numKey)
   return rt.decrypt(msg)

Here is some code samples:

z.tagInsert('liar')       # z is a liar
y.msgFilterTag('liar')    # y does not listen to liars
y.msgFilter(w)            # y does not listen to w

w.msgSend(y, 'greetings')
x.msgSend(y, 'hi object y')
z.msgSend(y, 'another msg')

while y.hasEvents():
   y.forceEvent()         # only receives message from object 'x'

Check the complete sample code if you want to try it in Python.

new stuff! Ok! Until now we were just practicing some concepts. Don't you feel something funny in the message object, for instance? Should the msgSend be a method of an object message? Or should it belong to objects that can send messages?...

I present now the first draft of the central class Object. I will join many stuff talked above, so I will present it all without many explanations (you should also check the new Message class). You will also find a nebulous reference to a Universe... At this moment, I use the Universe has the list of all executable objects. Each script has a little sample code that give some clues on how to use their methods.

Languages  

In a multilanguage world, a player can speak several different languages with different accuracy each. In principle, there must be an universal language (usually english), and several others that are spoken only by those who learned them. Let's see the abstract class Language.

class language:
  "The language class, for multilanguage worlds"

  ## translate a sentence from English to language
  def translate(self, sentence):

    for letter in self.dialect.keys():
      sentence = string.replace(sentence, letter,

                                self.dialect[letter] )

    for letter in self.base.keys():
      sentence = string.replace(sentence, letter, self.base[letter])

    return sentence

  ## Translate a sentence from language to English
  ##   knowledge is a value between [0,1] and gives the 
  ##   translation accuracy (1 means perfect translation)
  def english(self, sentence, knowledge = 1):

    for letter in self.base.keys():
      if whrandom.random()<knowledge:
        sentence = string.replace(sentence, self.base[letter], letter )

    for letter in self.dialect.keys():
      ## it's easier to know the main language features
      if whrandom.random()<knowledge*1.5:
        sentence = string.replace(sentence, self.dialect[letter],

                                  letter )
    return sentence


  def __init__(self, dialect, language):

    self.name     = dialect
    self.language = language
    self.base     = {}
    self.dialect  = {}

As you see, each language may have different dialects. But all dialects of a same language must share a set of common features. An example?

class orkish(language):
  "The Orkish Language"

  def __init__(self, dialect, newDict={}):
    language.__init__(self, dialect, 'orkish')

    ## base of all orkish languages

    self.base['a'] = 'arsh'
    self.base['e'] = 'errk'
    self.base['i'] = 'insh'
    self.base['o'] = 'orck'
    self.base['u'] = 'urgk'
    self.base['y'] = 'yxxh'

    ## different dialect (this can also be done with more subclassing)

    self.dialect = newDict

Let's see the following sample code and output.

rhumDict = {}
rhumDict['br'] = 'kr'

L1 = orkish('rhum', rhumDict)

urukDict = {}
urukDict['am'] = 'uruk'
urukDict['so'] = 'os'
urukDict['there'] = 'J'

L2 = orkish('uruk', urukDict)


print L1
s = L1.translate('so, did you find there those dammed Hobbits?')
print s
print L1.english(s)

print L2
s = L2.translate('so, did you find there those dammed Hobbits?')
print s
print L2.english(s, 0.750)

------------------- output ------------------------

rhum dialect of orkish language
sorck, dinshd yxxhorckurgk finshnd therrkrerrk thorckserrk darshmmerrkd Horckbbinshts?
so, did you find there those dammed Hobbits?
uruk dialect of orkish language
orcks, dinshd yxxhorckurgk finshnd J thorckserrk durgkrurgkkmerrkd Horckbbinshts?
so, did yourgk find there thsoerrk durgkrurgkkmerrkd Hobbits?

Check the complete sample code if you want to try it in Python.

 

Previous Section : Semantic Needs previous section to top next section Next Section : Links

Last modified: 27/09/04 by Joćo Pedro Neto send me an email zipped contents wanna search the matrix?