Mercurial > hg > batmud > maputils
changeset 28:81a329b34878
Added new utility
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Sun, 10 Dec 2006 01:37:38 +0000 |
parents | a49a85e4a043 |
children | 007cbc05d04f |
files | mkspecial.c |
diffstat | 1 files changed, 477 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mkspecial.c Sun Dec 10 01:37:38 2006 +0000 @@ -0,0 +1,477 @@ +/* + * 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 Tecnic Software productions (TNSP) + */ +#include "maputils.h" +#include <string.h> +#include <strings.h> +#include "th_args.h" +#include "th_string.h" + +#define MAX_FILES (256) + + +/* Variables + */ +DCHAR *progName = NULL; +DINT nsrcFiles = 0; +DCHAR *srcFiles[MAX_FILES], + *destFile = NULL; + +DINT optBlockW = 21, + optBlockH = 21, + optMapFactor = 2, + optRounds = 100; +DBOOL optHardDrop = FALSE; +DFLOAT optMatch = 90.0; +DCHAR *optInitialMap = NULL; + +/* Arguments + */ +t_opt optList[] = { + { 0, '?', "help", "Show this help", 0 }, + { 1, 'o', "output", "Specify output file", 1 }, + { 2, 'v', "verbose", "Be more verbose", 0 }, + { 3, 'q', "quiet", "Be quiet", 0 }, + { 4, 'm', "match", "Match percentage", 1 }, + { 5, 'r', "rounds", "Processing timeout # rounds", 1 }, + { 11,'d', "drop", "Use hard dropping (bailout on first mismatch)", 0 }, + { 8, 'w', "width", "Block width", 1 }, + { 9, 'h', "height", "Block height", 1 }, + { 10,'I', "initial", "Initial map file", 1 }, +}; + +const DINT optListN = (sizeof(optList) / sizeof(t_opt)); + + +void argShowHelp() +{ + th_args_help(stdout, optList, optListN, progName, + "[options] <inputfile> [inputfile#2..]"); +} + + +void argHandleOpt(const DINT optN, DCHAR *optArg, DCHAR *currArg) +{ + switch (optN) { + case 0: + argShowHelp(); + exit(0); + break; + + case 1: + if (optArg) { + destFile = optArg; + THMSG(1, "Output file '%s'\n", destFile); + } else { + THERR("No output filename argument provided.\n"); + exit(1); + } + break; + + case 2: + th_verbosityLevel++; + break; + + case 3: + th_verbosityLevel = -1; + break; + + case 4: + if (optArg) { + optMatch = atof(optArg); + THMSG(1, "Match at %1.4f%%\n", optMatch); + } + break; + + case 5: + if (optArg) { + optRounds = atoi(optArg); + THMSG(1, "Processing rounds timeout %d\n", optRounds); + } + break; + + case 8: + if (optArg) { + optBlockW = atoi(optArg); + THMSG(1, "Map block width = %d\n", optBlockW); + } + break; + + case 9: + if (optArg) { + optBlockH = atoi(optArg); + THMSG(1, "Map block height = %d\n", optBlockH); + } + break; + + case 10: + if (optArg) { + 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; + + default: + THERR("Unknown argument '%s'.\n", currArg); + exit(3); + break; + } +} + + +void argHandleFile(DCHAR *currArg) +{ + if (nsrcFiles < MAX_FILES) { + srcFiles[nsrcFiles] = currArg; + nsrcFiles++; + } else { + THERR("Too many input files specified (%d max)!\n", MAX_FILES); + exit(2); + } +} + + +/* Calculate matching percentage of given block against a map, + * with using specified x/y offsets. + */ +DFLOAT matchBlock(mapblock_t *map, mapblock_t *match, DINT ox, DINT oy) +{ + DINT x, y, c1, c2, dy, dx, n, k; + + n = k = 0; + + for (y = 0; y < match->h; y++) + for (x = 0; x < match->w; x++) { + dx = (ox + x); + dy = (oy + y); + k++; + + if (dx >= 0 && dx < map->w && dy >= 0 && dy < map->h) { + c1 = match->d[(y * match->w) + x]; + c2 = map->d[(dy * map->w) + dx]; + + if (c1 == 0 && c2 == 0) { + /* Both are zero (nothing) */ + } else + if (c1 == c2) { + /* Exact match, increase % */ + n++; + } else + if (c1 == 0 || c2 == 0) { + /* Either is zero */ + } else + if (optHardDrop) { + /* Mismatch, return failure */ + return -1; + } + } + } + + if (k > 0) + return ((DFLOAT) n * 100.0f) / (DFLOAT) k; + else + return 0.0f; +} + + +/* Clean given block from position markers and whitespaces + * TODO: specify position marker(s) somehow instead of hardcoding + */ +void cleanBlock(mapblock_t *map) +{ + DINT x, y, o; + + o = 0; + for (y = 0; y < map->h; y++) + for (x = 0; x < map->w; x++) { + switch (map->d[o]) { + case ' ': + case '*': +// case '@': + map->d[o] = 0; + break; + } + + o++; + } +} + + +/* Parse next block (marked by string optPattern) from + * input stream into a mapblock, return NULL if not found or error. + */ +mapblock_t * parseBlock(FILE *inFile, DINT inW, DINT inH) +{ + mapblock_t *tmp; + DINT x, y, o; + DBOOL isFound; + DCHAR s[4096]; + + isFound = FALSE; + while (!feof(inFile) && !isFound) { + fgets(s, sizeof(s), inFile); + + if (!strncmp(s, "!!PLR: ", 7)) + isFound = TRUE; + } + + if (!isFound) { + THERR("No block start marker found.\n"); + return NULL; + } + + if ((tmp = allocBlock(inW, inH)) == NULL) { + THERR("Could not allocate mapblock (%d, %d)\n", inW, inH); + return NULL; + } + + + for (i = 0; i < sizeof(s) && s[i] && s[i] != '\n'; i++); + s[i] = 0; + if (sscanf(s, "!!PLR: %d,%d", &(tmp->x), &(tmp->y)) < 2) { + THERR("Could not location coordinates\n"); + return NULL; + } + + + o = y = x = 0; + while (!feof(inFile) && (y < tmp->h)) { + DINT i; + fgets(s, sizeof(s), inFile); + + for (i = 0; i < sizeof(s) && s[i] && s[i] != '\n'; i++); + s[i] = 0; + + if (strlen(s) != (DUINT) tmp->w) { + THERR("Broken block, line width %d < %d!\n", + strlen(s), tmp->w, s); + freeBlock(tmp); + return NULL; + } + + for (x = 0; x < tmp->w; x++) { + tmp->d[o++] = s[x]; + } + + y++; + } + + if (y < tmp->h) { + THERR("Broken block, height %d < %d\n", + y, tmp->h); + freeBlock(tmp); + return NULL; + } + + return tmp; +} + + +/* Bruteforce search / block matching .. + */ +DBOOL blockSearchBrute(mapblock_t *map, mapblock_t *b, DINT *x, DINT *y, DFLOAT m) +{ + DINT ox, oy; + DFLOAT v; + + for (oy = -(b->h); oy < (map->h + b->h); oy++) + for (ox = -(b->w); ox < (map->w + b->w); ox++) { + v = matchBlock(map, b, ox, oy); + if (v >= m) { + *x = ox; + *y = oy; + return TRUE; + } + } + + return FALSE; +} + +DBOOL blockSearch(mapblock_t *map, mapblock_t *b, DINT *x, DINT *y, DFLOAT m, DINT q) +{ + DINT ox, oy; + DFLOAT v; + + for (oy = (*y - q); oy < (*y + q); oy++) + for (ox = (*x - q); ox < (*x + q); ox++) { + v = matchBlock(map, b, ox, oy); + if (v >= m) { + *x = ox; + *y = oy; + return TRUE; + } + } + + return FALSE; +} + + +int main(int argc, char *argv[]) +{ + FILE *tmpFile; + DBOOL isOK; + DINT i, n, ox, oy, nmapBlocks = 0; + mapblock_t *map = NULL, *tmp = NULL, *initial = NULL; + mapblock_t **mapBlocks = NULL; + + progName = argv[0]; + th_init("mkspecial", "Yet Another ASCII Map Auto-Stitcher", "0.1", NULL, NULL); + th_verbosityLevel = 1; + + /* Parse arguments */ + th_args_process(argc, argv, optList, optListN, + argHandleOpt, argHandleFile); + + 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); + initial = parseFile(optInitialMap); + if (initial) { + THMSG(2, "Initial dimensions %d x %d\n", + initial->w, initial->h); + } else { + THERR("Initial map could not be loaded!\n"); + exit(1); + } + } + + /* Read in continuous mapdata and parse it into map blocks + */ + for (i = 0; i < nsrcFiles; i++) { + if ((tmpFile = fopen(srcFiles[i], "rb")) == NULL) { + THERR("Error opening input file '%s'!\n", + srcFiles[i]); + exit(1); + } + + while (!feof(tmpFile)) { + if ((tmp = parseBlock(tmpFile, optBlockW, optBlockH, optPattern)) != NULL) { + nmapBlocks++; + mapBlocks = (mapblock_t **) th_realloc(mapBlocks, sizeof(mapblock_t *) * nmapBlocks); + if (!mapBlocks) { + THERR("Could not allocate/extend mapblock pointer structure (#%d)\n", nmapBlocks); + exit(3); + } + cleanBlock(tmp); + mapBlocks[nmapBlocks - 1] = tmp; + } + } + + fclose(tmpFile); + } + + THMSG(1, "Total of %d mapblocks read.\n", nmapBlocks); + + + /* ALGORITHM + * --------- + if (initial map IS available) { + find first initial match > threshold + compute block coordinate offset + } else { + just use 0,0 as coordinate offset + } + + 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 = initial; + } else if (nmapBlocks > 0) { + if (optInverse) + tmp = mapBlocks[nmapBlocks - 1]; + else + tmp = mapBlocks[0]; + + if ((map = allocBlock(tmp->w * optMapFactor, tmp->h * optMapFactor)) == NULL) { + THERR("Could not allocate initial map.\n"); + return 3; + } + + putBlockDo(map, tmp, + (map->w / 2) - (tmp->w / 2), + (map->h / 2) - (tmp->h / 2)); + } + + THMSG(1, "Initialized initial map of (%d x %d)\n", map->w, map->h); + + n = 0; + isOK = FALSE; + while ((n < optRounds) && !isOK) { + DINT q; + + /* Get number of marked blocks */ + for (q = i = 0; i < nmapBlocks; i++) + if (!mapBlocks[i]->mark) q++; + + /* Print out status information */ + n++; + THPRINT(2, "#%d [%d/%d]: ", n, q, nmapBlocks); + + /* Search and match blocks */ + isOK = TRUE; + for (i = 0; i < nmapBlocks; i++) + if (!mapBlocks[i]->mark) { + if (blockSearch(map, mapBlocks[i], &ox, &oy, mv)) { + THPRINT(2, "X"); + if (putBlock(&map, mapBlocks[i], ox, oy) < 0) { + THERR("putBlock(%d, %d, %d) failed!\n", + ox, oy, i); + } + isOK = FALSE; + } else { + THPRINT(2, "."); + } + } + + THPRINT(2, "\n"); + } + + + /* Output generated map + */ + if (map) { + THMSG(1, "Outputting generated map ...\n"); + if (destFile == NULL) + tmpFile = stdout; + else if ((tmpFile = fopen(destFile, "wb")) == NULL) { + THERR("Error opening output file '%s'!\n", + destFile); + exit(1); + } + + printBlock(tmpFile, map); + + fclose(tmpFile); + + for (n = i = 0; i < nmapBlocks; i++) + if (!mapBlocks[i]->mark) n++; + + THMSG(1, "%d mapblocks unused/discarded\n", n); + } else { + THERR("No map generated?\n"); + } + + return 0; +}