Module gameobject
[hide private]
[frames] | no frames]

Source Code for Module gameobject

  1  #!/usr/bin/python3 
  2  """ This module implements the (foundation) GameObject Class """ 
  3  import sys 
  4  from random import randint 
  5  from base import Base 
  6  from gameaction import GameAction 
  7   
  8   
9 -class GameObject(Base):
10 """ 11 This is the base class for all artifacts, actors, and contexts. 12 The abilities of this base class are: 13 - own a list of objects (which can be added and retrieved) 14 - return a list of GameActions that it enables 15 - accept and process non-ATTACK GameActions 16 17 @ivar objects: list of owned/contained GameObjects 18 19 objects can be I{hidden} in which case they might not be returned 20 """
21 - def __init__(self, name="actor", descr=None):
22 """ 23 create a new GameObject 24 @param name: display name of this object 25 @param descr: for players description of this object 26 """ 27 super(GameObject, self).__init__(name, descr) 28 self.objects = []
29
30 - def __str__(self):
31 """ 32 return the given name of this object 33 """ 34 return self.name
35
36 - def get_objects(self, hidden=False):
37 """ 38 return a list of GameObjects contained in/owned by this GameObject 39 40 if an object is hidden (has a positive RESISTANCE.SEARCH) it may not 41 be visible unless 42 - it has been successfully found (SEARCH > 0) 43 - caller specifies that hidden objects should be returned 44 45 @param hidden: return only hidden objects 46 @return: list of discoverd GameOjects 47 """ 48 reported = [] 49 for thing in self.objects: 50 # check object's RESISTANCE.SEARCH and SEARCH attributes 51 atr = thing.get("RESISTANCE.SEARCH") 52 concealed = atr is not None and atr > 0 53 atr = thing.get("SEARCH") 54 found = atr is not None and atr > 0 55 56 if hidden: 57 # if hidden specified, find all hidden objects 58 if concealed and not found: 59 reported.append(thing) 60 else: 61 # else find only visible objects 62 if found or not concealed: 63 reported.append(thing) 64 65 return reported
66
67 - def get_object(self, name):
68 """ 69 return a named object from my inventory 70 71 @param name: (string) of the desired object 72 @return: first matching object (or None) 73 """ 74 for thing in self.objects: 75 if name in thing.name: 76 return thing 77 return None
78
79 - def add_object(self, item):
80 """ 81 add another object to my C{objects} list (if not already there) 82 """ 83 if item not in self.objects: 84 self.objects.append(item)
85 86 # pylint: disable=too-many-locals
87 - def accept_action(self, action, actor, context):
88 """ 89 called by C{GameAction.act()} to receive GameAction, determine effects 90 91 NOTE: this base class cannot process ATTACK actions. 92 Those are processed by the C{GameActor} sub-class. 93 This base class can only process actions which (if successful), 94 increment the property who's name matches the action verb. 95 96 @param action: GameAction being performed 97 @param actor: GameActor initiating the action 98 @param context: GameContext in which action is occuring 99 100 NOTE: this base class makes no use of the C{actor} or C{context} 101 parameters, but they might be useful to a subc-class that could 102 process actions before passing them down to us. 103 104 @return: <(boolean) success, (string) description of the effect> 105 """ 106 # get the base verb and sub-type 107 if '.' in action.verb: 108 base_verb = action.verb.split('.')[0] 109 sub_type = action.verb.split('.')[1] 110 else: 111 base_verb = action.verb 112 sub_type = None 113 114 # look up our base resistance 115 res = self.get("RESISTANCE") 116 resistance = 0 if res is None else int(res) 117 118 # see if we have a RESISTANCE.base-verb 119 res = self.get("RESISTANCE." + base_verb) 120 if res is not None: 121 resistance += int(res) 122 123 # see if we have a RESISTANCE.base-verb.subtype 124 if sub_type is not None: 125 res = self.get("RESISTANCE." + base_verb + "." + sub_type) 126 if res is not None: 127 resistance += int(res) 128 129 # if sum of RESISTANCE >= TO_HIT, action has been resisted 130 power = int(action.get("TO_HIT")) - resistance 131 if power <= 0: 132 return (False, "{} resists {} {}" 133 .format(self.name, action.source.name, action.verb)) 134 135 # for each STACK instance, roll to see if roll+RESISTANCE > TO_HIT 136 incoming = abs(int(action.get("TOTAL"))) 137 received = 0 138 for _ in range(incoming): 139 roll = randint(1, 100) 140 # accumulate the number that get through 141 if roll <= power: 142 received += 1 143 144 # add number of successful STACKS to affected attribute 145 # (or if C{GameAction.TOTAL} is negative, subtract) 146 sign = 1 if int(action.get("TOTAL")) > 0 else -1 147 if received > 0: 148 have = self.get(action.verb) 149 have = 0 if have is None else int(have) 150 # special case: LIFE cannot be raised beyond HP 151 if action.verb == "LIFE" and self.get("HP") is not None: 152 max_hp = int(self.get("HP")) 153 if have + sign * received > max_hp: 154 have = max_hp - received 155 self.set(action.verb, have + (sign * received)) 156 157 # return <whether or not any succeed, accumulated results> 158 return (received > 0, 159 "{} resists {}/{} stacks of {} from {} in {}" 160 .format(self.name, incoming - received, incoming, 161 ("(negative) " if sign < 0 else "") + action.verb, 162 actor, context))
163 164 # pylint: disable=unused-argument; sub-classes are likely to use them 165 # pylint: disable=too-many-branches; there are a lot of cases 166 # pylint: disable=too-many-statements; there are a lot of cases
167 - def possible_actions(self, actor, context):
168 """ 169 return list of C{GameAction}s this object enables 170 171 verbs come from our (comma-separated-verbs) ACTIONS attribute 172 for each C{GameAction}, ACCURACY, DAMAGE, POWER, STACKS are the sum of 173 - our base ACCURACY, DAMAGE, POWER, STACKS 174 - our ACCURACY.verb, DAMAGE.verb, POWER.verb, STACKS.verb, 175 176 @param actor: GameActor initiating the action 177 @param context: GameContext in which the action is taken 178 179 NOTE: this base class makes no use of the C{actor} and C{context} 180 parameters, but a sub-class might want to determine whether or not 181 B{this actor} could perform this action in B{this context}. 182 183 @return: list of possible GameActions 184 """ 185 # get a list of possible actions with this object (e.g. weapon) 186 actions = [] 187 verbs = self.get("ACTIONS") 188 if verbs is None: 189 return [] 190 191 # get our base ACCURACY/DAMAGE/POWER/STACKS attributes 192 base_accuracy = self.get("ACCURACY") 193 base_damage = self.get("DAMAGE") 194 base_power = self.get("POWER") 195 base_stacks = self.get("STACKS") 196 197 # instantiate a GameAction for each verb in our ACTIONS list 198 for compound_verb in verbs.split(','): 199 action = GameAction(self, compound_verb) 200 201 # accumulate base and sub-type attributes 202 accuracies = "" 203 damages = "" 204 powers = "" 205 stacks = "" 206 207 # if a verb is compound (+), accumulate each sub-verb separately 208 for verb in compound_verb.split('+'): 209 if verb.startswith("ATTACK"): 210 # see if we have ATTACK sub-type ACCURACY/DAMAGE 211 sub_accuracy = None 212 sub_damage = None 213 if verb.startswith('ATTACK.'): 214 sub_type = verb.split('.')[1] 215 sub_accuracy = self.get("ACCURACY." + sub_type) 216 sub_damage = self.get("DAMAGE." + sub_type) 217 218 # combine the base and sub-type values 219 accuracy = 0 if base_accuracy is None \ 220 else int(base_accuracy) 221 accuracy += 0 if sub_accuracy is None \ 222 else int(sub_accuracy) 223 if accuracies == "": 224 accuracies = str(accuracy) 225 else: 226 accuracies += "," + str(accuracy) 227 228 # FIX GameAction.DAMAGE could be a (non-addable) D-formula 229 if sub_damage is not None: 230 damage = sub_damage 231 elif base_damage is not None: 232 damage = base_damage 233 else: 234 damage = 0 235 if damages == "": 236 damages = str(damage) 237 else: 238 damages += "," + str(damage) 239 else: 240 # see if we have verb sub-type POWER/STACKS 241 sub_power = self.get("POWER." + verb) 242 power = 0 if base_power is None else int(base_power) 243 power += 0 if sub_power is None else int(sub_power) 244 if powers == "": 245 powers = str(power) 246 else: 247 powers += "," + str(power) 248 249 # FIX GameAction.STACKS could be a (non-addable) D-formula 250 sub_stacks = self.get("STACKS." + verb) 251 if sub_stacks is not None: 252 stack = sub_stacks 253 elif base_stacks is not None: 254 stack = base_stacks 255 else: 256 stack = 1 257 if stacks == "": 258 stacks = str(stack) 259 else: 260 stacks += "," + str(stack) 261 262 # add accumulated ACCURACY/DAMAGE/POWER/STACKS to C{GameAction} 263 if accuracies != "": 264 action.set("ACCURACY", accuracies) 265 if damages != "": 266 action.set("DAMAGE", damages) 267 if powers != "": 268 action.set("POWER", powers) 269 if stacks != "": 270 action.set("STACKS", stacks) 271 272 # append the new C{GameAction} to the list to be returned 273 actions.append(action) 274 275 return actions
276
277 - def load(self, filename):
278 """ 279 read object definitions from a file 280 - blank lines and lines beginning w/# are ignored 281 - NAME string ... is the name of an object 282 - DESCRIPTION string ... is the description of that object 283 - ACTIONS string ... is the list of supported verbs 284 - OBJECT ... introduces definition of an object in our inventory 285 - anything else is an atribute and value (strings should be quoted) 286 287 NOTE: The object being defined can contain other objects. 288 (e.g. guard has a sword, box contains a scroll) 289 But contained objects cannot, themselves, contain other objects. 290 291 @param filename: name of file to be read 292 """ 293 cur_object = self 294 295 try: 296 infile = open(filename, "r") 297 for line in infile: 298 # for each non-comment line, read name and value 299 (name, value) = _lex(line) 300 if name is None: 301 continue 302 303 # check for special names: NAME, DESCRIPTION, OBJECT 304 if name == "NAME": 305 cur_object.name = value 306 elif name == "DESCRIPTION": 307 cur_object.description = value 308 elif name == "OBJECT": 309 cur_object = GameObject() 310 self.add_object(cur_object) 311 else: 312 # anything else is just an attribute of latest object 313 cur_object.set(name, value) 314 315 infile.close() 316 except IOError: 317 sys.stderr.write("Unable to read attributes from {}\n". 318 format(filename))
319 320
321 -def _lex(line):
322 """ 323 helper function to lex a name and (potentially quoted) value from a line 324 - treat (single or double) quoted strings as a single token 325 - if second token is an integer, return it as such, else a string 326 327 @param line: string to be lexed 328 @return: (name, value) ... or (None, None) for blank/comment lines 329 """ 330 # find the start of the first token 331 start = 0 332 eol = len(line) 333 while start < eol and line[start].isspace(): 334 start += 1 335 336 # if this is a comment or blank line, return (None, None) 337 if start >= eol or line[start] == "#": 338 return (None, None) 339 340 # lex off first (blank separated) token as a name 341 end = start + 1 342 while end < eol and not line[end].isspace(): 343 end += 1 344 name = line[start:end] 345 346 # lex off next token as a value 347 start = end 348 while start < eol and line[start].isspace(): 349 start += 1 350 351 if start >= eol or line[start] == "#": 352 return (name, None) 353 354 # either single or double quotes can surround a value 355 if line[start] == '"' or line[start] == "'": 356 # scan until the closing quote (or EOL) 357 quote = line[start] 358 start += 1 359 end = start + 1 360 while end < eol and line[end] != quote: 361 end += 1 362 value = line[start:end] 363 else: 364 end = start + 1 365 while end < eol and not line[end].isspace(): 366 end += 1 367 368 # an un-quoted number should be returned as an int 369 try: 370 value = int(line[start:end]) 371 except ValueError: 372 value = line[start:end] 373 374 return (name, value)
375 376 377 # UNIT TESTING
378 -def action_test():
379 """ 380 basic test GameObject test cases 381 """ 382 383 describe = "simple get/set test object" 384 go1 = GameObject("GameObject 1", describe) 385 386 # defaults to no actions 387 actions = go1.possible_actions(None, None) 388 assert (not actions), \ 389 "New object returns non-empty action list" 390 391 # added actions are returned 392 test_actions = "ACTION,SECOND ACTION" 393 go1.set("ACTIONS", test_actions) 394 print("Set actions='{}', possible_actions returns:".format(test_actions)) 395 actions = go1.possible_actions(None, None) 396 for action in actions: 397 print(" {}".format(action.verb)) 398 assert (len(actions) == 2), \ 399 "possible_actions returns wrong number of actions" 400 assert (actions[0].verb == "ACTION"), \ 401 "first action not correctly returned" 402 assert (actions[1].verb == "SECOND ACTION"), \ 403 "second action not correctly returned" 404 return (3, 3)
405 406 407 # pylint: disable=too-many-locals,too-many-statements
408 -def weapon_test():
409 """ 410 test for weapon actions and damage 411 """ 412 tried = 0 413 passed = 0 414 415 # by default a weapon has no actions or attributes 416 w_0 = GameObject("Null Weapon") 417 tried += 4 418 assert w_0.name == "Null Weapon", \ 419 "Incorrect name: expected w_0" 420 assert w_0.get("DAMAGE") is None, \ 421 "Incorrect default damage: expected None" 422 assert w_0.get("ACCURACY") is None, \ 423 "Incorrect default accuracy, expected None" 424 actions = w_0.possible_actions(None, None) 425 assert not actions, \ 426 "incorrect default actions, expected None" 427 passed += 4 428 print("test #1: " + str(w_0) + 429 " ... NO ATTACKS, ACCURACY or DAMAGE - CORRECT") 430 431 # if a weapon is created with damage, it has ATTACK 432 w_1 = GameObject("Simple Weapon") 433 w_1.set("ACTIONS", "ATTACK") 434 w_1.set("ACCURACY", 66) 435 w_1.set("DAMAGE", "666") 436 tried += 6 437 assert w_1.get("DAMAGE") == "666", \ 438 "Incorrect default damage: expected '666'" 439 assert w_1.get("ACCURACY") == 66, \ 440 "Incorrect default accuracy, expected 66" 441 actions = w_1.possible_actions(None, None) 442 assert len(actions) == 1, \ 443 "incorrect default actions, expected ['ATTACK'], got " + str(actions) 444 assert actions[0].verb == "ATTACK", \ 445 "incorrect default action, expected 'ATTACK', got " + str(actions[0]) 446 assert actions[0].get("DAMAGE") == "666", \ 447 "incorrect base damage, expected '666', got " + str(actions[0]) 448 assert actions[0].get("ACCURACY") == "66", \ 449 "incorrect base accuracy, expected 66, got " + str(actions[0]) 450 passed += 6 451 print("test #2: " + str(w_1) + 452 " ... BASE ATTACK, ACCURACY and DAMAGE - CORRECT") 453 454 # multi-attack weapons have (addative) damage and accuracy for each attack 455 # pylint: disable=bad-whitespace 456 w_2 = GameObject("multi-attack weapon") 457 458 attacks = [ 459 # verb, accuracy, damage, exp acc, exp dmg 460 ("ATTACK", 50, "D5", "50", "D5"), 461 ("ATTACK.60", 10, "D6", "60", "D6"), 462 ("ATTACK.70", 20, "D7", "70", "D7")] 463 verbs = None 464 for (verb, accuracy, damage, exp_acc, exp_dmg) in attacks: 465 if verbs is None: 466 verbs = verb 467 else: 468 verbs += "," + verb 469 if "." in verb: 470 sub_verb = verb.split(".")[1] 471 w_2.set("ACCURACY." + sub_verb, accuracy) 472 w_2.set("DAMAGE." + sub_verb, damage) 473 else: 474 w_2.set("ACCURACY", accuracy) 475 w_2.set("DAMAGE", damage) 476 477 w_2.set("ACTIONS", verbs) 478 actions = w_2.possible_actions(None, None) 479 tried += 1 480 assert len(actions) == 3, \ 481 "incorrect actions list, expected 3, got " + str(actions) 482 passed += 1 483 484 # pylint: disable=consider-using-enumerate; two parallel lists 485 for index in range(len(actions)): 486 (verb, accuracy, damage, exp_acc, exp_dmg) = attacks[index] 487 action = actions[index] 488 tried += 3 489 assert action.verb == verb, \ 490 "action {}, verb={}, expected {}".format(index, action.verb, verb) 491 assert action.get("ACCURACY") == exp_acc, \ 492 "action {}, expected ACCURACY={}, got {}". \ 493 format(action.verb, exp_acc, action.get("ACCURACY")) 494 assert action.get("DAMAGE") == exp_dmg, \ 495 "action {}, expected DAMAGE={}, got {}". \ 496 format(action.verb, exp_dmg, action.get("DAMAGE")) 497 passed += 3 498 print("test #3: {} {} ... ACCURACY({}) and DAMAGE({}) - CORRECT". 499 format(w_2.name, action.verb, 500 "base plus sub-type" if "." in verb else "base only", 501 "sub-type only" if "." in verb else "base only")) 502 return (tried, passed)
503 504 505 # pylint: disable=too-many-statements
506 -def compound_test():
507 """ 508 Test for attribute collection for compound actions 509 """ 510 obj = GameObject("Compound Actions w/base attributes") 511 first = "ATTACK.one+CONDITION.two+ATTACK.three+CONDITION.four" 512 second = "ATTACK.five+CONDITION.six" 513 obj.set("ACTIONS", first + "," + second) 514 obj.set("ACCURACY", 10) 515 obj.set("ACCURACY.one", 5) 516 obj.set("DAMAGE", "60") 517 obj.set("DAMAGE.one", 666) 518 519 obj.set("POWER", "20") 520 obj.set("POWER.CONDITION.two", 10) 521 obj.set("STACKS", 3) 522 obj.set("STACKS.CONDITION.two", 6) 523 524 obj.set("ACCURACY.five", 15) 525 obj.set("DAMAGE.five", 55) 526 obj.set("POWER.CONDITION.six", 6) 527 obj.set("STACKS.CONDITION.six", 66) 528 529 tried = 0 530 passed = 0 531 532 actions = obj.possible_actions(None, None) 533 for action in actions: 534 if action.verb == first: 535 tried += 8 536 # attack accuracy = [base+sub, base-only] 537 accuracies = action.get("ACCURACY").split(',') 538 assert accuracies[0] == "15", "ACCURACY.one not added" 539 assert accuracies[1] == "10", "base ACCURACY not used" 540 passed += 2 541 542 # attack damage = [sub, base-only] 543 damages = action.get("DAMAGE").split(',') 544 assert damages[0] == "666", "DAMAGE.one not used" 545 assert damages[1] == "60", "base DAMAGE not used" 546 passed += 2 547 print("test #4a: {} {} ...\n\t ACCURACY(S), DAMAGE(S) - CORRECT". 548 format(obj.name, action.verb)) 549 550 # condition power = [base+sub, base=only] 551 powers = action.get("POWER").split(',') 552 assert powers[0] == "30", "POWER.two not added in" 553 assert powers[1] == "20", "base POWER not used" 554 passed += 2 555 556 # condition stacks = [sub, base=only] 557 stacks = action.get("STACKS").split(',') 558 assert stacks[0] == "6", "STACKS.two not used" 559 assert stacks[1] == "3", "base STACKS not used" 560 passed += 2 561 print("test #4b: {} {} ...\n\t POWER(S), STACKS(S) - CORRECT". 562 format(obj.name, action.verb)) 563 elif action.verb == second: 564 tried += 4 565 accuracies = action.get("ACCURACY").split(',') 566 assert accuracies[0] == "25", "ACCURACY.five not added" 567 damages = action.get("DAMAGE").split(',') 568 assert damages[0] == "55", "DAMAGE.five not used" 569 passed += 2 570 571 powers = action.get("POWER").split(',') 572 assert powers[0] == "26", "POWER.six not added in" 573 stacks = action.get("STACKS").split(',') 574 assert stacks[0] == "66", "STACKS.six not used" 575 passed += 2 576 print("test #4c: {} {} ... \n\tPOWER, STACKS - CORRECT". 577 format(obj.name, action.verb)) 578 else: 579 tried += 1 580 assert False, "Incorrect verb: " + action.verb 581 582 # next set of tests are combinations w/no base attributes 583 obj = GameObject("Compound Actions w/o base attributes") 584 first = "ATTACK.seven+CONDITION.eight+ATTACK.nine+CONDITION.ten" 585 obj.set("ACTIONS", first) 586 obj.set("ACCURACY.seven", 7) 587 obj.set("DAMAGE.seven", "777") 588 589 obj.set("POWER.CONDITION.eight", 8) 590 obj.set("STACKS.CONDITION.eight", "88") 591 592 actions = obj.possible_actions(None, None) 593 assert len(actions) == 1, \ 594 "Incorrect actions: expected 1, got {}".format(len(actions)) 595 action = actions[0] 596 assert action.verb == first, \ 597 "Incorrect action: expected {}, got {}".format(first, action.verb) 598 599 # attack accuracy = [sub, None] 600 accuracies = action.get("ACCURACY").split(',') 601 assert accuracies[0] == "7", "ACCURACY.seven not used" 602 assert accuracies[1] == "0", \ 603 "expected ACCURACY=0, got {}".format(accuracies[1]) 604 605 # attack damage = [sub, None] 606 damages = action.get("DAMAGE").split(',') 607 assert damages[0] == "777", "DAMAGE.seven not used" 608 assert damages[1] == "0", \ 609 "expected DAMAGE=0, got {}".format(damages[1]) 610 611 print("test #4d: {} {} ... \n\tACCURACY, DAMAGE - CORRECT". 612 format(obj.name, action.verb)) 613 614 # condition power = [base+sub, base=only] 615 powers = action.get("POWER").split(',') 616 assert powers[0] == "8", "POWER.eight not used" 617 assert powers[1] == "0", \ 618 "expected POWER=0, got {}".format(powers[1]) 619 620 # condition stacks = [sub, base=only] 621 stacks = action.get("STACKS").split(',') 622 assert stacks[0] == "88", "STACKS.eight not used" 623 assert stacks[1] == "1", "default STACKS=1 not used" 624 625 print("test #4e: {} {} ... \n\tPOWER, STACKS - CORRECT". 626 format(obj.name, action.verb)) 627 628 return (tried, passed)
629 630
631 -def accept_test():
632 """ 633 tests for (non-ATTACK) accept_action() 634 """ 635 tried = 0 636 passed = 0 637 638 # create the initiator, recipient, and context 639 initiator = GameObject("tester") # won't need any GameActor functions 640 target = GameObject("target") 641 arena = GameObject("arena") # won't need any GameCOntext functions 642 643 # STACKS get through in proportion to TO_HIT - RESISTANCE 644 action = GameAction(initiator, "50/50") 645 action.set("STACKS", 100) 646 target.set("RESISTANCE", 50) # half should get through 647 (success, desc) = action.act(initiator, target, arena) 648 tried += 5 649 assert success,\ 650 "None of 100 50/50 STACKs got through" 651 passed += 1 652 653 # decode and check the returned status string 654 resisted = int(desc.split()[2].split('/')[0]) 655 expect = "{} resists {}/100 stacks of {} from {} in {}".\ 656 format(target.name, resisted, action.verb, initiator.name, arena.name) 657 assert desc == expect, \ 658 "Successful 50/50 did not return expected result string" 659 passed += 1 660 661 # confirm a reasonable number of stacks got through 662 assert resisted >= 35, \ 663 "too few 50/50 STACKS got through" 664 assert resisted <= 64, \ 665 "too many 50/50 STACKS got through" 666 attribute = target.get(action.verb) 667 assert attribute == (100 - resisted), \ 668 "target's {} does not reflect correct 100-{}". \ 669 format(action.verb, resisted) 670 passed += 3 671 672 print("test #5a: {} resists {}/100 STACKS of {} ... \n\t{}.{} 100 -> {}". 673 format(target.name, resisted, action.verb, target.name, 674 action.verb, attribute)) 675 676 # confirm that negative stacks also get through 677 action = GameAction(initiator, "SURE-THING") 678 action.set("STACKS", -50) 679 target.set("RESISTANCE", 0) # no resistance 680 target.set(action.verb, 100) # initial value 681 (success, desc) = action.act(initiator, target, arena) 682 tried += 4 683 assert success, \ 684 "{} action failed".format(action.verb) 685 686 resisted = int(desc.split()[2].split('/')[0]) 687 expect = "{} resists {}/50 stacks of (negative) {} from {} in {}".\ 688 format(target.name, resisted, action.verb, initiator.name, arena.name) 689 assert desc == expect, \ 690 "Successful SURE-THING did not return expected result string" 691 assert resisted == 0, \ 692 "{} resists {} STACKS of {}".format(target.name, resisted, action.verb) 693 694 attribute = target.get(action.verb) 695 assert attribute == (100 - 50), \ 696 "100 - 50 STACKS of {} -> {}".format(action.verb, attribute) 697 passed += 4 698 699 print("test #5b: {} delivers -50 STACKS of {} ... \n\t{}.{} 100 -> {}". 700 format(initiator.name, action.verb, target.name, action.verb, 701 attribute)) 702 703 # adequate RESISTANCE is total protection 704 target.set("RESISTANCE", 100) 705 action = GameAction(initiator, "BASE-RESISTED-ACTION") 706 action.set("STACKS", 100) 707 (success, desc) = action.act(initiator, target, arena) 708 tried += 3 709 assert not success, \ 710 "{} action succeeded".format(action.verb) 711 assert desc == "{} resists {} {}". \ 712 format(target.name, initiator.name, action.verb), \ 713 "{} action does not return correct failure message".format(action.verb) 714 assert target.get(action.verb) is None, \ 715 "target property was set by failed {} action".format(action.verb) 716 passed += 3 717 718 print("test #5c: {} delivers 100 STACKS of {} ... \n\t{}". 719 format(initiator.name, action.verb, desc)) 720 721 # adequate RESISTANCE.verb is total protection 722 target.set("RESISTANCE", None) 723 target.set("RESISTANCE.VERB-RESISTED-ACTION", 100) 724 action = GameAction(initiator, "VERB-RESISTED-ACTION") 725 action.set("STACKS", 100) 726 (success, desc) = action.act(initiator, target, arena) 727 tried += 3 728 assert not success, \ 729 "{} action succeeded".format(action.verb) 730 assert desc == "{} resists {} {}". \ 731 format(target.name, initiator.name, action.verb), \ 732 "{} action does not return correct failure message".format(action.verb) 733 assert target.get(action.verb) is None, \ 734 "target property was set by failed {} action".format(action.verb) 735 passed += 3 736 737 print("test #5d: {} delivers 100 STACKS of {} ... \n\t{}". 738 format(initiator.name, action.verb, desc)) 739 740 # adequate RESISTANCE.verb.subtype is total protection 741 target.set("RESISTANCE.VERB-RESISTED-ACTION", None) 742 target.set("RESISTANCE.RESISTED-ACTION.SUBTYPE", 100) 743 action = GameAction(initiator, "RESISTED-ACTION.SUBTYPE") 744 action.set("STACKS", 100) 745 (success, desc) = action.act(initiator, target, arena) 746 tried += 3 747 assert not success, \ 748 "{} action succeeded".format(action.verb) 749 assert desc == "{} resists {} {}". \ 750 format(target.name, initiator.name, action.verb), \ 751 "{} action does not return correct failure message".format(action.verb) 752 assert target.get(action.verb) is None, \ 753 "target property was set by failed {} action".format(action.verb) 754 passed += 3 755 756 print("test #5e: {} delivers 100 STACKS of {} ... \n\t{}". 757 format(initiator.name, action.verb, desc)) 758 759 target.set("RESISTANCE.RESISTED-ACTION.SUBTYPE", None) 760 761 # LIFE cannot be increased beyond HP 762 target.set("RESISTANCE.RESISTED-ACTION.SUBTYPE", None) 763 target.set("LIFE", 25) 764 target.set("HP", 50) 765 action = GameAction(initiator, "LIFE") 766 action.set("STACKS", 100) 767 (success, desc) = action.act(initiator, target, arena) 768 tried += 3 769 assert success, \ 770 "sure thing {} did not succeed".format(action.verb) 771 life = target.get(action.verb) 772 assert life <= target.get("HP"), \ 773 "{} raised above HP {}".format(action.verb, target.get("HP")) 774 assert life == target.get("HP"), \ 775 "25/50 + 100 STACKS of {} -> {}".format(action.verb, life) 776 passed += 3 777 778 print("test #5f: 25/50 + 100 STACKS of {} ... \n\t{}.{} 25 -> {}". 779 format(action.verb, target.name, action.verb, life)) 780 781 print() 782 return (tried, passed)
783 784
785 -def main():
786 """ 787 Run all unit-test cases and print out summary of results 788 """ 789 (t_1, p_1) = action_test() 790 (t_2, p_2) = weapon_test() 791 (t_3, p_3) = compound_test() 792 (t_4, p_4) = accept_test() 793 tried = t_1 + t_2 + t_3 + t_4 794 passed = p_1 + p_2 + p_3 + p_4 795 if tried == passed: 796 print("Passed all {} GameObject tests".format(passed)) 797 else: 798 print("FAILED {}/{} GameObject tests".format(tried-passed, tried))
799 800 801 if __name__ == "__main__": 802 main() 803