Tour of a Procedurally Generated Solar System

•February 1, 2017 • Leave a Comment

2017-01-31 — Here’s a little tour of a procedurally generated solar system in my little hobby project, an open source multiplayer networked starship bridge simulator game, Space Nerds In Space.

Code for 3D turret aiming

•January 15, 2017 • Leave a Comment

January 14, 2017 — Say you have a 3D space game, with NPC ships that have turrets on them, and the turrets can be oriented any which way on those ships. How do you make the turrets swivel around and aim smoothly and intelligently?

Note: The code in these examples is released under the GPL v2 license.  See:

Step 1: Understand your turret model.

In my case, my turret model is oriented such that it shoots down the positive X axis, and can rotate around the vertical Y axis and rotate around the horizontal Z axis. This is the same canonical orientation used by all the models in my game — the “unrotated state” has from the model’s point of view, down the X axis being straight ahead, positive Y as “up” and positive Z to the right.

Step 2. Understanding the turret’s state.

  • The turret has a location (x, y, z)
  • The turret has a “rest orientation” This is the orientation when the turret is in an unrotated state, but perhaps attached to some other object in a way that gives this state some arbitrary non-constant rotation that is different than the unrotated state the identity quaternion represents.
  • The turret has a “current orientation” This is the “rest orientation” plus some other arbitrary orientation accounting for where ever the turret might currently be pointed.
  • The turret cannot rotate infinitely fast. There is some limit to how much the turret can rotate in each of its two axes per unit time.

Step 3. What do we want to be able to do?

We’d like to be able to ask the turret to aim at something, and get a new orientation that obeys the constraints of a turret (not freely rotating, but only rotating on two axes) and allowing the turret to be attached to something.

So let’s say we’d like a C function something like this:

struct turret_params {
        float elevation_rate_limit;     /* how much can turret move in one step in radians */
        float azimuth_rate_limit;
};

/* union quat is a quaternion */

union quat *turret_aim(double target_x, double target_y, double target_z,       /* in: world coord position of target */
                        double turret_x, double turret_y, double turret_z,      /* in: world coord position of turret */
                        union quat *turret_rest_orientation,                    /* in: orientation of turret at rest */
                        union quat *current_turret_orientation,                 /* in: Current orientation of turret */
                        const struct turret_params *turret,                     /* in: turret params, can be NULL */
                        union quat *new_turret_orientation);                    /* out: new orientation of turret */

So how do we write such a function? Here’s one approach.

1. Transform the target into the turret’s coordinate space
2. Calculate the desired azimuth and elevation angles
3. Calculate the turret’s current azimuth and elevation
4. Calculate the delta between current and desired azimuth and elevation
5. clamp these deltas to the maximum angular rate the turret can move
6. Apply deltas to current elevation and azimuth
7. Convert new elevation and azimuth to the new orientation quaternion

So, going through these one by one:

Step 0. Declare some variables we’ll need:

        union vec3 yaw_axis = { { 0.0, 1.0, 0.0 } };
        union quat yaw, pitch, inverse_rest;
        union vec3 pitch_axis;
        union vec3 to_target, to_current_aim;
        float current_azimuth, azimuth;
        float current_elevation, elevation;
        float delta_elevation, delta_azimuth;
        float xzdist;

Step 1. Transform the target into the turret’s coordinate space
(because we want to make step 2 easier)

	/* Compute vector from turret to target */
	union vec3 to_target;
        to_target.v.x = target_x - turret_x;
        to_target.v.y = target_y - turret_y;
        to_target.v.z = target_z - turret_z;		
	/* Compute inverse rotation from turret rest orientation */
        quat_inverse(&inverse_rest, turret_rest_orientation);
	/* Convert target into turret's coordinate space */
	quat_rot_vec_self(&to_target, &inverse_rest);

Step 2. Calculate the desired azimuth and elevation angles:

        azimuth = atan2f(-to_target.v.z, to_target.v.x);
        xzdist = sqrtf(to_target.v.z * to_target.v.z + to_target.v.x * to_target.v.x);
        elevation = atan2f(to_target.v.y, xzdist);

At this point, you could skip to step 7, and the turret would snap instantly
to the correct orientation — but we want it to move smoothly.

