"""
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})