PC Games

Orb
Lasagne Monsters
Three Guys Apocalypse
Water Closet
Blob Wars : Attrition
The Legend of Edgar
TBFTSS: The Pandoran War
Three Guys
Blob Wars : Blob and Conquer
Blob Wars : Metal Blob Solid
Project: Starfighter
TANX Squadron

Android Games

DDDDD
Number Blocks
Match 3 Warriors

Tutorials

2D shoot 'em up
2D top-down shooter
2D platform game
Sprite atlas tutorial
Working with TTF fonts
2D adventure game
Widget tutorial
2D shoot 'em up sequel
2D run and gun
Roguelike
Medals (Achievements)
2D turn-based strategy game
2D isometric game
2D map editor
2D mission-based shoot 'em up
2D Santa game
2D split screen game
SDL 1 tutorials (outdated)

Latest Updates

SDL2 Versus game tutorial
Wed, 20th March 2024

Download keys for SDL2 tutorials on itch.io
Sat, 16th March 2024

The Legend of Edgar 1.37
Mon, 1st January 2024

SDL2 Santa game tutorial 🎅
Thu, 23rd November 2023

SDL2 Shooter 3 tutorial
Wed, 15th February 2023

All Updates »

Tags

android (3)
battle-for-the-solar-system (10)
blob-wars (10)
brexit (1)
code (6)
edgar (9)
games (43)
lasagne-monsters (1)
making-of (5)
match3 (1)
numberblocksonline (1)
orb (2)
site (1)
tanx (4)
three-guys (3)
three-guys-apocalypse (3)
tutorials (17)
water-closet (4)

Books


The Battle for the Solar System (Complete)

The Pandoran war machine ravaged the galaxy, driving the human race to the brink of destruction. Seven men and women stood in its way. This is their story.

Click here to learn more and read an extract!

« Back to tutorial listing

— Creating a lookup system —

Note: this tutorial assumes knowledge of C.

Introduction

While not directly related to creating a game (and not dependent on SDL at all), some might find this approach to mapping constants to text values rather useful. When loading and saving data, enumerated types and flag values will be stored as numbers. This can make it difficult later to understand what those values represent. Unlike languages such as Java, C is not a reflective language. In other words, there is no way to load a string named "FOOD_PIE" and know that it has a value of 12, even if it is defined in the code in such a way; all that data is lost when the code is compiled.

Consider the JSON below, from TBFTSS : The Pandoran War, used to define one of the starfighters:

{
	"name" : "Unique Sphinx",
	"description" : "",
	"affiliation" : "",
	"health" : 999,
	"shield" : 999,
	"speed" : 1.6,
	"reloadTime" : 14,
	"shieldRechargeRate" : 0,
	"texture" : "gfx/fighters/zakSphinx.png",
	"guns" : [
		{
			"type" : "BT_PLASMA",
			"x" : 0,
			"y" : -4
		},
		{
			"type" : "BT_PLASMA",
			"x" : 9,
			"y" : 0
		},
		{
			"type" : "BT_PLASMA",
			"x" : -9,
			"y" : 0
		},
		{
			"type" : "BT_LASER",
			"x" : 0,
			"y" : 0
		}
	],
	"missiles" : 2,
	"flags" : "EF_TAKES_DAMAGE+EF_IMMORTAL+EF_MISSION_TARGET+EF_AI_LEADER",
	"aiFlags" : "+AIF_WANDERS+AIF_UNLIMITED_RANGE+AIF_ZAK_SUSPICIOUS"
}

For the guns, we have two type - BT_PLASMA and BT_LASER. These are defined in the code as 2 and 3. I'm sure you'll agree that BT_PLASMA and BT_LASER are easier to read and understand instead of the arbitary numbers. Now consider the flags: EF_TAKES_DAMAGE+EF_IMMORTAL+EF_MISSION_TARGET+EF_AI_LEADER. These resolve in the code as 36888. Oof. Not exactly helpful if, like myself, you edit a lot of files by hand.

It was because of issues such as this that I created the lookup system, so that I could understand at a glance what an object's definition was. In this tutorial, we'll look at a way to output (and handle input) of data in a human readable fashion.

Extract the archive, run cmake CMakeLists.txt, then make, and then use ./lookups to run the code. You will see console output like the screenshot above.

Inspecting the code

