changeset 2088:a0eb0ccd6458

Rename mkspecial -> stitchmap.
author Matti Hamalainen <ccr@tnsp.org>
date Wed, 21 Aug 2019 15:31:06 +0300
parents f6109d5bd545
children 6fbd7ff22922
files Makefile.gen README mkspecial.c stitchmap.c
diffstat 4 files changed, 713 insertions(+), 713 deletions(-) [+]
line wrap: on
line diff
--- 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)))
--- 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.
--- 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 <ccr@tnsp.org>
- * (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> [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;
-}
--- /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 <ccr@tnsp.org>
+ * (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> [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;
+}