changeset 16:621c815c9509

tools: implement basic molko-map stub, continue #2448
author David Demelier <markand@malikania.fr>
date Tue, 07 Jan 2020 21:45:03 +0100
parents 98e091b9251e
children fc71221b7bee
files INSTALL.md Makefile tools/molko-map.c
diffstat 3 files changed, 380 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/INSTALL.md	Tue Jan 07 21:45:03 2020 +0100
+++ b/INSTALL.md	Tue Jan 07 21:45:03 2020 +0100
@@ -8,6 +8,7 @@
 
 - C99 compliant compiler,
 - POSIX system (make, ar, shell),
+- [Expat][], XML parsing library,
 - [SDL2][], Multimedia library,
 - [SDL2_image][], Image loading addon for SDL2,
 - [SDL2_ttf][], Fonts addon for SDL2,
@@ -37,6 +38,7 @@
 
 - *make*
 - *mingw-w64-x86_64-gcc*
+- *mingw-w64-x86_64-expat*
 - *mingw-w64-x86_64-SDL2*
 - *mingw-w64-x86_64-SDL2_image*
 - *mingw-w64-x86_64-SDL2_mixer*
@@ -44,6 +46,7 @@
 
 Note: replace `x86_64` with `i686` if you have a deprecated system.
 
+[Expat][]: https://libexpat.github.io
 [SDL2]: http://libsdl.org
 [SDL2_image]: https://www.libsdl.org/projects/SDL_image
 [SDL2_ttf]: https://www.libsdl.org/projects/SDL_ttf
--- a/Makefile	Tue Jan 07 21:45:03 2020 +0100
+++ b/Makefile	Tue Jan 07 21:45:03 2020 +0100
@@ -19,8 +19,7 @@
 .POSIX:
 
 CC=             clang
-CFLAGS=         -O3 -DNDEBUG -D_XOPEN_SOURCE=700 -std=c99 -Wall -Wextra
-CPPFLAGS=       -MMD
+CFLAGS=         -MMD -O3 -DNDEBUG -D_XOPEN_SOURCE=700 -std=c99 -Wall -Wextra
 PROG=           molko
 LIB=            libmolko.a
 SRCS=           src/animation.c \
@@ -30,25 +29,37 @@
                 src/sprite.c \
                 src/texture.c \
                 src/window.c
-TESTS=          tests/test-color.c
 OBJS=           ${SRCS:.c=.o}
 DEPS=           ${SRCS:.c=.d}
 
 SDL_CFLAGS=     `pkg-config --cflags sdl2 SDL2_image SDL2_ttf`
 SDL_LDFLAGS=    `pkg-config --libs sdl2 SDL2_image SDL2_ttf`
 
+EXPAT_CFLAGS=   `pkg-config --cflags expat`
+EXPAT_LDFLAGS=  `pkg-config --libs expat`
+
+TESTS=          tests/test-color.c
+TESTS_INCS=     -I extern/libgreatest -I src ${SDL_CFLAGS}
+TESTS_LIBS=     ${LIB} ${SDL_LDFLAGS} ${LDFLAGS}
+TESTS_OBJS=     ${TESTS:.c=}
+TESTS_DEPS=     ${TESTS:.c=.d}
+
+TOOLS=          tools/molko-map.c
+TOOLS_OBJS=     ${TOOLS:.c=}
+TOOLS_DEPS=     ${TOOLS:.c=.d}
+
 .SUFFIXES:
 .SUFFIXES: .c .o
 
 all: ${PROG}
 
--include ${DEPS}
+-include ${DEPS} ${TESTS_DEPS} ${TOOLS_DEPS}
 
 .c.o:
-	${CC} ${SDL_CFLAGS} ${CPPFLAGS} ${CFLAGS} -c $< -o $@
+	${CC} ${SDL_CFLAGS} ${CFLAGS} -c $< -o $@
 
 .c:
-	${CC} -I extern/libgreatest -I src -o $@ ${CPPFLAGS} ${CFLAGS} $< ${LIB} ${SDL_LDFLAGS} ${LDFLAGS}
+	${CC} ${TESTS_INCS} -o $@ ${CFLAGS} $< ${TESTS_LIBS}
 
 ${LIB}: ${OBJS}
 	${AR} -rcs $@ ${OBJS}
@@ -56,10 +67,19 @@
 ${PROG}: ${LIB} src/main.o
 	${CC} -o $@ src/main.o ${LIB} ${SDL_LDFLAGS} ${LDFLAGS}
 
-clean:
-	rm -f molko ${OBJS} ${DEPS}
+${TESTS_OBJS}: ${LIB}
 
-tests: ${TESTS:.c=}
+tests: ${TESTS_OBJS}
 	for t in $?; do ./$$t; done
 
