view mkmap.c @ 30:2eceda1c86ab

Fixes
author Matti Hamalainen <ccr@tnsp.org>
date Sun, 10 Dec 2006 02:38:23 +0000
parents a49a85e4a043
children 25e473a82ce3
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
 */
DCHAR	*progName = NULL;
DINT	nsrcFiles = 0;
DCHAR	*srcFiles[MAX_FILES],
	*destFile = NULL;

DINT	optBlockW = 21,
	optBlockH = 21,
	optMapFactor = 2,
	optRounds = 100;
DBOOL	optInverse = FALSE,
	optHardDrop = FALSE;
DFLOAT	optMatch = 90.0;
DCHAR	*optPattern = "You quack 'pfs.'";
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 },
	{ 6, 's', "start",	"Map block start marker string", 1 },
	{ 7, 'i', "inverse",	"Use inverse search of blocks", 0 },
	{ 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 6:
		if (optArg) {
			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:
		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;
		case 'X':
			map->d[o] = '.';
			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, DCHAR *inPattern)
{
	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, 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 = x = 0;
	while (!feof(inFile) && (y < tmp->h)) {
		DUINT i;
		fgets(s, sizeof(s), inFile);
		
		for (i = 0; i < sizeof(s) && s[i] && s[i] != '\r' && 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 ox, oy;
	DFLOAT v;
	
	for (oy = (*y - b->h); oy < (*y + b->h); oy++)
	for (ox = (*x - b->w); ox < (*x + b->w); ox++) {
		v = matchBlock(map, b, ox, oy);
		if (v >= m) {
			*x = ox;
			*y = oy;
			return TRUE;
		}
	}
	
	return blockSearchBrute(map, b, x, y, m);
}


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("mkmap", "ASCII Map Auto-Stitcher", "0.3", 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);
	
	/* Start matching the blocks with bruteforce
	 */
	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) {
		DFLOAT mv = optMatch;
		n++;
		THPRINT(2, "Round #%d [%1.2f%%]: ", n, mv);

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