In [1]:
import json
In [23]:
class Bot:
    _instance = None
    
    def __new__(cls, name):
        if cls._instance is not None:
            print("Overwriting active bot.")
        cls._instance = super().__new__(cls)
        return cls._instance
        
    def __init__(self, name, grammar=None):
        self.name = name
        self.states = {}
        self.grammar = {}
        self.exits = []
        if grammar is not None:
            self.grammar = grammar
        self.initial_blackboard = {}
        State._active_bot = self
    
    def to_dict(self):
        out_dict = {"grammar": self.grammar}
        out_dict["states"] = {state_name: state.to_dict() 
                              for state_name, state in self.states.items()}
        out_dict["exits"] = [str(exit) for exit in self.exits]
        out_dict["initialBlackboard"] = self.initial_blackboard
        return out_dict
        
        
class State:
    _active_bot = None
    
    def __init__(self, name, on_enter, exits=None, chips=None):
        '''
        
        Exits can either be strings, or take the form
        (word, Target) or (word, Target, action)
        '''
        self.name = name
        self.on_enter = on_enter
        
        self.exits = []
        if exits is not None:
            if type(exits) is list:
                self.exits = exits
            elif isinstance(exits, Exit):
                self.exits.append(exits)
        
        self.chips = []
        
        if self._active_bot is not None:
            self._active_bot.states[self.name] = self
    
    def to_dict(self):
        out_dict = {"onEnter": self.on_enter}
        out_dict["exits"] = [str(exit) for exit in self.exits]
        if len(self.chips) > 0:
            out_dict["chips"] = list(self.chips)
        return out_dict
        
class Exit:
    template = "{} ->{} {}"
    def __init__(self, target, word="", condition="", action=""):
        if word != "" and condition != "":
            raise Exception("An exit can be either a word or a condition but not both")
        self.word = word
        self.condition = condition
        self.action = action
        
        if isinstance(target, State):
            self.target = target.name
        else:
            self.target = target
    
    def __str__(self):
        
        condition = self.condition
        if self.word:
            condition = "'{}'".format(self.word)
        return self.template.format(condition, self.target, self.action).strip()

Basic kitten

In [3]:
kitten = Bot("Kitten")
In [4]:
origin = State("origin", "'You have a kitten!'", Exit("name"))
In [5]:
name = State("name", "'What do you want to name your kitten?'", 
            Exit("respondToName", "*", action="name=INPUT"))
In [6]:
respond = State("respondToName", "'I guess the kitten likes #/name#'")
In [7]:
print(json.dumps(kitten.to_dict(), indent=2))
{
  "states": {
    "respondToName": {
      "onEnter": "'I guess the kitten likes #/name#'",
      "exits": []
    },
    "name": {
      "onEnter": "'What do you want to name your kitten?'",
      "exits": [
        "'*' ->respondToName name=INPUT"
      ]
    },
    "origin": {
      "onEnter": "'You have a kitten!'",
      "exits": [
        "->name"
      ]
    }
  },
  "initialBlackboard": {},
  "grammar": {}
}

Full Kitten

In [24]:
kitten = Bot("Kitten")
In [25]:
hunger_wait = Exit("hungry", condition="wait:10")
angry_wait = Exit("angry", condition="wait:10")
idle_wait = Exit("idle", condition="wait:10")
In [26]:
State("origin", "'You have a kitten!' desired_pets=randomInt(1,5)", 
      Exit("name"))

State("name", "'What do you want to name your kitten'", 
      Exit("respond_to_name", word="*", action="name=INPUT"))

State("respond_to_name", "'The kitten purrs; I guess it likes #/name#!'",
     Exit("idle"))

State("pet", "'You pet the kitten' desired_pets--",
     [Exit("happy_pet", condition="desired_pets>=0"), Exit("angry_pet")])

State("happy_pet", "'#/name# loves you!'", idle_wait)

State("angry_pet", "'Screech! #/name# doesn't want to be petted' desired_pets=randomInt(1,5)", 
     Exit("angry"))

State("idle", "'#/name# makes cute noises!'", hunger_wait)

State("angry", "*bite*", Exit("sleeping", condition="wait:10"))

State("sleeping", "'#/name# is sleeping'", hunger_wait)

State("hungry", "'The kitten is hungry'", angry_wait)
Out[26]:
<__main__.State at 0x111f23278>
In [27]:
kitten.exits.append(Exit("pet", "pet"))
In [28]:
print(json.dumps(kitten.to_dict(), indent=2))
{
  "states": {
    "name": {
      "onEnter": "'What do you want to name your kitten'",
      "exits": [
        "'*' ->respond_to_name name=INPUT"
      ]
    },
    "sleeping": {
      "onEnter": "'#/name# is sleeping'",
      "exits": [
        "wait:10 ->hungry"
      ]
    },
    "origin": {
      "onEnter": "'You have a kitten!' desired_pets=randomInt(1,5)",
      "exits": [
        "->name"
      ]
    },
    "hungry": {
      "onEnter": "'The kitten is hungry'",
      "exits": [
        "wait:10 ->angry"
      ]
    },
    "happy_pet": {
      "onEnter": "'#/name# loves you!'",
      "exits": [
        "wait:10 ->idle"
      ]
    },
    "respond_to_name": {
      "onEnter": "'The kitten purrs; I guess it likes #/name#!'",
      "exits": [
        "->idle"
      ]
    },
    "pet": {
      "onEnter": "'You pet the kitten' desired_pets--",
      "exits": [
        "desired_pets>=0 ->happy_pet",
        "->angry_pet"
      ]
    },
    "angry": {
      "onEnter": "*bite*",
      "exits": [
        "wait:10 ->sleeping"
      ]
    },
    "angry_pet": {
      "onEnter": "'Screech! #/name# doesn't want to be petted' desired_pets=randomInt(1,5)",
      "exits": [
        "->angry"
      ]
    },
    "idle": {
      "onEnter": "'#/name# makes cute noises!'",
      "exits": [
        "wait:10 ->hungry"
      ]
    }
  },
  "initialBlackboard": {},
  "exits": [
    "'pet' ->pet"
  ],
  "grammar": {}
}