view mkmap.c @ 79:b821a8840c6b

Bailout on invalid options.
author Matti Hamalainen <ccr@tnsp.org>
date Sat, 16 Dec 2006 08:30:43 +0000
parents 8bad1e87b45d
children d422585c19b9
line wrap: on
line source

/*
 * mkmap - Compute complete ASCII map by stitching pieces
 * together with a semi-heuristic+fallback bruteforce algorithm
 *
 * 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
 */
char	*progName = NULL;
int	nsrcFiles = 0;
char	*srcFiles[MAX_FILES],
	*destFile = NULL;

int	optBlockW = 21,
	optBlockH = 21,
	optMapFactor = 2,
	optRounds = 100;
BOOL	optInverse = FALSE,
	optHardDrop = FALSE;
float	optMatch = 90.0f, optFudge = 50.0f;
char	*optPattern = "You quack 'pfs.'";
char	*optInitialMap = NULL;


/* Arguments
 */
optarg_t 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 },
	{ 12, 'f', "fudge",	"Matcher prevblock fudge %", OPT_ARGREQ },
	{ 5, 'r', "rounds",	"Processing timeout # rounds", OPT_ARGREQ },
	{ 6, 's', "start",	"Map block start marker string", OPT_ARGREQ },
	{ 7, 'i', "inverse",	"Use inverse search of blocks", OPT_NONE },
	{ 11,'d', "drop",	"Use hard dropping (bailout on first mismatch)", OPT_NONE },
	{ 8, 'w', "width",	"Block width", OPT_ARGREQ },
	{ 9, 'h', "height",	"Block height", OPT_ARGREQ },
	{ 10,'I', "initial",	"Initial map file", OPT_ARGREQ },
};

const int optListN = (sizeof(optList) / sizeof(optarg_t));


void argShowHelp()
{
	th_args_help(stdout, optList, optListN, progName,
		"[options] <inputfile> [inputfile#2..]");
}


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);
		if (optMatch < 10.0 || optMatch > 100.0) {
			THERR("Invalid match value %1.3f (x > 100% || x < 10%)\n",
				optMatch);
			return FALSE;
		}
		THMSG(1, "Match at %1.4f%%\n", optMatch);
		break;

	case 12:
		optFudge = atof(optArg);
		if (optFudge < 1.0 || optFudge > 100.0) {
			THERR("Invalid fudge value %1.3f (x > 100% || x < 1%)\n",
				optMatch);
			return FALSE;
		}
		THMSG(1, "Matching fudge factor = %1.4f%%\n", optFudge);
		break;

	case 5:
		optRounds = atoi(optArg);
		if (optRounds < 1) {
			THERR("Invalid rounds timeout %d! Must be > 1!\n",
				optRounds);
			return FALSE;
		}

		THMSG(1, "Processing rounds timeout = %d\n", optRounds);
		break;

	case 6:
		optPattern = optArg;
		THMSG(1, "Map block start marker = '%s'\n", optPattern);
		break;

	case 7:
		optInverse = TRUE;
		THMSG(1, "Using inverse processing of blocks.\n");
		break;

	case 8:
		optBlockW = atoi(optArg);
		if (optBlockW < 1) {
			THERR("Invalid block width %d, must be >= 1\n", optBlockW);
			return FALSE;
		}
		THMSG(1, "Map block width = %d\n", optBlockW);
		break;

	case 9:
		optBlockH = atoi(optArg);
		if (optBlockH < 1) {
			THERR("Invalid block height %d, must be >= 1\n", optBlockH);
			return FALSE;
		}
		THMSG(1, "Map block height = %d\n", optBlockH);
		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;

	default:
		THERR("Unknown argument '%s'.\n", currArg);
		return FALSE;
	}

	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_t *map, mapblock_t *match, int ox, int oy, BOOL matchEmpty, BOOL hardDrop)
{
	int 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);
		
		if (dx >= 0 && dx < map->w && dy >= 0 && dy < map->h)
			c2 = map->d[(dy * map->w) + dx];
		else
			c2 = 0;
		
		c1 = match->d[(y * match->w) + x];

		if ((c1 != 0 && c2 == 0) || (c1 == 0 && c2 != 0)) {
			/* Other is empty */
			if (matchEmpty) n++;
			k++;
		} else 
		if (c1 == 0 && c2 == 0) {
			/* Both empty ... */
		} else
		if (c1 == c2) {
			/* Exact match, increase % */
			n++;
			k++;
		} else
		if (hardDrop) {
			/* Mismatch, return failure */
			return -1;
		}
	}
	
	if (k > 0)
		return ((float) n * 100.0f) / (float) k;
	else
		return 0.0f;
}