Step 3. Calculate the turret’s current azimuth and elevation

        to_current_aim.v.x = 1.0; /* vector pointing down the x-axis */
        to_current_aim.v.y = 0.0;
        to_current_aim.v.z = 0.0;

	/* Rotate it into the current turret's orientation -- now the vector
	 * points in the same direction as the turret points in world coord
	 * system.
	 */
        quat_rot_vec_self(&to_current_aim, current_turret_orientation);

	/* Now convert into the turret's local coord system */
        quat_rot_vec_self(&to_current_aim, &inverse_rest);

	/* Now calculate current azimuth and elevation of the turret */
        current_azimuth = atan2f(-to_current_aim.v.z, to_current_aim.v.x);
        xzdist = sqrtf(to_current_aim.v.z * to_current_aim.v.z + to_current_aim.v.x * to_current_aim.v.x);
        current_elevation = atan2f(to_current_aim.v.y, xzdist);

Step 4. Calculate the delta between current and desired azimuth and elevation

        delta_elevation = elevation - current_elevation;
        delta_azimuth = azimuth - current_azimuth;

Step 5. Clamp these deltas to the maximum angular rate the turret can move in both axes.

        delta_elevation = elevation - current_elevation;
        delta_azimuth = azimuth - current_azimuth;
        if (delta_azimuth > turret->azimuth_rate_limit)
                delta_azimuth = turret->azimuth_rate_limit;
        else if (delta_azimuth < -turret->azimuth_rate_limit)
                delta_azimuth = -turret->azimuth_rate_limit;
        if (delta_elevation > turret->elevation_rate_limit)
                delta_elevation = turret->elevation_rate_limit;
        else if (delta_elevation < -turret->elevation_rate_limit)
                delta_elevation = -turret->elevation_rate_limit;

Step 6. Apply deltas to current elevation and azimuth

        azimuth = current_azimuth + delta_azimuth;
        elevation = current_elevation + delta_elevation;

Step 7. Convert new elevation and azimuth to the new orientation quaternion

	/* Recall yaw_axis is a vector pointing up the Y axis.  Rotate it
	 * into the turret's coordinate space, and build a yaw quaternion
	 * from this rotated yaw_axis and the azimuth
	 */
        quat_rot_vec_self(&yaw_axis, turret_rest_orientation);
        quat_init_axis(&yaw, yaw_axis.v.x, yaw_axis.v.y, yaw_axis.v.z, azimuth);
	/* Rotate the turret_rest_quaternion by the yaw quaternion */
        quat_mul(new_turret_orientation, &yaw, turret_rest_orientation);

	/* Set up a pitch axis -- around the Z axis */
        pitch_axis.v.x = 0.0;
        pitch_axis.v.y = 0.0;
        pitch_axis.v.z = 1.0;

	/* Rotate it into the turret's coordinate space (which has the yaw
	 * incorporated already now) and build a pitch quaternion out of the
	 * rotated pitch axis and elevation
	 */
        quat_rot_vec_self(&pitch_axis, new_turret_orientation);
        quat_init_axis(&pitch, pitch_axis.v.x, pitch_axis.v.y, pitch_axis.v.z, elevation);
	/* Rotate the turret orientation by the pitch quaternion */
        quat_mul(new_turret_orientation, &pitch, new_turret_orientation);

        return new_turret_orientation; /* and we're done. */

 

Space Nerds In Space Big Honking Death Machine

•January 3, 2017 • Leave a Comment

January 2, 2017 — Star Wars has its Death Star, Star Trek has its Borg Cube, its high time Space Nerds In Space had its Big Honking Death Machine, which is what I’ve been working on lately.

The simplest possible approach would have been to build a 3D model much as I’ve done previously and just put it in. But I wanted the players to be able to interact with this thing in a way that you can’t interact with a simple model, at least the way they are currently implemented. I wanted players to be able to fly around and even inside this giant thing, and bump into the solid parts but fly through the empty parts. Doing that with a normal 3D model is pretty hard, too hard for me at the moment. It occurred to me that if I could make a kind of generic “block” with arbitrary height, width, and depth and orientation and I could get texture mapping and more importantly collision detection working with such a block, then I could arrange such “block” objects in a hierarchy, with relative positions and orientations and in this way be able to construct large “ships” out of many of these “block” objects — much like LEGOs, but without the alignment constraints. And so that’s what I’ve done.

