Myro Test

From IPRE Wiki
Jump to: navigation, search

Here's a Python program to test Myro functionality. Tests Myro in general as well as the Scribbler and Roomba/Create.

#myroTest7_4.py

#Tests Myro functions for Roomba, Create, or Scribbler,
#and prints out which functions worked and which did not.


from myro import *

class Tester:
    def __init__(self, theLocals, robotType):
        self.locals = theLocals #the locals needs to be called outside of object!
        self.comConnected = 0 #not yet connected
        self.robotname = robotType
        self.robot = None

    def _isFunc(self, name):
        '''
        Returns True if name indicates a function
            (based on name being lowercase)
        Returns False if name indicates a class
            (based on name being uppercase).

        name        a string intended to represent some function
                        or class
        '''
        if name[0] in 'abcdefghijklmnopqrstuvwxyz_':
                #lowercase name: func
                return True
        else:
                #uppercase name: so class
                return False        
    def testOne(self, func, retval, args, kwargs):
        '''
        Tests one function with given args and kwargs.
        If function returns given retval, function information
        is added to passedFuncs list; otherwise, information
        is added to failedFuncs list.

        func        name of function (abstract version -- a string)
                        or of class (actual name) to test
        args        arguments for given function
        kwargs      key-word arguments for given function
        retval      expected return value for given function
        '''

        # Note: this function adds to "global" lists, but does _not_
        # return anything; "global" lists must be initialized elsewhere.

        functionWorked = False
        if not isinstance(args, list):
            args = [args]
        if isinstance(retval, list):
            #retval is in list form
            try:
                assert func(*args, **kwargs) in retval
                functionWorked = True
            except:
                functionWorked = False
                
        elif retval in [None, str, int, float, list, dict, object, unicode,
                        bool, myro.graphics.Picture, myro.graphics.Color,
                        myro.graphics.Sound, myro.Array]:
            #retval is a TYPE
            if None == retval:
                retval = type(None)
            try:
                assert isinstance(func(*args, **kwargs), retval)
                functionWorked = True
            except:
                functionWorked = False
                
                
        #later on: do more extensive 'conditions'...
                
        else:
            #retval is not list form (e.g. int, str, float,...)
            try:
                assert func(*args, **kwargs) == retval
                functionWorked = True

            except:
                functionWorked = False
                
        if functionWorked:
            self.passedFuncs.append([func, retval, args, kwargs])
        else:
            self.failedFuncs.append([func, retval, args, kwargs])


    def testAll(self, funcs, typ):
        '''
        Tests a list of functions of the given type.

        funcs       a list of functions to test
        typ         string representing the type of functions in
                        the list (e.g. 'robot', 'picture', etc.)
        '''
        #init passedFuncs & failedFuncs
        self.passedFuncs = []
        self.failedFuncs = []
        self.passes, self.fails = 0, 0 #may not be necessary

        #unpack / init
        if typ == 'robot':
            #initialize('com' + ask('Which port?'))
            pass
            
        elif typ == 'robotObj':            
            robot = self.robot
            
        elif typ == 'picture':
            picture = self.picture
            pixel = self.pixel
            color = self.color

        for t in funcs:
            #put t in acceptable format
            t = [ch for ch in t]
            if len(t) == 2:
                t.append([])
            if len(t) == 3:
                t.append({})

            #change function format (string to actual function)
            func, retval, args, kwargs = t            
            if typ not in ['robotObj', 'compObj']: #not class method
                if self._isFunc(func):
                    func = myro.globvars.getObject(self.locals, func)
                else: #class, not function
                    func = myro.globvars.getObject(self.locals, func, type = 'class')

            #test the function
            self.testOne(func, retval, args, kwargs)
            
        return len(self.passedFuncs), self.passedFuncs, len(self.failedFuncs), self.failedFuncs
    
    def tellResults(self, funcs, typ):
        '''Goes through list of functions, seeing if return
        value for each function matches the expected retval.
        Prints number of passed & failed functions, along with
        list of failed functions.'''

        #funcs list -- format: (func, retval, [args], {kwargs})
        #[args] & {kwargs} are optional
        #[args] need not be in list format if it's only 1 item
        #currently, retval can be item, list, or Type (e.g. str, int)
            #note, if expected retval IS a list, write [retval]
            #e.g.[[1,2,3]] if expecting [1,2,3]
        
        #self.passes, self.failedFuncs = 0, 0 #initialize to 0 --unnecessary
        passes, passedFuncs, fails, failedFuncs = self.testAll(funcs, typ)
        
        print "Number of passes:", passes
        print "Number of fails:", fails

        #print out passing functions, if any
        if len(passedFuncs) == 0:
            print ('\nNo passing functions here.')
        else:
            self._printFuncs(passedFuncs, 'Passing Functions')

        #print out failed functions, if any
        if len(failedFuncs) == 0:
            print ('\nNo failed functions here!')
        else:
            self._printFuncs(failedFuncs, 'Failed Functions')
        
        return passes, fails

    def _printFuncs(self, funcsInfo, header):
            print "\n\n%s\n" \
                  "function(args)\t\t{kwargs}\n" \
                  "------------- \t\t ------\n" %header 
            for func, retval, args, kwargs in funcsInfo:
                if args == []: args = ''
                if kwargs == {}: kwargs = ''
                try: funcName = func.__name__
                except: funcName = func #ever happen?
                print "%s(%s)\t\t%s" %(
                    funcName, args, kwargs)   
                
    def comprehensTest(self, *funcs):
        '''Tests myro commands in various categories:
        non-robot, robot, robot-as-object, computer-as-object.
        Prints information about functions failing/passing
        the test, both for each subcategory & as a total.'''


        funcList = [
            #(StrName, [funcName, [params]])
            
            ('Non-Robot Functions', [self.makeNonRobotFuncs(), 'noRobot']),
            ('Robot Functions', [self.makeRobotFuncs(), 'robot']),
            ('Robot Object Functions', [self.makeRobotObjFuncs(), 'robotObj']),
            ('Computer Object Functions', [self.makeCompObjFuncs(), 'compObj']),
            #('Picture Functions', [self.makePictureFuncs(), 'picture']),
            ('Gamepad Functions', [self.makeGamepadFuncs(), 'gamepad'])
            
            #makeChatFuncs
            ]
        if funcs != ():
            funcList = [ch for ch in funcList if (ch[0] in funcs or ch[1][1] in funcs)]
            #includes funcs based on StrName or funcName
        totPasses = 0
        totFails = 0
        for name, info in funcList: 
            print '\n%s\n%s:' %('-'*20, name)            
            passes, fails = self.tellResults(*info)
            totPasses += passes
            totFails += fails


        totFuncs = float(totPasses + totFails)

        #Reset changed settings.
        #...set('volume', 1)

        print '\n\n%s\n\nALL FUNCTIONS' %('-'*40)
        print 'Number of passes: %s (%3.2f%%)' %(
            totPasses, 100 * totPasses/totFuncs)
        print 'Number of fails: %s (%3.2f%%)' %(
            totFails, 100 * totFails/totFuncs)

    def init(self):
        if not self.comConnected:
            self.com = ask('Which port is robot connected to?')
            if self.robotname == 'scribbler':
                print 'initializing'
                initialize('com' + self.com)
            elif self.robotname == 'create' or self.robotname == 'roomba':
                print 'creating'
                self.robot = Create('com' + self.com)
                
            self.comConnected = 1 #have at least tried to connect

    def makeNonRobotFuncs(self):

        self.non_robot_funcs = [
            #format: (func, retval, [args], {kwargs})
            #[args] & {kwargs} are optional
            #[args] need not be in list format if it's only 1 item
            #currently, retval can be item, list, or Type (e.g. str, int)
                #note, if expected retval IS a list, write [retval]
                #e.g.[[1,2,3]] if expecting [1,2,3]

            #('pick a folder', str), #[r'C:/.*']()  #TAKES SIG TIME
            #('pick A File', str), #[r'C:/.*']()  #TAKES SIG TIME



            ##(readSong, ['condit', [(round(note[0]), note[1]) for note in readSong('testSong.txt')] ==
            ##            [(523.0, 1.0), (554.0, 1.0), (587.0, 1.0), (622.0, 1.0), (659.0, 1.0), (699.0, 1.0),
            ##             (740.0, 1.0), (784.0, 1.0), (831.0, 1.0), (880.0, 1.0), (932.0, 1.0), (988.0, 1.0),
            ##             (523.0, 1.0)] )

            ("read Song", [[(740.0, 1.0), (784.0, 1.0), (880.0, 1.0)]], 'testSong.txt'),
            ("make Song", [[(784.0, 0.25), (880.0, 0.5)]], "g 1/4; a 1/2;"),
            ("save Song", None, ['F# 1; G 1; A 1;', 'newSong.txt']),
            ("speak", None,''),
            ("stop Speaking", None),
            ("set Voice", None, getVoice()),
            ("get Voice", getVoices()),
            ("get Voice", unicode),
            ("get Voices", list),
            ("get Voices", [['MSMike', 'MSMary', 'MSSam', 'LHMICHAEL', 'LHMICHELLE']]),
            ("play Speech", None,'fn1.wav'),
            ("save Speech", None, ['', 'fn1.wav']),

            ("time Remaining", bool),
            #("timer", 'generator', 0), #"<type 'generator'>"
            
             #Sound
            ("make Sound", myro.graphics.Sound, 'fn1.wav'),
            ("play", None, makeSound('fn1.wav')),

            ("Array", myro.Array),
            ("make Array", myro.Array)
             
            ]

        return self.non_robot_funcs


    def makeRobotFuncs(self):
        self.init()
        print 'starting makeRobotFuncs'

        self.robot_funcs = [
##            ("simulator", None),#TAKES SIG TIME, note: _must_ re-init
##                #after simulator, or comp not recognize robot
##            ("initialize", None, 'com' + self.com), #TAKES SIG TIME


            ("forward", None, .7),
            ("stop", None), ####
            ("forward", None, [.7, .1]),
            ("backward", None, .7),
            ("backward", None, [.7, .1]),
            ("turn", None, ['left', .5]),
            ("turn", None, ['right', .5]),
            ("turn", None, ['straight', .5]),
            ("turn Left", None, .6),
            ("turn Right", None, .6),

            ("translate", None, 1),
            ("rotate", None, .5),
            ("move", None, [1, 1]),
            ("motors", None, [.4, .4]),
            ("stop", None),
            #("joy Stick", None),#TAKES SIG TIME; "<type 'instance'>"

    ##            ("close Connection", None),####?*! -- seems to mess things up
    ##            ("open Connection", None),####?*! -- seems to mess things up
            ("update", None),
            ("beep", None, [0.0001, 0, 0]),
            ("restart", None),#TAKES SIG TIME? ####?*!

            #Get Sensor and Data Functions
            ("get", int, "stall"),
            #("get", list, "ir"), #mesg invalid sensor name 
            ("get", list, "light"),
            ("get", list, "line"),
            ("get", dict, "all"),
            ("get", dict, "config"),
            #(get(sensor, pos)) #do later
            #getBright??
            #how to do expectation of nums w/in list
            ##
            ##>>> get("all")
            ##{'light': [235, 13], 'line': [1, 0], 'ir': [0, 0], 'stall': 0}

            #("get Light", dict, 'all'), #returning None... problem
            ("get Light", int, 'center'),
            ("get Light", int, 'left'),
            ("get Light", int, 'right'),
            #("get I R", dict, 'all'), #returning None... prob doesn't exist
            ("get I R", int, 'left'),
            ("get I R", int, 'right'),
            #("get Line", dict, 'all'), #returning None... prob doesn't exist
            ("get Line", int, 'left'),
            ("get Line", int, 'right'),
            ("get Stall", int),
            ("get All", dict),
            ("get Name", str),
            ("get Volume", [0,1]),
            ("get Start Song", str), # Not implemented yet.
            #(getData) #Not implemented yet. Return type??
            #(Info, dict) #Return type ?? #getInfo?? not Info...
            ("get", dict, 'info'),
            ("get Info", dict),
            ("get", str, ['info', 'fluke']),
            ("get", str, ['info', 'robot']),
            ("get", str, ['info', 'robot-version']),
            ("get", str, ['info', 'mode']),
            
            ("set", None, ["name", getName()]),
            ("set", None, ["volume", 'on']),
            ("set", None, ["led", 'center', 'off']),
            ("set", None, ["led", 'right', 'off']),
            ("set", None, ["led", 'left', 'off']),
            ("set", None, ["name", getName()]),
            ("set L E D", None, ['left', 'on']),
            ("set Name", None, getName()),
            ("set Volume", None, 0), # sets off
            ("set Volume", None, 'off'),
            ("set Volume", None, 1), #sets on
            ("set Volume", None, 'on'),



            #New Fluke Functions

            ("get Bright", int, 'left'),
            ("get Bright", int, 'middle'),
            ("get Bright", int, 'center'),
            ("get Bright", int, 'right'),
            ("get Bright", int, 0),
            ("get Bright", int, 1),
            ("get Bright", int, 2),
            #seems to come to about here & then stop

            ("get Obstacle", int, 'left'),
            ("get Obstacle", int, 'right'),
            ("get Obstacle", int, 0),
            ("get Obstacle", int, 1),

            ("set I R Power", None, 2),#value = 2 --works?
            ("set", None, ["irpower", 2]),#value = 2 --works?

            ("get Battery", float),



            ("take Picture", myro.graphics.Picture, "color"),
            ("take Picture", myro.graphics.Picture, "blob"),
            ("take Picture", myro.graphics.Picture, "gray"),
            ("set White Balance", None, 1),
            ("set White Balance", None, 'on'),
            ("set White Balance", None, 0),
            ("set White Balance", None, 'off'),
            ("set", None, ['whitebalance', 1]),
            ("set", None, ['whitebalance', 'on']),
            ("set", None, ['whitebalance', 0]),
            ("set", None, ['whitebalance', 'off']),

            ("set L E D Front", None, 1),
            ("set L E D Front", None, 'on'),
            ("set L E D Front", None, 0),
            ("set L E D Front", None, 'off'),
            ("set L E D Back", None, 1),
    ##            ("set L E D Back", None, 'on'),
            ("set L E D Back", None, 0),
    ##            ("set L E D Back", None, 'off'),
            ("set", None, ['led', 'back', 1]),
    ##            ("set", None, ['led', 'back', 'on']),
            ("set", None, ['led', 'back', 0]),
    ##            ("set", None, ['led', 'back', 'off']),
            ("set", None, ['led', 'front', 1]),
            ("set", None, ['led', 'front', 'on']),
            ("set", None, ['led', 'front', 0]),
            ("set", None, ['led', 'front', 'off']),


            ##setStartSong(songname): set the start-up song; default "tada"
            ##Not completed.
            ##
            ##setData(position, value): set a byte of data in the robot's memory to a value between 0 and 255.
            ##Not completed.

            #The Myweb webpages give some notes. The images that a user has will be available when he/she edits their page. Images are placed in /myweb/data/robotname/*.jpg. Therefore one can:

            #("send Picture", None, [takePicture(), "testPic", ask("What's your password?")])  ,    ####?*!    # assumes robot is connected, to take picture and get name
            #("send Picture", None, [takePicture(), "testPic2", ask("What's your password?"), getName()]), ####?*!       # you can provide the name explicitly, also

            #and refer to it picture with:  <img src="/myweb/data/kitty/my-house.jpg">

            #("take Picture", None, 'Name'), #takes color mode, NOT name


             #Web development
            #(register, None), #should be None if already registered... ####?*!
            #http://myro.roboteducation.org/myweb/

            #(sendPicture, None, [picture, photoname, password, robotname = None]) #get vars to send

            #setPassword(robotName, emailAddress, newPassword)

            ##("upgrade", None), #move this to dif area -- doesn't take robot object

            ]

        return self.robot_funcs






    def makeRobotObjFuncs(self):

        #requires creation & passing of robot object
        robot_funcs = []
        if self.robotname == 'scribbler':
            self.init()
            robot = Scribbler('com' + self.com)
            self.robot = robot
        elif self.robotname == 'roomba' or self.robotname == 'create':
            self.com = ask('Which port is robot connected to?')
            #robot = Create('com' + self.com)
            robot = self.robot
        #self.robot = robot
        print 'DOING ROB OBJ FUNCS...'  

        self.robot_obj_funcs = [

##            This section describes the constructors and methods of the object-oriented interface.
##            Constructors
##            #actually do & MUST PASS ROBOT object as param (or put w/in class as self.robot)
##            robot = Scribbler('com' + ask('Which port?'))
##            (  Scribbler(getName()), [ ([getName()], ['type', object]) ]  )
##            ##robot = Surveyor(port) - real robot constructor for the SRV-1; may ask for a port (this is under development)
##            ##
##            ##robot = SimScribbler() - simulator constructor (this is currently under development)
##            ##Movement
##
##            All velocities are in the range of -1 to 1 (floats and ints ok).
##
##            NOTE: forward, backward, turnLeft, turnRight, and turn are not independent. They will halt any movement on the other movement control.
##            #robot = ....
##            #for ch in test: X

            (robot.forward, None, 0),
            (robot.backward, None, 0),
            (robot.turn, None, ['center', 0]),
            (robot.turn, None, ['left', 0]),
            (robot.turn, None, ['right', 0]),

            (robot.turnLeft, None, 0),
            (robot.turnRight, None, 0),
            (robot.translate, None, 0),
            (robot.rotate, None, 0),
            (robot.move, None, [0,0]),
            (robot.motors, None, [0,0]),
            (robot.stop, None),
            #(robot.joyStick, None),  #evidently, can't do (must close window after this)
            (robot.get, str, 'name'),
            (robot.get, list, 'light'),
            (robot.get, list, 'line'),
            (robot.get, dict, 'all'),
            #(robot.get, list, 'bright')  #have this??
            (robot.get, str, 'name'),
            (robot.get, list, 'data'),


            (robot.get, int, ['ir', 'left']),
            (robot.get, int, ['ir', 'right']),
            (robot.get, int, ['line', 'left']),
            (robot.get, int, ['line', 'right']),
            (robot.get, int, 'stall'),
            (robot.get, int, ['light', 'left']),
            (robot.get, int, ['light', 'right']),
            (robot.get, int, ['light', 'center']),
             #may not be extensive -- get(sensor, position) -- 'all'?, 'bright'?
            (robot.getLight, int, 'left'),
            (robot.getLight, int, 'right'),
            (robot.getLight, int, 'center'),
            (robot.getIR, int, 'left'),
            (robot.getIR, int, 'right'),
            (robot.getLine, int, 'left'),
            (robot.getLine, int, 'right'),
            (robot.getStall, int),
            (robot.getAll, dict),
            (robot.getName, str),
            #(robot.getVolume, float),#doc says can do -- add func to myro (call get('volume'))
##            (robot.getData, list), #usu list of len 8 ;;; MAY WORK W/ SCRIBBLER; DOESN'T WORK W/ ROOMBA
##            (robot.getData, int, 0), # MAY WORK W/ SCRIBBLER; DOESN'T WORK W/ ROOMBA
            (robot.getInfo, dict),
            
            #these vary depending on version!!:
##            (robot.getInfo, str, 'dongle'),
##            (robot.getInfo, str, 'api'),
            (robot.getInfo, str, 'robot'),
            (robot.getInfo, str, 'fluke'),
            (robot.getInfo, str, 'robot-version'),
            (robot.getInfo, str, 'mode'),

            
##            (robot.setData, None, [0, 1]), # MAY WORK W/ SCRIBBLER; DOESN'T WORK W/ ROOMBA

            (robot.set, None, ['data', 0, 1]),
            (robot.set, None, ['volume', getVolume()] ), #approp. value? -- doesn't have getVolume
            (robot.set, None, ['name', robot.getName()] ), #approp. value?

            (robot.set, None, ['led', 'center', 'on']),
            (robot.set, None, ['led', 'center', 1]),
            (robot.set, None, ['led', 'center', 'off']),
            (robot.set, None, ['led', 'center', 0]),
            (robot.set, None, ['led', 'left', 'on']),
            (robot.set, None, ['led', 'left', 1]),
            (robot.set, None, ['led', 'left', 'off']),
            (robot.set, None, ['led', 'left', 0]),
            (robot.set, None, ['led', 'right', 'on']),
            (robot.set, None, ['led', 'right', 1]),
            (robot.set, None, ['led', 'right', 'off']),

            (robot.setLED, None, ['center', 'on']),
            (robot.setLED, None, ['center', 1]),
            (robot.setLED, None, ['center', 'off']),
            (robot.setLED, None, ['center', 0]),
            (robot.setLED, None, ['left', 'on']),
            (robot.setLED, None, ['left', 1]),
            (robot.setLED, None, ['left', 'off']),
            (robot.setLED, None, ['left', 0]),
            (robot.setLED, None, ['right', 'on']),
            (robot.setLED, None, ['right', 1]),
            (robot.setLED, None, ['right', 'off']),
            (robot.setLED, None, ['right', 0]),

            (robot.setName, None, getName() ),
            (robot.setVolume, None, 'off'),
            (robot.setVolume, None, '0'),
            (robot.setVolume, None, 'on'),
            (robot.setVolume, None, '1'),

            #(robot.setStartSong, None, 'tada') #Not implemented yet.

            #robot.setData(position, value) #Not implemented yet.


            #Robot commands
            (robot.update, None),
            (robot.beep, None, [0,0,0]),
            (robot.restart, None),
            (robot.open, None),
            (robot.close, None),
            (robot.playSong, None, 'F# 1; G 1; A 1;')
        

            ]

        return self.robot_obj_funcs

    def makeCompObjFuncs(self):

        #note: doesn't require creation & passing of computer object
        #since computer is pre-bound method
        self.comp_obj_funcs = [
            (computer.speak, None, ''),
            (computer.stopSpeaking, None),
            (computer.setVoice, None, getVoice() ),
            (computer.getVoice, getVoices()),
            (computer.getVoice, str),
            (computer.getVoices, list),
            (computer.getVoices, [['MSMike', 'MSMary', 'MSSam', 'LHMICHAEL', 'LHMICHELLE']]),
            (computer.playSpeech, None,'fn1.wav'),
            (computer.saveSpeech, None, ['', 'fn1.wav']),
            (computer.beep, None, [0.0001, 0, 0]),  #on this comp, 8000 hz produces no sound
            (computer.playSong, None, 'F# 1; G 1; A 1;')


            ]

        return self.comp_obj_funcs


    def makeChatObjFuncs(self):
        self.chat_obj_funcs = [
                        
            #Remote Robot Control

            #The robot that will be controlled:

            (robot.initializeRemoteControl, None, "mypassword"),
            (robot.processRemoteControl, list),
            (robot.processRemoteControlLoop, list), # threaded, infinite loop -- how end?

            #The computer that will be the controller:

            #chat = Chat("myname", "mypassword")
            (chat.send, None, ["remoterobotname", "robot.turnLeft(.4)"] ),
            (chat.receive, list),

            #There is also a RemoteRobot constructor which acts like a regular robot, but sends the commands to the other robot.

            #robot = RemoteRobot("remoterobotname")
            (robot.turnLeft, None, .4)

            ]

        return self.chat_obj_funcs

    def makeGamepadFuncs(self):
        self.gamepad_funcs = [
            ("get Gamepad", int, "count"), #number of gamepads connected
            ('get Gamepad Now', dict), #waits for user to press gamepad
            #the following wait for user to press approp gamepad keys,
            #and if they don't exist on given gamepad, waits infinitely
            ##('get Gamepad', list, 'ball')
            ##('get Gamepad', list, 'button')
            ##('get Gamepad', list, 'hat')
            ##('get Gamepad', list, 'axis')

                          
            ('get Gamepad Now', list, 'ball'),
            ('get Gamepad Now', str, 'name'),
            ('get Gamepad Now', int, 'init'),
            ('get Gamepad Now', list, 'button'),
            ('get Gamepad Now', int, 'count'),
            ('get Gamepad Now', list, 'hat'),
            ('get Gamepad Now', list, 'axis')
            
            ]

        return self.gamepad_funcs

    def reqUserInteraction(self):
        #most require user to close window
        #joyStick() & joyStick(1), show(picture), simulate()
        pass




def main(theLocals, robotType, functions = None):
    '''Tests robot functions, prints out results.

    theLocals   locals()
    robotType   string representing type of robot to be tested
    functions   tuple of strings representing types of functions
        on which to test robot
    '''
    
    #initialize('com' + ask('Which port is robot connected to?')) #Helps?
    t = Tester(theLocals, robotType)
    if functions == None:
        if robotType == 'scribbler':
            functions = ('Non-Robot Functions', 'Robot Functions', 'Robot Object Functions',
                         'Computer Object Functions', 'Picture Functions', 'Gamepad Functions')
        else:
            functions = ('Robot Functions', 'Robot Object Functions')

if __name__ == "__main__":
    robot = askQuestion("Which robot are you testing?", ["Roomba or Create", "Scribbler"], "Robot Type")
    if 'roomba' in robot.lower():
        main(locals(), 'roomba')
    else:
        main(locals(), 'scribbler')
<\pre>