[MEGA] The Adventure Continues

Posted by Astryl on Jan. 21, 2012, 12:36 a.m.

Part dev log, part ramble, all Mega.

Because of the length of this blog, and the picky nature of anybody reading it, I've divided it into neat little sections again.

This time I'm dealing with four topics:

> Text Parsing for Adventure Games

> How far my adventure game is coming along

> My impressions of Terraria so far

> Another show I'm watching now which is awesome.

Text Parsing for Adventure Games

I don't know about any of you other programmers out there, but a text adventure game was one of the first things I tried to make the second I 'got' the basics of my language.

Of course, along the way lie many stumbling blocks, and one of these is that mystical 'black box' in a text-adventure that somehow translates your English sentences into actions. Believe me, it took me a while to figure it out. Also keep this in mind:

Quote:
Rule for working with unfamiliar projects/ideas

Don't try to make an elegant approach. Make a messy one, figure out the pitfalls, and refine the design later.

Well, I was sitting around a few days ago trying to figure out in my mind the way that the text parser was going to work. I settled on a large nested if-else block (The brute force approach, but I'm working by 'Make games, not engines').

Then I figured something out halfway: This isn't much different to the way that I, and many other programmers, handle command-line arguments in our programs.

Let me examine a single piece of user data, a single line of input, and then work through the processing steps involved:

> Reading the text (Input)

> Parsing the text (Processing)

> Executing the correct function(s) based on the text (Output)

Any of you who have taken that boring ol' introductory design course will recognize the three sections there. Let's pick them apart.

Note: The code here is C++, but it shouldn't be hard at all to 'translate' it to your favorite language

Reading the text

The first stumbling block, and often the one that beginners will give up on. Sounds easy, right? Read a line of text? Not if you're not told how to. JAVA courses tend to leave out console input for a long time because it requires at least a minor explanation of exception handling in order to implement it, for instance.

But that's only half of the problem.

Most console input functions have one serious problem: They truncate the resulting input at the first whitespace character (That is, a press of the spacebar, or a press of the enter key).

In cases like this, we'd rather read in a whole line of input, up until the user presses Enter.

Of course, not having any offline documentation for MingW and it's libraries, I went ahead and made my own function, called getline(), to read a whole line… only to find out a month later that the standard library has a getline() function that does the exact same thing. Here's a code snippet:

// Returns a string containing a line of input
std::string getString() {
   std::string res;
   std::getline(std::cin, res);

   return res;
}

(std:: identifiers left in to avoid confusion as to where the datatypes and functions come from).

As you can see, getline takes two arguments. The first is the Source, the second is a Reference to the Destination. In this case, I'm using the Stream CIN (Console IN) as the source. You can also use a file input stream (ifstream).

It blocks until the user has finished entering a line of text and places that text into a string, sans the '\n' escape sequence at the end.

Well, now we can get text. Now how do we do anything meaningful with it?

Parsing the text

Python is a wonderful language for text processing. It contains many functions that modify and process strings in useful ways. But C, or C++, do not.

C++ does offer the upper() and lower() functions, in ctypes.h, but there was another function that I needed: Something that could split a string into sub-strings given an arbitrary 'delimiter'. So I made the split() function.

std::vector<std::string> split(std::string str, char delim)
{
    std::vector<std::string> temp_v;
    std::string temp = "";

    for(int i = 0; i < str.size(); i++)
    {
        if(str[i] != delim)temp += str[i];
        else
        {
            if(!temp.empty())temp_v.push_back(temp);
            temp = "";
        }
        if(i == str.size()-1)
        {
            if(!temp.empty())temp_v.push_back(temp);
            temp = "";
        }
    }

    return temp_v;
}

This is a slow, but sure way to split the string up into sub-strings (Stored in the vector container, which allows for easy random access).

My step with this whole process, after getting the input and splitting it, is to copy the split set into another, but leaving out any unnecessary words, such as AN, THE, A, AT.

Usually for this I create a function that returns bool and scans each word for a match with words in a std::list/vector, but for my game I hard coded an if block like this: (I'm using my own upper() and lower() functions, because I know how they work):

vector<string> strip_uwords(vector<string> text_set) {

vector<string> new_set;

for(int i = 0; i < text_set.size(); i++)
{
	if(( mio::same( mio::lower(text_set[i]), "an") ) ||
	   ( mio::same( mio::lower(text_set[i]), "the") ) ||
	   ( mio::same( mio::lower(text_set[i]), "a") ) ) // Etc
	{
	   // Do not perform any operations
	}
	else
	{
	   // Copy to new set
	   new_set.push_back(text_set[i]);
	}
}

return new_set;
}

Simple, not too fast, but effective (Not fast because I'm not overly concerned about speed when it comes to a text adventure game; I want the codebase to be easy to work with though, which becomes more difficult if I spend time creating a more efficient system.)

Executing the correct function(s) based on the text

This step is a bit difficult and simple to explain at the same time. Difficult because I have to explain the system I currently have going.

In my code, there exist some primary objects (classes):

m_room

m_player

m_item

m_npc

The m_room class contains lists of items and npcs.

On the global level, I have these three important variables defined:

m_room* map_frame[100]; // A 10x10 map grid at a time

m_room* room_ptr; // A pointer to the currently active room.

m_player* player_ptr; // A global pointer to the player

With this in mind, observe a snippet from my code that deals with part of the EXAMINE command:

if(same(lower(tx[0]), "examine"))
{
   // Check element count
   if(tx.size() > 1)
   {
	// Special 'inventory' or 'i' or 'items' cases.
        if(same(lower(tx[1]), "inventory") ||
           same(lower(tx[1]), "i") ||
           same(lower(tx[1]), "items") )
	{
            // Call player's inventory list function
            if(player_ptr)
            {
               player_ptr->act_inventory();
               return 0;
            }
	}
        else
        {
            // Passes 'argument' to the current room's examine code, which 
            // checks it's item list for a match, and displays an 
            // "I don't see the <x> here" message if it can't find one.
            
            if(room_ptr)
	    {
  	       room_ptr->act_examine(tx[1]);
	       return 0;
  	    }
                
        }
   }
   else
   {
      // Call room look code since no 'arguments' provided.
      if(room_ptr)
      {
          room_ptr->act_look();
	  return 0;
      }
   }
}

Now, if you take the time to read it carefully, you'll notice that the whole lot is just a bunch of simple if-else blocks. Nothing spectacular.

It's not a very elegant approach, but that's the whole point: It's not meant to be. It's meant to be readable, changeable, and above all: Bug free.

An interesting thing to note is that all the object types have a set of act_*() functions, which are overridden by inherited classes to define

extra behavior (One NPC's act_conversation() will be different from another's). In the spirit of Java, I'm including all new items/npcs/rooms in

their own header/source sets, in sub-folders to keep everything neat.

Hope this provided an interesting read. I had to get it out.

How far my adventure game is coming along

Well between yesterday and now (The 20th, as of this moment), nowhere. Terraria.

That is, I got nowhere with the code, but I managed to soar ahead with the story and

overall idea. I'm not going to spoil any story, but I can mention settings:

A crazy half-French half-British hamlet during the 1500's. Whether this is historically accurate or not, I don't know (nor care); it suits my story though. What is the story about?

Love, Loss, Revenge, and most importantly: ALE.

I'm an amateur writer. I've written countless beginnings (And even some endings). But like all writers, it takes me ages to actually get anywhere without motivation.

Also, I'm way out of my comfort zone with this story. Writing a romantic comedy is not something I'm used to (It has to be comical. Monkey Island changed my view of adventure games forever).

Anyway, time to devote some time to my game before the Terraria shortcut beckons once more.

– UPDATE AFTER THIS BLOG WAS WRITTEN –

I managed to complete the basics of item/environment interaction.

Also, you now get an amusing assortment of satire when you attempt to walk through a non-existant exit.

Oh. And you can sing… Heh.

So far, so good. I'm very pleased with the way this is turning out. Now I just need to figure out the area layout on paper, and map it into the game. Also, I need to create some puzzles. I've still got nearly 20 days for this, so I ought to be able to flesh it out nicely. I'll probably even be nice and release the source code for it when I'm done =P

My impressions of Terraria so far

Well, I connected my PC to the ADSL yesterday (After transporting it to the other house

on the property, the one my parents dwell in). And soon, I had three of my Steam games

downloaded and ready to play.

Except for L4D2. I put that into my gifts inventory, since I can't possibly download it anytime this decade, unless I miraculously receive unlimited internet from some unknown source soon. Which I'm working on.

So, first of all I tried The Polynomial. Took a bit of getting used to (And it took a while to see past all the funky lights), and I discovered a perfectly challenging shooter underneath.

Then… Terraria. I started it up with my two brothers watching me. First impressions: lol Simple graphics, lol Minecraft knockoff, lol crafting bench, lol NPC help/hint/crafting guide.

And then I started noticing the differences. Terraria makes the exploration factor fun. So much, in fact, that it seemed I just blinked and suddenly it was 1AM. I almost laughed myself into a coma when I discovered and crafted the Goldfish Bowl, and figured it's use out, as well as the bucket.

Single Player has me extremely impressed so far; Multiplayer is going to be a veritable time-drainer, so I daren't play it yet. Not until I have lots of free time.

It's a conspiracy. You just let me win the last comp so I'd have a soul-sucking game to keep me distracted and inable to get any other work done. I SEE YOUR PLANS…. *shifty eyes* I won't give in…. just let me finish exploring this little bit of cave quickly…

– UPDATE, NEXT MORNING AT ABOUT 3AM –

It got me again. Dammit. Also, something amusing. I accidentally summoned another Eye of Cthulhu when one was already on-screen. Twice the fun (I won, of course). Oh, and the villagers in my village were all slaughtered by a vicious army of goblins.

That other TV show I'm watching that's awesome

OK. I've only watched ONE episode, and it's also on The Hub.

No, it's not Transformers. I can't stand the new generations.

The Dan vs. series.

Here's the episode I watched yesterday with my brother:

What made this funnier is that my name is Daniel, and I have a black and white cat.

I do not keep a list of things I dislike intensely though. Heh.

Give it a watch if you dare.

Well, that's another 'larger than 10kb' blog successfully written. Not counting the YT embed's data of course.

I'm going to force myself now to work some more on my game, and actually design some of the story areas before Terraria sucks me in again.

Comments

Astryl 12 years, 10 months ago

Quote:
The best way is to just dig a straight hole down with picks. It works REALLY well with two people since you can each do either the left or right block and just both hold with LMB until you reach the bottom.

And there my problem becomes apparent. You see, I downloaded Terraria to MY PC. Which I had to lug over to my dad's house in order to use the ADSL. And then I switched it into offline mode; I haven't the advantage of cooperation.

Rob 12 years, 10 months ago

It's still going to be a long way down by blowing stuff up. Do you really have that much money?

You could always just transfer the save over when you have online time I guess.

Astryl 12 years, 10 months ago

Quote:
It's still going to be a long way down by blowing stuff up. Do you really have that much money?

You could always just transfer the save over when you have online time I guess.

Yeah. I have a lot of money. And about 14 chests full of crap that I can sell for more. I can't believe how many Water Bolt spells I found in that dungeon…

Also, the idea of jumping into a large hole to reach the underworld is an amusing one.

Rob 12 years, 10 months ago

But a nice, clean 2 block wide hole is better!

Unaligned 12 years, 10 months ago

Also, get a cobalt shield or lucky horse shoe, falling damage isn't removed when getting hit airborne.

(Damnit we should organize a 64D Terraria night)

Toast 12 years, 10 months ago

Quote:
I think writing a romantic story is probably the hardest kind of writing a person can do
I have to disagree. It's probably the most formulaic genre there is (x meets y, x befriends y, z gets in the way of love, x and y have to separate but eventually overcome z and come to some mutual understanding (not necessarily love)).

Now, writing an interesting, original romantic story, that's nearly impossible.

Astryl 12 years, 10 months ago

Quote:
But a nice, clean 2 block wide hole is better!

Actually, I ended up doing this anyway. Got a nice long drop to the Underworld.

Quote:
Also, get a cobalt shield or lucky horse shoe, falling damage isn't removed when getting hit airborne.
Got the shield. Not the lucky shoe though.

Quote:

I have to disagree. It's probably the most formulaic genre there is (x meets y, x befriends y, z gets in the way of love, x and y have to separate but eventually overcome z and come to some mutual understanding (not necessarily love)).

Now, writing an interesting, original romantic story, that's nearly impossible.

I'm following the formula <x> meets <y>. <x> is commoner, <y> is not. <x> asks for <y>'s hand in marriage, <y> accepts. Douchebag uncle <z> locks <y> up in local chateu. <x> does what you'd expect him to do: Tries to assassinate <z>.

F1ak3r 12 years, 10 months ago

Quote:
Dan vs
http://dan-vs-fim.tumblr.com/

It is glorious.

Astryl 12 years, 10 months ago

Quote:
That's how I found out about the show. Indeed 'tis glorious.