Each of the blocks uses the same mesh, a unit cube, but each block has its own non-uniform scaling factors for x, y, and z. The collision detection is done using “oriented bounding boxes”, as described in Christer Ericsson’s book, Real-Time Collision Detection. The collision resolution is very simple minded at the moment. In the detection phase, the “closest” point on the surface of the block is identified, and a vector is constructed from this point to the player’s ship and the ship is “pushed back” a fixed amount in this direction and the velocity is set to zero. This mostly works, though it doesn’t look or feel all that natural, and once in a while penetration of walls may occur.

 

It is up to us

•June 30, 2016 • 6 Comments

June 29, 2016 — It’s been a bit over 24 hours since the terrorist attacks at Ataturk airport in Turkey as I write this.

It seems quite obvious to me that the attacks are religiously motivated. If those people weren’t adherents of a religion, they wouldn’t do those things. They might still be unhappy, they might still have various grievances, but they would not explode themselves in a quest to kill strangers perceived to be enemies in some purported exchange for some imagined eternal blissful afterlife awarded by some imagined deity for fighting in some imagined holy war. For that, religion is required. And the throbbing core that powers the Death Star known as religion is faith.

Faith is the idea that it is a good idea and a virtuous deed to deliberately attempt to be more certain about something than what is warranted by the available evidence. Deliberately willing oneself to be excessively certain. “Look at me! I’m so certain I’m right! And I clearly don’t have enough evidence to be so certain! I have faith! I’m so faithful! What a good boy am I for being so faithful!”

Faith is never described by the faithful so baldly as I have done, because to do so would be to highlight the idiocy of faith, and nobody wants to think of themselves as having behaved like an idiot. But faith is clearly idiotic. It is not a good idea to deliberately be excessively certain. It is not a vritue. It is the opposite of a virtue. The all too common and ridiculous cry of the defeated theist, “Well, it takes more faith to be an atheist…” betrays the believer’s subconscious realization that faith is something to be avoided, that it is not a reliable way to know what’s true.

How should we combat the type of faith that drives people to commit such acts? I would suggest the most effective way (not that I think it will be all that effective) is not to coerce compliance by means of threats, weaponry, and soldiers — I do not think this will do anything to mitigate cancerous faith. Likely, it will only make it stronger. I suggest the most effective tactic — or at least a tactic which should be tried — is to educate the faithful by argumentation — help them to reason their way out of their faith. And most importantly, I think we should directly attack the basic concept of faith itself. The concept of faith should be given the reputation it deserves. The statement, “He is a man of great faith” should not be regarded as praise, but as an insult.

We cannot rely on members of other religions to do this. A Christian and a Muslim and a Jew arguing about religion are like three blind men arguing about which of three unremarkable Thomas Kinkade paintings is the best possible painting in the universe. Their arguments will all be constructed out of the sheerest nonsense.

And you will not see any U.S. politician making any sort of argument that Islam or Christianity or Judaism or any religion has no basis in fact, or that faith is the idiocy that it so obviously is. That is because (among other reasons) the U.S. Constitution requires that the government remain neutral towards religion, not favoring any religion (or no religion) over any other religion (or no religion). Their hands are tied — even if they wanted to make such an argument, they are not permitted to do so. Nor are members of the United States armed forces while acting on behalf of the United States, as I understand it.

So it is left to us — private individuals — to make such arguments.

Space Nerds In Space: 3D “demon” screen

•May 30, 2016 • Leave a Comment

May 29, 2016 — When Space Nerds In Space was converted from a fundamentally 2D game to a proper 3D game a couple of years ago, one of the things which didn’t really make the transition was the “demon” screen, or “gamemaster” screen. That screen was fundamentally a 2D screen, and it was not and still is not obvious how such a thing ought to be done in 3D. I have finally gotten around to taking a stab at it though. Whether I’ve been successful, I’m not sure. In any case, here’s how it looks.

Speech Recognition and Natural Language Processing in Space Nerds In Space