-.PHONY: clean
+tools: ${TOOLS_OBJS}
+
+tools/molko-map: tools/molko-map.c
+	${CC} -o $@ $< ${CFLAGS} ${EXPAT_CFLAGS} ${EXPAT_LDFLAGS}
+
+clean:
+	rm -f molko ${LIB} ${OBJS} ${DEPS}
+	rm -f ${TESTS_OBJS} ${TESTS_DEPS}
+	rm -f ${TOOLS_OBJS} ${TOOLS_DEPS}
+
+.PHONY: clean tests tools
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/molko-map.c	Tue Jan 07 21:45:03 2020 +0100
@@ -0,0 +1,347 @@
+/*
+ * molko-map.c -- convert tiled .tmx into custom files
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <assert.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <expat.h>
+
+enum state {
+	STATE_ROOT,             /* no tag read yet */
+	STATE_MAP,              /* top <map> */
+	STATE_MAP_PROPERTIES,   /* <map><properties> */
+	STATE_TILESET,          /* <tileset> */
+	STATE_TILESET_IMAGE,    /* <tileset><image> */
+	STATE_LAYER,            /* <layer> */
+	STATE_LAYER_DATA,       /* <layer><data> */
+};
+
+enum layer {
+	LAYER_NONE,
+	LAYER_BACKGROUND,
+	LAYER_FOREGROUND
+};
+
+struct context {
+	enum state state;
+	enum layer layer;
+};
+
+static const int layer_indices[] = {
+	[LAYER_NONE] = -1,
+	[LAYER_BACKGROUND] = 0,
+	[LAYER_FOREGROUND] = 1
+};
+
+static void on_data(struct context *, const char *, const char **);
+static void on_image(struct context *, const char *, const char **);
+static void on_layer(struct context *, const char *, const char **);
+static void on_map(struct context *, const char *, const char **);
+static void on_properties(struct context *, const char *, const char **);
+static void on_property(struct context *, const char *, const char **);
+static void on_tile(struct context *, const char *, const char **);
+static void on_tileset(struct context *, const char *, const char **);
+static void die(const char *, ...);
+
+static const struct {
+	const char *name;
+	void (*begin)(struct context *, const char *, const char **);
+	void (*end)(struct context *, const char *);
+} handlers[] = {
+	{ "data",       on_data,        NULL    },
+	{ "image",      on_image,       NULL    },
+	{ "layer",      on_layer,       NULL    },
+	{ "map",        on_map,         NULL    },
+	{ "properties", on_properties,  NULL    },
+	{ "property",   on_property,    NULL    },
+	{ "tile",       on_tile,        NULL    },
+	{ "tileset",    on_tileset,     NULL    },
+	{ NULL,         NULL,           NULL    },
+};
+
+static bool
+is_known_map_property(const char *name)
+{
+	static const char *known[] = {
+		"title",
+		NULL
+	};
+
+	for (size_t i = 0; known[i]; ++i)
+		if (strcmp(known[i], name) == 0)
+			return true;
+
+	return false;
+}
+
+static void
+write_map_property(const char **attrs)
+{
+	/*
+	 * This is how <map><properties> are defined:
+	 *
+	 *             0         1          2          3
+	 * <property name="PROPERTY NAME" value="PROPERTY VALUE">
+	 */
+	for (size_t i = 0; attrs[i]; i += 4) {
+		if (is_known_map_property(attrs[i + 1]))
+			printf("%s|%s\n", attrs[i + 1], attrs[i + 3]);
+		else
+			die("unsupported map property: %s\n", attrs[i + 1]);
+	}
+}
+
+static const char *
+find_attr(const char **attrs, const char *key)
+{
+	for (size_t i = 0; attrs[i]; i += 2)
+		if (strcmp(attrs[i], key) == 0)
+			return attrs[i + 1];
+
+	return NULL;
+}
+
+static void
+on_map(struct context *ctx, const char *name, const char **attrs)
+{
+	(void)ctx;
+	(void)name;
+	(void)attrs;
+
+	ctx->state = STATE_MAP;
+}
+
+static void
+on_properties(struct context *ctx, const char *name, const char **attrs)
+{
+	(void)name;
+	(void)attrs;
+
+	/*
+	 * <properties> exist in different parent tags
+	 */
+	switch (ctx->state) {
+	case STATE_MAP:
+		ctx->state = STATE_MAP_PROPERTIES;
+		break;
+	default:
+		fprintf(stderr, "warning: unsupported <properties>\n");
+		break;
+	}
+}
+
+static void
+on_property(struct context *ctx, const char *name, const char **attrs)
+{
+	(void)name;
+	(void)attrs;
+
+	switch (ctx->state) {
+	case STATE_MAP_PROPERTIES:
+		write_map_property(attrs);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+on_layer(struct context *ctx, const char *name, const char **attrs)
+{
+	(void)name;
+
+	/*
+	 * The layer is defined like this:
+	 *
+	 * Values of attrs:
+	 *          0       1       IGNORED
+	 * <layer name="background" width="10" height="10">
+	 */
+	const char *layername = find_attr(attrs, "name");
+
+	if (!layername)
+		die("layer is missing 'name' attribute\n");
+
+	if (strcmp(layername, "background") == 0)
+		ctx->layer = LAYER_BACKGROUND;
+	else
+		die("invalid '%s' value for 'name' property\n", layername);
+
+	ctx->state = STATE_LAYER;
+}
+
+static void
+on_data(struct context *ctx, const char *name, const char **attrs)
+{
+	(void)name;
+	(void)attrs;
+
+	if (ctx->state != STATE_LAYER)
+		die("reading <data> outside a <layer> node\n");
+
+	ctx->state = STATE_LAYER_DATA;
+}
+
+static void
+on_tile(struct context *ctx, const char *name, const char **attrs)
+{
+	(void)name;
+
+	/* TODO: add more layer */
+	if (ctx->state != STATE_LAYER_DATA)
+		die("reading <tile> outside a <data> node\n");
+
+	/*
+	 * The tile is defined as following:
+	 *
+	 * Values of attrs:
+	 *        0   1
+	 * <tile gid="1" />
+	 */
+	const char *gid = find_attr(attrs, "gid");
+
+	if (!gid)
+		die("missing 'gid' attribute in <tile> node\n");
+
+	printf("tile|%d|%s\n", layer_indices[ctx->layer], gid);
+}
+
+static void
+on_tileset(struct context *ctx, const char *name, const char **attrs)
+{
+	(void)ctx;
+	(void)name;
+	(void)attrs;
+
+	ctx->state = STATE_TILESET;
+}
+
+static void
+on_image(struct context *ctx, const char *name, const char **attrs)
+{
+	(void)name;
+
+	if (ctx->state != STATE_TILESET)
+		die("reading <image> outside a <tileset> node\n");
+
+	/**
+	 * The tile is defined as following:
+	 *
+	 * Values of attrs:
+	 *        0   1
+	 * <image source="name.png" ... />
+	 */
+	const char *src = find_attr(attrs, "source");
+
+	if (!src)
+		die("missing 'source' attribute in <image> node\n");
+
+	printf("tileset|%s\n", src);
+}
+
+static void
+on_start_element(struct context *ctx, const char *name, const char **attrs)
+{
+	assert(ctx);
+	assert(name);
+	assert(attrs);
+
+	size_t i;
+
+	for (i = 0; handlers[i].name; ++i) {
+		if (strcmp(name, handlers[i].name) == 0) {
+			handlers[i].begin(ctx, name, attrs);
+			break;
+		}
+	}
+
+	if (!handlers[i].name)
+		fprintf(stderr, "unsupported <%s> tag\n", name);
+}
+
+static void
+on_end_element(void *data, const char *name)
+{
+	(void)data;
+	(void)name;
+#if 0
+	size_t i;
+
+	for (i = 0; handlers[i].name; ++i) {
+		if (strcmp(name, handlers[i].name) == 0) {
+			handlers[i].end(ctx, name, attrs);
+			break;
+		}
+	}
+
+	if (!handlers[i].name)
+		fprintf(stderr, "unsupported <%s> tag\n", name);
+#endif
+}
+
+static void
+run(XML_Parser parser)
+{
+	char buffer[BUFSIZ];
+	size_t nbread;
+	struct context ctx = {0};
+
+	XML_SetUserData(parser, &ctx);
+
+	while ((nbread = fread(buffer, 1, sizeof (buffer), stdin)) > 0) {
+		bool done = nbread < sizeof (buffer);
+
+		if (XML_Parse(parser, buffer, nbread, done) == XML_STATUS_ERROR) {
+			fprintf(stderr, "%lu: %s\n",
+			    XML_GetCurrentLineNumber(parser),
+			    XML_ErrorString(XML_GetErrorCode(parser)));
+			exit(1);
+		}
+	}
+}
+
+static void
+die(const char *fmt, ...)
+{
+	assert(fmt);
+
+	va_list ap;
+
+	va_start(ap, fmt);
+	vfprintf(stderr, fmt, ap);
+	va_end(ap);
+	exit(1);
+}
+
+int
+main(void)
+{
+	XML_Parser parser;
+
+	parser = XML_ParserCreate(NULL);
+	XML_SetElementHandler(parser,
+		(XML_StartElementHandler)on_start_element, on_end_element);
+
+	run(parser);
+
+	XML_ParserFree(parser);
+}