As this tutorial results in console-only output, there isn't too much setup involved.

Starting with defs.h:


enum {
	FRUIT_APPLE,
	FRUIT_BANANA,
	FRUIT_GRAPE,
	FRUIT_PEAR,
	FRUIT_ORANGE,
	FRUIT_BLUEBERRY
};

enum {
	VEGETABLE_CARROT,
	VEGETABLE_POTATO,
	VEGETABLE_TOMATO,
	VEGETABLE_LETTUCE,
	VEGETABLE_CUCUMBER
};

enum {
	COLOR_RED,
	COLOR_YELLOW,
	COLOR_GREEN,
	COLOR_BLUE,
	COLOR_PURPLE,
	COLOR_WHITE,
	COLOR_BLACK
};

#define FLAG_NONE             0
#define FLAG_SWEET            (2 << 0)
#define FLAG_SOUR             (2 << 1)
#define FLAG_CRUNCHY          (2 << 2)
#define FLAG_SOFT             (2 << 3)
#define FLAG_WATERY           (2 << 4)
#define FLAG_HARD             (2 << 5)
#define FLAG_NUTTY            (2 << 6)
#define FLAG_TANGY            (2 << 7)
#define FLAG_BITTER           (2 << 8)
#define FLAG_SOLID            (2 << 9)
#define FLAG_LIQUID           (2 << 10)

We've added in a number of enums, some for fruit, some for vegetables, and some for colours. We've also got a few defines, to act as flags to describe attributes.

If we now move over to structs.h, we can see we have a single struct defined:


struct Lookup {
	char name[MAX_NAME_LENGTH];
	long value;
	Lookup *next;
};

Lookup will be used to handle our lookup data. `name` is the name of the lookup (the key, if you prefer), `value` is the value of the lookup, and `next` is a pointer to the next Lookup in our linked list.

With that done, we can head over to lookups.c, where all our lookup logic lives. A number of functions are to be found here, but none overly complicated. Starting with initLookups:


void initLookups(void)
{
	memset(&head, 0, sizeof(Lookup));
	tail = &head;

	addLookup("FRUIT_APPLE", FRUIT_APPLE);
	addLookup("FRUIT_BANANA", FRUIT_BANANA);
	addLookup("FRUIT_GRAPE", FRUIT_GRAPE);
	addLookup("FRUIT_PEAR", FRUIT_PEAR);
	addLookup("FRUIT_ORANGE", FRUIT_ORANGE);
	addLookup("FRUIT_BLUEBERRY", FRUIT_BLUEBERRY);

	addLookup("VEGETABLE_CARROT", VEGETABLE_CARROT);
	addLookup("VEGETABLE_POTATO", VEGETABLE_POTATO);
	addLookup("VEGETABLE_TOMATO", VEGETABLE_TOMATO);
	addLookup("VEGETABLE_LETTUCE", VEGETABLE_LETTUCE);
	addLookup("VEGETABLE_CUCUMBER", VEGETABLE_CUCUMBER);

	addLookup("COLOR_RED", COLOR_RED);
	addLookup("COLOR_YELLOW", COLOR_YELLOW);
	addLookup("COLOR_GREEN", COLOR_GREEN);
	addLookup("COLOR_BLUE", COLOR_BLUE);
	addLookup("COLOR_PURPLE", COLOR_PURPLE);
	addLookup("COLOR_WHITE", COLOR_WHITE);
	addLookup("COLOR_BLACK", COLOR_BLACK);

	addLookup("FLAG_NONE", FLAG_NONE);
	addLookup("FLAG_SWEET", FLAG_SWEET);
	addLookup("FLAG_SOUR", FLAG_SOUR);
	addLookup("FLAG_CRUNCHY", FLAG_CRUNCHY);
	addLookup("FLAG_SOFT", FLAG_SOFT);
	addLookup("FLAG_WATERY", FLAG_WATERY);
	addLookup("FLAG_HARD", FLAG_HARD);
	addLookup("FLAG_NUTTY", FLAG_NUTTY);
	addLookup("FLAG_TANGY", FLAG_TANGY);
	addLookup("FLAG_BITTER", FLAG_BITTER);
	addLookup("FLAG_SOLID", FLAG_SOLID);
	addLookup("FLAG_LIQUID", FLAG_LIQUID);
}