•May 14, 2016 • 1 Comment

May 14, 2016 — Recently while working on my multiplayer networked starship bridge simulator game, Space Nerds In Space, it occurred to me that it would be cool if you could talk to the ship’s computer, somewhat like in the TV show Star Trek. So I made it so.

Watch demo on Youtube

To get such a thing working, There are three main problems that need to be solved:

  • Text to Speech — The computer has to be able to respond verbally.
  • Speech recognition — Your verbal commands need to get into the computer some how.
  • Natural Language Processing — The computer needs to extract semantic meaning from the recognized words in order to be able to obey your commands in a meaningful way.

Text To Speech

Text to Speech is mostly a solved problem — not perfectly solved by any means — but solved well enough for our purposes, and a difficult enough problem that rolling our own solution or even improving an existing solution is pretty much out of the question. There are three main text to speech contenders on linux, Festival, espeak, and pico2wave. Of these, pico2wave and espeak are easy to get working, and pico2wave seems to sound the best to me, especially if you use the “-l en-GB” flag to make it speak like a woman with an English accent.

	$ pico2wave -l en-GB -w output.wav "Hello there, I am the ship's computer, at your service."
	$ aplay output.wav

The interface of pico2wave is a bit unfortunate, in that it can only take input from the command line, and produce a .wav file as output, but wrapping it in a small script to create a temporary .wav file which is then played with aplay is not a big deal. Besides pico2wave, espeak is another option that is easy to make work.

	$ espeak "Hello there."

When I tried out Festival, it didn’t immediately work for me, and since the speech which pico2wave produced seemed to be of quite good quality and pico2wave was easy to get working, I didn’t really persist in trying to get Festival working, so I cannot really say how well it works or what it sounds like.

Speech recognition

Speech recognition is a harder problem than text to speech, and less well solved, but fortunately, due to the hard work of others, there are some options. For this, I used pocketsphinx. Some configuration and customization is required, as trying to use pocketsphinx out of the box as a general English recognizer does not really work all that well. However, if you constrain its vocabulary and give it some idea ahead of time what sorts of things it should be expecting, it does a much better job. To do that, you need to create a sample corpus of text which you can then feed through a utility to generate a set of files which pocketsphinx can use to help it. The corpus of text I am currently using for Space Nerds In Space is here, snis_vocab.txt.

The easiest way to do this is to use the web service at CMU: http://www.speech.cs.cmu.edu/tools/lmtool-new.html.

This site will allow you to upload and process your corpus of text to produce a number of files, including a gzipped tarball that contains all the files pocketsphinx needs. Download and unpack the produced tarball which will be named something like TAR1234.tgz, which should contain 5 other files (the numbers that make up the filename will differ, obviously):

	1234.dic
	1234.log_pronounce
	1234.vocab
	1234.lm
	1234.sent

You can then use a script like the following:

export language_model=1234
stdbuf -o 0 pocketsphinx_continuous -inmic yes -lm "$language_model".lm -dict "$language_model".dic 2>/dev/null |\
	stdbuf -o 0 egrep -v 'READY...|Listening...|Stopped listening' |\
	stdbuf -o 0 sed -e 's/^[0-9][0-9]*[:] //' |\
	stdbuf -o 0 grep COMPUTER |\
	stdbuf -o 0 sed -e 's/^.*COMPUTER //' |\
	stdbuf -o 0 tee recog.txt | \
	stdbuf -o 0 cat > /tmp/snis-natural-language-fifo

The use of “stdbuf -o 0” is to make sure there is no buffering between the programs so all output from each program is immediately transmitted to the next program in the pipeline without waiting for newlines or a full block, or anything like that. The filtering with grep and sed is just to cut out various extraneous output from pocketsphinx, and to cut out any lines not containing the word “COMPUTER”, and to remove all text up to and includeing the word “COMPUTER”. This essentially implements “hot word” functionality, so that it appears the computer knows when you’re talking to it. In reality, it’s listening all the time, and just throwing away anything that is not immediately preceded by the word “COMPUTER”.

The /tmp/snis-natural-language-fifo is a named pipe that Space Nerds In Space that serves as a means for text commands to be fed into “the ship’s computer”.

