00001
00002
00003 """
00004 pi_trees_lib.py - Version 0.1 2013-08-28
00005
00006 Core classes for implementing Behavior Trees in Python
00007
00008 Created for the Pi Robot Project: http://www.pirobot.org
00009 Copyright (c) 2013 Patrick Goebel. All rights reserved.
00010
00011 This program is free software; you can redistribute it and/or modify
00012 it under the terms of the GNU General Public License as published by
00013 the Free Software Foundation; either version 2 of the License, or
00014 (at your option) any later version.
00015
00016 This program is distributed in the hope that it will be useful,
00017 but WITHOUT ANY WARRANTY; without even the implied warranty of
00018 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00019 GNU General Public License for more details at:
00020
00021 http://www.gnu.org/licenses/gpl.html
00022 """
00023
00024 import string
00025
00026
00027
00028
00029
00030
00031
00032
00033 class TaskStatus(object):
00034 """ A class for enumerating task statuses """
00035 FAILURE = 0
00036 SUCCESS = 1
00037 RUNNING = 2
00038
00039 class Task(object):
00040 """ "The base Task class """
00041 def __init__(self, name, children=None, *args, **kwargs):
00042 self.name = name
00043 self.status = None
00044
00045 if children is None:
00046 children = []
00047
00048 self.children = children
00049
00050 def run(self):
00051 pass
00052
00053 def reset(self):
00054 for c in self.children:
00055 c.reset()
00056
00057 def add_child(self, c):
00058 self.children.append(c)
00059
00060 def remove_child(self, c):
00061 self.children.remove(c)
00062
00063 def prepend_child(self, c):
00064 self.children.insert(0, c)
00065
00066 def insert_child(self, c, i):
00067 self.children.insert(i, c)
00068
00069 def get_status(self):
00070 return self.status
00071
00072 def set_status(self, s):
00073 self.status = s
00074
00075 def announce(self):
00076 print("Executing task " + str(self.name))
00077
00078
00079 def __enter__(self):
00080 return self.name
00081
00082 def __exit__(self, exc_type, exc_val, exc_tb):
00083 if exc_type is not None:
00084 return False
00085 return True
00086
00087 class Selector(Task):
00088 """ A selector runs each task in order until one succeeds,
00089 at which point it returns SUCCESS. If all tasks fail, a FAILURE
00090 status is returned. If a subtask is still RUNNING, then a RUNNING
00091 status is returned and processing continues until either SUCCESS
00092 or FAILURE is returned from the subtask.
00093 """
00094 def __init__(self, name, *args, **kwargs):
00095 super(Selector, self).__init__(name, *args, **kwargs)
00096
00097 def run(self):
00098 for c in self.children:
00099
00100 c.status = c.run()
00101
00102 if c.status != TaskStatus.FAILURE:
00103 return c.status
00104
00105 return TaskStatus.FAILURE
00106
00107 class Sequence(Task):
00108 """
00109 A sequence runs each task in order until one fails,
00110 at which point it returns FAILURE. If all tasks succeed, a SUCCESS
00111 status is returned. If a subtask is still RUNNING, then a RUNNING
00112 status is returned and processing continues until either SUCCESS
00113 or FAILURE is returned from the subtask.
00114 """
00115 def __init__(self, name, *args, **kwargs):
00116 super(Sequence, self).__init__(name, *args, **kwargs)
00117
00118 def run(self):
00119 for c in self.children:
00120
00121 c.status = c.run()
00122
00123 if c.status != TaskStatus.SUCCESS:
00124 return c.status
00125
00126 return TaskStatus.SUCCESS
00127
00128 class Iterator(Task):
00129 """
00130 Iterate through all child tasks ignoring failure.
00131 """
00132 def __init__(self, name, *args, **kwargs):
00133 super(Iterator, self).__init__(name, *args, **kwargs)
00134
00135 def run(self):
00136 for c in self.children:
00137
00138 c.status = c.run()
00139
00140 if c.status != TaskStatus.SUCCESS and c.status != TaskStatus.FAILURE:
00141 return c.status
00142
00143 return TaskStatus.SUCCESS
00144
00145 class ParallelOne(Task):
00146 """
00147 A parallel task runs each child task at (roughly) the same time.
00148 The ParallelOne task returns success as soon as any child succeeds.
00149 """
00150 def __init__(self, name, *args, **kwargs):
00151 super(ParallelOne, self).__init__(name, *args, **kwargs)
00152
00153 def run(self):
00154 for c in self.children:
00155 c.status = c.run()
00156
00157 if c.status == TaskStatus.SUCCESS:
00158 return TaskStatus.SUCCESS
00159
00160 return TaskStatus.FAILURE
00161
00162 class ParallelAll(Task):
00163 """
00164 A parallel task runs each child task at (roughly) the same time.
00165 The ParallelAll task requires all subtasks to succeed for it to succeed.
00166 """
00167 def __init__(self, name, *args, **kwargs):
00168 super(ParallelAll, self).__init__(name, *args, **kwargs)
00169
00170 def run(self):
00171 n_success = 0
00172 n_children = len(self.children)
00173
00174 for c in self.children:
00175 c.status = c.run()
00176 if c.status == TaskStatus.SUCCESS:
00177 n_success += 1
00178
00179 if c.status == TaskStatus.FAILURE:
00180 return TaskStatus.FAILURE
00181
00182 if n_success == n_children:
00183 return TaskStatus.SUCCESS
00184 else:
00185 return TaskStatus.RUNNING
00186
00187 class Loop(Task):
00188 """
00189 Loop over one or more subtasks for the given number of iterations
00190 Use the value -1 to indicate a continual loop.
00191 """
00192 def __init__(self, name, announce=True, *args, **kwargs):
00193 super(Loop, self).__init__(name, *args, **kwargs)
00194
00195 self.iterations = kwargs['iterations']
00196 self.announce = announce
00197 self.loop_count = 0
00198 self.name = name
00199 print("Loop iterations: " + str(self.iterations))
00200
00201 def run(self):
00202
00203 while True:
00204 if self.iterations != -1 and self.loop_count >= self.iterations:
00205 return TaskStatus.SUCCESS
00206
00207 for c in self.children:
00208 while True:
00209 c.status = c.run()
00210
00211 if c.status == TaskStatus.SUCCESS:
00212 break
00213
00214 return c.status
00215
00216 c.reset()
00217
00218 self.loop_count += 1
00219
00220 if self.announce:
00221 print(self.name + " COMPLETED " + str(self.loop_count) + " LOOP(S)")
00222
00223
00224 class IgnoreFailure(Task):
00225 """
00226 Always return either RUNNING or SUCCESS.
00227 """
00228 def __init__(self, name, *args, **kwargs):
00229 super(IgnoreFailure, self).__init__(name, *args, **kwargs)
00230
00231 def run(self):
00232
00233 for c in self.children:
00234
00235 c.status = c.run()
00236
00237 if c.status == TaskStatus.FAILURE:
00238 return TaskStatus.SUCCESS
00239 else:
00240 return c.status
00241
00242 return TaskStatus.SUCCESS
00243
00244 class TaskNot(Task):
00245 """
00246 Turn SUCCESS into FAILURE and vice-versa
00247 """
00248 def __init__(self, name, *args, **kwargs):
00249 super(TaskNot, self).__init__(name, *args, **kwargs)
00250
00251 def run(self):
00252
00253 for c in self.children:
00254
00255 c.status = c.run()
00256
00257 if c.status == TaskStatus.FAILURE:
00258 return TaskStatus.SUCCESS
00259
00260 elif c.status == TaskStatus.FAILURE:
00261 return TaskStatus.SUCCESS
00262
00263 else:
00264 return c.status
00265
00266 class AutoRemoveSequence(Task):
00267 """
00268 Remove each successful subtask from a sequence
00269 """
00270 def __init__(self, name, *args, **kwargs):
00271 super(AutoRemoveSequence, self).__init__(name, *args, **kwargs)
00272
00273 def run(self):
00274 for c in self.children:
00275 c.status = c.run()
00276
00277 if c.status == TaskStatus.FAILURE:
00278 return TaskStatus.FAILURE
00279
00280 if c.statuss == TaskStatus.RUNNING:
00281 return TaskStatus.RUNNING
00282
00283 try:
00284 self.children.remove(self.children[0])
00285 except:
00286 return TaskStatus.FAILURE
00287
00288 return TaskStatus.SUCCESS
00289
00290 class CallbackTask(Task):
00291 """
00292 Turn any callback function (cb) into a task
00293 """
00294 def __init__(self, name, cb=None, cb_args=[], cb_kwargs={}, **kwargs):
00295 super(CallbackTask, self).__init__(name, cb=None, cb_args=[], cb_kwargs={}, **kwargs)
00296
00297 self.name = name
00298 self.cb = cb
00299 self.cb_args = cb_args
00300 self.cb_kwargs = cb_kwargs
00301
00302 def run(self):
00303 status = self.cb(*self.cb_args, **self.cb_kwargs)
00304
00305 if status == 0 or status == False:
00306 return TaskStatus.FAILURE
00307
00308 elif status == 1 or status == True:
00309 return TaskStatus.SUCCESS
00310
00311 else:
00312 return TaskStatus.RUNNING
00313
00314
00315 class loop(Task):
00316 """
00317 Loop over one or more subtasks a given number of iterations
00318 """
00319 def __init__(self, task, iterations=-1):
00320 new_name = task.name + "_loop_" + str(iterations)
00321 super(loop, self).__init__(new_name)
00322
00323 self.task = task
00324 self.iterations = iterations
00325 self.old_run = task.run
00326 self.old_reset = task.reset
00327 self.old_children = task.children
00328 self.loop_count = 0
00329
00330 print("Loop iterations: " + str(self.iterations))
00331
00332 def run(self):
00333 if self.iterations != -1 and self.loop_count >= self.iterations:
00334 return TaskStatus.SUCCESS
00335
00336 print("Loop " + str(self.loop_count))
00337
00338 while True:
00339 self.status = self.old_run()
00340
00341 if self.status == TaskStatus.SUCCESS:
00342 break
00343 else:
00344 return self.status
00345
00346 self.old_reset()
00347 self.loop_count += 1
00348
00349 self.task.run = self.run
00350
00351 return self.task
00352
00353 class ignore_failure(Task):
00354 """
00355 Always return either RUNNING or SUCCESS.
00356 """
00357 def __init__(self, task):
00358 new_name = task.name + "_ignore_failure"
00359 super(ignore_failure, self).__init__(new_name)
00360
00361 self.task = task
00362 self.old_run = task.run
00363
00364 def run(self):
00365 while True:
00366 self.status = self.old_run()
00367
00368 if self.status == TaskStatus.FAILURE:
00369 return TaskStatus.SUCCESS
00370 else:
00371 return self.status
00372
00373 self.task.run = self.run
00374
00375 return self.task
00376
00377 class task_not(Task):
00378 """
00379 Turn SUCCESS into FAILURE and vice-versa
00380 """
00381 def __init__(self, task):
00382 new_name = task.name + "_not"
00383 super(task_not, self).__init__(new_name)
00384
00385 self.task = task
00386 self.old_run = task.run
00387
00388 def run(self):
00389 while True:
00390 self.status = self.old_run()
00391
00392 if self.status == TaskStatus.FAILURE:
00393 return TaskStatus.SUCCESS
00394
00395 elif self.status == TaskStatus.FAILURE:
00396 return TaskStatus.SUCCESS
00397
00398 else:
00399 return self.status
00400
00401 self.task.run = self.run
00402
00403 return self.task
00404
00405 def print_tree(tree, indent=0):
00406 """
00407 Print an ASCII representation of the tree
00408 """
00409 for c in tree.children:
00410 print " " * indent, "-->", c.name
00411
00412 if c.children != []:
00413 print_tree(c, indent+1)
00414
00415 def print_phpsyntax_tree(tree):
00416 """
00417 Print an output compatible with ironcreek.net/phpSyntaxTree
00418 """
00419 for c in tree.children:
00420 print "[" + string.replace(c.name, "_", "."),
00421 if c.children != []:
00422 print_phpsyntax_tree(c),
00423 print "]",
00424
00425
00426
00427
00428
00429
00430
00431
00432
00433
00434
00435
00436
00437
00438
00439
00440
00441
00442
00443
00444
00445
00446
00447
00448
00449
00450
00451
00452
00453
00454
00455
00456
00457
00458
00459