We first memset a variable called `head`, which is acting as the head of our lookup chain. We're then setting `tail` (the end) of the linked list to `head`.

After that, we're calling addLookup a number of times, for each of the enum values and defines that we're interested in. We'll see more on this function at the end. For now, know that it is adding a key-value lookup to our lookup linked list. So, at the end of the function, we will have a mapping of our enum and define names to their number values.

Coming to the `lookup` function next:


long lookup(char *name)
{
	Lookup *l;

	for (l = head.next ; l != NULL ; l = l->next)
	{
		if (strcmp(l->name, name) == 0)
		{
			return l->value;
		}
	}

	printf("No such lookup value '%s'", name);

	exit(1);

	return 0;
}

This function takes one argument - `name`, being the name of the lookup to search for. We loop through all our lookups in our linked list until we find the matching name, and then return the value. That's all. Notice how if we fail to find one, we're printing an error and then exiting. This might seem quite heavy handled, but it does help to guard against typos or lookups that you might have forgotten to add. If you preferred, you could omit the exit call. Keep in mind you would need to always return something, which could lead to some unexpected results in your program (the return 0 here exists to keep the compiler happy).

Moving onto getLookupName next:


char *getLookupName(char *prefix, long value)
{
	Lookup *l;

	for (l = head.next ; l != NULL ; l = l->next)
	{
		if (l->value == value && strncmp(prefix, l->name, strlen(prefix)) == 0)
		{
			return l->name;
		}
	}

	printf("No such lookup value %ld, prefix=%s", value, prefix);

	exit(1);

	return "";
}

This funciton takes two arguments: `prefix`, the prefix of the define to look for (such as FRUIT_ or COLOR_), and `value`, the value to search for. Once again, we're looping through all our lookups, but now we're searching for a lookup with the same `value` as the value we passed in, and also with a `name` that starts with the `prefix` that was passed in.

So, if we passed in "FRUIT_" and 2, the code would find FRUIT_GRAPE. We have several other lookups with values of 2, but since their names don't begin with "FRUIT_", they won't be matched.

Once again, notice that we're calling exit if no matching lookup is found, for the same reasons as outlined before.

flagsToLong comes next:


long flagsToLong(char *in)
{
	char *flag, *flags;
	long total;

	total = 0;

	flags = malloc(strlen(in) + 1);
	STRNCPY(flags, in, strlen(in) + 1);

	flag = strtok(flags, "+");

	while (flag)
	{
		total += lookup(flag);
		flag = strtok(NULL, "+");
	}

	free(flags);

	return total;
}

This function takes a single argument: `in`, a char array that will be holding our flags. The string is expected to be of the format "FLAG_NAME_1+FLAG_NAME_2", etc. So, the name of the flags, delimited by plus signs.

We start by setting a variable called `total` to 0. This variable will hold the return value. We then malloc a variable called `flags`, to the length of `in`, plus one. Next, we copy the contents of `in` into `flags` (using our STRNCPY macro to limit the number of character copied, and also add the null terminator). Next, we use strtok on `flags`, delimiting by "+", and assigning the resulting token to a pointer called `flag`. We're making a copy of the string passed into the function because the strtok function modifies it, and this may be undesirable.

We then use a while-loop, calling `lookup` and passing `flag` over to the function. The result of the call to lookup is added to `total`. strtok is called again at the end of each loop (passing through NULL, to continue working with the current data). We finally free `flags`, and return the `total`.

We now have the ability to convert a human-readable list of flags into their number value equivalent.

Next, we have getFlagValues:


const char *getFlagValues(const char *prefix, long flags)
{
	static char flagStr[MAX_LINE_LENGTH];
	Lookup *l;

	memset(flagStr, '\0', MAX_LINE_LENGTH);

	for (l = head.next ; l != NULL ; l = l->next)
	{
		if (flags & l->value && strncmp(prefix, l->name, strlen(prefix)) == 0)
		{
			if (flagStr[0] != '\0')
			{
				strcat(flagStr, "+");
			}

			strcat(flagStr, l->name);
		}
	}

	return flagStr;
}

This function operates much like getLookupName, except it works on flags. It takes two parameters: `prefix` and `flags`. We start by memsetting a char array named flagStr (with a length of MAX_LINE_LENGTH - 1024 characters). We then begin looping through all our lookups, performing a bitwise test against each one's `value` with the `flags` we passed into the function, to see if there is a match. If so, we'll also test the `name` of the lookup against the `prefix` passed over. If this check returns true, we have found a flag value bit to match those passed in.