Natural Langage Processing

Supposing that we can transform speech into text, or failing that, just type in the text, we still have the problem of extracting meaning from the text.

In the last couple days, Google announced Parsey McParseface, an open source English text parser that uses neural networks to achieve unprecedented levels of accuracy in parsing English sentences. This might be interesting to look into someday, but for now, I have relied on my own home grown Zork-like 1980s technology, because I find such things to be fun to play with and easy enough. I have little doubt that Parsey McParseFace could beat the snot out of my little toy though.

The API for my natural language library is defined in snis_nl.h, documented in snis_nl.txt, and implemented in snis_nl.c.

The idea is you pass a string containing English text to the function snis_nl_parse_natural_language_request(), and then this calls a function which you have defined to do the requested action. For example, you might call:

        snis_nl_parse_natural_language_request(my_context, "turn on the lights");

Supposing you have arranged things so that after “turn on the lights” has been parsed, snis_nl_parse_natural_language_request will call a function you have provided which will interact with your home automation systme and turn on the lights, et voila! You’re living in Star Trek.

The library doesn’t have any vocabulary, you have to provide that to the library by adding words to its internal dictionary. The library does know some “parts of speech”, to wit, the following:

	#define POS_UNKNOWN             0
	#define POS_NOUN                1
	#define POS_VERB                2
	#define POS_ARTICLE             3
	#define POS_PREPOSITION         4
	#define POS_SEPARATOR           5
	#define POS_ADJECTIVE           6
	#define POS_ADVERB              7
	#define POS_NUMBER              8
	#define POS_NAME                9
	#define POS_PRONOUN             10
	#define POS_EXTERNAL_NOUN       11

To use the library, first you have to teach it some vocabulary. This is done with the following three functions, which teach it about synonyms, verbs, and words that aren’t verbs:

	void snis_nl_add_synonym(char *synonym, char *canonical_word);
	void snis_nl_add_dictionary_word(char *word, char *canonical_word, int part_of_speech);
	void snis_nl_add_dictionary_verb(char *word, char *canonical_word, char *syntax, snis_nl_verb_function action);

Synonyms are done first, as these are simple word substitutions which are done prior to any attempt to parse for meaning. For example, the following defines a few synonyms:

	snis_nl_add_synonym("lay in a course", "set a course");
	snis_nl_add_synonym("rotate", "turn");
	snis_nl_add_synonym("cut", "lower");
	snis_nl_add_synonym("decrease", "lower");
	snis_nl_add_synonym("boost", "raise");
	snis_nl_add_synonym("booster", "rocket");
	snis_nl_add_synonym("increase", "raise");

If you attempt to parse “lay in a course for saturn and rotate 90 degrees left and increase shields” the parser would see this as being identical to “set a course for saturn and turn 90 degrees left and raise shields”. Synonyms are simple token substitutions done before parsing, and in the order they are listed. Note that later substitutions may operate on results of earlier substitutions, and that this is affected by the order in which the synonyms are defined. Note also that the unit of substitution is the word, or token. So “booster” would not be transformed into “raiseer”, but rather into “rocket”.

Adding words (other than verbs) to the dictionary is done via the “snis_nl_add_dictionary_word” function. Some examples:

	snis_nl_add_dictionary_word("planet", "planet", POS_NOUN);
	snis_nl_add_dictionary_word("star", "star", POS_NOUN);
	snis_nl_add_dictionary_word("money", "money", POS_NOUN);
	snis_nl_add_dictionary_word("coins", "money", POS_NOUN);
	snis_nl_add_dictionary_word("of", "of", POS_PREPOSITION);
	snis_nl_add_dictionary_word("for", "for", POS_PREPOSITION);
	snis_nl_add_dictionary_word("red", "red", POS_ADJECTIVE);
	snis_nl_add_dictionary_word("a", "a", POS_ARTICLE);
	snis_nl_add_dictionary_word("an", "an", POS_ARTICLE);
	snis_nl_add_dictionary_word("the", "the", POS_ARTICLE);

Note that words with equivalent meanings can be implemented by using the same “canonical” word (e.g. “coins” and “money” mean the same thing, above).

