Commit 7d0bf4b7 authored by Colomban Wendling's avatar Colomban Wendling

Update TilesDef for the new tile descriptions and rewrite it in Vala

parent 2326bdfd
<!ELEMENT tiles (tile+)>
<!ELEMENT tile (center?,side+)>
<!ELEMENT center (#PCDATA)>
<!ELEMENT side (type,relations?,attributes?)>
<!ELEMENT type (#PCDATA)>
<!ELEMENT relations (left|right|top|bottom)*>
<!ELEMENT left (#PCDATA)>
<!ELEMENT right (#PCDATA)>
<!ELEMENT top (#PCDATA)>
<!ELEMENT bottom (#PCDATA)>
<!ELEMENT attributes (bonus)*>
<!ELEMENT bonus (#PCDATA)>
<!ELEMENT tile (object+)>
<!ELEMENT object (link*,attribute*)>
<!ELEMENT link (#PCDATA)>
<!ELEMENT attribute (#PCDATA)>
<!ATTLIST tile id ID #REQUIRED>
<!ATTLIST side position (left|right|top|bottom) #REQUIRED>
<!ATTLIST tile id ID #REQUIRED>
<!ATTLIST object name (#PCDATA) #IMPLIED>
<!ATTLIST object type (field|path|city|monastery) #REQUIRED>
......@@ -12,15 +12,14 @@ libovcc_la_SOURCES = enumtypes.c \
tile.vala \
tileobject.vala \
tileset.c \
tilesdef.c \
tilesdef.vala \
utils.vala \
xmlutils.c
ovccinclude_HEADERS = ovcc.h \
board.h \
game.h \
player.h \
tileset.h \
tilesdef.h
tileset.h
test_LDADD = libovcc.la -lpthread
test_SOURCES = test.c
......
/*
*
* 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 "tilesdef.h"
#include <libxml/parser.h>
#include <libxml/tree.h>
#include "typeutils.h"
#include "xmlutils.h"
#include "tile.h"
/**
* SECTION: tilesdef
* @short_description: Tiles definitions loading and management
* @see_also: #OVCCTile, #OVCCTileSet
*
* This module manages definitions of Tiles. Definitions of tiles are a set of
* loaded tiles, generally from a XML file defining them, but they can be added
* manually too.
*
* Tile definitions are represented by #OVCCTilesDef objects.
*
* A Tiles definition object is created with ovcc_tilesdef_new() and freed using
* ovcc_tilesdef_unref(). Tiles definitions can be loaded from XML definitions
* with ovcc_tilesdef_load();
* Adding a tile to a Tiles definition is done using ovcc_tilesdef_add(),
* removing one is done using ovcc_tilesdef_remove(), and removing all of them
* is done using ovcc_tilesdef_clear().
*
* <example>
* <title>Simple loading of tiles definitions from an URI</title>
* <programlisting>
* #include <glib.h>
* #include <gio/gio.h>
* #include "tileset.h"
*
* OVCCTilesDef *
* load_our_tiles (const gchar *uri)
* {
* OVCCTilesDef *tiles;
* GFile *file;
* GError *err = NULL;
*
* file = g_file_new_for_uri (uri);
* tiles = ovcc_tilesdef_new ();
* if (! ovcc_tilesdef_load (tiles, file, &err)) {
* g_critical ("Failed to load tiles: %s", err->message);
* g_error_free (err);
* // Return NULL if the tiles cannot be loaded
* ovcc_tilesdef_unref (tiles), tiles = NULL;
* }
* g_object_unref (file);
*
* return tiles;
* }
* </programlisting>
* </example>
*
*
* <example>
* <title>Example of a tiles definition file</title>
* <programlisting><![CDATA[
* <?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>
* ]]></programlisting>
* </example>
*
*/
/**
* OVCCTilesDef
*
* Opaque object representing some tile definitions.
*/
struct _OVCCTilesDef
{
gint ref_count;
GHashTable *tiles;
};
/* Use ref and unref to make it a singleton */
DEFINE_BOXED_TYPE (OVCCTilesDef, ovcc_tilesdef,
ovcc_tilesdef_ref, ovcc_tilesdef_unref)
static void
free_tile (gpointer data)
{
OVCCTile *tile = data;
/* ovcc_tile_dump (tile, OVCC_TILE_DUMP_ALL); */
g_object_unref (tile);
}
/**
* ovcc_tilesdef_error_quark:
*
* Gets the error domain <link linkend="GQuark">quark</link> for #OVCCTilesDef
* errors.
*
* Returns: The #OVCCTilesDef's error domain quark.
*/
GQuark
ovcc_tilesdef_error_quark (void)
{
static GQuark error_quark = 0;
if (G_UNLIKELY (error_quark == 0)) {
error_quark = g_quark_from_static_string ("OVCCTilesDefError");
}
return error_quark;
}
/**
* ovcc_tilesdef_new:
*
* Creates a new #OVCCTilesDef.
*
* Returns: The newly created #OVCCTilesDef.
*/
OVCCTilesDef *
ovcc_tilesdef_new (void)
{
OVCCTilesDef *def;
def = g_malloc (sizeof *def);
if (def) {
def->ref_count = 1;
def->tiles = g_hash_table_new_full (g_direct_hash, g_direct_equal,
NULL, /*g_object_unref*/ free_tile);
}
return def;
}
/**
* ovcc_tilesdef_ref:
* @def: A valid #OVCCTilesDef
*
* Increases reference count of @def.
*
* Returns: @def.
*/
OVCCTilesDef *
ovcc_tilesdef_ref (OVCCTilesDef *def)
{
g_atomic_int_inc (&def->ref_count);
return def;
}
/**
* ovcc_tilesdef_unref:
* @def: A valid #OVCCTilesDef
*
* Decreases reference count of @def and frees it if count drops to 0.
*/
void
ovcc_tilesdef_unref (OVCCTilesDef *def)
{
if (g_atomic_int_dec_and_test (&def->ref_count)) {
g_hash_table_destroy (def->tiles);
g_free (def);
}
}
/**
* ovcc_tilesdef_clear:
* @def: The #OVCCTilesDef to clear.
*
* Removes all tiles in the given tile def.
*/
void
ovcc_tilesdef_clear (OVCCTilesDef *def)
{
g_hash_table_remove_all (def->tiles);
}
/**
* ovcc_tilesdef_remove:
* @def: A #OVCCTilesDef
* @id: The ID of the tile to remove from the def.
*
* Removes a tile from a 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));
}
/**
* ovcc_tilesdef_get_tile:
* @def: An #OVCCTilesDef
* @id: The ID of the tile to search for
*
* Get a tile from a def.
*
* Returns: The tile of the given ID or %NULL if not found.
*/
OVCCTile *
ovcc_tilesdef_get_tile (const OVCCTilesDef *def,
OVCCTileID id)
{
return g_hash_table_lookup (def->tiles, GINT_TO_POINTER (id));
}
/**
* ovcc_tilesdef_has_tile:
* @def: An #OVCCTilesDef
* @id: The ID of the tile to search for
*
* Get whether a tile is in a def.
*
* Returns: %TRUE if the tile is found in the def, %FALSE otherwise.
*/
gboolean
ovcc_tilesdef_has_tile (const OVCCTilesDef *def,
OVCCTileID id)
{
return ovcc_tilesdef_get_tile (def, id) != NULL;
}
/**
* ovcc_tilesdef_is_empty:
* @def: An #OVCCTilesDef
*
* Gets whether an #OVCCTilesDef contains any tile.
*
* Returns: %TRUE it the #OVCCTilesDef is empty, %FALSE otherwise.
*/
gboolean
ovcc_tilesdef_is_empty (const OVCCTilesDef *def)
{
return g_hash_table_size (def->tiles) == 0;
}
/**
* ovcc_tilesdef_size:
* @def: An #OVCCTilesDef
*
* Gives the number of tiles in the given #OVCCTilesDef.
*
* Returns: The number of tiles in the #OVCCTilesDef.
*/
guint
ovcc_tilesdef_size (const OVCCTilesDef *def)
{
return g_hash_table_size (def->tiles);
}
/**
* ovcc_tilesdef_add:
* @def: An #OVCCTilesDef
* @tile: The #OVCCTile to add
*
* Adds a Tile to an #OVCCTilesDef.
*
* <note>
* <para>
* This function adds a reference to the tile; therefore if you want to
* leave the reference to the set, you need to unref the tile you given.
* </para>
* </note>
*/
void
ovcc_tilesdef_add (OVCCTilesDef *def,
OVCCTile *tile)
{
g_hash_table_insert (def->tiles, GINT_TO_POINTER (ovcc_tile_get_id (tile)),
g_object_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 ((const xmlChar *)"tiles")
#define TF_TILE ((const xmlChar *)"tile")
#define TF_TILE_ID ((const xmlChar *)"id")
#define TF_CENTER ((const xmlChar *)"center")
#define TF_SIDES ((const xmlChar *)"sides")
#define TF_SIDE ((const xmlChar *)"side")
#define TF_SIDE_POS ((const xmlChar *)"position")
#define TF_SIDE_TYPE ((const xmlChar *)"type")
#define TF_SIDE_RELS ((const xmlChar *)"relations")
#define TF_SIDE_ATTRS ((const xmlChar *)"attributes")
#define TF_REL_LEFT ((const xmlChar *)"left")
#define TF_REL_RIGHT ((const xmlChar *)"right")
#define TF_REL_TOP ((const xmlChar *)"top")
#define TF_REL_BOTTOM ((const xmlChar *)"bottom")
#define TF_TRUE ((const xmlChar *)"true")
#define TF_FALSE ((const xmlChar *)"false")
#define TF_BONUS ((const xmlChar *)"bonus")
static int
position_from_string (const xmlChar *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 ((const gchar *)type, types[i].name) == 0) {
return types[i].type;
}
}
}
g_warning ("Invalid side type '%s'", (const gchar *)type);
return -1;
}
static gboolean
bool_from_string (const xmlChar *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 ((const gchar *)boolstr, boolstrs[i].name) == 0) {
return boolstrs[i].value;
}
}
}
g_warning ("Invalid boolean '%s'", (const gchar *)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 OVCCTileSideAttribute
read_side_attributes (xmlNode *node)
{
xmlNode *child;
OVCCTileSideAttribute attributes = OVCC_TILE_SIDE_ATTR_NONE;
for (child = node->children; child; child = child->next) {
if (child->type == XML_ELEMENT_NODE) {
if (xmlStrcmp (TF_BONUS, child->name) == 0) {
if (bool_from_string (child->children->content)) {
attributes |= OVCC_TILE_SIDE_ATTR_BONUS;
}
} /*else {
g_warning ("Malformed file! Unexpected element %s inside %s",
child->name, node->name);
}*/
}
}
return attributes;
}
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;
OVCCTileSideAttribute attributes = OVCC_TILE_SIDE_ATTR_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 ((const gchar *)child->children->content);
} else if (xmlStrcmp (TF_SIDE_RELS, child->name) == 0) {
/* g_debug ("Found side relations"); */
relations = read_side_relations (child);
} else if (xmlStrcmp (TF_SIDE_ATTRS, child->name) == 0) {
/* g_debug ("Found side attributes"); */
attributes = read_side_attributes (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, attributes) : 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) {
const gchar *nptr = (const gchar *)cur_attr->children->content;
GError *err = NULL;
id = ovcc_xmlutils_read_id (nptr, "t", &err);
if (err) {
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
"[%s:%u] Invalid value '%s' for attribute '%s' of element <%s>: %s",
node->doc->URL, node->line, nptr, cur_attr->name, node->name,
err->message);
g_error_free (err);
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 ((const gchar *)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 %u", id); */
/* create the tile */
tile = ovcc_tile_new_auto_v (id, sides, center_type, &err);
if (! tile) {
g_set_error (error, OVCC_TILESDEF_ERROR, OVCC_TILESDEF_ERROR_LOAD_TILE_FAILED,
"[%s:%u] Failed to load tile #%u: %s",
node->doc->URL, node->line, id, err->message);