Next, we check to see if flagStr contains any data already; we can do this by checking if the first character is a NULL terminator. If it's not, we'll want to first add a + (plus) to the string, to act as a delimiter. We then concatenate the name of the lookup to the string. We continue this until we reach the end of our lookup list, and then return flagStr.

This allows us to feed in a number such as 6 and a prefix such as "FLAG_", and convert it to a string containing "FLAG_SWEET+FLAG_SOUR".

The final function to consider is addLookup:


static void addLookup(char *name, long value)
{
	Lookup *lookup;

	lookup = malloc(sizeof(Lookup));
	memset(lookup, 0, sizeof(Lookup));
	tail->next = lookup;
	tail = lookup;

	STRNCPY(lookup->name, name, MAX_NAME_LENGTH);
	lookup->value = value;
}

This is a basic function to create a lookup using the name and value passed into the function, and add it to our linked list.

That's all our code for lookups done, so we can now move onto main.c, where we can see our lookups being put to use. Starting with main:


int main(int argc, char *argv[])
{
	initLookups();

	printValue("FRUIT_GRAPE");
	printValue("FRUIT_BLUEBERRY");
	printValue("FRUIT_PEAR");
	printf("\n");

	printValue("VEGETABLE_CUCUMBER");
	printValue("VEGETABLE_TOMATO");
	printf("\n");

	printValue("COLOR_BLUE");
	printValue("COLOR_BLACK");
	printValue("COLOR_YELLOW");
	printf("\n");

	printValue("FLAG_SOFT");
	printValue("FLAG_HARD");
	printf("\n");

	printFlagValues("FLAG_SWEET+FLAG_SOUR+FLAG_CRUNCHY");
	printFlagValues("FLAG_HARD+FLAG_NUTTY");
	printf("\n");

	printKeyName("FRUIT_", 1);
	printKeyName("COLOR_", 1);
	printKeyName("VEGETABLE_", 1);
	printKeyName("FRUIT_", 3);
	printKeyName("COLOR_", 3);
	printKeyName("VEGETABLE_", 3);
	printf("\n");

	printKeyName("FRUIT_", 1);
	printKeyName("COLOR_", 1);
	printKeyName("VEGETABLE_", 1);
	printKeyName("FRUIT_", 3);
	printKeyName("COLOR_", 3);
	printKeyName("VEGETABLE_", 3);
	printf("\n");

	printFlagNames("FLAG_", 520);
	printFlagNames("FLAG_", 50);
	printFlagNames("FLAG_", 2084);
}

We're first calling initLookups, to create our lookup table. After that, we're calling a number of functions, to demonstrate the lookup usage: printValue, printFlagValues, printKeyName, and printFlagNames. We'll go through these one at a time.

Starting with printValue:


static void printValue(char *key)
{
	printf("%s=%ld\n", key, lookup(key));
}

This function takes the name of the lookup value we want to print (as `key`), calls lookup, passing over `key`, and uses printf to print the output.

Next, we have printFlagValues:


static void printFlagValues(char *key)
{
	printf("%s=%ld\n", key, flagsToLong(key));
}

Again, this function takes the name of the lookup we want to print (`key`), calls flagsToLong, passing over key, and uses printf to print the output.

printKeyName follows:


static void printKeyName(char *prefix, long num)
{
	printf("prefix=%s, num=%ld = %s\n", prefix, num, getLookupName(prefix, num));
}

This function is being used to display the output of getLookupName. We're passing over the prefix and num (the value) parameters to the function, and using printf to print the output.

Finally, we have printFlagNames:


static void printFlagNames(char *prefix, long flags)
{
	printf("prefix=%s, flags=%ld = %s\n", prefix, flags, getFlagValues(prefix, flags));
}

Once again, we're passing over the prefix and flags parameter to getFlagValues, to show the function working. printf is once more used to display the output.

And there we have it, a system to allow us to translate key-value pairs between their human readable format and numbers. I'm sure you'll agree that having the values printed out as text makes them a lot easy to work with and understand.

This lookup system has been used in many of our games, and continues to be useful.

Download

This code is available to download by clicking the link below.

lookups.tar.gz (3Kb)

Mobile site