Adding verbs to the dictionary is a little more complicated, and is done with the snis_nl_add_dictionary_verb function. The arguments to this function require a little explanation.

	void snis_nl_add_dictionary_verb(char *word, char *canonical_word, char *syntax, snis_nl_verb_function action);
  • word: This is simply which verb you are adding to the dictionary, e.g.: “get”, “take”, “set”, “scan”, or whatever verb you are trying to define.
  • canonical_word: This is useful for defining verbs that mean the same, or almost the same thing but which may have different ways of being used in a sentence.
  • syntax: This is a string defining the “syntax” of the verb, or the “arguments” to the verb, how it may be used. The syntax is specified as a string of characters with each character representing a part of speech.
    • ‘a’ : adjective
    • ‘p’ : preposition
    • ‘n’ : noun
    • ‘l’ : a list of nouns (Note: this is not implemented.)
    • ‘q’ : a quantity — that is, a number.

      So, a syntax of “npn” means the verb requires a noun, a preposition, and another noun. For example “set a course for saturn” (The article “a” is not required.)

  • action: This is a function pointer which will be called which should have the following signature which will be explained later.
    	union snis_nl_extra_data;
    	typedef void (*snis_nl_verb_function)(void *context, int argc, char *argv[], int part_of_speech[],
    					union snis_nl_extra_data *extra_data);
    

Some examples:

	snis_nl_add_dictionary_verb("describe",		"describe",	"n", nl_describe_n);   /* describe the blah */
	snis_nl_add_dictionary_verb("describe",		"describe",	"an", nl_describe_an); /* describe the red blah */
	snis_nl_add_dictionary_verb("navigate",		"navigate",	"pn", nl_navigage_pn); /* navigate to home */
	snis_nl_add_dictionary_verb("set",		"set",		"npq", nl_set_npq);    /* set the knob to 100 */
	snis_nl_add_dictionary_verb("set",		"set",		"npa", nl_set_npa);    /* set the knob to maximum */
	snis_nl_add_dictionary_verb("set",		"set",		"npn", nl_set_npn);    /* set a course for home */
	snis_nl_add_dictionary_verb("set",		"set",		"npan", nl_set_npn);   /* set a course for the nearest starbase */
	snis_nl_add_dictionary_verb("plot",		"plot",		"npn", nl_set_npn);    /* plot a course fo home */
	snis_nl_add_dictionary_verb("plot",		"plot",		"npan", nl_set_npn);   /* plot a course for the nearest planet */

Note that the same verb is often added to the dictionary multiple times with different syntaxes, sometimes associated with the same action function, sometimes with a different action function. This is really the key to how the whole thing works.

The parameters which are passed to your function are:

  • context: This is just a void pointer which is passed from your program through the parsing function and finally back to your program. It is for you to use as a “cookie”, so that you can pass along some context to know for example, what the current topic is, or which entity in your system is requesting something to be parsed, etc. It is for you to use (or not) however you like.
  • argc: This is simply count of the elements of the following parallel array parameters.
  • argv[]: This is an array of the words that were parsed. It will contain the “canonical” version of the word in most cases (the exception is if the word is of type POS_EXTERNAL_NOUN, in which case there is no canonical noun, so it’s whatever was passed in to be parsed.)
  • pos[]: This is an array of the parts of speech for each word in argv[].
  • extra_data[]: This is an array of “extra data” for each word in argv[]. The use cases here are for the two parts of speech POS_EXTERNAL_NOUN and POS_NUMBER. For POS_NUMBER, the value of the number is in extra_data[x].number.value, which is a float. For POS_EXTERNAL_NOUN, the item of interest is a uint32_t value, extra_data[x].external_noun.handle. External nouns are described later.

Some ancilliary functions

	typedef void (*snis_nl_multiword_preprocessor_fn)(char *word, int encode_or_decode);
	#define SNIS_NL_ENCODE 1
	#define SNIS_NL_DECODE 2
	void snis_nl_add_multiword_preprocessor(snis_nl_multiword_preprocessor_fn multiword_processor);

