Commit cdf90534 authored by Colomban Wendling's avatar Colomban Wendling

Improved the tile/tileset loader.

Now there is two parts:
* The Tile Definitions Loader & Manager (tilesdef.[ch])
* The Tile Set Loader & Manager (tileset.[ch])

Updated the test accordingly.
parent 2b1fedc4
......@@ -4,6 +4,7 @@ noinst_PROGRAMS = test
libovcc_la_CPPFLAGS = -DG_LOG_DOMAIN=\"libovcc\"
libovcc_la_SOURCES = tile.c \
tileset.c \
tilesdef.c \
stack.c \
board.c
......
......@@ -18,17 +18,32 @@
*
*/
#include <stdio.h>
#include <libxml/parser.h>
#include <glib.h>
#include <gio/gio.h>
#include "tilesdef.h"
#include "tileset.h"
#include "tile.h"
static gboolean
foreach_cb (OVCCTileSet *set,
OVCCTile *tile,
guint count,
gpointer data)
{
printf ("%.3u x #%.3u\n", count, ovcc_tile_get_id (tile));
return TRUE;
}
int
main (int argc,
char **argv)
{
GFile *file;
OVCCTileSet *tileset;
OVCCTilesDef *tilesdef;
GError *err = NULL;
gboolean success = FALSE;
......@@ -37,15 +52,30 @@ main (int argc,
if (argc >= 2) {
file = g_file_new_for_commandline_arg (argv[1]);
tileset = ovcc_tileset_new ();
if (! ovcc_tileset_load (tileset, file, NULL, &err)) {
g_critical ("Load error: %s", err ? err->message : "???");
tilesdef = ovcc_tilesdef_new ();
if (! ovcc_tilesdef_load (tilesdef, file, &err)) {
g_critical ("Tilesdef load error: %s", err ? err->message : "???");
g_clear_error (&err);
} else {
success = TRUE;
}
ovcc_tileset_free (tileset);
g_object_unref (file);
if (argc >= 3) {
OVCCTileSet *set;
file = g_file_new_for_commandline_arg (argv[2]);
set = ovcc_tileset_new ();
if (! ovcc_tileset_load (set, tilesdef, file, &err)) {
g_critical ("Tileset load error: %s", err ? err->message : "???");
g_clear_error (&err);
success = FALSE;
} else {
ovcc_tileset_foreach (set, foreach_cb, NULL);
}
g_object_unref (file);
ovcc_tileset_free (set);
}
ovcc_tilesdef_free (tilesdef);
}
return success ? 0 : 1;
......
/*
*
* Copyright (C) 2009 Colomban Wendling <ban@herbesfolles.org>
* Jonathan Michalon <studios.chalmion@no-log.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <libxml/parser.h>
#include <libxml/tree.h>
#include "tilesdef.h"
#include "tile.h"
struct s_OVCCTilesDef
{
GHashTable *tiles;
};
static void
free_tile (gpointer data)
{
OVCCTile *tile = data;
ovcc_tile_dump (tile);
ovcc_tile_unref (tile);
}
/**
* @brief Creates a new TilesDef
* @returns The newly created TilesDef
*
*/
OVCCTilesDef *
ovcc_tilesdef_new (void)
{
OVCCTilesDef *def;
def = g_malloc (sizeof *def);
if (def) {
def->tiles = g_hash_table_new_full (g_direct_hash, g_direct_equal,
NULL, /*ovcc_tile_unref*/ free_tile);
}
return def;
}
/**
* @brief Frees a TilesDef
* @param def The TilesDef to free
*
*/
void
ovcc_tilesdef_free (OVCCTilesDef *def)
{
if (def) {
g_hash_table_destroy (def->tiles);
g_free (def);
}
}
/**
* @brief Clears a TilesDef
* @param def The TilesDef to clear.
*
* Removes all tiles in the given tile def.
*/
void
ovcc_tilesdef_clear (OVCCTilesDef *def)
{
g_hash_table_remove_all (def->tiles);
}
/**
* @brief Removes a tile from a def
* @param def A TilesDef
* @param id The ID of the tile to remove from the def.
* @returns TRUE if the tile was removed, false if it wasn't found in the def.
*
*
*/
gboolean
ovcc_tilesdef_remove (OVCCTilesDef *def,
OVCCTileID id)
{
return g_hash_table_remove (def->tiles, GINT_TO_POINTER (id));
}
/**
* @brief Get a tile from a def
* @param def A TilesDef
* @param id The ID of the tile to search for
* @returns The tile of the given ID or NULL if not found
*
* Searches for a tile in a def.
*/
OVCCTile *
ovcc_tilesdef_get_tile (const OVCCTilesDef *def,
OVCCTileID id)
{
return g_hash_table_lookup (def->tiles, GINT_TO_POINTER (id));
}
/**
* @brief Get whether a tile is in a def
* @param def A TilesDef
* @param id The ID of the tile to search for
* @returns TRUE if the tile is found in the def, FALSE otherwise.
*
* Searches for a tile in a def.
*/
gboolean
ovcc_tilesdef_has_tile (const OVCCTilesDef *def,
OVCCTileID id)
{
return ovcc_tilesdef_get_tile (def, id) != NULL;
}
/**
* @brief Gets whether a TilesDef contains any tile
* @param def A TilesDef
* @returns TRUE it the TilesDef is empty, FALSE otherwise.
*
*/
gboolean
ovcc_tilesdef_is_empty (const OVCCTilesDef *def)
{
return g_hash_table_size (def->tiles) == 0;
}
/**
* @brief Give the number of tiles in the TilesDef
* @param def A TilesDef
* @return The number of tiles in the TilesDef.
*
*/
guint
ovcc_tilesdef_size (const OVCCTilesDef *def)
{
return g_hash_table_size (def->tiles);
}
/**
* @brief Add a Tile to a TilesDef
* @param def The TilesDef to use
* @param tile The Tile to add
*
* @note This function adds a reference to the tile; then it you want to leave
* the reverence to the set, you need to unref the tile you given.
*/
void
ovcc_tilesdef_add_tile (OVCCTilesDef *def,
OVCCTile *tile)
{
g_hash_table_insert (def->tiles, GINT_TO_POINTER (ovcc_tile_get_id (tile)),
ovcc_tile_ref (tile));
}
/* FAKE FUNCTION */
static void
print_element_names (xmlNode *a_node, guint d)
{
xmlNode *cur_node;
for (cur_node = a_node; cur_node; cur_node = cur_node->next) {
if (cur_node->type == XML_ELEMENT_NODE) {
xmlAttr *cur_attr;
printf ("%*s%s\n", d * 2, "", cur_node->name);
for (cur_attr = cur_node->properties; cur_attr; cur_attr = cur_attr->next) {
printf ("%*s%s:%s = %s\n", d * 2, "", cur_node->name, cur_attr->name, cur_attr->children->content);
}
}
print_element_names (cur_node->children, d + 1);
}
}
#define TF_TILES "tiles"
#define TF_TILE "tile"
#define TF_TILE_ID "id"
#define TF_CENTER "center"
#define TF_SIDES "sides"
#define TF_SIDE "side"
#define TF_SIDE_POS "position"
#define TF_SIDE_TYPE "type"
#define TF_SIDE_RELS "relations"
#define TF_REL_LEFT "left"
#define TF_REL_RIGHT "right"
#define TF_REL_TOP "top"
#define TF_REL_BOTTOM "bottom"
#define TF_TRUE "true"
#define TF_FALSE "false"
static int
position_from_string (const gchar *type) {
static const struct {
const gchar *name;
int type;
} types[] = {
{ "left", OVCC_TILE_SIDE_POS_LEFT },
{ "top", OVCC_TILE_SIDE_POS_TOP },
{ "right", OVCC_TILE_SIDE_POS_RIGHT },
{ "bottom", OVCC_TILE_SIDE_POS_BOTTOM }
};
size_t i;
if (type) {
for (i = 0; i < G_N_ELEMENTS (types); i++) {
if (g_ascii_strcasecmp (type, types[i].name) == 0) {
return types[i].type;
}
}
}
g_warning ("Invalid side type '%s'", type);
return -1;
}
static gboolean
bool_from_string (const gchar *boolstr) {
static const struct {
const gchar *name;
gboolean value;
} boolstrs[] = {
{ "true", TRUE },
{ "false", FALSE },
};
size_t i;
if (boolstr) {
for (i = 0; i < G_N_ELEMENTS (boolstrs); i++) {
if (g_ascii_strcasecmp (boolstr, boolstrs[i].name) == 0) {
return boolstrs[i].value;
}
}
}
g_warning ("Invalid boolean '%s'", boolstr);
return FALSE;
}
static OVCCTileSideRelation
read_side_relations (xmlNode *node)
{
xmlNode *child;
OVCCTileSideRelation relations = OVCC_TILE_SIDE_REL_NONE;
for (child = node->children; child; child = child->next) {
if (child->type == XML_ELEMENT_NODE) {
if (xmlStrcmp (TF_REL_LEFT, child->name) == 0) {
if (bool_from_string (child->children->content)) {
relations |= OVCC_TILE_SIDE_REL_LEFT;
}
} else if (xmlStrcmp (TF_REL_RIGHT, child->name) == 0) {
if (bool_from_string (child->children->content)) {
relations |= OVCC_TILE_SIDE_REL_RIGHT;
}
} else if (xmlStrcmp (TF_REL_TOP, child->name) == 0) {
if (bool_from_string (child->children->content)) {
relations |= OVCC_TILE_SIDE_REL_TOP;
}
} else if (xmlStrcmp (TF_REL_BOTTOM, child->name) == 0) {
if (bool_from_string (child->children->content)) {
relations |= OVCC_TILE_SIDE_REL_BOTTOM;
}
} /*else {
g_warning ("Malformed file! Unexpected element %s inside %s",
child->name, node->name);
}*/
}
}
return relations;
}
static OVCCTileSide *
read_side (xmlNode *node,
int *pos_)
{
gboolean success = TRUE;
OVCCTileContentType type = OVCC_TILE_CONTENT_TYPE_NONE;
OVCCTileSideRelation relations = OVCC_TILE_SIDE_REL_NONE;
xmlNode *child;
xmlAttr *cur_attr;
int position = -1;
/* read attributes */
for (cur_attr = node->properties; success && cur_attr; cur_attr = cur_attr->next) {
if (cur_attr->type == XML_ATTRIBUTE_NODE) {
/* the position attribute */
if (xmlStrcmp (TF_SIDE_POS, cur_attr->name) == 0) {
position = position_from_string (cur_attr->children->content);
if (position < 0) {
success = FALSE;
}
}
}
}
/* read children */
for (child = node->children; success && child; child = child->next) {
if (child->type == XML_ELEMENT_NODE) {
if (xmlStrcmp (TF_SIDE_TYPE, child->name) == 0) {
g_debug ("Found a side type (%s)", child->children->content);
type = ovcc_tile_content_type_from_string (child->children->content);
} else if (xmlStrcmp (TF_SIDE_RELS, child->name) == 0) {
g_debug ("Found side relations");
relations = read_side_relations (child);
} /*else {
g_warning ("Malformed file! Unexpected element <%s> inside <%s>",
child->name, node->name);
successs = FALSE;
}*/
}
}
if (pos_) *pos_ = position;
return success ? ovcc_tile_side_new (type, relations) : NULL;
}
static OVCCTile *
read_tile (xmlNode *node,
GError **error)
{
xmlNode *child;
xmlAttr *cur_attr;
gboolean success = TRUE;
OVCCTileID id = 0;
OVCCTileContentType center_type = OVCC_TILE_CONTENT_TYPE_NONE;
OVCCTile *tile = NULL;
OVCCTileSide *sides[OVCC_TILE_SIDE_NUM] = {NULL};
/* read attributes */
for (cur_attr = node->properties; success && cur_attr; cur_attr = cur_attr->next) {
if (cur_attr->type == XML_ATTRIBUTE_NODE) {
/* tile ID (t[0-9]+) */
if (xmlStrcmp (TF_TILE_ID, cur_attr->name) == 0) {
char *endptr;
char *nptr = cur_attr->children->content;
id = strtoul (nptr+1, &endptr, 0);
if (nptr[0] != 't' || nptr[1] == 0 || *endptr != 0) {
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
"[%s:%u] Invalid value '%s' for attribute '%s' of element <%s>",
node->doc->URL, node->line, nptr, cur_attr->name, node->name);
success = FALSE;
}
}
#if 0
else {
/* not sure it is good to check the validity here as some attributes
* may be there even if they are not semcific to the tag, but more
* XML-generic like the xml:lang attribute. */
if (xmlStrcmp (cur_attr->ns->prefix, "xml") != 0) {
g_warning ("Malformed file! tile element <%s> have no attribute %s",
node->name, cur_attr->name);
}
break;
}
#endif
}
}
/* read children */
for (child = node->children; success && child; child = child->next) {
if (child->type == XML_ELEMENT_NODE) {
if (xmlStrcmp (TF_CENTER, child->name) == 0) {
g_debug ("Found a center (%s)", child->children->content);
center_type = ovcc_tile_content_type_from_string (child->children->content);
} else if (xmlStrcmp (TF_SIDE, child->name) == 0) {
OVCCTileSide *side;
int side_pos;
side = read_side (child, &side_pos);
if (side) {
sides[side_pos] = side;
}
g_debug ("Found sides");
} /*else {
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
"[%s:%u] Element <%s> contains unknown element <%s>",
child->doc->URL, child->line, node->name, child->name);
success = FALSE;
}*/
}
}
if (success) {
GError *err = NULL;
g_debug ("Tile ID is %d", id);
/* create the tile */
tile = ovcc_tile_new_auto_v (id, sides, center_type, &err);
if (! tile) {
g_set_error (error, 0, 0, "[%s:%u] Failed to load tile #%d: %s",
node->doc->URL, node->line, id, err->message);
g_error_free (err);
}
}
return tile;
}
static gboolean
read_tiles (xmlNode *node,
OVCCTilesDef *def,
GError **error)
{
xmlNode *child;
gboolean success = TRUE;
for (child = node->children; success && child; child = child->next) {
if (child->type == XML_ELEMENT_NODE) {
if (xmlStrcmp (TF_TILE, child->name) == 0) {
OVCCTile *tile;
tile = read_tile (child, error);
if (! tile) {
success = FALSE;
} else {
ovcc_tilesdef_add_tile (def, tile);
ovcc_tile_unref (tile);
}
} /*else {
success = FALSE;
g_warning ("Malformed file! Unexpected element <%s> inside <%s>",
child->name, node->name);
}*/
}
}
return success;
}
static gboolean
read_tiledoc (xmlNode *node,
OVCCTilesDef *def,
GError **error)
{
xmlNode *child;
gboolean success = TRUE;
for (child = node; success && child; child = child->next) {
if (child->type == XML_ELEMENT_NODE) {
if (xmlStrcmp (TF_TILES, child->name) == 0) {
success = read_tiles (child, def, error);
} /*else {
success = FALSE;
g_warning ("Malformed file! Unexpected element <%s> inside <%s>",
child->name, node->name);
}*/
}
}
return success;
}
/**
* @brief Loads a tile description file to a TilesDef
* @param def A TilesDef
* @param tiles The file containing the tiles definitions
* @param error Location to store the error, or NULL to ignore errors
* @returns TRUE on success, FALSE otherwise.
*
*/
gboolean
ovcc_tilesdef_load (OVCCTilesDef *def,
GFile *tiles,
GError **error)
{
gchar *buffer = NULL;
gsize length = 0;
gboolean success = FALSE;
gchar *tiles_uri;
tiles_uri = g_file_get_uri (tiles);
g_debug ("Loading tiles definitions from %s...", tiles_uri);
if (g_file_load_contents (tiles, NULL, &buffer, &length, NULL, error)) {
xmlDoc *doc;
doc = xmlReadMemory (buffer, length, tiles_uri, NULL,
XML_PARSE_DTDVALID | XML_PARSE_PEDANTIC);
if (! doc) {
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
"Invalid XML file");
} else {
xmlNode *root;
root = xmlDocGetRootElement (doc);
/*print_element_names (root, 0);*/
success = read_tiledoc (root, def, error);
if (! success) {
g_warning ("Document read failed");
}
xmlFreeDoc (doc);
}
g_free (buffer);
}
g_free (tiles_uri);
return success;
}
/*
<?xml version="1.0" encoding="utf8"?>
<!DOCTYPE tiles SYSTEM "tiles.dtd">
<tiles>
<tile id="t1">
<center>None</center>
<side position="left">
<type>path</type>
<relations right="true"/>
</side>
<side position="top">
<type>field</type>
<relations>
<left>true</left>
</relations>
</side>
</tile>
<tile id="t2">
<center>Monastery</center>
<side position="left">
<type>field</type>
<relations>
<left>true</left>
<front>true</front>
</relations>
</side>
<side position="bottom">
<type>path</type>
</side>
</tile>
</tiles>
*/