975 lines
40 KiB
Python
975 lines
40 KiB
Python
"""
|
|
Testing puzzles.
|
|
|
|
"""
|
|
|
|
# Test of the Puzzles module
|
|
|
|
import re
|
|
import itertools
|
|
from mock import Mock
|
|
from evennia.utils import search
|
|
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
|
from evennia.utils.create import create_object
|
|
from . import puzzles
|
|
|
|
|
|
class TestPuzzles(BaseEvenniaCommandTest):
|
|
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)
|
|
|
|
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
|
|
|
|
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()
|
|
|
|
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
|
|
|
|
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)
|
|
|
|
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()
|
|
|
|
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-3", "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})
|
|
|
|
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,
|
|
)
|
|
|
|
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)
|
|
|
|
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,
|
|
}
|
|
)
|
|
|
|
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})
|