snis_nl_add_multiword_preprocessor allows you to provide a pre-processing function to encode multi-word tokens in a way that they won’t be broken apart when tokenized. Typically this function will look for certain word combinations and “encode” them by replacing internal spaces with dashes, and “decode” them by replacing dashes with spaces. This allows your program to have tokens like “warp drive” made of multiple words that will be interpreted as if they are a single word. It’s also possible that you might not know ahead of time what the multiword tokens are, which is why this is implemented through a function pointer to a function which you may implement. If you do not have such multiword tokens, you don’t need to use this.

	typedef void (*snis_nl_error_function)(void *context);
	void snis_nl_add_error_function(snis_nl_error_function error_func);

You can add an error function. This will get called whenever the parser is not able to find a match for the provided text — that is, the parser is unable to extract a meaning from the provided text. You should make this function do whatever you want to happen when the provided text is not understood (e.g. print “I didn’t understand.”, for example.)

External nouns:

Your program may have some nouns which you don’t know ahead of time. For example, in a space game, you may have planets, creatures, spaceships, etc. that all have procedurally generated names like “Borto 7”, “Capricorn Cutlass”, or “despair squid” which you would like to be able to refer to by name. This is what external nouns are for. To use them, you provide a lookup function to the parse via:

	void snis_nl_add_external_lookup(snis_nl_external_noun_lookup lookup);

Your function should have a prototype like:

	uint32_t my_lookup_function(void *context, char *word);

This function should lookup the word that it is passed, and return a uint32_t handle. Later, in your verb function, when you encounter the type POS_EXTERNAL_NOUN in the pos[] array passed to your verb function, you can look in extra_data[].external_noun.handle and get this handle back, and thus know *which* external noun is being referred to. For example, if in your space game, you have 1000s of spaceships, and the player refers to “Capricorn Cutlass”, (first you will need a multiword token encoder/decoder to prevent that from being treated as two tokens, see above) then your lookup function should return as the handle, say, the unique ID of the matching spaceship in the form of a uint32_t, so that when your verb function is called, you can extract the handle, and lookup the spaceship to which it refers.

The main parsing function

	void snis_nl_parse_natural_language_request(void *context, char *text);

snis_nl_parse_natural_language_request is the main function that parses the input text and calls the verb functions. You pass it a context pointer which can be anything you want it to be, and a string to parse. It will either call back an appropriate verb function, or the error function if you provided one.

Example program

snis_nl.c contains a main() function which is guarded behind some ifdef TEST_NL. You cand build the test program by the command “make snis_nl”

Below is a sample run of the snis_nl test program. Note that there is a lot of debugging output. This is because by default the snis_nl test program has debug mode turned on. You can turn this on in your program by sending as string like “nldebugmode 1” to be parsed by the parser, and turn it off by sending as string like “nldebugmode 0”. “nldebugmode” is the one verb the parser knows. This nldebugmode verb is added to the dictionary when the first user defined verb is added to the dictionary.

