summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDarshan Shaligram <dshaligram@users.sourceforge.net>2010-01-05 01:31:26 +0530
committerDarshan Shaligram <dshaligram@users.sourceforge.net>2010-01-05 01:34:27 +0530
commited85466e0202b396bb8d5469d2babd2a24664afc (patch)
treedc8803ffada803a7910d1cea36d96361c916f59d
parente88370a40d2cad80bf114500d42291f11dcff5fb (diff)
downloadcrawl-ref-ed85466e0202b396bb8d5469d2babd2a24664afc.tar.gz
crawl-ref-ed85466e0202b396bb8d5469d2babd2a24664afc.zip
Fix Shoal:$ generation bugs: stairs could be surrounded by deep water, hut entrances could be blocked by rock.
shoal-hut.lua test generates 1k Shoal:$ levels and verifies that the levels pass sanity tests.
-rw-r--r--crawl-ref/source/cluautil.cc8
-rw-r--r--crawl-ref/source/cluautil.h8
-rw-r--r--crawl-ref/source/ctest.cc8
-rw-r--r--crawl-ref/source/dat/clua/dungeon.lua33
-rw-r--r--crawl-ref/source/dat/clua/iter.lua17
-rw-r--r--crawl-ref/source/dat/clua/test.lua23
-rw-r--r--crawl-ref/source/dgn-shoals.cc49
-rw-r--r--crawl-ref/source/dlua.cc10
-rw-r--r--crawl-ref/source/dungeon.cc10
-rw-r--r--crawl-ref/source/dungeon.h2
-rw-r--r--crawl-ref/source/l_dgn.cc124
-rw-r--r--crawl-ref/source/l_libs.h2
-rw-r--r--crawl-ref/source/main.cc1
-rw-r--r--crawl-ref/source/test/shoal-hut.lua98
14 files changed, 350 insertions, 43 deletions
diff --git a/crawl-ref/source/cluautil.cc b/crawl-ref/source/cluautil.cc
index a9cb1cec7b..d774b3318a 100644
--- a/crawl-ref/source/cluautil.cc
+++ b/crawl-ref/source/cluautil.cc
@@ -46,12 +46,6 @@ void clua_push_map(lua_State *ls, map_def *map)
*mapref = map;
}
-void clua_push_coord(lua_State *ls, const coord_def &c)
-{
- lua_pushnumber(ls, c.x);
- lua_pushnumber(ls, c.y);
-}
-
void clua_push_dgn_event(lua_State *ls, const dgn_event *devent)
{
const dgn_event **de =
@@ -110,6 +104,8 @@ int clua_stringtable(lua_State *ls, const std::vector<std::string> &s)
return clua_gentable(ls, s, clua_pushcxxstring);
}
+// Pushes a coord_def as a dgn.point Lua object. Note that this is quite
+// different from dlua_pushcoord.
int clua_pushpoint(lua_State *ls, const coord_def &pos)
{
lua_pushnumber(ls, pos.x);
diff --git a/crawl-ref/source/cluautil.h b/crawl-ref/source/cluautil.h
index 8b3a91937b..60f8f73e14 100644
--- a/crawl-ref/source/cluautil.h
+++ b/crawl-ref/source/cluautil.h
@@ -104,10 +104,14 @@ inline void dlua_push_userdata(lua_State *ls, T udata, const char *meta)
}
template <class T>
-static void dlua_push_object_type(lua_State *ls, const char *meta, const T &data)
+static int dlua_push_object_type(lua_State *ls, const char *meta, const T &data)
{
T **ptr = clua_new_userdata<T*>(ls, meta);
- *ptr = new T(data);
+ if (ptr)
+ *ptr = new T(data);
+ else
+ lua_pushnil(ls);
+ return (1);
}
/*
diff --git a/crawl-ref/source/ctest.cc b/crawl-ref/source/ctest.cc
index f8a3f74001..cfff5edecb 100644
--- a/crawl-ref/source/ctest.cc
+++ b/crawl-ref/source/ctest.cc
@@ -75,6 +75,7 @@ namespace crawl_tests
{
lua_stack_cleaner clean(dlua);
luaL_openlib(dlua, "crawl", crawl_test_lib, 0);
+ dlua.execfile("clua/test.lua", true, true);
}
bool is_test_selected(const std::string &testname)
@@ -119,6 +120,13 @@ namespace crawl_tests
init_test_bindings();
+ if (crawl_state.tests_selected.empty()
+ || (crawl_state.tests_selected[0].find("makeitem") !=
+ std::string::npos))
+ {
+ makeitem_tests();
+ }
+
// Get a list of Lua files in test. Order of execution of
// tests should be irrelevant.
const std::vector<std::string> tests(
diff --git a/crawl-ref/source/dat/clua/dungeon.lua b/crawl-ref/source/dat/clua/dungeon.lua
index 0a11bdd330..4359733855 100644
--- a/crawl-ref/source/dat/clua/dungeon.lua
+++ b/crawl-ref/source/dat/clua/dungeon.lua
@@ -182,6 +182,16 @@ function dgn.fnum_map(map)
return fnmap
end
+-- Given a list of feature names, returns a dictionary mapping feature
+-- numbers to true.
+function dgn.feature_number_set(feature_names)
+ local dict = { }
+ for _, name in ipairs(feature_names) do
+ dict[dgn.fnum(name)] = true
+ end
+ return dict
+end
+
-- Replaces all features matching
function dgn.replace_feat(rmap)
local cmap = dgn.fnum_map(rmap)
@@ -197,6 +207,27 @@ function dgn.replace_feat(rmap)
end
end
+function dgn.feature_set_fn(...)
+ local chosen_features = dgn.feature_number_set({ ... })
+ return function (fnum)
+ return chosen_features[fnum]
+ end
+end
+
+-- Finds all points in the map satisfying the supplied predicate.
+function dgn.find_points(predicate)
+ local points = { }
+ for x = 0, dgn.GXM - 1 do
+ for y = 0, dgn.GYM - 1 do
+ local p = dgn.point(x, y)
+ if predicate(p) then
+ table.insert(points, p)
+ end
+ end
+ end
+ return points
+end
+
-- Returns a function that returns true if the point specified is
-- travel-passable and is not one of the features specified.
function dgn.passable_excluding(...)
@@ -395,7 +426,7 @@ dgn.good_scrolls = [[
w:10 scroll of acquirement / scroll of acquirement q:2 w:4 /
scroll of acquirement q:3 w:1/
w:5 scroll of vorpalise weapon /
- w:5 scroll of immolation /
+ w:5 scroll of immolation /
w:5 scroll of vulnerability
]]
diff --git a/crawl-ref/source/dat/clua/iter.lua b/crawl-ref/source/dat/clua/iter.lua
index d9496bdb3c..e4d1409781 100644
--- a/crawl-ref/source/dat/clua/iter.lua
+++ b/crawl-ref/source/dat/clua/iter.lua
@@ -136,6 +136,11 @@ function iter.rect_iterator(top_corner, bottom_corner, filter, rvi)
return iter.rectangle_iterator:new(top_corner, bottom_corner, filter, rvi)
end
+function iter.rect_size_iterator(top_corner, size, filter, rvi)
+ return iter.rect_iterator(top_corner, top_corner + size - dgn.point(1, 1),
+ filter, rvi)
+end
+
function iter.mons_rect_iterator (top_corner, bottom_corner, filter)
return iter.rect_iterator(top_corner, bottom_corner, iter.monster_filter(filter), true)
end
@@ -212,7 +217,7 @@ function iter.adjacent_iterator (ic, filter, center, rvi)
local function check_adj (point)
local _x, _y = point:xy()
- local npoint = nil
+ local npoint = point
if filter ~= nil then
if rvi then
@@ -246,8 +251,12 @@ function iter.mons_adjacent_iterator (ic, filter, center)
return iter.adjacent_iterator(ic, iter.monster_filter(filter), center, true)
end
+function iter.adjacent_iterator_to(center, include_center, filter)
+ return iter.adjacent_iterator(include_center, filter, center, true)
+end
+
-------------------------------------------------------------------------------
--- circle_iterator
+-- Circle_iterator
-------------------------------------------------------------------------------
function iter.circle_iterator (radius, ic, filter, center, rvi)
@@ -449,7 +458,7 @@ end
function iter.point_iterator:check_filter(point)
if self.filter ~= nil then
if self.filter(point) then
- if self.rvi then
+ if self.rvi then
return self.filter(point)
else
return point
@@ -513,7 +522,7 @@ end
function iter.invent_iterator:check_filter(item)
if self.filter ~= nil then
if self.filter(item) then
- if self.rvi then
+ if self.rvi then
return self.filter(item)
else
return item
diff --git a/crawl-ref/source/dat/clua/test.lua b/crawl-ref/source/dat/clua/test.lua
new file mode 100644
index 0000000000..404b2e1d1f
--- /dev/null
+++ b/crawl-ref/source/dat/clua/test.lua
@@ -0,0 +1,23 @@
+-- Support code used primarily for tests. This is loaded only when running
+-- tests, not during normal Crawl execution.
+
+util.namespace('test')
+
+test.FAILMAP = 'level-fail.map'
+
+function test.map_assert(condition, message)
+ if not condition then
+ debug.dump_map(test.FAILMAP)
+ assert(false, message .. " (map dumped to " .. test.FAILMAP .. ")")
+ end
+ return condition
+end
+
+function test.regenerate_level(place)
+ if place then
+ debug.goto_place(place)
+ end
+ debug.flush_map_memory()
+ dgn.reset_level()
+ debug.generate_level()
+end \ No newline at end of file
diff --git a/crawl-ref/source/dgn-shoals.cc b/crawl-ref/source/dgn-shoals.cc
index 428a6cf90e..3a263cccf3 100644
--- a/crawl-ref/source/dgn-shoals.cc
+++ b/crawl-ref/source/dgn-shoals.cc
@@ -398,15 +398,36 @@ static void _shoals_furniture(int margin)
unwind_var<dungeon_feature_set> vault_exc(dgn_Vault_Excavatable_Feats);
dgn_Vault_Excavatable_Feats.insert(DNGN_STONE_WALL);
- const coord_def c = _pick_shoals_island();
- // Put all the stairs on one island.
- grd(c) = DNGN_STONE_STAIRS_UP_I;
- grd(c + coord_def(1, 0)) = DNGN_STONE_STAIRS_UP_II;
- grd(c - coord_def(1, 0)) = DNGN_STONE_STAIRS_UP_III;
- dgn_excavate(c, dgn_random_direction());
-
- const coord_def p = _pick_shoals_island_distant_from(c);
- const map_def *vault = random_map_for_tag("shoal_rune");
+ int stair_tries = 50;
+ bool did_place_stairs = false;
+ coord_def stair_place;
+ while (stair_tries-- > 0)
+ {
+ stair_place = _pick_shoals_island();
+ if (grd(stair_place) == DNGN_FLOOR)
+ {
+ // Put all the stairs on one island.
+ grd(stair_place) = DNGN_STONE_STAIRS_UP_I;
+ grd(stair_place + coord_def(1, 0)) = DNGN_STONE_STAIRS_UP_II;
+ grd(stair_place - coord_def(1, 0)) = DNGN_STONE_STAIRS_UP_III;
+ did_place_stairs = true;
+ break;
+ }
+ else
+ {
+ _shoals_islands.push_back(stair_place);
+ }
+ }
+
+ if (!did_place_stairs)
+ {
+ dgn_veto_level();
+ return;
+ }
+
+ const coord_def p = _pick_shoals_island_distant_from(stair_place);
+ const char *SHOAL_RUNE_HUT = "shoal_rune";
+ const map_def *vault = random_map_for_tag(SHOAL_RUNE_HUT);
{
// Place the rune
dgn_map_parameters mp("rune");
@@ -425,7 +446,15 @@ static void _shoals_furniture(int margin)
vault = random_map_for_tag("shoal_rune");
while (!vault && --tries > 0);
if (vault)
- dgn_place_map(vault, false, true, _pick_shoals_island(), 0);
+ dgn_place_map(vault, false, false, _pick_shoals_island(), 0);
+ }
+
+ // Fixup pass to connect vaults.
+ for (int i = 0, size = Level_Vaults.size(); i < size; ++i)
+ {
+ vault_placement &vp(Level_Vaults[i]);
+ if (vp.map.has_tag(SHOAL_RUNE_HUT))
+ dgn_dig_vault_loose(vp);
}
}
else
diff --git a/crawl-ref/source/dlua.cc b/crawl-ref/source/dlua.cc
index 218819a4d5..5583fb3c1c 100644
--- a/crawl-ref/source/dlua.cc
+++ b/crawl-ref/source/dlua.cc
@@ -273,16 +273,8 @@ void init_dungeon_lua()
dluaopen_mapgrd(dlua);
dluaopen_monsters(dlua);
dluaopen_you(dlua);
+ dluaopen_dgn(dlua);
- luaL_openlib(dlua, "dgn", dgn_dlib, 0);
- luaL_openlib(dlua, "dgn", dgn_build_dlib, 0);
- luaL_openlib(dlua, "dgn", dgn_event_dlib, 0);
- luaL_openlib(dlua, "dgn", dgn_grid_dlib, 0);
- luaL_openlib(dlua, "dgn", dgn_item_dlib, 0);
- luaL_openlib(dlua, "dgn", dgn_level_dlib, 0);
- luaL_openlib(dlua, "dgn", dgn_mons_dlib, 0);
- luaL_openlib(dlua, "dgn", dgn_subvault_dlib, 0);
- luaL_openlib(dlua, "dgn", dgn_tile_dlib, 0);
luaL_openlib(dlua, "feat", feat_dlib, 0);
luaL_openlib(dlua, "spells", spells_dlib, 0);
luaL_openlib(dlua, "debug", debug_dlib, 0);
diff --git a/crawl-ref/source/dungeon.cc b/crawl-ref/source/dungeon.cc
index d432d479ae..71dffc3610 100644
--- a/crawl-ref/source/dungeon.cc
+++ b/crawl-ref/source/dungeon.cc
@@ -978,6 +978,11 @@ static void _dgn_init_vault_excavatable_feats()
dgn_Vault_Excavatable_Feats.insert(DNGN_ROCK_WALL);
}
+void dgn_veto_level()
+{
+ dgn_level_vetoed = true;
+}
+
void dgn_reset_level()
{
dgn_level_vetoed = false;
@@ -4204,6 +4209,11 @@ bool dgn_place_map(const map_def *mdef,
return (did_map);
}
+void dgn_dig_vault_loose(vault_placement &vp)
+{
+ _dig_vault_loose(vp, vp.exits);
+}
+
// Places a vault somewhere in an already built level if possible.
// Returns true if the vault was successfully placed.
static bool _build_secondary_vault(int level_number, const map_def *vault,
diff --git a/crawl-ref/source/dungeon.h b/crawl-ref/source/dungeon.h
index 9eb1cf0eb2..0bdbac5b50 100644
--- a/crawl-ref/source/dungeon.h
+++ b/crawl-ref/source/dungeon.h
@@ -176,6 +176,7 @@ void read_level_connectivity(reader &th);
void write_level_connectivity(writer &th);
bool builder(int level_number, int level_type);
+void dgn_veto_level();
void dgn_flush_map_memory();
@@ -276,6 +277,7 @@ void dgn_replace_area(int sx, int sy, int ex, int ey,
unsigned mmask = 0, bool needs_update = false);
void dgn_excavate(coord_def dig_at, coord_def dig_dir);
+void dgn_dig_vault_loose(vault_placement &vp);
coord_def dgn_random_direction();
bool dgn_ensure_vault_placed(bool vault_success,
diff --git a/crawl-ref/source/l_dgn.cc b/crawl-ref/source/l_dgn.cc
index e0cb80bf33..918c66a452 100644
--- a/crawl-ref/source/l_dgn.cc
+++ b/crawl-ref/source/l_dgn.cc
@@ -24,6 +24,8 @@
#endif
#include "view.h"
+const char *VAULT_PLACEMENT_METATABLE = "crawl.vault-placement";
+
///////////////////////////////////////////////////////////////////////////
// Lua dungeon bindings (in the dgn table).
@@ -653,7 +655,7 @@ static int dgn_has_exit_from(lua_State *ls)
return dgn_map_pathfind(ls, 3, &map_flood_finder::has_exit_from);
}
-static void dlua_push_coord(lua_State *ls, const coord_def &c)
+static void dlua_push_coordinates(lua_State *ls, const coord_def &c)
{
lua_pushnumber(ls, c.x);
lua_pushnumber(ls, c.y);
@@ -665,7 +667,7 @@ static int dgn_gly_point(lua_State *ls)
coord_def c = map->find_first_glyph(*luaL_checkstring(ls, 2));
if (c.x != -1 && c.y != -1)
{
- dlua_push_coord(ls, c);
+ dlua_push_coordinates(ls, c);
return (2);
}
return (0);
@@ -677,7 +679,7 @@ static int dgn_gly_points(lua_State *ls)
std::vector<coord_def> cs = map->find_glyph(*luaL_checkstring(ls, 2));
for (int i = 0, size = cs.size(); i < size; ++i)
- dlua_push_coord(ls, cs[i]);
+ dlua_push_coordinates(ls, cs[i]);
return (cs.size() * 2);
}
@@ -1129,7 +1131,7 @@ static int dgn_random_walk(lua_State *ls)
dist_left -= (dir % 2 == 0) ? 1.0 : SQRT_2;
}
- dlua_push_coord(ls, pos);
+ dlua_push_coordinates(ls, pos);
return (2);
}
@@ -1377,8 +1379,8 @@ static bool _lua_map_place_valid(const map_def &map,
// Push map, pos.x, pos.y, size.x, size.y
clua_push_map(ls, const_cast<map_def*>(&map));
- clua_push_coord(ls, c);
- clua_push_coord(ls, size);
+ dlua_push_coordinates(ls, c);
+ dlua_push_coordinates(ls, size);
const int err = lua_pcall(ls, 5, 1, 0);
@@ -1527,6 +1529,16 @@ LUAFN(_dgn_map_parameters)
return clua_stringtable(ls, map_parameters);
}
+int dgn_push_vault_placement(lua_State *ls, const vault_placement &vp)
+{
+ return dlua_push_object_type(ls, VAULT_PLACEMENT_METATABLE, vp);
+}
+
+LUAFN(_dgn_maps_used_here)
+{
+ return clua_gentable(ls, Level_Vaults, dgn_push_vault_placement);
+}
+
LUAFN(_dgn_find_marker_position_by_prop)
{
const char *prop = luaL_checkstring(ls, 1);
@@ -1534,7 +1546,7 @@ LUAFN(_dgn_find_marker_position_by_prop)
lua_gettop(ls) >= 2 ? luaL_checkstring(ls, 2) : "");
const coord_def place = find_marker_position_by_prop(prop, value);
if (map_bounds(place))
- clua_push_coord(ls, place);
+ dlua_push_coordinates(ls, place);
else
{
lua_pushnil(ls);
@@ -1602,8 +1614,8 @@ LUAFN(dgn_get_special_room_info)
}
lua_pushnumber(ls, lua_special_room_level);
- dlua_push_coord(ls, lua_special_room_spec.tl);
- dlua_push_coord(ls, lua_special_room_spec.br);
+ dlua_push_coordinates(ls, lua_special_room_spec.tl);
+ dlua_push_coordinates(ls, lua_special_room_spec.br);
return (5);
}
@@ -1792,6 +1804,8 @@ const struct luaL_reg dgn_dlib[] =
{ "map_parameters", _dgn_map_parameters },
+{ "maps_used_here", _dgn_maps_used_here },
+
{ "find_marker_position_by_prop", _dgn_find_marker_position_by_prop },
{ "find_marker_positions_by_prop", _dgn_find_marker_positions_by_prop },
{ "find_markers_by_prop", _dgn_find_markers_by_prop },
@@ -1805,3 +1819,95 @@ const struct luaL_reg dgn_dlib[] =
{ NULL, NULL }
};
+
+#define VP(name) \
+ vault_placement &name = \
+ **clua_get_userdata<vault_placement*>( \
+ ls, VAULT_PLACEMENT_METATABLE)
+
+LUAFN(_vp_pos)
+{
+ VP(vp);
+ clua_pushpoint(ls, vp.pos);
+ return 1;
+}
+
+LUAFN(_vp_size)
+{
+ VP(vp);
+ clua_pushpoint(ls, vp.size);
+ return 1;
+}
+
+LUAFN(_vp_orient)
+{
+ VP(vp);
+ PLUARET(number, vp.orient)
+}
+
+LUAFN(_vp_map)
+{
+ VP(vp);
+ clua_push_map(ls, &vp.map);
+ return 1;
+}
+
+LUAFN(_vp_exits)
+{
+ VP(vp);
+ return clua_gentable(ls, vp.exits, clua_pushpoint);
+}
+
+LUAFN(_vp_level_number)
+{
+ VP(vp);
+ PLUARET(number, vp.level_number)
+}
+
+LUAFN(_vp_num_runes)
+{
+ VP(vp);
+ PLUARET(number, vp.num_runes)
+}
+
+LUAFN(_vp_rune_subst)
+{
+ VP(vp);
+ PLUARET(number, vp.rune_subst)
+}
+
+static const luaL_reg dgn_vaultplacement_ops[] =
+{
+ { "pos", _vp_pos },
+ { "size", _vp_size },
+ { "orient", _vp_orient },
+ { "map", _vp_map },
+ { "exits", _vp_exits },
+ { "level_number", _vp_level_number },
+ { "num_runes", _vp_num_runes },
+ { "rune_subst", _vp_rune_subst },
+ { NULL, NULL }
+};
+
+static void _dgn_register_metatables(lua_State *ls)
+{
+ clua_register_metatable(ls,
+ VAULT_PLACEMENT_METATABLE,
+ dgn_vaultplacement_ops,
+ lua_object_gc<vault_placement*>);
+}
+
+void dluaopen_dgn(lua_State *ls)
+{
+ _dgn_register_metatables(ls);
+
+ luaL_openlib(ls, "dgn", dgn_dlib, 0);
+ luaL_openlib(ls, "dgn", dgn_build_dlib, 0);
+ luaL_openlib(ls, "dgn", dgn_event_dlib, 0);
+ luaL_openlib(ls, "dgn", dgn_grid_dlib, 0);
+ luaL_openlib(ls, "dgn", dgn_item_dlib, 0);
+ luaL_openlib(ls, "dgn", dgn_level_dlib, 0);
+ luaL_openlib(ls, "dgn", dgn_mons_dlib, 0);
+ luaL_openlib(ls, "dgn", dgn_subvault_dlib, 0);
+ luaL_openlib(ls, "dgn", dgn_tile_dlib, 0);
+}
diff --git a/crawl-ref/source/l_libs.h b/crawl-ref/source/l_libs.h
index d09434be0c..d6008ed2c7 100644
--- a/crawl-ref/source/l_libs.h
+++ b/crawl-ref/source/l_libs.h
@@ -68,7 +68,7 @@ void dluaopen_file(lua_State *ls);
void dluaopen_mapgrd(lua_State *ls);
void dluaopen_monsters(lua_State *ls);
void dluaopen_you(lua_State *ls);
-
+void dluaopen_dgn(lua_State *ls);
/*
* Some shared helper functions.
diff --git a/crawl-ref/source/main.cc b/crawl-ref/source/main.cc
index 7de3206765..2854529db9 100644
--- a/crawl-ref/source/main.cc
+++ b/crawl-ref/source/main.cc
@@ -3792,7 +3792,6 @@ static bool _initialise(void)
tiles.initialise_items();
#endif
Options.show_more_prompt = false;
- makeitem_tests();
crawl_tests::run_tests(true);
// Superfluous, just to make it clear that this is the end of
// the line.
diff --git a/crawl-ref/source/test/shoal-hut.lua b/crawl-ref/source/test/shoal-hut.lua
new file mode 100644
index 0000000000..392a57e42d
--- /dev/null
+++ b/crawl-ref/source/test/shoal-hut.lua
@@ -0,0 +1,98 @@
+-- Generates lots of Shoal:$ maps and tests that a) all huts are
+-- connected to the exterior b) the stairs are not completely surrounded by
+-- deep water.
+
+local iterations = 1000
+
+local isdoor = dgn.feature_set_fn("closed_door", "open_door", "secret_door")
+local floor = dgn.fnum("floor")
+
+local function find_vault_doors(vault)
+ local doors = { }
+ local size = vault:size()
+ if size.x == 0 and size.y == 0 then
+ return doors
+ end
+ for p in iter.rect_size_iterator(vault:pos(), size) do
+ local thing = dgn.grid(p.x, p.y)
+ if isdoor(thing) then
+ table.insert(doors, p)
+ end
+ end
+ return doors
+end
+
+local function shoal_hut_doors()
+ local maps = dgn.maps_used_here()
+ test.map_assert(#maps > 0, "No maps used on Shoal:$?")
+ local doors = { }
+ for _, vault in ipairs(maps) do
+ -- Sweep the vault looking for (secret) doors.
+ local vault_doors = find_vault_doors(vault)
+ if #vault_doors > 0 then
+ table.insert(doors, vault_doors)
+ end
+ end
+ test.map_assert(#doors > 0, "No hut doors found on Shoal:$")
+ return doors
+end
+
+-- The hut door is blocked if there is no adjacent square that is not solid
+-- or in a vault.
+local function hut_door_blocked(door)
+ for p in iter.adjacent_iterator_to(door) do
+ if not feat.is_solid(dgn.grid(p.x, p.y)) then
+ return false
+ end
+ end
+ return true
+end
+
+local function verify_stair_connected(p)
+ local function good_square(c)
+ return dgn.grid(c.x, c.y) == floor
+ end
+ local function traversable_square(c)
+ local dfeat = dgn.grid(c.x, c.y)
+ return dfeat == floor or feat.is_stone_stair(dfeat)
+ end
+ test.map_assert(dgn.find_adjacent_point(p, good_square, traversable_square),
+ "Stairs not connected at " .. p)
+end
+
+local function verify_stair_connectivity()
+ local function is_stair(p)
+ return feat.is_stone_stair(dgn.grid(p.x, p.y))
+ end
+
+ local stair_pos = dgn.find_points(is_stair)
+ test.map_assert(#stair_pos > 0, "No stairs in map?")
+
+ for _, stair in ipairs(stair_pos) do
+ verify_stair_connected(stair)
+ end
+end
+
+local function verify_hut_connectivity()
+ for _, vault_doors in ipairs(shoal_hut_doors()) do
+ if util.forall(vault_doors, hut_door_blocked) then
+ for _, door in ipairs(vault_doors) do
+ dgn.grid(door.x, door.y, "floor_special")
+ end
+ test.map_assert(false, "Shoal hut doors blocked")
+ end
+ end
+end
+
+local function test_shoal_huts(nlevels)
+ debug.goto_place("Shoal:$")
+ for i = 1, nlevels do
+ crawl.mesclr()
+ crawl.mpr("Shoal test " .. i .. " of " .. nlevels)
+ test.regenerate_level()
+ verify_stair_connectivity()
+ verify_hut_connectivity()
+ end
+end
+
+test_shoal_huts(iterations) \ No newline at end of file