Source code for evennia.contrib.game_systems.puzzles.tests

"""
Testing puzzles.

"""

# Test of the Puzzles module

import itertools
import re

from mock import Mock

from evennia.commands.default.tests import BaseEvenniaCommandTest
from evennia.utils import search
from evennia.utils.create import create_object

from . import puzzles


[docs]class TestPuzzles(BaseEvenniaCommandTest):
[docs] def setUp(self): super().setUp() self.steel = create_object(self.object_typeclass, key="steel", location=self.char1.location) self.flint = create_object(self.object_typeclass, key="flint", location=self.char1.location) self.fire = create_object(self.object_typeclass, key="fire", location=self.char1.location) self.steel.tags.add("tag-steel") self.steel.tags.add("tag-steel", category="tagcat") self.flint.tags.add("tag-flint") self.flint.tags.add("tag-flint", category="tagcat") self.fire.tags.add("tag-fire") self.fire.tags.add("tag-fire", category="tagcat")
def _assert_msg_matched(self, msg, regexs, re_flags=0): matches = [] for regex in regexs: m = re.search(regex, msg, re_flags) self.assertIsNotNone(m, "%r didn't match %r" % (regex, msg)) matches.append(m) return matches def _assert_recipe(self, name, parts, results, and_destroy_it=True, expected_count=1): def _keys(items): return [item["key"] for item in items] recipes = search.search_script_tag("", category=puzzles._PUZZLES_TAG_CATEGORY) self.assertEqual(expected_count, len(recipes)) self.assertEqual(name, recipes[expected_count - 1].db.puzzle_name) self.assertEqual(parts, _keys(recipes[expected_count - 1].db.parts)) self.assertEqual(results, _keys(recipes[expected_count - 1].db.results)) self.assertEqual( puzzles._PUZZLES_TAG_RECIPE, recipes[expected_count - 1].tags.get(category=puzzles._PUZZLES_TAG_CATEGORY), ) recipe_dbref = recipes[expected_count - 1].dbref if and_destroy_it: recipes[expected_count - 1].delete() return recipe_dbref if not and_destroy_it else None def _assert_no_recipes(self): self.assertEqual( 0, len(search.search_script_tag("", category=puzzles._PUZZLES_TAG_CATEGORY)) ) # good recipes def _good_recipe(self, name, parts, results, and_destroy_it=True, expected_count=1): regexs = [] for p in parts: regexs.append(r"^Part %s\(#\d+\)$" % (p)) for r in results: regexs.append(r"^Result %s\(#\d+\)$" % (r)) regexs.append(r"^Puzzle '%s' %s\(#\d+\) has been created successfully.$" % (name, name)) lhs = [name] + parts cmdstr = ",".join(lhs) + "=" + ",".join(results) msg = self.call(puzzles.CmdCreatePuzzleRecipe(), cmdstr, caller=self.char1) recipe_dbref = self._assert_recipe(name, parts, results, and_destroy_it, expected_count) self._assert_msg_matched(msg, regexs, re_flags=re.MULTILINE | re.DOTALL) return recipe_dbref def _check_room_contents(self, expected, check_test_tags=False): by_obj_key = lambda o: o.key room1_contents = sorted(self.room1.contents, key=by_obj_key) for key, grp in itertools.groupby(room1_contents, by_obj_key): if key in expected: grp = list(grp) self.assertEqual( expected[key], len(grp), "Expected %d but got %d for %s" % (expected[key], len(grp), key), ) if check_test_tags: for gi in grp: tags = gi.tags.all(return_key_and_category=True) self.assertIn(("tag-" + gi.key, "tagcat"), tags) def _arm(self, recipe_dbref, name, parts): regexs = [ r"^Puzzle Recipe %s\(#\d+\) '%s' found.$" % (name, name), r"^Spawning %d parts ...$" % (len(parts)), ] for p in parts: regexs.append(r"^Part %s\(#\d+\) spawned .*$" % (p)) regexs.append(r"^Puzzle armed successfully.$") msg = self.call(puzzles.CmdArmPuzzle(), recipe_dbref, caller=self.char1) self._assert_msg_matched(msg, regexs, re_flags=re.MULTILINE | re.DOTALL)
[docs] def test_cmdset_puzzle(self): self.char1.cmdset.add("evennia.contrib.game_systems.puzzles.PuzzleSystemCmdSet")
# FIXME: testing nothing, this is just to bump up coverage
[docs] def test_cmd_puzzle(self): self._assert_no_recipes() # bad syntax def _bad_syntax(cmdstr): self.call( puzzles.CmdCreatePuzzleRecipe(), cmdstr, "Usage: @puzzle name,<part1[,...]> = <result1[,...]>", caller=self.char1, ) _bad_syntax("") _bad_syntax("=") _bad_syntax("nothing =") _bad_syntax("= nothing") _bad_syntax("nothing") _bad_syntax(",nothing") _bad_syntax("name, nothing") _bad_syntax("name, nothing =") self._assert_no_recipes() self._good_recipe("makefire", ["steel", "flint"], ["fire", "steel", "flint"]) self._good_recipe("hot steels", ["steel", "fire"], ["steel", "fire"]) self._good_recipe( "furnace", ["steel", "steel", "fire"], ["steel", "steel", "fire", "fire", "fire", "fire"], ) # bad recipes def _bad_recipe(name, parts, results, fail_regex): cmdstr = ",".join([name] + parts) + "=" + ",".join(results) msg = self.call(puzzles.CmdCreatePuzzleRecipe(), cmdstr, caller=self.char1) self._assert_no_recipes() self.assertIsNotNone(re.match(fail_regex, msg), msg) _bad_recipe("name", ["nothing"], ["neither"], r"Could not find 'nothing'.") _bad_recipe("name", ["steel"], ["nothing"], r"Could not find 'nothing'.") _bad_recipe("", ["steel", "fire"], ["steel", "fire"], r"^Invalid puzzle name ''.") self.steel.location = self.char1 _bad_recipe("name", ["steel"], ["fire"], r"^Invalid location for steel$") _bad_recipe("name", ["flint"], ["steel"], r"^Invalid location for steel$") _bad_recipe("name", ["self"], ["fire"], r"^Invalid typeclass for Char$") _bad_recipe("name", ["here"], ["fire"], r"^Invalid typeclass for Room$") self._assert_no_recipes()
[docs] def test_cmd_armpuzzle(self): # bad arms self.call( puzzles.CmdArmPuzzle(), "1", "A puzzle recipe's #dbref must be specified", caller=self.char1, ) self.call(puzzles.CmdArmPuzzle(), "#1", "Invalid puzzle '#1'", caller=self.char1) recipe_dbref = self._good_recipe( "makefire", ["steel", "flint"], ["fire", "steel", "flint"], and_destroy_it=False ) # delete proto parts and proto result self.steel.delete() self.flint.delete() self.fire.delete() # good arm self._arm(recipe_dbref, "makefire", ["steel", "flint"]) self._check_room_contents({"steel": 1, "flint": 1}, check_test_tags=True)
def _use(self, cmdstr, expmsg): msg = self.call(puzzles.CmdUsePuzzleParts(), cmdstr, expmsg, caller=self.char1) return msg
[docs] def test_cmd_use(self): self._use("", "Use what?") self._use("something", "There is no something around.") self._use("steel", "You have no idea how this can be used") self._use("steel flint", "There is no steel flint around.") self._use("steel, flint", "You have no idea how these can be used") recipe_dbref = self._good_recipe( "makefire", ["steel", "flint"], ["fire"], and_destroy_it=False ) recipe2_dbref = self._good_recipe( "makefire2", ["steel", "flint"], ["fire"], and_destroy_it=False, expected_count=2 ) # although there is steel and flint # those aren't valid puzzle parts because # the puzzle hasn't been armed self._use("steel", "You have no idea how this can be used") self._use("steel, flint", "You have no idea how these can be used") self._arm(recipe_dbref, "makefire", ["steel", "flint"]) self._check_room_contents({"steel": 2, "flint": 2}, check_test_tags=True) # there are duplicated objects now self._use("steel", "Which steel. There are many") self._use("flint", "Which flint. There are many") # delete proto parts and proto results self.steel.delete() self.flint.delete() self.fire.delete() # solve puzzle self._use("steel, flint", "You are a Genius") self.assertEqual( 1, len( list( filter( lambda o: o.key == "fire" and ("makefire", puzzles._PUZZLES_TAG_CATEGORY) in o.tags.all(return_key_and_category=True) and (puzzles._PUZZLES_TAG_MEMBER, puzzles._PUZZLES_TAG_CATEGORY) in o.tags.all(return_key_and_category=True), self.room1.contents, ) ) ), ) self._check_room_contents({"steel": 0, "flint": 0, "fire": 1}, check_test_tags=True) # trying again will fail as it was resolved already # and the parts were destroyed self._use("steel, flint", "There is no steel around") self._use("flint, steel", "There is no flint around") # arm same puzzle twice so there are duplicated parts self._arm(recipe_dbref, "makefire", ["steel", "flint"]) self._arm(recipe_dbref, "makefire", ["steel", "flint"]) self._check_room_contents({"steel": 2, "flint": 2, "fire": 1}, check_test_tags=True) # try solving with multiple parts but incomplete set self._use( "steel-1, steel-2", "You try to utilize these but nothing happens ... something amiss?" ) # arm the other puzzle. Their parts are identical self._arm(recipe2_dbref, "makefire2", ["steel", "flint"]) self._check_room_contents({"steel": 3, "flint": 3, "fire": 1}, check_test_tags=True) # solve with multiple parts for # multiple puzzles. Both can be solved but # only one is. self._use( "steel-1, flint-2, steel-3, flint-3", "Your gears start turning and 2 different ideas come to your mind ... ", ) self._check_room_contents({"steel": 2, "flint": 2, "fire": 2}, check_test_tags=True) self.room1.msg_contents = Mock() # solve all self._use("steel-1, flint-1", "You are a Genius") self.room1.msg_contents.assert_called_once_with( "|cChar|n performs some kind of tribal dance and |yfire|n seems to appear from thin air", exclude=(self.char1,), ) self._use("steel, flint", "You are a Genius") self._check_room_contents({"steel": 0, "flint": 0, "fire": 4}, check_test_tags=True)
[docs] def test_puzzleedit(self): recipe_dbref = self._good_recipe( "makefire", ["steel", "flint"], ["fire"], and_destroy_it=False ) def _puzzleedit(swt, dbref, args, expmsg): if (swt is None) and (dbref is None) and (args is None): cmdstr = "" else: cmdstr = "%s %s%s" % (swt, dbref, args) self.call(puzzles.CmdEditPuzzle(), cmdstr, expmsg, caller=self.char1) # delete proto parts and proto results self.steel.delete() self.flint.delete() self.fire.delete() sid = self.script.id # bad syntax _puzzleedit( None, None, None, "A puzzle recipe's #dbref must be specified.\nUsage: @puzzleedit" ) _puzzleedit("", "1", "", "A puzzle recipe's #dbref must be specified.\nUsage: @puzzleedit") _puzzleedit("", "", "", "A puzzle recipe's #dbref must be specified.\nUsage: @puzzleedit") _puzzleedit( "", recipe_dbref, "dummy", "A puzzle recipe's #dbref must be specified.\nUsage: @puzzleedit", ) _puzzleedit("", self.script.dbref, "", "Script(#{}) is not a puzzle".format(sid)) # edit use_success_message and use_success_location_message _puzzleedit( "", recipe_dbref, "/use_success_message = Yes!", "makefire(%s) use_success_message = Yes!" % recipe_dbref, ) _puzzleedit( "", recipe_dbref, "/use_success_location_message = {result_names} Yeah baby! {caller}", "makefire(%s) use_success_location_message = {result_names} Yeah baby! {caller}" % recipe_dbref, ) self._arm(recipe_dbref, "makefire", ["steel", "flint"]) self.room1.msg_contents = Mock() self._use("steel, flint", "Yes!") self.room1.msg_contents.assert_called_once_with( "fire Yeah baby! Char", exclude=(self.char1,) ) self.room1.msg_contents.reset_mock() # edit mask: exclude location and desc during matching _puzzleedit( "", recipe_dbref, "/mask = location,desc", "makefire(%s) mask = ('location', 'desc')" % recipe_dbref, ) self._arm(recipe_dbref, "makefire", ["steel", "flint"]) # change location and desc self.char1.search("steel").db.desc = "A solid bar of steel" self.char1.search("steel").location = self.char1 self.char1.search("flint").db.desc = "A flint steel" self.char1.search("flint").location = self.char1 self._use("steel, flint", "Yes!") self.room1.msg_contents.assert_called_once_with( "fire Yeah baby! Char", exclude=(self.char1,) ) # delete _puzzleedit("/delete", recipe_dbref, "", "makefire(%s) was deleted" % recipe_dbref) self._assert_no_recipes()
[docs] def test_puzzleedit_add_remove_parts_results(self): recipe_dbref = self._good_recipe( "makefire", ["steel", "flint"], ["fire"], and_destroy_it=False ) def _puzzleedit(swt, dbref, rhslist, expmsg): cmdstr = "%s %s = %s" % (swt, dbref, ", ".join(rhslist)) self.call(puzzles.CmdEditPuzzle(), cmdstr, expmsg, caller=self.char1) red_steel = create_object( self.object_typeclass, key="red steel", location=self.char1.location ) smoke = create_object(self.object_typeclass, key="smoke", location=self.char1.location) _puzzleedit("/addresult", recipe_dbref, ["smoke"], "smoke were added to results") _puzzleedit( "/addpart", recipe_dbref, ["red steel", "steel"], "red steel, steel were added to parts" ) # create a box so we can put all objects in # so that they can't be found during puzzle resolution self.box = create_object(self.object_typeclass, key="box", location=self.char1.location) def _box_all(): for o in self.room1.contents: if o not in [self.char1, self.char2, self.exit, self.obj1, self.obj2, self.box]: o.location = self.box _box_all() self._arm(recipe_dbref, "makefire", ["steel", "flint", "red steel", "steel"]) self._check_room_contents({"steel": 2, "red steel": 1, "flint": 1}) self._use( "steel-1, flint", "You try to utilize these but nothing happens ... something amiss?" ) self._use("steel-1, flint, red steel, steel-2", "You are a Genius") self._check_room_contents({"smoke": 1, "fire": 1}) _box_all() self.fire.location = self.room1 self.steel.location = self.room1 _puzzleedit("/delresult", recipe_dbref, ["fire"], "fire were removed from results") _puzzleedit( "/delpart", recipe_dbref, ["steel", "steel"], "steel, steel were removed from parts" ) _box_all() self._arm(recipe_dbref, "makefire", ["flint", "red steel"]) self._check_room_contents({"red steel": 1, "flint": 1}) self._use("red steel, flint", "You are a Genius") self._check_room_contents({"smoke": 1, "fire": 0})
[docs] def test_lspuzzlerecipes_lsarmedpuzzles(self): msg = self.call(puzzles.CmdListPuzzleRecipes(), "", caller=self.char1) self._assert_msg_matched( msg, [r"^-+$", r"^Found 0 puzzle\(s\)\.$", r"-+$"], re.MULTILINE | re.DOTALL ) recipe_dbref = self._good_recipe( "makefire", ["steel", "flint"], ["fire"], and_destroy_it=False ) msg = self.call(puzzles.CmdListPuzzleRecipes(), "", caller=self.char1) self._assert_msg_matched( msg, [ r"^-+$", r"^Puzzle 'makefire'.*$", r"^Success Caller message:$", r"^Success Location message:$", r"^Mask:$", r"^Parts$", r"^.*key: steel$", r"^.*key: flint$", r"^Results$", r"^.*key: fire$", r"^.*key: steel$", r"^.*key: flint$", r"^-+$", r"^Found 1 puzzle\(s\)\.$", r"^-+$", ], re.MULTILINE | re.DOTALL, ) msg = self.call(puzzles.CmdListArmedPuzzles(), "", caller=self.char1) self._assert_msg_matched( msg, [r"^-+$", r"^-+$", r"^Found 0 armed puzzle\(s\)\.$", r"^-+$"], re.MULTILINE | re.DOTALL, ) self._arm(recipe_dbref, "makefire", ["steel", "flint"]) msg = self.call(puzzles.CmdListArmedPuzzles(), "", caller=self.char1) self._assert_msg_matched( msg, [ r"^-+$", r"^Puzzle name: makefire$", r"^.*steel.* at \s+ Room.*$", r"^.*flint.* at \s+ Room.*$", r"^Found 1 armed puzzle\(s\)\.$", r"^-+$", ], re.MULTILINE | re.DOTALL, )
[docs] def test_e2e(self): def _destroy_objs_in_room(keys): for obj in self.room1.contents: if obj.key in keys: obj.delete() # parts don't survive resolution # but produce a large result set tree = create_object(self.object_typeclass, key="tree", location=self.char1.location) axe = create_object(self.object_typeclass, key="axe", location=self.char1.location) sweat = create_object(self.object_typeclass, key="sweat", location=self.char1.location) dull_axe = create_object( self.object_typeclass, key="dull axe", location=self.char1.location ) timber = create_object(self.object_typeclass, key="timber", location=self.char1.location) log = create_object(self.object_typeclass, key="log", location=self.char1.location) parts = ["tree", "axe"] results = (["sweat"] * 10) + ["dull axe"] + (["timber"] * 20) + (["log"] * 50) recipe_dbref = self._good_recipe("lumberjack", parts, results, and_destroy_it=False) _destroy_objs_in_room(set(parts + results)) sps = sorted(parts) expected = {key: len(list(grp)) for key, grp in itertools.groupby(sps)} expected.update({r: 0 for r in set(results)}) self._arm(recipe_dbref, "lumberjack", parts) self._check_room_contents(expected) self._use(",".join(parts), "You are a Genius") srs = sorted(set(results)) expected = {(key, len(list(grp))) for key, grp in itertools.groupby(srs)} expected.update({p: 0 for p in set(parts)}) self._check_room_contents(expected) # parts also appear in results # causing a new puzzle to be armed 'automatically' # i.e. the puzzle is self-sustaining hole = create_object(self.object_typeclass, key="hole", location=self.char1.location) shovel = create_object(self.object_typeclass, key="shovel", location=self.char1.location) dirt = create_object(self.object_typeclass, key="dirt", location=self.char1.location) parts = ["shovel", "hole"] results = ["dirt", "hole", "shovel"] recipe_dbref = self._good_recipe( "digger", parts, results, and_destroy_it=False, expected_count=2 ) _destroy_objs_in_room(set(parts + results)) nresolutions = 0 sps = sorted(set(parts)) expected = {key: len(list(grp)) for key, grp in itertools.groupby(sps)} expected.update({"dirt": nresolutions}) self._arm(recipe_dbref, "digger", parts) self._check_room_contents(expected) for i in range(10): self._use(",".join(parts), "You are a Genius") nresolutions += 1 expected.update({"dirt": nresolutions}) self._check_room_contents(expected) # Uppercase puzzle name balloon = create_object(self.object_typeclass, key="Balloon", location=self.char1.location) parts = ["Balloon"] results = ["Balloon"] recipe_dbref = self._good_recipe( "boom!!!", parts, results, and_destroy_it=False, expected_count=3 ) _destroy_objs_in_room(set(parts + results)) sps = sorted(parts) expected = {key: len(list(grp)) for key, grp in itertools.groupby(sps)} self._arm(recipe_dbref, "boom!!!", parts) self._check_room_contents(expected) self._use(",".join(parts), "You are a Genius") srs = sorted(set(results)) expected = {(key, len(list(grp))) for key, grp in itertools.groupby(srs)} self._check_room_contents(expected)
[docs] def test_e2e_accumulative(self): flashlight = create_object( self.object_typeclass, key="flashlight", location=self.char1.location ) flashlight_w_1 = create_object( self.object_typeclass, key="flashlight-w-1", location=self.char1.location ) flashlight_w_2 = create_object( self.object_typeclass, key="flashlight-w-2", location=self.char1.location ) flashlight_w_3 = create_object( self.object_typeclass, key="flashlight-w-3", location=self.char1.location ) battery = create_object(self.object_typeclass, key="battery", location=self.char1.location) battery.tags.add("flashlight-1", category=puzzles._PUZZLES_TAG_CATEGORY) battery.tags.add("flashlight-2", category=puzzles._PUZZLES_TAG_CATEGORY) battery.tags.add("flashlight-3", category=puzzles._PUZZLES_TAG_CATEGORY) # TODO: instead of tagging each flashlight, # arm and resolve each puzzle in order so they all # are tagged correctly # it will be necessary to add/remove parts/results because # each battery is supposed to be consumed during resolution # as the new flashlight has one more battery than before flashlight_w_1.tags.add("flashlight-2", category=puzzles._PUZZLES_TAG_CATEGORY) flashlight_w_2.tags.add("flashlight-3", category=puzzles._PUZZLES_TAG_CATEGORY) recipe_fl1_dbref = self._good_recipe( "flashlight-1", ["flashlight", "battery"], ["flashlight-w-1"], and_destroy_it=False, expected_count=1, ) recipe_fl2_dbref = self._good_recipe( "flashlight-2", ["flashlight-w-1", "battery"], ["flashlight-w-2"], and_destroy_it=False, expected_count=2, ) recipe_fl3_dbref = self._good_recipe( "flashlight-3", ["flashlight-w-2", "battery"], ["flashlight-w-3"], and_destroy_it=False, expected_count=3, ) # delete protoparts for obj in [battery, flashlight, flashlight_w_1, flashlight_w_2, flashlight_w_3]: obj.delete() def _group_parts(parts, excluding=set()): group = dict() dbrefs = dict() for o in self.room1.contents: if o.key in parts and o.dbref not in excluding: if o.key not in group: group[o.key] = [] group[o.key].append(o.dbref) dbrefs[o.dbref] = o return group, dbrefs # arm each puzzle and group its parts self._arm(recipe_fl1_dbref, "flashlight-1", ["battery", "flashlight"]) fl1_parts, fl1_dbrefs = _group_parts(["battery", "flashlight"]) self._arm(recipe_fl2_dbref, "flashlight-2", ["battery", "flashlight-w-1"]) fl2_parts, fl2_dbrefs = _group_parts( ["battery", "flashlight-w-1"], excluding=list(fl1_dbrefs.keys()) ) self._arm(recipe_fl3_dbref, "flashlight-3", ["battery", "flashlight-w-2"]) fl3_parts, fl3_dbrefs = _group_parts( ["battery", "flashlight-w-2"], excluding=set(list(fl1_dbrefs.keys()) + list(fl2_dbrefs.keys())), ) self._check_room_contents( { "battery": 3, "flashlight": 1, "flashlight-w-1": 1, "flashlight-w-2": 1, "flashlight-w-3": 0, } ) # all batteries have identical protodefs battery_1 = fl1_dbrefs[fl1_parts["battery"][0]] battery_2 = fl2_dbrefs[fl2_parts["battery"][0]] battery_3 = fl3_dbrefs[fl3_parts["battery"][0]] protodef_battery_1 = puzzles.proto_def(battery_1, with_tags=False) del protodef_battery_1["prototype_key"] protodef_battery_2 = puzzles.proto_def(battery_2, with_tags=False) del protodef_battery_2["prototype_key"] protodef_battery_3 = puzzles.proto_def(battery_3, with_tags=False) del protodef_battery_3["prototype_key"] assert protodef_battery_1 == protodef_battery_2 == protodef_battery_3 # each battery can be used in every other puzzle b1_parts_dict, b1_puzzlenames, b1_protodefs = puzzles._lookups_parts_puzzlenames_protodefs( [battery_1] ) _puzzles = puzzles._puzzles_by_names(b1_puzzlenames.keys()) assert set(["flashlight-1", "flashlight-2", "flashlight-3"]) == set( [p.db.puzzle_name for p in _puzzles] ) matched_puzzles = puzzles._matching_puzzles(_puzzles, b1_puzzlenames, b1_protodefs) assert 0 == len(matched_puzzles) b2_parts_dict, b2_puzzlenames, b2_protodefs = puzzles._lookups_parts_puzzlenames_protodefs( [battery_2] ) _puzzles = puzzles._puzzles_by_names(b2_puzzlenames.keys()) assert set(["flashlight-1", "flashlight-2", "flashlight-3"]) == set( [p.db.puzzle_name for p in _puzzles] ) matched_puzzles = puzzles._matching_puzzles(_puzzles, b2_puzzlenames, b2_protodefs) assert 0 == len(matched_puzzles) b3_parts_dict, b3_puzzlenames, b3_protodefs = puzzles._lookups_parts_puzzlenames_protodefs( [battery_3] ) _puzzles = puzzles._puzzles_by_names(b3_puzzlenames.keys()) assert set(["flashlight-1", "flashlight-2", "flashlight-3"]) == set( [p.db.puzzle_name for p in _puzzles] ) matched_puzzles = puzzles._matching_puzzles(_puzzles, b3_puzzlenames, b3_protodefs) assert 0 == len(matched_puzzles) assert battery_1 == list(b1_parts_dict.values())[0] assert battery_2 == list(b2_parts_dict.values())[0] assert battery_3 == list(b3_parts_dict.values())[0] assert b1_puzzlenames.keys() == b2_puzzlenames.keys() == b3_puzzlenames.keys() for puzzle_name in ["flashlight-1", "flashlight-2", "flashlight-3"]: assert puzzle_name in b1_puzzlenames assert puzzle_name in b2_puzzlenames assert puzzle_name in b3_puzzlenames assert ( list(b1_protodefs.values())[0] == list(b2_protodefs.values())[0] == list(b3_protodefs.values())[0] == protodef_battery_1 == protodef_battery_2 == protodef_battery_3 ) # all flashlights have similar protodefs except their key flashlight_1 = fl1_dbrefs[fl1_parts["flashlight"][0]] flashlight_2 = fl2_dbrefs[fl2_parts["flashlight-w-1"][0]] flashlight_3 = fl3_dbrefs[fl3_parts["flashlight-w-2"][0]] protodef_flashlight_1 = puzzles.proto_def(flashlight_1, with_tags=False) del protodef_flashlight_1["prototype_key"] assert protodef_flashlight_1["key"] == "flashlight" del protodef_flashlight_1["key"] protodef_flashlight_2 = puzzles.proto_def(flashlight_2, with_tags=False) del protodef_flashlight_2["prototype_key"] assert protodef_flashlight_2["key"] == "flashlight-w-1" del protodef_flashlight_2["key"] protodef_flashlight_3 = puzzles.proto_def(flashlight_3, with_tags=False) del protodef_flashlight_3["prototype_key"] assert protodef_flashlight_3["key"] == "flashlight-w-2" del protodef_flashlight_3["key"] assert protodef_flashlight_1 == protodef_flashlight_2 == protodef_flashlight_3 # each flashlight can only be used in its own puzzle f1_parts_dict, f1_puzzlenames, f1_protodefs = puzzles._lookups_parts_puzzlenames_protodefs( [flashlight_1] ) _puzzles = puzzles._puzzles_by_names(f1_puzzlenames.keys()) assert set(["flashlight-1"]) == set([p.db.puzzle_name for p in _puzzles]) matched_puzzles = puzzles._matching_puzzles(_puzzles, f1_puzzlenames, f1_protodefs) assert 0 == len(matched_puzzles) f2_parts_dict, f2_puzzlenames, f2_protodefs = puzzles._lookups_parts_puzzlenames_protodefs( [flashlight_2] ) _puzzles = puzzles._puzzles_by_names(f2_puzzlenames.keys()) assert set(["flashlight-2"]) == set([p.db.puzzle_name for p in _puzzles]) matched_puzzles = puzzles._matching_puzzles(_puzzles, f2_puzzlenames, f2_protodefs) assert 0 == len(matched_puzzles) f3_parts_dict, f3_puzzlenames, f3_protodefs = puzzles._lookups_parts_puzzlenames_protodefs( [flashlight_3] ) _puzzles = puzzles._puzzles_by_names(f3_puzzlenames.keys()) assert set(["flashlight-3"]) == set([p.db.puzzle_name for p in _puzzles]) matched_puzzles = puzzles._matching_puzzles(_puzzles, f3_puzzlenames, f3_protodefs) assert 0 == len(matched_puzzles) assert flashlight_1 == list(f1_parts_dict.values())[0] assert flashlight_2 == list(f2_parts_dict.values())[0] assert flashlight_3 == list(f3_parts_dict.values())[0] for puzzle_name in set( list(f1_puzzlenames.keys()) + list(f2_puzzlenames.keys()) + list(f3_puzzlenames.keys()) ): assert puzzle_name in ["flashlight-1", "flashlight-2", "flashlight-3", "puzzle_member"] protodef_flashlight_1["key"] = "flashlight" assert list(f1_protodefs.values())[0] == protodef_flashlight_1 protodef_flashlight_2["key"] = "flashlight-w-1" assert list(f2_protodefs.values())[0] == protodef_flashlight_2 protodef_flashlight_3["key"] = "flashlight-w-2" assert list(f3_protodefs.values())[0] == protodef_flashlight_3 # each battery can be matched with every other flashlight # to potentially resolve each puzzle for batt in [battery_1, battery_2, battery_3]: parts_dict, puzzlenames, protodefs = puzzles._lookups_parts_puzzlenames_protodefs( [batt, flashlight_1] ) assert set([batt.dbref, flashlight_1.dbref]) == set(puzzlenames["flashlight-1"]) assert set([batt.dbref]) == set(puzzlenames["flashlight-2"]) assert set([batt.dbref]) == set(puzzlenames["flashlight-3"]) _puzzles = puzzles._puzzles_by_names(puzzlenames.keys()) assert set(["flashlight-1", "flashlight-2", "flashlight-3"]) == set( [p.db.puzzle_name for p in _puzzles] ) matched_puzzles = puzzles._matching_puzzles(_puzzles, puzzlenames, protodefs) assert 1 == len(matched_puzzles) parts_dict, puzzlenames, protodefs = puzzles._lookups_parts_puzzlenames_protodefs( [batt, flashlight_2] ) assert set([batt.dbref]) == set(puzzlenames["flashlight-1"]) assert set([batt.dbref, flashlight_2.dbref]) == set(puzzlenames["flashlight-2"]) assert set([batt.dbref]) == set(puzzlenames["flashlight-3"]) _puzzles = puzzles._puzzles_by_names(puzzlenames.keys()) assert set(["flashlight-1", "flashlight-2", "flashlight-3"]) == set( [p.db.puzzle_name for p in _puzzles] ) matched_puzzles = puzzles._matching_puzzles(_puzzles, puzzlenames, protodefs) assert 1 == len(matched_puzzles) parts_dict, puzzlenames, protodefs = puzzles._lookups_parts_puzzlenames_protodefs( [batt, flashlight_3] ) assert set([batt.dbref]) == set(puzzlenames["flashlight-1"]) assert set([batt.dbref]) == set(puzzlenames["flashlight-2"]) assert set([batt.dbref, flashlight_3.dbref]) == set(puzzlenames["flashlight-3"]) _puzzles = puzzles._puzzles_by_names(puzzlenames.keys()) assert set(["flashlight-1", "flashlight-2", "flashlight-3"]) == set( [p.db.puzzle_name for p in _puzzles] ) matched_puzzles = puzzles._matching_puzzles(_puzzles, puzzlenames, protodefs) assert 1 == len(matched_puzzles) # delete all parts for part in ( list(fl1_dbrefs.values()) + list(fl2_dbrefs.values()) + list(fl3_dbrefs.values()) ): part.delete() self._check_room_contents( { "battery": 0, "flashlight": 0, "flashlight-w-1": 0, "flashlight-w-2": 0, "flashlight-w-3": 0, } ) # arm first puzzle 3 times and group its parts so we can solve # all puzzles with the parts from the 1st armed for i in range(3): self._arm(recipe_fl1_dbref, "flashlight-1", ["battery", "flashlight"]) fl1_parts, fl1_dbrefs = _group_parts(["battery", "flashlight"]) # delete the 2 extra flashlights so we can start solving for flashlight_dbref in fl1_parts["flashlight"][1:]: fl1_dbrefs[flashlight_dbref].delete() self._check_room_contents( { "battery": 3, "flashlight": 1, "flashlight-w-1": 0, "flashlight-w-2": 0, "flashlight-w-3": 0, } ) self._use("battery-1, flashlight", "You are a Genius") self._check_room_contents( { "battery": 2, "flashlight": 0, "flashlight-w-1": 1, "flashlight-w-2": 0, "flashlight-w-3": 0, } ) self._use("battery-1, flashlight-w-1", "You are a Genius") self._check_room_contents( { "battery": 1, "flashlight": 0, "flashlight-w-1": 0, "flashlight-w-2": 1, "flashlight-w-3": 0, } ) self._use("battery, flashlight-w-2", "You are a Genius") self._check_room_contents( { "battery": 0, "flashlight": 0, "flashlight-w-1": 0, "flashlight-w-2": 0, "flashlight-w-3": 1, } )
[docs] def test_e2e_interchangeable_parts_and_results(self): # Parts and Results can be used in multiple puzzles egg = create_object(self.object_typeclass, key="egg", location=self.char1.location) flour = create_object(self.object_typeclass, key="flour", location=self.char1.location) boiling_water = create_object( self.object_typeclass, key="boiling water", location=self.char1.location ) boiled_egg = create_object( self.object_typeclass, key="boiled egg", location=self.char1.location ) dough = create_object(self.object_typeclass, key="dough", location=self.char1.location) pasta = create_object(self.object_typeclass, key="pasta", location=self.char1.location) # Three recipes: # 1. breakfast: egg + boiling water = boiled egg & boiling water # 2. dough: egg + flour = dough # 3. entree: dough + boiling water = pasta & boiling water # tag interchangeable parts according to their puzzles' name egg.tags.add("breakfast", category=puzzles._PUZZLES_TAG_CATEGORY) egg.tags.add("dough", category=puzzles._PUZZLES_TAG_CATEGORY) dough.tags.add("entree", category=puzzles._PUZZLES_TAG_CATEGORY) boiling_water.tags.add("breakfast", category=puzzles._PUZZLES_TAG_CATEGORY) boiling_water.tags.add("entree", category=puzzles._PUZZLES_TAG_CATEGORY) # create recipes recipe1_dbref = self._good_recipe( "breakfast", ["egg", "boiling water"], ["boiled egg", "boiling water"], and_destroy_it=False, ) recipe2_dbref = self._good_recipe( "dough", ["egg", "flour"], ["dough"], and_destroy_it=False, expected_count=2 ) recipe3_dbref = self._good_recipe( "entree", ["dough", "boiling water"], ["pasta", "boiling water"], and_destroy_it=False, expected_count=3, ) # delete protoparts for obj in [egg, flour, boiling_water, boiled_egg, dough, pasta]: obj.delete() # arm each puzzle and group its parts def _group_parts(parts, excluding=set()): group = dict() dbrefs = dict() for o in self.room1.contents: if o.key in parts and o.dbref not in excluding: if o.key not in group: group[o.key] = [] group[o.key].append(o.dbref) dbrefs[o.dbref] = o return group, dbrefs self._arm(recipe1_dbref, "breakfast", ["egg", "boiling water"]) breakfast_parts, breakfast_dbrefs = _group_parts(["egg", "boiling water"]) self._arm(recipe2_dbref, "dough", ["egg", "flour"]) dough_parts, dough_dbrefs = _group_parts( ["egg", "flour"], excluding=list(breakfast_dbrefs.keys()) ) self._arm(recipe3_dbref, "entree", ["dough", "boiling water"]) entree_parts, entree_dbrefs = _group_parts( ["dough", "boiling water"], excluding=set(list(breakfast_dbrefs.keys()) + list(dough_dbrefs.keys())), ) # create a box so we can put all objects in # so that they can't be found during puzzle resolution self.box = create_object(self.object_typeclass, key="box", location=self.char1.location) def _box_all(): # print "boxing all\n", "-"*20 for o in self.room1.contents: if o not in [self.char1, self.char2, self.exit, self.obj1, self.obj2, self.box]: o.location = self.box # print o.key, o.dbref, "boxed" else: # print "skipped", o.key, o.dbref pass def _unbox(dbrefs): # print "unboxing", dbrefs, "\n", "-"*20 for o in self.box.contents: if o.dbref in dbrefs: o.location = self.room1 # print "unboxed", o.key, o.dbref # solve dough puzzle using breakfast's egg # and dough's flour. A new dough will be created _box_all() _unbox(breakfast_parts.pop("egg") + dough_parts.pop("flour")) self._use("egg, flour", "You are a Genius") # solve entree puzzle with newly created dough # and breakfast's boiling water. A new # boiling water and pasta will be created _unbox(breakfast_parts.pop("boiling water")) self._use("boiling water, dough", "You are a Genius") # solve breakfast puzzle with dough's egg # and newly created boiling water. A new # boiling water and boiled egg will be created _unbox(dough_parts.pop("egg")) self._use("boiling water, egg", "You are a Genius") # solve entree puzzle using entree's dough # and newly created boiling water. A new # boiling water and pasta will be created _unbox(entree_parts.pop("dough")) self._use("boiling water, dough", "You are a Genius") self._check_room_contents({"boiling water": 1, "pasta": 2, "boiled egg": 1})