Mercurial > hg > batmud > maputils
view mkspecial.c @ 1822:892d5277f1ff
Remove note about the search pattern parser being not very tolerant, it's somewhat better now.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Mon, 30 Oct 2017 12:55:21 +0200 |
parents | 72adabd8e746 |
children | 79dd960610cb |
line wrap: on
line source
/* * mkspecial - Compute complete ASCII map by stitching pieces * together based on a matcher and coordinates * * Programmed by Matti 'ccr' Hämäläinen (Ggr Pupunen) * (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_verbosityLevel++; break; case 3: th_verbosityLevel = -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, int ox, int oy, 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, char centerCh) { for (int y = 0; y < block->height; y++) for (int x = 0; x < block->width; x++) { int c = block->data[(y * block->scansize) + x]; if (c == centerCh) { block->x -= x; block->y -= y; 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, size_t bufSize) { if (feof(inFile) || !fgets(buf, bufSize, inFile)) { THERR("Unexpected end of file.\n"); return FALSE; } else return TRUE; } int getLineWidth(char *buf, 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; int x, y, tmpW, tmpH; uint_t i; BOOL isFound; char s[4096], *p = NULL; isFound = FALSE; while (!feof(inFile) && !isFound && fgets(s, sizeof(s), inFile)) { if ((p = strstr(s, ": !!PLR: ")) != NULL && (strstr(s, "party") || strstr(s, "report"))) isFound = TRUE; } if (!isFound) { THERR("No block start marker found.\n"); return NULL; } for (i = 0; i < sizeof(s) && s[i] && s[i] != '\r' && s[i] != '\n'; i++); s[i] = 0; if (sscanf(p, ": !!PLR: %d,%d", &x, &y) < 2) { THERR("Could not get location coordinates:\n'%s'\n", s); return NULL; } // Check for discardable lines if (!getLineData(inFile, s, sizeof(s))) return NULL; if (!th_strcasecmp(s, "You glance around.")) { if (!getLineData(inFile, s, sizeof(s))) return NULL; } // New 'map' output in city maps has an empty line before the data, ignore it if ((tmpW = getLineWidth(s, sizeof(s))) <= 9) { if (!getLineData(inFile, s, sizeof(s))) return NULL; tmpW = getLineWidth(s, sizeof(s)); } 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, x, y); return NULL; break; } } else tmpH = tmpW - 8; if ((tmp = mapBlockAlloc(tmpW, tmpH)) == NULL) { THERR("Could not allocate mapblock (%d, %d)\n", tmpW, tmpH); return NULL; } tmp->x = x; tmp->y = y; y = 0; do { i = getLineWidth(s, tmpW); if (i != (uint_t) tmp->width) { THERR("Broken block, line width %d != %d!\n", i, tmp->width, s); mapBlockFree(tmp); return NULL; } for (x = 0; x < tmp->width; x++) { tmp->data[(y * tmp->scansize) + x] = s[x]; } y++; } while (!feof(inFile) && (y < tmp->height) && fgets(s, sizeof(s), inFile)); if (y < tmp->height) { THERR("Broken block, height %d < %d\n", y, tmp->height); mapBlockFree(tmp); return NULL; } return tmp; } /* Find the min/max of given coordinates. */ void findWorldSize(MapBlock *tmp, int *worldX0, int *worldY0, int *worldX1, int *worldY1) { if (tmp->x < *worldX0) *worldX0 = tmp->x; if ((tmp->x + tmp->width) > *worldX1) *worldX1 = (tmp->x + tmp->width); if (tmp->y < *worldY0) *worldY0 = tmp->y; if ((tmp->y + tmp->height) > *worldY1) *worldY1 = (tmp->y + 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_verbosityLevel = 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, " "); } 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, 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->x = optInitialX; initialMap->y = 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->x, offsetY + initialMap->y) < 0) { THERR("Initial map mapBlockPut() failed!\n"); exit(9); } } else { i = 0; if (mapBlockPut(&worldMap, mapBlocks[i], offsetX + mapBlocks[i]->x, offsetY + mapBlocks[i]->y) < 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->x, qy = offsetY + tmp->y; 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]->x, mapBlocks[i]->y, mapBlocks[i]->x + offsetX, mapBlocks[i]->y + offsetY); mapBlockPrint(stderr, mapBlocks[i]); } unusedBlocks++; } THMSG(1, "%d mapblocks unused/discarded\n", unusedBlocks); } else { THERR("No map generated?\n"); } return 0; }