/* 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, int inW, int inH, char *inPattern)
{
	mapblock_t *tmp;
	int y, o;
	BOOL isFound;
	char s[4096];
	
	isFound = FALSE;
	while (!feof(inFile) && !isFound && fgets(s, sizeof(s), inFile)) {
		if (!strncmp(s, inPattern, strlen(inPattern)))
			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;
	}
	
	o = y = 0;
	while (!feof(inFile) && (y < tmp->h)) {
		int x = 0, c = 0;
		
		while (x < tmp->w) {
			c = fgetc(inFile);
			if (c == '\n' || c == EOF)
				break;
			else
				tmp->d[o++] = c;
			x++;
		}
		
		while ((c = fgetc(inFile)) != EOF && c != '\n') x++;

		if (x != tmp->w) {
			THERR("Broken block, line width %d != %d on block y=%d\n",
				x, tmp->w, y);
			freeBlock(tmp);
			return NULL;
		}
		
		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 ..
 */
BOOL blockSearchBruteDo2(mapblock_t *map, mapblock_t *b,
	int *x, int *y, float m,
	int dx, int dy, float *vm)
{
	int ox, oy;
	float v;

THPRINT(3, "\nDo2: x=%d, y=%d, m=%1.2f, dx=%d, dy=%d, vm=%1.2f\n",
	*x, *y, m, dx, dy, *vm);
	
	for (oy = (*y - dy); oy < (*y + dy); oy++)
	for (ox = (*x - dx); ox < (*x + dx); ox++) {
		v = matchBlock(map, b, ox, oy, FALSE, optHardDrop);
		if (v >= m) {
			*vm = v;
			*x = ox;
			*y = oy;
			return TRUE;
		}
	}

	return FALSE;
}

BOOL blockSearchBruteDo(mapblock_t *map, mapblock_t *b,
	int *x, int *y, float m,
	int dx, int dy, float *vm, int skipx, int skipy, int *chkx, int *chky)
{
	int ox, oy;
	float v;


	
THPRINT(3, "\nDo: m=%1.2f, dx=%d, dy=%d, vm=%1.2f, skipx=%d, skipy=%d\n",
	m, dx, dy, *vm, skipx, skipy);


	for (oy = -(dy); oy < (map->h + dy); oy += skipy)
	for (ox = -(dx); ox < (map->w + dx); ox += skipx) {
		v = matchBlock(map, b, ox+skipx-1, oy+skipy-1, FALSE, optHardDrop);
		if (v >= *vm) {
			*vm = v;
			*chkx = ox;
			*chky = oy;
		}
		
		if (v >= m) {
			*x = ox;
			*y = oy;
			return TRUE;
		}
	}
	
	return FALSE;
}


BOOL blockSearchBrute(mapblock_t *map, mapblock_t *b, int *x, int *y, float m, float q)
{
	int dx, dy, skip, chkx, chky;
	float pvm = m * 0.5f, vm = -1;
	
	
	for (skip = 64; skip >= 1; skip /= 2) {
		dx = ((float) b->w) * q;
		dy = ((float) b->h) * q;
		
		if (blockSearchBruteDo(map, b, x, y, m,
			dx, dy, &vm, skip, skip, &chkx, &chky))
			return TRUE;
		
		THPRINT(3, "[%d, %d: vm=%1.2f, pvm=%1.2f]\n", chkx, chky, vm, pvm);
		if (vm > pvm) {
			pvm = vm;
			if (blockSearchBruteDo2(map, b, &chkx, &chky, m,
				skip/2, skip/2, &vm))
				return TRUE;
			THPRINT(3, "[Q: %d, %d: vm=%1.2f, pvm=%1.2f]\n", chkx, chky, vm, pvm);
		}
	}
	
	return FALSE;
}


