summaryrefslogtreecommitdiffstats
path: root/crawl-ref
diff options
context:
space:
mode:
authorDarshan Shaligram <dshaligram@users.sourceforge.net>2010-01-09 01:21:44 +0530
committerDarshan Shaligram <dshaligram@users.sourceforge.net>2010-01-09 01:47:09 +0530
commit9841ce45ea91c1ad394d706fd748ffc1fd9d9de0 (patch)
tree047cbb69276a6b8c55d9b22c1e01442a8bd7d173 /crawl-ref
parent915b92e5527c1300fa701a31e3dcbc070b48d177 (diff)
downloadcrawl-ref-9841ce45ea91c1ad394d706fd748ffc1fd9d9de0.tar.gz
crawl-ref-9841ce45ea91c1ad394d706fd748ffc1fd9d9de0.zip
Add -script option to Crawl to run a Lua script. Scripts are similar to tests, but can be parameterised.
Add a script to generate 150 level at a named place and report on all the monsters generated there.
Diffstat (limited to 'crawl-ref')
-rw-r--r--crawl-ref/.gitignore1
-rw-r--r--crawl-ref/source/ctest.cc57
-rw-r--r--crawl-ref/source/dat/clua/test.lua45
-rw-r--r--crawl-ref/source/dat/clua/util.lua14
-rw-r--r--crawl-ref/source/initfile.cc20
-rw-r--r--crawl-ref/source/l_debug.cc16
-rw-r--r--crawl-ref/source/l_file.cc19
-rw-r--r--crawl-ref/source/scripts/place-population.lua136
-rw-r--r--crawl-ref/source/state.h2
-rw-r--r--crawl-ref/source/test/snake-rune.lua9
10 files changed, 297 insertions, 22 deletions
diff --git a/crawl-ref/.gitignore b/crawl-ref/.gitignore
index b207c2b3bb..8f227bbba9 100644
--- a/crawl-ref/.gitignore
+++ b/crawl-ref/.gitignore
@@ -33,6 +33,7 @@ morgue
*.map
map.dump
mapgen.log
+*.out
*.stat
# Tile copied files
diff --git a/crawl-ref/source/ctest.cc b/crawl-ref/source/ctest.cc
index cfff5edecb..dcfd78fe98 100644
--- a/crawl-ref/source/ctest.cc
+++ b/crawl-ref/source/ctest.cc
@@ -17,10 +17,13 @@
#if DEBUG_DIAGNOSTICS || DEBUG_TESTS
#include "clua.h"
+#include "cluautil.h"
#include "dlua.h"
#include "files.h"
+#include "libutil.h"
#include "maps.h"
#include "message.h"
+#include "ng-init.h"
#include "state.h"
#include "stuff.h"
@@ -30,9 +33,11 @@
namespace crawl_tests
{
const std::string test_dir = "test";
+ const std::string script_dir = "scripts";
const std::string test_player_name = "Superbug99";
const species_type test_player_species = SP_HUMAN;
const job_type test_player_job = JOB_FIGHTER;
+ const char *activity = "test";
int ntests = 0;
int nsuccess = 0;
@@ -52,22 +57,31 @@ namespace crawl_tests
int crawl_begin_test(lua_State *ls)
{
- mprf(MSGCH_PROMPT, "Starting tests: %s", luaL_checkstring(ls, 1));
+ mprf(MSGCH_PROMPT, "Starting %s: %s",
+ activity,
+ luaL_checkstring(ls, 1));
lua_pushnumber(ls, ++ntests);
return (1);
}
int crawl_test_success(lua_State *ls)
{
- mprf(MSGCH_PROMPT, "Test success: %s", luaL_checkstring(ls, 1));
+ if (!crawl_state.script)
+ mprf(MSGCH_PROMPT, "Test success: %s", luaL_checkstring(ls, 1));
lua_pushnumber(ls, ++nsuccess);
return (1);
}
+ int crawl_script_args(lua_State *ls)
+ {
+ return clua_stringtable(ls, crawl_state.script_args);
+ }
+
static const struct luaL_reg crawl_test_lib[] =
{
{ "begin_test", crawl_begin_test },
{ "test_success", crawl_test_success },
+ { "script_args", crawl_script_args },
{ NULL, NULL }
};
@@ -76,6 +90,7 @@ namespace crawl_tests
lua_stack_cleaner clean(dlua);
luaL_openlib(dlua, "crawl", crawl_test_lib, 0);
dlua.execfile("clua/test.lua", true, true);
+ initialise_branch_depths();
}
bool is_test_selected(const std::string &testname)
@@ -98,11 +113,12 @@ namespace crawl_tests
return;
++ntests;
- mprf(MSGCH_DIAGNOSTICS, "Running test %d: %s",
- ntests, file.c_str());
+ mprf(MSGCH_DIAGNOSTICS, "Running %s %d: %s",
+ activity, ntests, file.c_str());
flush_prev_message();
- const std::string path(catpath(test_dir, file));
+ const std::string path(
+ catpath(crawl_state.script? script_dir : test_dir, file));
dlua.execfile(path.c_str(), true, false);
if (dlua.error.empty())
++nsuccess;
@@ -113,6 +129,9 @@ namespace crawl_tests
// Assumes curses has already been initialized.
bool run_tests(bool exit_on_complete)
{
+ if (crawl_state.script)
+ activity = "script";
+
flush_prev_message();
run_map_preludes();
@@ -120,9 +139,10 @@ namespace crawl_tests
init_test_bindings();
- if (crawl_state.tests_selected.empty()
- || (crawl_state.tests_selected[0].find("makeitem") !=
- std::string::npos))
+ if ((crawl_state.tests_selected.empty()
+ || (crawl_state.tests_selected[0].find("makeitem") !=
+ std::string::npos))
+ && !crawl_state.script)
{
makeitem_tests();
}
@@ -130,22 +150,33 @@ namespace crawl_tests
// Get a list of Lua files in test. Order of execution of
// tests should be irrelevant.
const std::vector<std::string> tests(
- get_dir_files_ext(test_dir, ".lua"));
+ get_dir_files_ext(crawl_state.script? script_dir : test_dir,
+ ".lua"));
std::for_each(tests.begin(), tests.end(),
run_test);
+ if (failures.empty() && !ntests && crawl_state.script)
+ failures.push_back(
+ file_error(
+ "Script setup",
+ "No scripts found matching " +
+ comma_separated_line(crawl_state.tests_selected.begin(),
+ crawl_state.tests_selected.end(),
+ ", ",
+ ", ")));
+
if (exit_on_complete)
{
cio_cleanup();
for (int i = 0, size = failures.size(); i < size; ++i)
{
const file_error &fe(failures[i]);
- fprintf(stderr, "Test error: %s\n",
- fe.second.c_str());
+ fprintf(stderr, "%s error: %s\n",
+ activity, fe.second.c_str());
}
const int code = failures.empty() ? 0 : 1;
- end(code, false, "%d tests, %d succeeded, %d failed",
- ntests, nsuccess, failures.size());
+ end(code, false, "%d %ss, %d succeeded, %d failed",
+ ntests, activity, nsuccess, failures.size());
}
return (failures.empty());
}
diff --git a/crawl-ref/source/dat/clua/test.lua b/crawl-ref/source/dat/clua/test.lua
index 22a4005d20..03a23842e5 100644
--- a/crawl-ref/source/dat/clua/test.lua
+++ b/crawl-ref/source/dat/clua/test.lua
@@ -31,4 +31,49 @@ function test.level_contains_item(item)
end
end
return false
+end
+
+function test.level_monster_iterator(filter)
+ return iter.mons_rect_iterator(dgn.point(1, 1),
+ dgn.point(dgn.GXM - 2, dgn.GYM - 2),
+ filter)
+end
+
+test.is_down_stair = dgn.feature_set_fn("stone_stairs_down_i",
+ "stone_stairs_down_ii",
+ "stone_stairs_down_iii")
+
+function test.level_has_down_stair()
+ for y = 1, dgn.GYM - 2 do
+ for x = 1, dgn.GXM - 2 do
+ local dfeat = dgn.grid(x, y)
+ if test.is_down_stair(dfeat) then
+ return true
+ end
+ end
+ end
+ return false
+end
+
+function test.deeper_place_from(place)
+ if test.level_has_down_stair() then
+ local _, _, branch, depth = string.find(place, "(%w+):(%d+)")
+ return branch .. ":" .. (tonumber(depth) + 1)
+ end
+ return nil
+end
+
+util.namespace('script')
+
+function script.simple_args()
+ local args = crawl.script_args()
+ return util.filter(function (arg)
+ return string.find(arg, '-') ~= 1
+ end,
+ args)
+end
+
+function script.usage(ustr)
+ ustr = string.gsub(string.gsub(ustr, "^%s+", ""), "%s+$", "")
+ error("\n" .. ustr)
end \ No newline at end of file
diff --git a/crawl-ref/source/dat/clua/util.lua b/crawl-ref/source/dat/clua/util.lua
index d6e842fa7e..8dd0b35eba 100644
--- a/crawl-ref/source/dat/clua/util.lua
+++ b/crawl-ref/source/dat/clua/util.lua
@@ -56,7 +56,7 @@ end
-- Returns a list of the keys in the given map.
function util.keys(map)
local keys = { }
- for key, _ in pairs(ziggurat_builder_map) do
+ for key, _ in pairs(map) do
table.insert(keys, key)
end
return keys
@@ -65,12 +65,22 @@ end
-- Returns a list of the values in the given map.
function util.values(map)
local values = { }
- for _, value in pairs(ziggurat_builder_map) do
+ for _, value in pairs(map) do
table.insert(values, value)
end
return values
end
+-- Returns a list of lists built from the given map, each sublist being
+-- in the form { key, value } for each key-value pair in the map.
+function util.pairs(map)
+ local mappairs = { }
+ for key, value in pairs(map) do
+ table.insert(mappairs, { key, value })
+ end
+ return mappairs
+end
+
-- Creates a string of the elements in list joined by separator.
function util.join(sep, list)
return table.concat(list, sep)
diff --git a/crawl-ref/source/initfile.cc b/crawl-ref/source/initfile.cc
index c3208c425b..f5e0296422 100644
--- a/crawl-ref/source/initfile.cc
+++ b/crawl-ref/source/initfile.cc
@@ -3472,6 +3472,7 @@ enum commandline_option_type {
CLO_MAPSTAT,
CLO_ARENA,
CLO_TEST,
+ CLO_SCRIPT,
CLO_BUILDDB,
CLO_HELP,
CLO_VERSION,
@@ -3485,8 +3486,8 @@ enum commandline_option_type {
static const char *cmd_ops[] = {
"scores", "name", "species", "job", "plain", "dir", "rc",
"rcdir", "tscores", "vscores", "scorefile", "morgue", "macro",
- "mapstat", "arena", "test", "builddb", "help", "version", "save-version",
- "extra-opt-first", "extra-opt-last",
+ "mapstat", "arena", "test", "script", "builddb", "help", "version",
+ "save-version", "extra-opt-first", "extra-opt-last"
};
const int num_cmd_ops = CLO_NOPS;
@@ -3792,6 +3793,21 @@ bool parse_args( int argc, char **argv, bool rc_only )
}
break;
+ case CLO_SCRIPT:
+ crawl_state.test = true;
+ crawl_state.script = true;
+ if (current < argc - 1)
+ {
+ crawl_state.tests_selected = split_string(",", next_arg);
+ for (int extra = current + 2; extra < argc; ++extra)
+ crawl_state.script_args.push_back(argv[extra]);
+ current = argc;
+ }
+ else
+ end(1, false,
+ "-script must specify comma-separated script names");
+ break;
+
case CLO_BUILDDB:
if (next_is_param)
return (false);
diff --git a/crawl-ref/source/l_debug.cc b/crawl-ref/source/l_debug.cc
index f500b631ce..8fef92b954 100644
--- a/crawl-ref/source/l_debug.cc
+++ b/crawl-ref/source/l_debug.cc
@@ -9,6 +9,7 @@
#include "l_libs.h"
#include "beam.h"
+#include "branch.h"
#include "chardump.h"
#include "coordit.h"
#include "dungeon.h"
@@ -24,16 +25,31 @@
#include "wiz-dgn.h"
// WARNING: This is a very low-level call.
+//
+// Usage: goto_place("placename", <bind_entrance>)
+// "placename" is the name of the place as used in maps, such as "Lair:2",
+// "Vault:$", etc.
+//
+// If <bind_entrance> is specified, the entrance point of
+// the branch specified in place_name is bound to the given level in the
+// parent branch (the entrance level should be 1-based). This can be helpful
+// when testing scenarios that depend on the absolute depth of the current
+// place.
LUAFN(debug_goto_place)
{
try
{
const level_id id = level_id::parse_level_id(luaL_checkstring(ls, 1));
+ const int bind_entrance =
+ lua_isnumber(ls, 2)? luaL_checkint(ls, 2) : -1;
you.level_type = id.level_type;
if (id.level_type == LEVEL_DUNGEON)
{
you.where_are_you = static_cast<branch_type>(id.branch);
you.your_level = absdungeon_depth(id.branch, id.depth);
+
+ if (bind_entrance != -1)
+ branches[you.where_are_you].startdepth = bind_entrance;
}
}
catch (const std::string &err)
diff --git a/crawl-ref/source/l_file.cc b/crawl-ref/source/l_file.cc
index bde31cb558..fae724fcfd 100644
--- a/crawl-ref/source/l_file.cc
+++ b/crawl-ref/source/l_file.cc
@@ -168,6 +168,23 @@ LUAFN(_file_datadir_files)
return clua_stringtable(ls, files);
}
+LUAFN(_file_writefile)
+{
+ const std::string fname(luaL_checkstring(ls, 1));
+ FILE *f = fopen(fname.c_str(), "w");
+ if (f)
+ {
+ fprintf(f, "%s", luaL_checkstring(ls, 2));
+ fclose(f);
+ lua_pushboolean(ls, true);
+ }
+ else
+ {
+ lua_pushboolean(ls, false);
+ }
+ return (1);
+}
+
static const struct luaL_reg file_dlib[] =
{
{ "marshall", file_marshall },
@@ -176,7 +193,7 @@ static const struct luaL_reg file_dlib[] =
{ "unmarshall_number", file_unmarshall_number },
{ "unmarshall_string", file_unmarshall_string },
{ "unmarshall_fn", file_unmarshall_fn },
-
+ { "writefile", _file_writefile },
{ "datadir_files", _file_datadir_files },
{ NULL, NULL }
};
diff --git a/crawl-ref/source/scripts/place-population.lua b/crawl-ref/source/scripts/place-population.lua
new file mode 100644
index 0000000000..9a95616cdc
--- /dev/null
+++ b/crawl-ref/source/scripts/place-population.lua
@@ -0,0 +1,136 @@
+-- Counts monsters in a specify level, or level range.
+local niters = 150
+local output_file = "monster-report.out"
+
+local excluded_things = util.set({ "plant", "fungus", "bush" })
+
+local function count_monsters_at(place, set)
+ debug.goto_place(place)
+ test.regenerate_level()
+
+ local monster_set = set or { }
+ for mons in test.level_monster_iterator() do
+ local mname = mons.name
+ if not excluded_things[mname] then
+ local count = monster_set[mname] or 0
+ monster_set[mname] = count + 1
+ end
+ end
+ return monster_set
+end
+
+local function report_monster_counts_at(place, mcount_map)
+ local text = ''
+ text = text .. "\n-------------------------------------------\n"
+ text = text .. place .. " monsters per-level\n"
+ text = text .. "-------------------------------------------\n"
+
+ local monster_counts = util.pairs(mcount_map)
+ table.sort(monster_counts, function (a, b)
+ return a[2] > b[2]
+ end)
+
+ local total = 0
+ for _, monster_pop in ipairs(monster_counts) do
+ total = total + monster_pop[2]
+ end
+
+ for _, monster_pop in ipairs(monster_counts) do
+ text = text .. string.format("%6.2f (%6.2f%%) %s\n",
+ monster_pop[2] * 1.0 / niters,
+ monster_pop[2] * 100.0 / total,
+ monster_pop[1])
+ end
+ return text
+end
+
+local function report_monster_counts(mcounts)
+ local places = util.keys(mcounts)
+ table.sort(places)
+
+ local text = ''
+ for _, place in ipairs(places) do
+ text = text .. report_monster_counts_at(place, mcounts[place])
+ end
+ file.writefile(output_file, text)
+ crawl.mpr("Dumped monster stats to " .. output_file)
+end
+
+local function count_monsters_from(start_place, end_place)
+ local place = start_place
+ local mcounts = { }
+ while place do
+ local mset = { }
+ for i = 1, niters do
+ crawl.mesclr()
+ crawl.mpr("Counting monsters at " .. place .. " (" ..
+ i .. "/" .. niters .. ")")
+ count_monsters_at(place, mset)
+ end
+ mcounts[place] = mset
+
+ if place == end_place then
+ break
+ end
+
+ place = test.deeper_place_from(place)
+ end
+
+ report_monster_counts(mcounts)
+end
+
+local function parse_resets(resets)
+ local pieces = crawl.split(resets, ",")
+ local resets = { }
+ for _, p in ipairs(pieces) do
+ local _, _, place, depth = string.find(p, "^(.+)=(%d+)$")
+ table.insert(resets, { place, tonumber(depth) })
+ end
+ return resets
+end
+
+local function branch_resets()
+ local args = crawl.script_args()
+ local resets = { }
+ for _, arg in ipairs(args) do
+ local _, _, rawresets = string.find(arg, "^-reset=(.*)")
+ if rawresets then
+ util.append(resets, parse_resets(rawresets))
+ end
+ end
+ return resets
+end
+
+local function set_branch_depths()
+ for _, reset in ipairs(branch_resets()) do
+ debug.goto_place(reset[1], reset[2])
+ end
+end
+
+local function start_end_levels()
+ local args = script.simple_args()
+ if #args == 0 then
+ script.usage([[
+Usage: place-population <start> [<end>]
+For instance: place-population Shoal:1 Shoal:5
+ place-population Lair:3
+
+You may optionally force branches to have entrances at specific places
+with:
+ place-population -reset=Lair:1=8,Snake:1=3 Snake:5
+
+With the general form:
+
+ -reset=<place>=<depth>[,<place>=<depth>,...]
+
+where <place> is a valid place name as used in .des files, and <depth> is the
+depth of the branch's entrance in its parent branch. Thus -reset=Snake:1=3
+implies that the entrance of the Snake Pit is assumed to be on Lair:3.
+]])
+ end
+ return args[1], args[2] or args[1]
+end
+
+set_branch_depths()
+local lstart, lend = start_end_levels()
+count_monsters_from(lstart, lend)
diff --git a/crawl-ref/source/state.h b/crawl-ref/source/state.h
index 387b0e8b33..ffb9f56429 100644
--- a/crawl-ref/source/state.h
+++ b/crawl-ref/source/state.h
@@ -50,8 +50,10 @@ struct game_state
// suspended.
bool test; // Set if we want to run self-tests and exit.
+ bool script; // Set if we want to run a Lua script and exit.
bool build_db; // Set if we want to rebuild the db and exit.
std::vector<std::string> tests_selected; // Tests to be run.
+ std::vector<std::string> script_args; // Arguments to scripts.
bool unicode_ok; // Is unicode support available?
diff --git a/crawl-ref/source/test/snake-rune.lua b/crawl-ref/source/test/snake-rune.lua
index 56ea8b8273..dbf973afa7 100644
--- a/crawl-ref/source/test/snake-rune.lua
+++ b/crawl-ref/source/test/snake-rune.lua
@@ -1,3 +1,7 @@
+-- Walks down the dungeon to Snake:$ and tests for the existence of the rune.
+-- This is a more exhaustive test than rune-gen.lua since it generates all
+-- intermediate levels and activates the dungeon connectivity code.
+
local niters = 500
local current_iter = 0
@@ -7,9 +11,6 @@ local branch_entrance_feats = {
}
local junk_feat_fn = dgn.feature_set_fn("rock_wall", "floor", "stone_wall")
-local down_stair_fn = dgn.feature_set_fn("stone_stairs_down_i",
- "stone_stairs_down_ii",
- "stone_stairs_down_iii")
local function thing_exists_fn(thing)
return function()
@@ -38,7 +39,7 @@ local function visit_branch_end_from(start, stair_places, final_predicate)
end
end
- if down_stair_fn(dfeat) then
+ if test.is_down_stair(dfeat) then
table.insert(downstairs, dgn.point(x, y))
end
end