1 /**
2  * @file
3  * @brief Map generation statistics/testing.
4 **/
5 
6 #include "AppHdr.h"
7 
8 #include "dbg-maps.h"
9 
10 #include "branch.h"
11 #include "chardump.h"
12 #include "crash.h"
13 #include "dbg-objstat.h"
14 #include "dungeon.h"
15 #include "env.h"
16 #include "initfile.h"
17 #include "libutil.h"
18 #include "maps.h"
19 #include "message.h"
20 #include "ng-init.h"
21 #include "player.h"
22 #include "shopping.h"
23 #include "state.h"
24 #include "stringutil.h"
25 #include "tag-version.h"
26 #include "view.h"
27 
28 #ifdef DEBUG_STATISTICS
29 // Map statistics generation.
30 
31 static map<string, int> try_count;
32 static map<string, int> use_count;
33 static map<string, int> success_count;
34 static vector<level_id> generated_levels;
35 static int branch_count;
36 static map<level_id, int> level_mapcounts;
37 static map< level_id, pair<int,int> > map_builds;
38 static map< level_id, set<string> > level_mapsused;
39 
40 typedef map< string, set<level_id> > mapname_place_map;
41 static mapname_place_map map_levelsused;
42 static map<string, string> errors;
43 static string last_error;
44 
45 static int levels_tried = 0, levels_failed = 0;
46 static int build_attempts = 0, level_vetoes = 0;
47 // Map from message to counts.
48 static map<string, int> veto_messages;
49 
mapstat_report_map_build_start()50 void mapstat_report_map_build_start()
51 {
52     build_attempts++;
53     map_builds[level_id::current()].first++;
54 }
55 
mapstat_report_map_veto(const string & message)56 void mapstat_report_map_veto(const string &message)
57 {
58     level_vetoes++;
59     ++veto_messages[message];
60     map_builds[level_id::current()].second++;
61 }
62 
_is_disconnected_level()63 static bool _is_disconnected_level()
64 {
65     // Don't care about non-Dungeon levels.
66     if (!player_in_connected_branch()
67         || (branches[you.where_are_you].branch_flags & brflag::islanded))
68     {
69         return false;
70     }
71 
72     return dgn_count_disconnected_zones(true);
73 }
74 
_do_build_level()75 static bool _do_build_level()
76 {
77     clear_messages();
78     mprf("On %s; %d g, %d fail, %u err%s, %u uniq, "
79          "%d try, %d (%.2f%%) vetos",
80          level_id::current().describe().c_str(), levels_tried, levels_failed,
81          (unsigned int)errors.size(), last_error.empty() ? ""
82          : (" (" + last_error + ")").c_str(), (unsigned int) use_count.size(),
83          build_attempts, level_vetoes,
84          build_attempts ? level_vetoes * 100.0 / build_attempts : 0.0);
85 
86     watchdog();
87 
88     msg::suppress mx;
89     if (kbhit() && key_is_escape(getch_ck()))
90     {
91         mprf(MSGCH_WARN, "User requested cancel");
92         return false;
93     }
94 
95     ++levels_tried;
96     if (!builder())
97     {
98         ++levels_failed;
99         // Abort level build failure in objstat since the statistics will be
100         // off.
101         // XXX - Maybe try the level build some small number of times instead
102         // of aborting on first fail.
103         if (crawl_state.obj_stat_gen)
104         {
105             fprintf(stderr, "Level build failed on %s. Aborting.\n",
106                     level_id::current().describe().c_str());
107             return false;
108         }
109         return true;
110     }
111 
112     for (int y = 0; y < GYM; ++y)
113         for (int x = 0; x < GXM; ++x)
114         {
115             // objstat tallying of features, monsters, and shop items.
116             if (crawl_state.obj_stat_gen)
117             {
118                 const coord_def pos(x, y);
119 
120                 objstat_record_feature(env.grid[x][y], map_masked(pos, MMT_VAULT));
121 
122                 monster *mons = monster_at(pos);
123                 if (mons)
124                     objstat_record_monster(mons);
125 
126                 const shop_struct * const shop = shop_at(pos);
127                 if (shop && shop->defined())
128                 {
129                     for (const auto &item : shop->stock)
130                         if (item.defined())
131                             objstat_record_item(item);
132                 }
133             }
134 
135             if (env.grid[x][y] == DNGN_RUNED_DOOR)
136                 env.grid[x][y] = DNGN_CLOSED_DOOR;
137             else if (env.grid[x][y] == DNGN_RUNED_CLEAR_DOOR)
138                 env.grid[x][y] = DNGN_CLOSED_CLEAR_DOOR;
139         }
140 
141 
142     // Record floor items for objstat.
143     if (crawl_state.obj_stat_gen)
144         for (auto &item : env.item)
145             if (item.defined())
146                 objstat_record_item(item);
147 
148     {
149         unwind_bool wiz(you.wizard, true);
150         magic_mapping(1000, 100, true, true, false,
151                       coord_def(GXM/2, GYM/2));
152     }
153 
154     // Dump the map of any disconnected level if this CLO is set.
155     if (_is_disconnected_level() && crawl_state.map_stat_dump_disconnect)
156     {
157         string vaults = comma_separated_fn(
158                 begin(env.level_vaults), end(env.level_vaults),
159                 [](unique_ptr<vault_placement> &lp) { return lp->map.name; },
160                 ", ", ", ");
161 
162         if (!vaults.empty())
163             vaults = " (" + vaults + ")";
164 
165         FILE *fp = fopen("map.dump", "w");
166         fprintf(fp, "Bad (disconnected) level (%s) on %s%s.\n\n",
167                 env.level_build_method.c_str(),
168                 level_id::current().describe().c_str(),
169                 vaults.c_str());
170 
171         dump_map(fp, true);
172         fclose(fp);
173 
174         mprf(MSGCH_ERROR,
175              "Bad (disconnected) level on %s%s",
176              level_id::current().describe().c_str(),
177              vaults.c_str());
178 
179         return false;
180     }
181     return true;
182 }
183 
_dungeon_places()184 static void _dungeon_places()
185 {
186     generated_levels.clear();
187     branch_count = 0;
188     for (branch_iterator it; it; ++it)
189     {
190         if (brdepth[it->id] == -1)
191             continue;
192 #if TAG_MAJOR_VERSION == 34
193         // Don't want to include branches that no longer generate.
194         if (branch_is_unfinished(it->id))
195             continue;
196 #endif
197 
198         bool new_branch = true;
199         for (int depth = 1; depth <= brdepth[it->id]; ++depth)
200         {
201             level_id l(it->id, depth);
202             if (SysEnv.map_gen_range && !SysEnv.map_gen_range->is_usable_in(l))
203                 continue;
204             generated_levels.push_back(l);
205             if (new_branch)
206                 ++branch_count;
207             new_branch = false;
208         }
209     }
210 }
211 
_build_dungeon()212 static bool _build_dungeon()
213 {
214     for (const level_id &lid: generated_levels)
215     {
216         you.where_are_you = lid.branch;
217         you.depth = lid.depth;
218 
219 #if TAG_MAJOR_VERSION == 34
220         // An unholy hack, FIXME!
221         if (!brentry[BRANCH_FOREST].is_valid()
222             && lid.branch == BRANCH_FOREST && lid.depth == 5)
223         {
224             you.unique_creatures.set(MONS_THE_ENCHANTRESS, false);
225         }
226 #endif
227         if (!_do_build_level())
228             return false;
229     }
230     return true;
231 }
232 
233 /**
234  * Build dungeon levels for mapstat or objstat.
235  *
236  * The exact branches/levels built and number of build iterations is set by the
237  * command-line options for mapstat/objstat.
238 
239  * @returns True if all iterations built successfully. For mapstat, this can
240  * return false if an iteration produced a disconnected level, since for
241  * diagnostic purposes we record the map in detail to a file and exit. For
242  * objstat, this only returns false if the primary dungeon generation function
243  * builder() fails, as the level may be in an invalid state and any object
244  * statistics erroneous.
245 */
mapstat_build_levels()246 bool mapstat_build_levels()
247 {
248     if (!generated_levels.size())
249         _dungeon_places();
250     printf("Iteration: ");
251     fflush(stdout);
252     for (int i = 0; i < SysEnv.map_gen_iters; ++i)
253     {
254         clear_messages();
255         mprf("On %d of %d; %d g, %d fail, %u err%s, %u uniq, "
256              "%d try, %d (%.2f%%) vetoes",
257              i, SysEnv.map_gen_iters, levels_tried, levels_failed,
258              (unsigned int)errors.size(),
259              last_error.empty() ? "" : (" (" + last_error + ")").c_str(),
260              (unsigned int)use_count.size(), build_attempts, level_vetoes,
261              build_attempts ? level_vetoes * 100.0 / build_attempts : 0.0);
262         printf("%d..", i + 1);
263         fflush(stdout);
264         dlua.callfn("dgn_clear_data", "");
265         you.uniq_map_tags.clear();
266         you.uniq_map_names.clear();
267         you.uniq_map_tags_abyss.clear();
268         you.uniq_map_names_abyss.clear();
269         you.unique_creatures.reset();
270         initialise_branch_depths();
271         init_level_connectivity();
272         if (!_build_dungeon())
273             return false;
274         if (crawl_state.obj_stat_gen)
275             objstat_iteration_stats();
276     }
277     printf("Finished.\n");
278     fflush(stdout);
279     return true;
280 }
281 
mapstat_report_map_try(const map_def & map)282 void mapstat_report_map_try(const map_def &map)
283 {
284     try_count[map.name]++;
285 }
286 
mapstat_report_map_use(const map_def & map)287 void mapstat_report_map_use(const map_def &map)
288 {
289     use_count[map.name]++;
290     level_mapcounts[level_id::current()]++;
291     level_mapsused[level_id::current()].insert(map.name);
292     map_levelsused[map.name].insert(level_id::current());
293 }
294 
mapstat_report_map_success(const string & map_name)295 void mapstat_report_map_success(const string &map_name)
296 {
297     success_count[map_name]++;
298 }
299 
mapstat_report_error(const map_def &,const string & err)300 void mapstat_report_error(const map_def &/*map*/, const string &err)
301 {
302     last_error = err;
303 }
304 
_report_available_random_vaults(FILE * outf)305 static void _report_available_random_vaults(FILE *outf)
306 {
307     you.uniq_map_tags.clear();
308     you.uniq_map_names.clear();
309     you.uniq_map_tags_abyss.clear();
310     you.uniq_map_names_abyss.clear();
311 
312     fprintf(outf, "\n\nRandom vaults available by dungeon level:\n");
313     for (auto lvl : generated_levels)
314     {
315         // The watchdog has already been activated by _do_build_level.
316         // Reporting all the vaults could take a while.
317         watchdog();
318         fprintf(outf, "\n%s -------------\n", lvl.describe().c_str());
319         clear_messages();
320         mprf("Examining random maps at %s", lvl.describe().c_str());
321         mapstat_report_random_maps(outf, lvl);
322         if (kbhit() && key_is_escape(getch_ck()))
323             break;
324         fprintf(outf, "---------------------------------\n");
325     }
326 }
327 
_check_mapless(const level_id & lid,vector<level_id> & mapless)328 static void _check_mapless(const level_id &lid, vector<level_id> &mapless)
329 {
330     if (!level_mapsused.count(lid))
331         mapless.push_back(lid);
332 }
333 
_write_map_stats()334 static void _write_map_stats()
335 {
336     const char *out_file = "mapstat.log";
337     FILE *outf = fopen(out_file, "w");
338     printf("Writing map stats to %s...", out_file);
339     fflush(stdout);
340     fprintf(outf, "Map Generation Stats\n\n");
341     fprintf(outf, "Levels attempted: %d, built: %d, failed: %d\n",
342             levels_tried, levels_tried - levels_failed,
343             levels_failed);
344     if (!errors.empty())
345     {
346         fprintf(outf, "\n\nMap errors:\n");
347         for (const auto &err : errors)
348             fprintf(outf, "%s: %s\n", err.first.c_str(), err.second.c_str());
349     }
350 
351     vector<level_id> mapless;
352     for (branch_iterator it; it; ++it)
353     {
354         if (brdepth[it->id] == -1)
355             continue;
356 
357         for (int dep = 1; dep <= brdepth[it->id]; ++dep)
358         {
359             const level_id lid(it->id, dep);
360             if (SysEnv.map_gen_range
361                 && !SysEnv.map_gen_range->is_usable_in(lid))
362             {
363                 continue;
364             }
365             _check_mapless(lid, mapless);
366         }
367     }
368 
369     if (!mapless.empty())
370     {
371         fprintf(outf, "\n\nLevels with no maps:\n");
372         for (int i = 0, size = mapless.size(); i < size; ++i)
373             fprintf(outf, "%3d) %s\n", i + 1, mapless[i].describe().c_str());
374     }
375 
376     _report_available_random_vaults(outf);
377 
378     vector<string> unused_maps;
379     for (int i = 0, size = map_count(); i < size; ++i)
380     {
381         const map_def *map = map_by_index(i);
382         if (!try_count.count(map->name)
383             && !map->has_tag("dummy"))
384         {
385             unused_maps.push_back(map->name);
386         }
387     }
388 
389     if (level_vetoes)
390     {
391         fprintf(outf, "\n\nMost vetoed levels:\n");
392         multimap<int, level_id> sortedvetos;
393         for (const auto &entry : map_builds)
394         {
395             if (!entry.second.second)
396                 continue;
397 
398             sortedvetos.insert(make_pair(entry.second.second, entry.first));
399         }
400 
401         int count = 0;
402         for (auto i = sortedvetos.rbegin(); i != sortedvetos.rend(); ++i)
403         {
404             const int vetoes = i->first;
405             const int tries  = map_builds[i->second].first;
406             fprintf(outf, "%3d) %s (%d of %d vetoed, %.2f%%)\n",
407                     ++count, i->second.describe().c_str(),
408                     vetoes, tries, vetoes * 100.0 / tries);
409         }
410 
411         fprintf(outf, "\n\nVeto reasons:\n");
412         multimap<int, string> sortedreasons;
413         for (const auto &entry : veto_messages)
414             sortedreasons.insert(make_pair(entry.second, entry.first));
415 
416         for (auto i = sortedreasons.rbegin(); i != sortedreasons.rend(); ++i)
417             fprintf(outf, "%3d) %s\n", i->first, i->second.c_str());
418     }
419 
420     if (!unused_maps.empty() && !SysEnv.map_gen_range)
421     {
422         fprintf(outf, "\n\nUnused maps:\n\n");
423         for (int i = 0, size = unused_maps.size(); i < size; ++i)
424             fprintf(outf, "%3d) %s\n", i + 1, unused_maps[i].c_str());
425     }
426 
427     fprintf(outf, "\n\nMaps by level:\n\n");
428     for (const auto &entry : level_mapsused)
429     {
430         string line =
431             make_stringf("%s ------------\n", entry.first.describe().c_str());
432         const set<string> &maps = entry.second;
433         for (auto j = maps.begin(); j != maps.end(); ++j)
434         {
435             if (j != maps.begin())
436                 line += ", ";
437             if (line.length() + j->length() > 79)
438             {
439                 fprintf(outf, "%s\n", line.c_str());
440                 line = *j;
441             }
442             else
443                 line += *j;
444         }
445 
446         if (!line.empty())
447             fprintf(outf, "%s\n", line.c_str());
448 
449         fprintf(outf, "------------\n\n");
450     }
451 
452     fprintf(outf, "\n\nMaps used (successful, placed incl. vetoed, tried):\n\n");
453     multimap<int, string> usedmaps;
454     for (const auto &entry : try_count)
455         usedmaps.insert(make_pair(entry.second, entry.first));
456 
457     for (const auto &entry : usedmaps)
458     {
459         const int tries = entry.first;
460         const int uses = lookup(use_count, entry.second, 0);
461         const int succ = lookup(success_count, entry.second, 0);
462         fprintf(outf, "%4d, %4d, %4d: %s\n",
463                 succ, uses, tries, entry.second.c_str());
464     }
465 
466     fprintf(outf, "\n\nMaps and where used:\n\n");
467     for (const auto &entry : map_levelsused)
468     {
469         fprintf(outf, "%s ============\n", entry.first.c_str());
470         string line;
471         for (auto lvl : entry.second)
472         {
473             if (!line.empty())
474                 line += ", ";
475             string level = lvl.describe();
476             if (line.length() + level.length() > 79)
477             {
478                 fprintf(outf, "%s\n", line.c_str());
479                 line = level;
480             }
481             else
482                 line += level;
483         }
484         if (!line.empty())
485             fprintf(outf, "%s\n", line.c_str());
486 
487         fprintf(outf, "==================\n\n");
488     }
489     fclose(outf);
490     printf("\n");
491 }
492 
mapstat_find_forced_map()493 bool mapstat_find_forced_map()
494 {
495     const map_def *map = find_map_by_name(crawl_state.force_map);
496 
497     if (!map)
498     {
499         printf("Can't find map named '%s'.\n", crawl_state.force_map.c_str());
500         return false;
501     }
502 
503     if (map->is_minivault())
504         you.props["force_minivault"] = map->name;
505     else
506         you.props["force_map"] = map->name;
507 
508     return true;
509 }
510 
mapstat_generate_stats()511 void mapstat_generate_stats()
512 {
513     // Warn assertions about possible oddities like the artefact list being
514     // cleared.
515     you.wizard = true;
516 
517     // Let "acquire foo" have skill aptitudes to work with.
518     you.species = SP_HUMAN;
519 
520     if (!crawl_state.force_map.empty() && !mapstat_find_forced_map())
521         return;
522 
523     initialise_item_descriptions();
524     initialise_branch_depths();
525 
526     // We have to run map preludes ourselves.
527     run_map_global_preludes();
528     run_map_local_preludes();
529 
530     _dungeon_places();
531 
532     clear_messages();
533     mpr("Generating dungeon map stats");
534     printf("Generating map stats for %d iteration(s) of %d level(s) over "
535            "%d branch(es).\n", SysEnv.map_gen_iters,
536            (int) generated_levels.size(), branch_count);
537     fflush(stdout);
538 
539     mapstat_build_levels();
540 
541     _write_map_stats();
542     printf("Map stats complete.\n");
543 }
544 
545 #endif // DEBUG_STATISTICS
546