$./snis_nl
Enter string to parse: this is a test
--- iteration 0 ---
State machine 0 ('v,0, ', RUNNING) -- score = 0.000000
this[-1]:is[-1]:a[-1]:test[-1]:--- iteration 1 ---
State machine 0 ('v,0, ', RUNNING) -- score = 0.000000
this[-1]:is[-1]:a[-1]:test[-1]:--- iteration 2 ---
State machine 0 ('v,0, ', RUNNING) -- score = 0.000000
this[-1]:is[-1]:a[-1]:test[-1]:--- iteration 3 ---
State machine 0 ('v,0, ', RUNNING) -- score = 0.000000
this[-1]:is[-1]:a[-1]:test[-1]:--- iteration 4 ---
Failure to comprehend 'this is a test'
Enter string to parse: set a course for earth
--- iteration 0 ---
State machine 0 ('v,0, ', RUNNING) -- score = 0.000000
set[-1]:a[-1]:course[-1]:for[-1]:earth[-1]:--- iteration 1 ---
State machine 0 ('npn,0, ', RUNNING) -- score = 0.000000
set[2]:(npn verb (set))
a[-1]:course[-1]:for[-1]:earth[-1]:State machine 1 ('npa,0, ', RUNNING) -- score = 0.000000
set[1]:(npa verb (set))
a[-1]:course[-1]:for[-1]:earth[-1]:State machine 2 ('npq,0, ', RUNNING) -- score = 0.000000
set[0]:(npq verb (set))
a[-1]:course[-1]:for[-1]:earth[-1]:--- iteration 2 ---
State machine 0 ('npn,0, ', RUNNING) -- score = 0.000000
set[2]:(npn verb (set))
a[0]:(article (a))
course[-1]:for[-1]:earth[-1]:State machine 1 ('npa,0, ', RUNNING) -- score = 0.000000
set[1]:(npa verb (set))
a[0]:(article (a))
course[-1]:for[-1]:earth[-1]:State machine 2 ('npq,0, ', RUNNING) -- score = 0.000000
set[0]:(npq verb (set))
a[0]:(article (a))
course[-1]:for[-1]:earth[-1]:--- iteration 3 ---
State machine 0 ('npn,1, ', RUNNING) -- score = 0.000000
set[2]:(npn verb (set))
a[0]:(article (a))
course[0]:(noun (course))
for[-1]:earth[-1]:State machine 1 ('npa,1, ', RUNNING) -- score = 0.000000
set[1]:(npa verb (set))
a[0]:(article (a))
course[0]:(noun (course))
for[-1]:earth[-1]:State machine 2 ('npq,1, ', RUNNING) -- score = 0.000000
set[0]:(npq verb (set))
a[0]:(article (a))
course[0]:(noun (course))
for[-1]:earth[-1]:--- iteration 4 ---
State machine 0 ('npn,2, ', RUNNING) -- score = 0.000000
set[2]:(npn verb (set))
a[0]:(article (a))
course[0]:(noun (course))
for[0]:(preposition (for))
earth[-1]:State machine 1 ('npa,2, ', RUNNING) -- score = 0.000000
set[1]:(npa verb (set))
a[0]:(article (a))
course[0]:(noun (course))
for[0]:(preposition (for))
earth[-1]:State machine 2 ('npq,2, ', RUNNING) -- score = 0.000000
set[0]:(npq verb (set))
a[0]:(article (a))
course[0]:(noun (course))
for[0]:(preposition (for))
earth[-1]:--- iteration 5 ---
State machine 0 ('npn,3, ', RUNNING) -- score = 0.000000
set[2]:(npn verb (set))
a[0]:(article (a))
course[0]:(noun (course))
for[0]:(preposition (for))
earth[0]:(noun (earth))
--- iteration 6 ---
State machine 0 ('npn,3, ', SUCCESS) -- score = 0.000000
set[2]:(npn verb (set))
a[0]:(article (a))
course[0]:(noun (course))
for[0]:(preposition (for))
earth[0]:(noun (earth))
-------- Final interpretation: ----------
State machine 0 ('npn,3, ', SUCCESS) -- score = 1.000000
set[2]:(npn verb (set))
a[0]:(article (a))
course[0]:(noun (course))
for[0]:(preposition (for))
earth[0]:(noun (earth))
generic_verb_action: set(verb) a(article) course(noun) for(preposition) earth(noun) 
Enter string to parse: ^D
$

Building a wooden prop sword

•March 20, 2016 • Leave a Comment

March 20, 2016 — Awhile ago, I watched a video of Adam Savage building a prop Hellboy sword. He built it out of plywood, bondo, aluminum tape, and spray paint. It came out looking better than it had any right to. Here is that video:

So, being stuck at home for a couple days, I thought I’d try something similar.

First, I had to shape a (nominally) 1×2 inch board into something vaguely sword shaped. For this I used a coping saw, a pocket knife, and a rasp, and sandpaper. A table saw, jigsaw, belt sander, and various other power tools would have made the job go a lot easier and quicker, but I don’t have those, so I had to do it by hand. Using the rasp to shape the blade by hand was tiring, to say the least.

wooden-sword-1

wooden-sword-2

wooden-sword-3

Then I glued on some bits of wood I had sawed out with the coping saw to form a cross guard.

wooden-sword-4

Then on with the aluminum tape, spray paint and steel wool.

wooden-sword-5

wooden-sword-6

I used a ball-point pen to do some “elvish” “engraving”.

sword-engraving

Came out pretty cool.