phantasia

Phantasia - 2D SDL3 RPG prototype.
git clone git://git.beep.wimdupont.com/phantasia.git
Log | Files | Refs | README | LICENSE

validate.c (6792B)


      1 #include "engine/validate.h"
      2 #include "game/content.h"
      3 
      4 #include <stdint.h>
      5 #include <stdio.h>
      6 #include <string.h>
      7 
      8 #define CHECK(test) do { if (!(test)) { \
      9 	fprintf(stderr, "validation test failed at line %d: %s\n", __LINE__, #test); \
     10 	return 1; \
     11 } } while (0)
     12 
     13 static int
     14 ph_reject(const PhWorldSetup *setup, const char *expected)
     15 {
     16 	char error[256];
     17 
     18 	if (ph_validate_world(setup, error, sizeof(error)) < 0 && strstr(error, expected)) return 1;
     19 	fprintf(stderr, "expected '%s', got '%s'\n", expected, error);
     20 	return 0;
     21 }
     22 
     23 /* Write a fixture in the same format as mapc, without changing production maps. */
     24 static int
     25 ph_write_map(const char *path, const PhArea *area)
     26 {
     27 	struct {
     28 		uint32_t magic, version, width, height, markers;
     29 		char name[PH_AREA_NAME_MAX];
     30 	} header = { PH_MAP_MAGIC, PH_MAP_VERSION, area->width, area->height,
     31 		area->marker_count, "Validation fixture" };
     32 	FILE *fp = fopen(path, "wb");
     33 	int i, ok;
     34 	size_t count = (size_t)area->width * area->height;
     35 
     36 	if (!fp) return -1;
     37 	ok = fwrite(&header, sizeof(header), 1, fp) == 1 && fwrite(area->tiles, 1, count, fp) == count;
     38 	for (i = 0; i < area->marker_count && ok; ++i) {
     39 		uint32_t marker[] = { area->markers[i].symbol, area->markers[i].tile_x, area->markers[i].tile_y };
     40 		ok = fwrite(marker, sizeof(marker), 1, fp) == 1;
     41 	}
     42 	if (fclose(fp)) ok = 0;
     43 	return ok ? 0 : -1;
     44 }
     45 
     46 static int
     47 ph_map_tests(PhWorldSetup *setup, PhAreaDef *defs)
     48 {
     49 	const char *path = "obj/validation-fixture.phmap";
     50 	PhArea area;
     51 	PhAreaMarker saved;
     52 	const char *original = defs[0].path;
     53 	unsigned char tile;
     54 	int result = 1;
     55 	int i;
     56 
     57 	if (ph_area_load(&area, original) < 0) return 1;
     58 	defs[0].path = path;
     59 	tile = area.tiles[0];
     60 	area.tiles[0] = 255;
     61 	if (ph_write_map(path, &area) < 0 || !ph_reject(setup, "invalid tile index 255")) goto done;
     62 	area.tiles[0] = tile;
     63 	saved = area.markers[0];
     64 	area.markers[0].symbol = '?';
     65 	if (ph_write_map(path, &area) < 0 || !ph_reject(setup, "undefined marker")) goto done;
     66 	area.markers[0] = saved;
     67 	area.markers[0].tile_x = area.width;
     68 	if (ph_write_map(path, &area) < 0 || !ph_reject(setup, "cannot load map")) goto done;
     69 	area.markers[0] = saved;
     70 	area.markers[1] = saved;
     71 	if (ph_write_map(path, &area) < 0 || !ph_reject(setup, "overlapping markers")) goto done;
     72 	ph_area_free(&area);
     73 	if (ph_area_load(&area, original) < 0) goto done;
     74 	/* Dropping the starting marker must be diagnosed before play. */
     75 	for (i = 0; i < area.marker_count; ++i) {
     76 		const PhMarkerDef *marker = ph_area_catalog_marker(setup->areas, defs[0].id, area.markers[i].symbol);
     77 		if (marker->kind != PH_MARKER_PLAYER) continue;
     78 		area.markers[i] = area.markers[--area.marker_count];
     79 		break;
     80 	}
     81 	if (ph_write_map(path, &area) < 0 || !ph_reject(setup, "invalid spawn counts")) goto done;
     82 	result = 0;
     83 done:
     84 	defs[0].path = original;
     85 	ph_area_free(&area);
     86 	remove(path);
     87 	return result;
     88 }
     89 
     90 int
     91 main(void)
     92 {
     93 	PhWorldSetup setup = ph_game_world;
     94 	PhAreaCatalog catalog = *setup.areas;
     95 	PhEntityDef entities[PH_ENTITY_DEF_COUNT];
     96 	PhItemDef items[PH_ITEM_DEF_COUNT];
     97 	PhAreaDef areas[PH_AREA_COUNT];
     98 	PhTileDef tiles[PH_TILE_DEF_COUNT];
     99 	PhMarkerDef markers[PH_MARKER_DEF_COUNT + 1];
    100 	PhSpellDef spells[PH_SPELL_DEF_COUNT];
    101 	PhSpellBook book = ph_player_spellbook;
    102 	char error[256];
    103 	int equipment = 9999;
    104 	int i;
    105 
    106 	if (ph_game_validate_content(error, sizeof(error)) < 0) {
    107 		fprintf(stderr, "content: %s\n", error);
    108 		return 1;
    109 	}
    110 	memcpy(entities, ph_entity_defs, sizeof(entities));
    111 	memcpy(items, ph_item_defs, sizeof(items));
    112 	memcpy(areas, ph_area_defs, sizeof(areas));
    113 	memcpy(tiles, ph_tile_defs, sizeof(tiles));
    114 	memcpy(markers, ph_marker_defs, sizeof(ph_marker_defs));
    115 	memcpy(spells, ph_spell_defs, sizeof(spells));
    116 	setup.areas = &catalog;
    117 	setup.content.entities = entities;
    118 	setup.content.items = items;
    119 	catalog.areas = areas;
    120 	catalog.tiles = tiles;
    121 	catalog.markers = markers;
    122 	book.defs = spells;
    123 	CHECK(ph_validate_world(&setup, error, sizeof(error)) == 0);
    124 	CHECK(ph_reject(NULL, "missing setup"));
    125 	catalog.tiles = NULL;
    126 	CHECK(ph_reject(&setup, "missing arrays"));
    127 	catalog.tiles = tiles;
    128 	catalog.marker_count = -1;
    129 	CHECK(ph_reject(&setup, "invalid area catalog counts"));
    130 	catalog.marker_count = PH_MARKER_DEF_COUNT;
    131 	markers[1] = markers[0];
    132 	CHECK(ph_reject(&setup, "duplicate definition"));
    133 	markers[1] = ph_marker_defs[1];
    134 	entities[1].animation_frames = PH_ANIMATION_FRAMES + 1;
    135 	CHECK(ph_reject(&setup, "invalid definition"));
    136 	entities[1] = ph_entity_defs[1];
    137 	entities[1].id = entities[0].id;
    138 	CHECK(ph_reject(&setup, "duplicate ID"));
    139 	entities[1] = ph_entity_defs[1];
    140 	entities[1].reward.item_id = 9999;
    141 	CHECK(ph_reject(&setup, "invalid reward item 9999"));
    142 	entities[1] = ph_entity_defs[1];
    143 	items[1].id = items[0].id;
    144 	CHECK(ph_reject(&setup, "duplicate ID"));
    145 	items[1] = ph_item_defs[1];
    146 	tiles[1].symbol = tiles[0].symbol;
    147 	CHECK(ph_reject(&setup, "duplicate symbol"));
    148 	tiles[1] = ph_tile_defs[1];
    149 	areas[1].id = areas[0].id;
    150 	CHECK(ph_reject(&setup, "duplicate ID"));
    151 	areas[1] = ph_area_defs[1];
    152 	areas[1].path = "obj/no-such-validation-map.phmap";
    153 	CHECK(ph_reject(&setup, "cannot load map"));
    154 	areas[1] = ph_area_defs[1];
    155 	setup.start_area_id = 9999;
    156 	CHECK(ph_reject(&setup, "unknown start area 9999"));
    157 	setup.start_area_id = ph_game_world.start_area_id;
    158 	setup.equipment = &equipment;
    159 	setup.equipment_count = 1;
    160 	CHECK(ph_reject(&setup, "invalid item 9999"));
    161 	setup.equipment = ph_game_world.equipment;
    162 	setup.equipment_count = ph_game_world.equipment_count;
    163 	markers[0].content_id = 9999;
    164 	CHECK(ph_reject(&setup, "invalid entity 9999"));
    165 	markers[0] = ph_marker_defs[0];
    166 	for (i = 0; markers[i].kind != PH_MARKER_PORTAL; ++i) {}
    167 	markers[i].destination_area_id = 9999;
    168 	CHECK(ph_reject(&setup, "invalid destination 9999"));
    169 	markers[i] = ph_marker_defs[i];
    170 	markers[PH_MARKER_DEF_COUNT] = (PhMarkerDef){
    171 		.area_id = markers[i].destination_area_id, .symbol = '?', .tile_symbol = '.',
    172 		.kind = PH_MARKER_ARRIVAL };
    173 	++catalog.marker_count;
    174 	markers[i].destination_symbol = '?';
    175 	CHECK(ph_reject(&setup, "needs one arrival '?' (found 0)"));
    176 	--catalog.marker_count;
    177 	markers[i] = ph_marker_defs[i];
    178 	CHECK(ph_map_tests(&setup, areas) == 0);
    179 	spells[1].id = spells[0].id;
    180 	CHECK(ph_validate_spells(&book, error, sizeof(error)) < 0 && strstr(error, "duplicate ID"));
    181 	spells[1] = ph_spell_defs[1];
    182 	book.learned[0] = 9999;
    183 	CHECK(ph_validate_spells(&book, error, sizeof(error)) < 0 && strstr(error, "unknown learned spell 9999"));
    184 	book = ph_player_spellbook;
    185 	book.learned_count = PH_SPELL_MAX + 1;
    186 	CHECK(ph_validate_spells(&book, error, sizeof(error)) < 0);
    187 	CHECK(ph_validate_world(&setup, NULL, 0) == 0);
    188 	CHECK(ph_validate_world(NULL, error, 1) < 0 && error[0] == '\0');
    189 	puts("content check: catalogs, all maps, and invalid-data regression tests passed");
    190 	return 0;
    191 }