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;
+}