BOOL blockSearch(mapblock_t *map, mapblock_t *b, int *x, int *y, float m, float q)
{
	int ox, oy, dx, dy;
	float v;
	
	dx = ((float) b->w) * q;
	dy = ((float) b->h) * q;

	for (oy = (*y - dy); oy < (*y + dy); oy++)
	for (ox = (*x - dx); ox < (*x + dx); ox++) {
		v = matchBlock(map, b, ox, oy, FALSE, optHardDrop);
		if (v >= m) {
			*x = ox;
			*y = oy;
			return TRUE;
		}
	}

	THPRINT(2, "?");
	
	return blockSearchBrute(map, b, x, y, m, q);
}


int main(int argc, char *argv[])
{
	FILE *tmpFile;
	BOOL isOK, isFirst;
	int i, n, ox, oy, nmapBlocks = 0;
	mapblock_t *worldMap = NULL, *tmp = NULL, *initialMap = NULL;
	mapblock_t **mapBlocks = NULL;

	progName = argv[0];
	th_init("mkmap", "ASCII Map Auto-Stitcher", "0.3", NULL, NULL);
	th_verbosityLevel = 1;
	
	/* Parse arguments */
	if (!th_args_process(argc, argv, optList, optListN,
		argHandleOpt, argHandleFile, TRUE))
		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 = parseFile(optInitialMap);
		if (initialMap) {
			THMSG(2, "Initial dimensions %d x %d\n",
				initialMap->w, initialMap->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, " *X");
				mapBlocks[nmapBlocks - 1] = tmp;
			}
		}
		
		fclose(tmpFile);
	}
	
	THMSG(1, "Total of %d mapblocks read.\n", nmapBlocks);
	

	/* Start matching the blocks with bruteforce
	 */
	if (initialMap) {
		worldMap = initialMap;
	} else if (nmapBlocks > 0) {
		if (optInverse)
			tmp = mapBlocks[nmapBlocks - 1];
		else
			tmp = mapBlocks[0];
		
		if ((worldMap = allocBlock(tmp->w * optMapFactor, tmp->h * optMapFactor)) == NULL) {
			THERR("Could not allocate world map.\n");
			exit(3);
		}
		
		putBlockDo(worldMap, tmp,
			(worldMap->w / 2) - (tmp->w / 2),
			(worldMap->h / 2) - (tmp->h / 2));
	} else {
		if ((worldMap = allocBlock(optBlockW * optMapFactor, optBlockH * optMapFactor)) == NULL) {
			THERR("Could not allocate world map.\n");
			exit(3);
		}
	}

	
	THMSG(1, "Initialized world map of (%d x %d)\n",
		worldMap->w, worldMap->h);

	n = 0;
	isFirst = TRUE;
	isOK = FALSE;
	while ((n < optRounds) && !isOK) {
		n++;
		THPRINT(2, "Round #%d: ", n);

		isOK = TRUE;
		for (i = 0; i < nmapBlocks; i++)
		if (!mapBlocks[i]->mark) {
			if ((isFirst &&
			blockSearchBrute(worldMap, mapBlocks[i], &ox, &oy, optMatch, optFudge/100.0f)) ||
			blockSearch(worldMap, mapBlocks[i], &ox, &oy, optMatch, optFudge/100.0f)) {
				THPRINT(2, "X");
				if (putBlock(&worldMap, mapBlocks[i], ox, oy, 15) < 0) {
					THERR("putBlock(%d, %d, %d) failed!\n",
						ox, oy, i);
				}
				isFirst = isOK = FALSE;
			} else {
				THPRINT(2, ".");
			}
		}
		
		THPRINT(2, "\n");
	}
	

	/* Output generated map
	 */
	if (worldMap) {
		THMSG(1, "Outputting generated map of (%d x %d) ...\n",
			worldMap->w, worldMap->h);

		if (destFile == NULL)
			tmpFile = stdout;
		else if ((tmpFile = fopen(destFile, "wb")) == NULL) {
			THERR("Error opening output file '%s'!\n",
				destFile);
			exit(1);
		}
	
		printBlock(tmpFile, worldMap);
	
		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");
	}
	
	exit(0);
	return 0;
}