# HG changeset patch # User Matti Hamalainen # Date 1566390666 -10800 # Node ID a0eb0ccd6458d6d8fa44f14f6a823ceda829ba59 # Parent f6109d5bd5454a1a9a3e787debdc939c1138cdbb Rename mkspecial -> stitchmap. diff -r f6109d5bd545 -r a0eb0ccd6458 Makefile.gen --- a/Makefile.gen Wed Aug 21 15:29:43 2019 +0300 +++ b/Makefile.gen Wed Aug 21 15:31:06 2019 +0300 @@ -19,7 +19,7 @@ ### ### Objects ### -MKSPECIAL_BIN=$(BINPATH)mkspecial$(EXEEXT) +STITCHMAP_BIN=$(BINPATH)stitchmap$(EXEEXT) COLORMAP_BIN=$(BINPATH)colormap$(EXEEXT) MKCITYMAP_BIN=$(BINPATH)mkcitymap$(EXEEXT) DIFFMAP_BIN=$(BINPATH)diffmap$(EXEEXT) @@ -35,7 +35,7 @@ TARGETS += $(LIBMAPUTILS_OBJ) $(LIBLOCFILE_OBJ) \ - $(COLORMAP_BIN) $(MKSPECIAL_BIN) $(MKCITYMAP_BIN) \ + $(COLORMAP_BIN) $(STITCHMAP_BIN) $(MKCITYMAP_BIN) \ $(DIFFMAP_BIN) $(PATCHMAP_BIN) $(MAP2PPM_BIN) \ $(MKLOC_BIN) $(COMBINE_BIN) $(MAPSTATS_BIN) \ $(addprefix $(MAP_PATH),$(addsuffix .html,$(MAP_FILES))) diff -r f6109d5bd545 -r a0eb0ccd6458 README --- a/README Wed Aug 21 15:29:43 2019 +0300 +++ b/README Wed Aug 21 15:31:06 2019 +0300 @@ -160,7 +160,7 @@ Combines several ASCII maps into a bigger one, based on coordinate offsets. -* mkspecial +* stitchmap Given an input with "map pieces" (generated with help of TF scripts) and sufficient parameters for interpreting, this utility stitches together a bigger ASCII map from the pieces. diff -r f6109d5bd545 -r a0eb0ccd6458 mkspecial.c --- a/mkspecial.c Wed Aug 21 15:29:43 2019 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,710 +0,0 @@ -/* - * mkspecial - Compute complete ASCII map by stitching pieces - * together based on a matcher and coordinates - * - * Programmed by Matti 'ccr' Hämäläinen - * (C) Copyright 2006-2015 Tecnic Software productions (TNSP) - */ -#include "libmaputils.h" -#include "th_args.h" -#include "th_string.h" - -#define MAX_FILES (256) - - -/* Variables - */ -int nsrcFiles = 0; -char *srcFiles[MAX_FILES], - *destFile = NULL; - -int optInitialX = 0, - optInitialY = 0, - optMapFactor = 10, - optRounds = 100, - optReset = 50; -BOOL optHardDrop = FALSE, - optDumpRejected = FALSE, - optAdjustBlocks = FALSE, - optWalkMode = FALSE; -float optMatch = 40.0; -char *optInitialMap = NULL, - *optCleanChars = " *@", - optCenterCh = -1; - - -/* Arguments - */ -static const th_optarg optList[] = -{ - { 0, '?', "help", "Show this help", OPT_NONE }, - { 1, 'o', "output", "Specify output file", OPT_ARGREQ }, - { 2, 'v', "verbose", "Be more verbose", OPT_NONE }, - { 3, 'q', "quiet", "Be quiet", OPT_NONE }, - { 4, 'm', "match", "Match percentage", OPT_ARGREQ }, - { 5, 'r', "rounds", "Processing timeout # rounds", OPT_ARGREQ }, - { 12,'R', "reset", "Round reset after", OPT_ARGREQ }, - { 11,'d', "drop", "Use hard dropping (bailout on first mismatch)", OPT_NONE }, - { 10,'I', "initial", "Initial map file", OPT_ARGREQ }, - { 14,'x', "initial-x", "Initial map X offset", OPT_ARGREQ }, - { 15,'y', "initial-y", "Initial map Y offset", OPT_ARGREQ }, - { 16,'D', "dump", "Dump rejected pieces", OPT_NONE }, - { 17,'a', "adjust-size","Adjust to variable size blocks", OPT_NONE }, - { 18,'W', "walk-mode", "Walk mode (instead of ship mode)", OPT_NONE }, - { 19,'c', "clean-chars","Characters to filter from input", OPT_ARGREQ }, - { 20,'C', "center-char","Center character symbol (for -a)", OPT_ARGREQ }, -}; - -static const int optListN = sizeof(optList) / sizeof(optList[0]); - - -void argShowHelp() -{ - th_print_banner(stdout, th_prog_name, - "[options] [inputfile#2..]"); - - th_args_help(stdout, optList, optListN, 0); -} - - -BOOL argHandleOpt(const int optN, char *optArg, char *currArg) -{ - switch (optN) - { - case 0: - argShowHelp(); - exit(0); - break; - - case 1: - destFile = optArg; - THMSG(1, "Output file '%s'\n", destFile); - break; - - case 2: - th_verbosity++; - break; - - case 3: - th_verbosity = -1; - break; - - case 4: - optMatch = atof(optArg); - THMSG(1, "Match at %1.4f%%\n", optMatch); - break; - - case 5: - optRounds = atoi(optArg); - THMSG(1, "Processing rounds timeout %d\n", optRounds); - break; - - case 12: - optReset = atoi(optArg); - THMSG(1, "Round reset after %d blocks\n", optReset); - break; - - case 10: - optInitialMap = optArg; - THMSG(1, "Using initial map file = '%s'\n", optArg); - break; - - case 11: - optHardDrop = TRUE; - THMSG(1, "Using hard dropping (instant bailout on mismatch).\n"); - break; - - case 14: - optInitialX = atoi(optArg); - THMSG(1, "Initial map X offset = %d\n", optInitialX); - break; - - case 15: - optInitialY = atoi(optArg); - THMSG(1, "Initial map Y offset = %d\n", optInitialY); - break; - - case 16: - THMSG(1, "Dumping rejected blocks.\n"); - optDumpRejected = TRUE; - break; - - case 17: - THMSG(1, "Handling variable size blocks via adjustment.\n"); - optAdjustBlocks = TRUE; - break; - - case 18: - THMSG(1, "Walk mode enabled.\n"); - optWalkMode = TRUE; - break; - - case 19: - optCleanChars = optArg; - THMSG(1, "Removing '%s' from input blocks\n", optArg); - break; - - case 20: - if (strlen(optArg) != 1) - { - THERR("Invalid argument '%s' for -C, only one character symbol can be specified.\n", optArg); - return FALSE; - } - else - { - optCenterCh = optArg[0]; - THMSG(1, "Using '%c' as centering character symbol\n", optCenterCh); - } - break; - - default: - THERR("Unknown option '%s'.\n", currArg); - return FALSE; - break; - } - - return TRUE; -} - - -BOOL argHandleFile(char *currArg) -{ - if (nsrcFiles < MAX_FILES) - { - srcFiles[nsrcFiles] = currArg; - nsrcFiles++; - } - else - { - THERR("Too many input files specified (%d max)!\n", MAX_FILES); - return FALSE; - } - - return TRUE; -} - - -/* Calculate matching percentage of given block against a map, - * with using specified x/y offsets. - */ -float matchBlock(MapBlock *map, MapBlock *match, - const int ox, const int oy, const BOOL hardDrop) -{ - int n, k; - - n = k = 0; - - for (int y = 0; y < match->height; y++) - for (int x = 0; x < match->width; x++) - { - const int - dx = ox + x, - dy = oy + y; - int c1, c2; - - k++; - - if (dx >= 0 && dx < map->width && dy >= 0 && dy < map->height) - c2 = map->data[(dy * map->scansize) + dx]; - else - c2 = 0; - - c1 = match->data[(y * match->scansize) + x]; - - if (c1 == 0 || c2 == 0) - { - // Both empty ... - } - else - if (c1 != 0 && c1 == c2) - { - // Exact match, increase % - n++; - } - else - if (hardDrop) - { - // Mismatch, return failure - return -1; - } - } - - if (k > 0) - return ((float) n * 100.0f) / (float) k; - else - return 0.0f; -} - - -BOOL adjustBlockCenter(MapBlock *block, const char centerCh) -{ - for (int yc = 0; yc < block->height; yc++) - for (int xc = 0; xc < block->width; xc++) - { - int ch = block->data[(yc * block->scansize) + xc]; - if (ch == centerCh) - { - block->xc -= xc; - block->yc -= yc; - return TRUE; - } - } - - return FALSE; -} - - -/* Parse next block (marked by string optPattern) from - * input stream into a mapblock, return NULL if not found or error. - */ -BOOL getLineData(FILE *inFile, char *buf, const size_t bufSize) -{ - if (feof(inFile) || !fgets(buf, bufSize, inFile)) - { - THERR("Unexpected end of file.\n"); - return FALSE; - } - else - return TRUE; -} - - -size_t getLineWidth(char *buf, const size_t bufSize) -{ - size_t width; - - for (width = 0; width < bufSize && buf[width] && buf[width] != '\r' && buf[width] != '\n'; width++); - buf[width] = 0; - - return width; -} - - -MapBlock * parseBlock(FILE *inFile) -{ - MapBlock *tmp = NULL; - int xc, yc, tmpW, tmpH; - char str[4096], *pos = NULL; - BOOL isFound; - size_t i; - - isFound = FALSE; - while (!feof(inFile) && !isFound && fgets(str, sizeof(str), inFile)) - { - if ((pos = strstr(str, ": !!PLR: ")) != NULL && - (strstr(str, "party") || strstr(str, "report"))) - isFound = TRUE; - } - - if (!isFound) - { - THERR("No block start marker found.\n"); - goto err; - } - - // Trim end - for (i = 0; i < sizeof(str) && str[i] && str[i] != '\r' && str[i] != '\n'; i++); - str[i] = 0; - - if (sscanf(pos, ": !!PLR: %d,%d", &xc, &yc) < 2) - { - THERR("Could not get location coordinates:\n'%s'\n", - str); - goto err; - } - - // Check for discardable lines - if (!getLineData(inFile, str, sizeof(str))) - goto err; - - if (!th_strcasecmp(str, "You glance around.")) - { - if (!getLineData(inFile, str, sizeof(str))) - goto err; - } - - // New 'map' output in city maps has an empty line before the data, ignore it - if ((tmpW = getLineWidth(str, sizeof(str))) <= 9) - { - if (!getLineData(inFile, str, sizeof(str))) - goto err; - - tmpW = getLineWidth(str, sizeof(str)); - } - - if (optWalkMode) - { - switch (tmpW) - { - case 31: tmpH = 17; break; - case 13: tmpH = 7; break; - case 9: tmpH = 9; break; - default: - THERR("Invalid block width %d [%d, %d], rejecting.\n", tmpW, xc, yc); - return NULL; - break; - } - } - else - tmpH = tmpW - 8; - - if ((tmp = mapBlockAlloc(tmpW, tmpH)) == NULL) - { - THERR("Could not allocate mapblock (%d, %d)\n", tmpW, tmpH); - goto err; - } - - tmp->xc = xc; - tmp->yc = yc; - - yc = 0; - do - { - i = getLineWidth(str, tmpW); - - if (i != (size_t) tmp->width) - { - THERR("Broken block, line width %d != %d!\n", - i, tmp->width, str); - goto err; - } - - for (xc = 0; xc < tmp->width; xc++) - { - tmp->data[(yc * tmp->scansize) + xc] = str[xc]; - } - - yc++; - } while (!feof(inFile) && (yc < tmp->height) && fgets(str, sizeof(str), inFile)); - - - if (yc < tmp->height) - { - THERR("Broken block, height %d < %d\n", - yc, tmp->height); - goto err; - } - - return tmp; - -err: - mapBlockFree(tmp); - return NULL; -} - - -/* Find the min/max of given coordinates. - */ -void findWorldSize(MapBlock *tmp, - int *worldX0, int *worldY0, - int *worldX1, int *worldY1) -{ - if (tmp->xc < *worldX0) *worldX0 = tmp->xc; - if ((tmp->xc + tmp->width) > *worldX1) *worldX1 = (tmp->xc + tmp->width); - - if (tmp->yc < *worldY0) *worldY0 = tmp->yc; - if ((tmp->yc + tmp->height) > *worldY1) *worldY1 = (tmp->yc + tmp->height); -} - - -int main(int argc, char *argv[]) -{ - BOOL isOK; - int i, currRounds, nmapBlocks, currBlocks, - worldX0, worldY0, worldX1, worldY1, - offsetX = 0, offsetY = 0; - MapBlock *worldMap = NULL, *initialMap = NULL; - MapBlock **mapBlocks = NULL; - - th_init("mkspecial", "Yet Another ASCII Map Auto-Stitcher", "0.5", NULL, NULL); - th_verbosity = 1; - - // Parse arguments - if (!th_args_process(argc, argv, optList, optListN, - argHandleOpt, argHandleFile, OPTH_BAILOUT)) - exit(1); - - if (nsrcFiles < 1) - { - THERR("Nothing to do. (try --help)\n"); - exit(0); - } - - // Read initial map - if (optInitialMap) - { - THMSG(1, "Reading initial map '%s'\n", optInitialMap); - initialMap = mapBlockParseFile(optInitialMap, FALSE); - if (initialMap) - { - THMSG(2, "Initial dimensions %d x %d\n", - initialMap->width, initialMap->height); - - mapBlockClean(initialMap, (unsigned char*) " ", 1); - } - else - { - THERR("Initial map could not be loaded!\n"); - exit(1); - } - } - - /* Read in continuous mapdata and parse it into map blocks - */ - for (nmapBlocks = i = 0; i < nsrcFiles; i++) - { - FILE *tmpFile; - char centerCh; - - if (optCenterCh > 0) - { - centerCh = optCenterCh; - } - else - { - if (optWalkMode) - centerCh = '@'; - else - centerCh = '*'; - } - - - - if ((tmpFile = fopen(srcFiles[i], "rb")) == NULL) - { - THERR("Error opening input file '%s'!\n", - srcFiles[i]); - exit(1); - } - - while (!feof(tmpFile)) - { - MapBlock *tmp; - if ((tmp = parseBlock(tmpFile)) != NULL) - { - if (optAdjustBlocks && !adjustBlockCenter(tmp, centerCh)) - { - THERR("Block center not found, rejected.\n"); - mapBlockFree(tmp); - } - else - { - nmapBlocks++; - mapBlocks = (MapBlock **) th_realloc(mapBlocks, sizeof(MapBlock *) * nmapBlocks); - if (!mapBlocks) - { - fclose(tmpFile); - THERR("Could not allocate/extend mapblock pointer structure (#%d)\n", nmapBlocks); - exit(3); - } - - mapBlockClean(tmp, (unsigned char *) optCleanChars, strlen(optCleanChars)); - mapBlocks[nmapBlocks - 1] = tmp; - } - } - } - - fclose(tmpFile); - } - - THMSG(1, "Total of %d mapblocks read.\n", nmapBlocks); - - if (nmapBlocks <= 0) - { - THERR("No mapblocks, nothing to do.\n"); - exit(11); - } - - - /* ALGORITHM - * --------- - find dimensions of the world map: - for (each block in list) { - if (block->x < mapx0) mapx0 = block->x; else - if (block->x+block->w > mapx1) mapx1 = block->x+block->w; - ... - } - - allocate map - - start placing blocks: - for (n = 0; n < rounds; n++) { - for (each block in list) { - if (check match % at given coordinates > threshold) - place block - } - - if (all blocks placed) break - } - */ - - - // If initial map is available, find a match and determine coords - if (initialMap) - { - initialMap->xc = optInitialX; - initialMap->yc = optInitialY; - } - - // Clear marks - for (i = 0; i < nmapBlocks; i++) - mapBlocks[i]->mark = FALSE; - - - // Get world dimensions - worldX0 = worldY0 = worldX1 = worldY1 = 0; - for (i = 0; i < nmapBlocks; i++) - findWorldSize(mapBlocks[i], &worldX0, &worldY0, &worldX1, &worldY1); - - if (initialMap) - findWorldSize(initialMap, &worldX0, &worldY0, &worldX1, &worldY1); - - // Compute offsets, allocate world map - // FIXME: check dimensions - offsetX = -worldX0; - offsetY = -worldY0; - - if ((worldMap = mapBlockAlloc(worldX1 - worldX0 + 1, worldY1 - worldY0 + 1)) == NULL) - { - THERR("Error allocating world map!\n"); - exit(4); - } - - // Place optional initial map - if (initialMap) - { - if (mapBlockPut(&worldMap, initialMap, offsetX + initialMap->xc, offsetY + initialMap->yc) < 0) - { - THERR("Initial map mapBlockPut() failed!\n"); - exit(9); - } - } - else - { - i = 0; - if (mapBlockPut(&worldMap, mapBlocks[i], offsetX + mapBlocks[i]->xc, offsetY + mapBlocks[i]->yc) < 0) - { - THERR("Initial map mapBlockPut() failed!\n"); - exit(9); - } - mapBlocks[i]->mark = TRUE; - } - - THMSG(1, "Initialized world map of (%d x %d), offset (%d, %d)\n", - worldMap->width, worldMap->height, offsetX, offsetY); - - - // Start placing blocks - currRounds = 0; - isOK = FALSE; - while (currRounds++ < optRounds && !isOK) - { - int usedBlocks; - - // Get number of used blocks - for (usedBlocks = i = 0; i < nmapBlocks; i++) - if (mapBlocks[i]->mark) usedBlocks++; - - // Print out status information - THPRINT(2, "#%d [%d/%d]: ", - currRounds, usedBlocks, nmapBlocks); - - // Place and match blocks - isOK = TRUE; - currBlocks = 0; - for (i = 0; i < nmapBlocks && currBlocks < optReset; i++) - { - MapBlock *tmp = mapBlocks[i]; - - if (!tmp->mark) - { - int qx = offsetX + tmp->xc, - qy = offsetY + tmp->yc; - - isOK = FALSE; - - if (matchBlock(worldMap, tmp, qx, qy, optHardDrop) >= optMatch) - { - if (mapBlockPut(&worldMap, tmp, qx, qy) < 0) - { - THERR("mapBlockPut(%d, %d, %d) failed!\n", - offsetX, offsetY, i); - exit(9); - } - tmp->mark = TRUE; - - currBlocks++; - THPRINT(2, "X"); - } - else - { - THPRINT(2, "."); - -#if 0 - // Debug unmatching blocks - char mysti[512]; - snprintf(mysti, sizeof(mysti), - "[%d]: %d,%d (%d,%d)", - i, qx, qy, tmp->x, tmp->y); - fprintf(stderr, "\n--- %.30s ---\n", mysti); - mapBlockPrint(stderr, tmp); - fprintf(stderr, "---------\n"); - mapBlockPrint(stderr, worldMap); -#endif - } - } - } - - THPRINT(2, "\n"); - } - - /* Output generated map - */ - if (worldMap) - { - int unusedBlocks; - FILE *tmpFile; - - THMSG(1, "Outputting generated map of (%d x %d) ...\n", - worldMap->width, worldMap->height); - - if (destFile == NULL) - tmpFile = stdout; - else - if ((tmpFile = fopen(destFile, "wb")) == NULL) - { - THERR("Error opening output file '%s'!\n", - destFile); - exit(1); - } - - mapBlockPrint(tmpFile, worldMap); - - fclose(tmpFile); - - // Compute number of unused blocks - for (unusedBlocks = i = 0; i < nmapBlocks; i++) - if (!mapBlocks[i]->mark) - { - if (optDumpRejected) - { - fprintf(stderr, "\n#%d: %d,%d (%d,%d)\n", - i, - mapBlocks[i]->xc, mapBlocks[i]->yc, - mapBlocks[i]->xc + offsetX, - mapBlocks[i]->yc + offsetY); - - mapBlockPrint(stderr, mapBlocks[i]); - } - unusedBlocks++; - } - - THMSG(1, "%d mapblocks unused/discarded\n", unusedBlocks); - } - else - { - THERR("No map generated?\n"); - } - - return 0; -} diff -r f6109d5bd545 -r a0eb0ccd6458 stitchmap.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/stitchmap.c Wed Aug 21 15:31:06 2019 +0300 @@ -0,0 +1,710 @@ +/* + * stitchmap - Compute complete ASCII map by stitching pieces + * together based on a matcher and coordinates + * + * Programmed by Matti 'ccr' Hämäläinen + * (C) Copyright 2006-2015 Tecnic Software productions (TNSP) + */ +#include "libmaputils.h" +#include "th_args.h" +#include "th_string.h" + +#define MAX_FILES (256) + + +/* Variables + */ +int nsrcFiles = 0; +char *srcFiles[MAX_FILES], + *destFile = NULL; + +int optInitialX = 0, + optInitialY = 0, + optMapFactor = 10, + optRounds = 100, + optReset = 50; +BOOL optHardDrop = FALSE, + optDumpRejected = FALSE, + optAdjustBlocks = FALSE, + optWalkMode = FALSE; +float optMatch = 40.0; +char *optInitialMap = NULL, + *optCleanChars = " *@", + optCenterCh = -1; + + +/* Arguments + */ +static const th_optarg optList[] = +{ + { 0, '?', "help", "Show this help", OPT_NONE }, + { 1, 'o', "output", "Specify output file", OPT_ARGREQ }, + { 2, 'v', "verbose", "Be more verbose", OPT_NONE }, + { 3, 'q', "quiet", "Be quiet", OPT_NONE }, + { 4, 'm', "match", "Match percentage", OPT_ARGREQ }, + { 5, 'r', "rounds", "Processing timeout # rounds", OPT_ARGREQ }, + { 12,'R', "reset", "Round reset after", OPT_ARGREQ }, + { 11,'d', "drop", "Use hard dropping (bailout on first mismatch)", OPT_NONE }, + { 10,'I', "initial", "Initial map file", OPT_ARGREQ }, + { 14,'x', "initial-x", "Initial map X offset", OPT_ARGREQ }, + { 15,'y', "initial-y", "Initial map Y offset", OPT_ARGREQ }, + { 16,'D', "dump", "Dump rejected pieces", OPT_NONE }, + { 17,'a', "adjust-size","Adjust to variable size blocks", OPT_NONE }, + { 18,'W', "walk-mode", "Walk mode (instead of ship mode)", OPT_NONE }, + { 19,'c', "clean-chars","Characters to filter from input", OPT_ARGREQ }, + { 20,'C', "center-char","Center character symbol (for -a)", OPT_ARGREQ }, +}; + +static const int optListN = sizeof(optList) / sizeof(optList[0]); + + +void argShowHelp() +{ + th_print_banner(stdout, th_prog_name, + "[options] [inputfile#2..]"); + + th_args_help(stdout, optList, optListN, 0); +} + + +BOOL argHandleOpt(const int optN, char *optArg, char *currArg) +{ + switch (optN) + { + case 0: + argShowHelp(); + exit(0); + break; + + case 1: + destFile = optArg; + THMSG(1, "Output file '%s'\n", destFile); + break; + + case 2: + th_verbosity++; + break; + + case 3: + th_verbosity = -1; + break; + + case 4: + optMatch = atof(optArg); + THMSG(1, "Match at %1.4f%%\n", optMatch); + break; + + case 5: + optRounds = atoi(optArg); + THMSG(1, "Processing rounds timeout %d\n", optRounds); + break; + + case 12: + optReset = atoi(optArg); + THMSG(1, "Round reset after %d blocks\n", optReset); + break; + + case 10: + optInitialMap = optArg; + THMSG(1, "Using initial map file = '%s'\n", optArg); + break; + + case 11: + optHardDrop = TRUE; + THMSG(1, "Using hard dropping (instant bailout on mismatch).\n"); + break; + + case 14: + optInitialX = atoi(optArg); + THMSG(1, "Initial map X offset = %d\n", optInitialX); + break; + + case 15: + optInitialY = atoi(optArg); + THMSG(1, "Initial map Y offset = %d\n", optInitialY); + break; + + case 16: + THMSG(1, "Dumping rejected blocks.\n"); + optDumpRejected = TRUE; + break; + + case 17: + THMSG(1, "Handling variable size blocks via adjustment.\n"); + optAdjustBlocks = TRUE; + break; + + case 18: + THMSG(1, "Walk mode enabled.\n"); + optWalkMode = TRUE; + break; + + case 19: + optCleanChars = optArg; + THMSG(1, "Removing '%s' from input blocks\n", optArg); + break; + + case 20: + if (strlen(optArg) != 1) + { + THERR("Invalid argument '%s' for -C, only one character symbol can be specified.\n", optArg); + return FALSE; + } + else + { + optCenterCh = optArg[0]; + THMSG(1, "Using '%c' as centering character symbol\n", optCenterCh); + } + break; + + default: + THERR("Unknown option '%s'.\n", currArg); + return FALSE; + break; + } + + return TRUE; +} + + +BOOL argHandleFile(char *currArg) +{ + if (nsrcFiles < MAX_FILES) + { + srcFiles[nsrcFiles] = currArg; + nsrcFiles++; + } + else + { + THERR("Too many input files specified (%d max)!\n", MAX_FILES); + return FALSE; + } + + return TRUE; +} + + +/* Calculate matching percentage of given block against a map, + * with using specified x/y offsets. + */ +float matchBlock(MapBlock *map, MapBlock *match, + const int ox, const int oy, const BOOL hardDrop) +{ + int n, k; + + n = k = 0; + + for (int y = 0; y < match->height; y++) + for (int x = 0; x < match->width; x++) + { + const int + dx = ox + x, + dy = oy + y; + int c1, c2; + + k++; + + if (dx >= 0 && dx < map->width && dy >= 0 && dy < map->height) + c2 = map->data[(dy * map->scansize) + dx]; + else + c2 = 0; + + c1 = match->data[(y * match->scansize) + x]; + + if (c1 == 0 || c2 == 0) + { + // Both empty ... + } + else + if (c1 != 0 && c1 == c2) + { + // Exact match, increase % + n++; + } + else + if (hardDrop) + { + // Mismatch, return failure + return -1; + } + } + + if (k > 0) + return ((float) n * 100.0f) / (float) k; + else + return 0.0f; +} + + +BOOL adjustBlockCenter(MapBlock *block, const char centerCh) +{ + for (int yc = 0; yc < block->height; yc++) + for (int xc = 0; xc < block->width; xc++) + { + int ch = block->data[(yc * block->scansize) + xc]; + if (ch == centerCh) + { + block->xc -= xc; + block->yc -= yc; + return TRUE; + } + } + + return FALSE; +} + + +/* Parse next block (marked by string optPattern) from + * input stream into a mapblock, return NULL if not found or error. + */ +BOOL getLineData(FILE *inFile, char *buf, const size_t bufSize) +{ + if (feof(inFile) || !fgets(buf, bufSize, inFile)) + { + THERR("Unexpected end of file.\n"); + return FALSE; + } + else + return TRUE; +} + + +size_t getLineWidth(char *buf, const size_t bufSize) +{ + size_t width; + + for (width = 0; width < bufSize && buf[width] && buf[width] != '\r' && buf[width] != '\n'; width++); + buf[width] = 0; + + return width; +} + + +MapBlock * parseBlock(FILE *inFile) +{ + MapBlock *tmp = NULL; + int xc, yc, tmpW, tmpH; + char str[4096], *pos = NULL; + BOOL isFound; + size_t i; + + isFound = FALSE; + while (!feof(inFile) && !isFound && fgets(str, sizeof(str), inFile)) + { + if ((pos = strstr(str, ": !!PLR: ")) != NULL && + (strstr(str, "party") || strstr(str, "report"))) + isFound = TRUE; + } + + if (!isFound) + { + THERR("No block start marker found.\n"); + goto err; + } + + // Trim end + for (i = 0; i < sizeof(str) && str[i] && str[i] != '\r' && str[i] != '\n'; i++); + str[i] = 0; + + if (sscanf(pos, ": !!PLR: %d,%d", &xc, &yc) < 2) + { + THERR("Could not get location coordinates:\n'%s'\n", + str); + goto err; + } + + // Check for discardable lines + if (!getLineData(inFile, str, sizeof(str))) + goto err; + + if (!th_strcasecmp(str, "You glance around.")) + { + if (!getLineData(inFile, str, sizeof(str))) + goto err; + } + + // New 'map' output in city maps has an empty line before the data, ignore it + if ((tmpW = getLineWidth(str, sizeof(str))) <= 9) + { + if (!getLineData(inFile, str, sizeof(str))) + goto err; + + tmpW = getLineWidth(str, sizeof(str)); + } + + if (optWalkMode) + { + switch (tmpW) + { + case 31: tmpH = 17; break; + case 13: tmpH = 7; break; + case 9: tmpH = 9; break; + default: + THERR("Invalid block width %d [%d, %d], rejecting.\n", tmpW, xc, yc); + return NULL; + break; + } + } + else + tmpH = tmpW - 8; + + if ((tmp = mapBlockAlloc(tmpW, tmpH)) == NULL) + { + THERR("Could not allocate mapblock (%d, %d)\n", tmpW, tmpH); + goto err; + } + + tmp->xc = xc; + tmp->yc = yc; + + yc = 0; + do + { + i = getLineWidth(str, tmpW); + + if (i != (size_t) tmp->width) + { + THERR("Broken block, line width %d != %d!\n", + i, tmp->width, str); + goto err; + } + + for (xc = 0; xc < tmp->width; xc++) + { + tmp->data[(yc * tmp->scansize) + xc] = str[xc]; + } + + yc++; + } while (!feof(inFile) && (yc < tmp->height) && fgets(str, sizeof(str), inFile)); + + + if (yc < tmp->height) + { + THERR("Broken block, height %d < %d\n", + yc, tmp->height); + goto err; + } + + return tmp; + +err: + mapBlockFree(tmp); + return NULL; +} + + +/* Find the min/max of given coordinates. + */ +void findWorldSize(MapBlock *tmp, + int *worldX0, int *worldY0, + int *worldX1, int *worldY1) +{ + if (tmp->xc < *worldX0) *worldX0 = tmp->xc; + if ((tmp->xc + tmp->width) > *worldX1) *worldX1 = (tmp->xc + tmp->width); + + if (tmp->yc < *worldY0) *worldY0 = tmp->yc; + if ((tmp->yc + tmp->height) > *worldY1) *worldY1 = (tmp->yc + tmp->height); +} + + +int main(int argc, char *argv[]) +{ + BOOL isOK; + int i, currRounds, nmapBlocks, currBlocks, + worldX0, worldY0, worldX1, worldY1, + offsetX = 0, offsetY = 0; + MapBlock *worldMap = NULL, *initialMap = NULL; + MapBlock **mapBlocks = NULL; + + th_init("stitchmap", "Yet Another ASCII Map Auto-Stitcher", "0.5", NULL, NULL); + th_verbosity = 1; + + // Parse arguments + if (!th_args_process(argc, argv, optList, optListN, + argHandleOpt, argHandleFile, OPTH_BAILOUT)) + exit(1); + + if (nsrcFiles < 1) + { + THERR("Nothing to do. (try --help)\n"); + exit(0); + } + + // Read initial map + if (optInitialMap) + { + THMSG(1, "Reading initial map '%s'\n", optInitialMap); + initialMap = mapBlockParseFile(optInitialMap, FALSE); + if (initialMap) + { + THMSG(2, "Initial dimensions %d x %d\n", + initialMap->width, initialMap->height); + + mapBlockClean(initialMap, (unsigned char*) " ", 1); + } + else + { + THERR("Initial map could not be loaded!\n"); + exit(1); + } + } + + /* Read in continuous mapdata and parse it into map blocks + */ + for (nmapBlocks = i = 0; i < nsrcFiles; i++) + { + FILE *tmpFile; + char centerCh; + + if (optCenterCh > 0) + { + centerCh = optCenterCh; + } + else + { + if (optWalkMode) + centerCh = '@'; + else + centerCh = '*'; + } + + + + if ((tmpFile = fopen(srcFiles[i], "rb")) == NULL) + { + THERR("Error opening input file '%s'!\n", + srcFiles[i]); + exit(1); + } + + while (!feof(tmpFile)) + { + MapBlock *tmp; + if ((tmp = parseBlock(tmpFile)) != NULL) + { + if (optAdjustBlocks && !adjustBlockCenter(tmp, centerCh)) + { + THERR("Block center not found, rejected.\n"); + mapBlockFree(tmp); + } + else + { + nmapBlocks++; + mapBlocks = (MapBlock **) th_realloc(mapBlocks, sizeof(MapBlock *) * nmapBlocks); + if (!mapBlocks) + { + fclose(tmpFile); + THERR("Could not allocate/extend mapblock pointer structure (#%d)\n", nmapBlocks); + exit(3); + } + + mapBlockClean(tmp, (unsigned char *) optCleanChars, strlen(optCleanChars)); + mapBlocks[nmapBlocks - 1] = tmp; + } + } + } + + fclose(tmpFile); + } + + THMSG(1, "Total of %d mapblocks read.\n", nmapBlocks); + + if (nmapBlocks <= 0) + { + THERR("No mapblocks, nothing to do.\n"); + exit(11); + } + + + /* ALGORITHM + * --------- + find dimensions of the world map: + for (each block in list) { + if (block->x < mapx0) mapx0 = block->x; else + if (block->x+block->w > mapx1) mapx1 = block->x+block->w; + ... + } + + allocate map + + start placing blocks: + for (n = 0; n < rounds; n++) { + for (each block in list) { + if (check match % at given coordinates > threshold) + place block + } + + if (all blocks placed) break + } + */ + + + // If initial map is available, find a match and determine coords + if (initialMap) + { + initialMap->xc = optInitialX; + initialMap->yc = optInitialY; + } + + // Clear marks + for (i = 0; i < nmapBlocks; i++) + mapBlocks[i]->mark = FALSE; + + + // Get world dimensions + worldX0 = worldY0 = worldX1 = worldY1 = 0; + for (i = 0; i < nmapBlocks; i++) + findWorldSize(mapBlocks[i], &worldX0, &worldY0, &worldX1, &worldY1); + + if (initialMap) + findWorldSize(initialMap, &worldX0, &worldY0, &worldX1, &worldY1); + + // Compute offsets, allocate world map + // FIXME: check dimensions + offsetX = -worldX0; + offsetY = -worldY0; + + if ((worldMap = mapBlockAlloc(worldX1 - worldX0 + 1, worldY1 - worldY0 + 1)) == NULL) + { + THERR("Error allocating world map!\n"); + exit(4); + } + + // Place optional initial map + if (initialMap) + { + if (mapBlockPut(&worldMap, initialMap, offsetX + initialMap->xc, offsetY + initialMap->yc) < 0) + { + THERR("Initial map mapBlockPut() failed!\n"); + exit(9); + } + } + else + { + i = 0; + if (mapBlockPut(&worldMap, mapBlocks[i], offsetX + mapBlocks[i]->xc, offsetY + mapBlocks[i]->yc) < 0) + { + THERR("Initial map mapBlockPut() failed!\n"); + exit(9); + } + mapBlocks[i]->mark = TRUE; + } + + THMSG(1, "Initialized world map of (%d x %d), offset (%d, %d)\n", + worldMap->width, worldMap->height, offsetX, offsetY); + + + // Start placing blocks + currRounds = 0; + isOK = FALSE; + while (currRounds++ < optRounds && !isOK) + { + int usedBlocks; + + // Get number of used blocks + for (usedBlocks = i = 0; i < nmapBlocks; i++) + if (mapBlocks[i]->mark) usedBlocks++; + + // Print out status information + THPRINT(2, "#%d [%d/%d]: ", + currRounds, usedBlocks, nmapBlocks); + + // Place and match blocks + isOK = TRUE; + currBlocks = 0; + for (i = 0; i < nmapBlocks && currBlocks < optReset; i++) + { + MapBlock *tmp = mapBlocks[i]; + + if (!tmp->mark) + { + int qx = offsetX + tmp->xc, + qy = offsetY + tmp->yc; + + isOK = FALSE; + + if (matchBlock(worldMap, tmp, qx, qy, optHardDrop) >= optMatch) + { + if (mapBlockPut(&worldMap, tmp, qx, qy) < 0) + { + THERR("mapBlockPut(%d, %d, %d) failed!\n", + offsetX, offsetY, i); + exit(9); + } + tmp->mark = TRUE; + + currBlocks++; + THPRINT(2, "X"); + } + else + { + THPRINT(2, "."); + +#if 0 + // Debug unmatching blocks + char mysti[512]; + snprintf(mysti, sizeof(mysti), + "[%d]: %d,%d (%d,%d)", + i, qx, qy, tmp->x, tmp->y); + fprintf(stderr, "\n--- %.30s ---\n", mysti); + mapBlockPrint(stderr, tmp); + fprintf(stderr, "---------\n"); + mapBlockPrint(stderr, worldMap); +#endif + } + } + } + + THPRINT(2, "\n"); + } + + /* Output generated map + */ + if (worldMap) + { + int unusedBlocks; + FILE *tmpFile; + + THMSG(1, "Outputting generated map of (%d x %d) ...\n", + worldMap->width, worldMap->height); + + if (destFile == NULL) + tmpFile = stdout; + else + if ((tmpFile = fopen(destFile, "wb")) == NULL) + { + THERR("Error opening output file '%s'!\n", + destFile); + exit(1); + } + + mapBlockPrint(tmpFile, worldMap); + + fclose(tmpFile); + + // Compute number of unused blocks + for (unusedBlocks = i = 0; i < nmapBlocks; i++) + if (!mapBlocks[i]->mark) + { + if (optDumpRejected) + { + fprintf(stderr, "\n#%d: %d,%d (%d,%d)\n", + i, + mapBlocks[i]->xc, mapBlocks[i]->yc, + mapBlocks[i]->xc + offsetX, + mapBlocks[i]->yc + offsetY); + + mapBlockPrint(stderr, mapBlocks[i]); + } + unusedBlocks++; + } + + THMSG(1, "%d mapblocks unused/discarded\n", unusedBlocks); + } + else + { + THERR("No map generated?\n"); + } + + return 0; +}