Coder Social home page Coder Social logo

xenonlab-studio / dungeon_adventure Goto Github PK

View Code? Open in Web Editor NEW
4.0 1.0 0.0 59 KB

Text adventure (Interactive Fiction) inspired by the classic "Colossal Cave Adventure" of 1976.

License: GNU General Public License v3.0

C 75.72% CMake 2.68% C++ 4.75% Shell 1.14% Awk 15.71%
interactive-fiction text-adventure zork colossal-cave advent open-adventure linux-game macos-game windows-game terminal-game

dungeon_adventure's People

Stargazers

 avatar  avatar  avatar  avatar

Watchers

 avatar

dungeon_adventure's Issues

Objects

Let me be clear that I am using the term "object" in a philosophical sense here. It has nothing to do with object-oriented programming, nor does it have anything in common with the default "Object" type in programming languages like Java, C# and Python. I will define a new type of data called object; any other name will look just as good if you find that the object is confusing, or if it provides a namespace conflict in the programming environment you are using.

Most puzzles in adventure games revolve around objects and / or people. Examples:

  • the key must be found, then used to unlock a specific door.
  • A guard must be killed, corrupted or attracted to access a room.

Naturally, the guard could also be a dog, a troll, a dragon or a robot. In this sense, the non-player character is a common term, but I do not want to make a distinction between player and non-player characters (the guard could be an NPC). And only 'character' is easily confused with a type of character data, so I will limit myself to "person".

Locations

Traditionally, a text adventure is a virtual world made up of (many) different places. Although this is not essential (some adventures take place in a single room!), It's a good way to experience the use of data structures.

I define a structure to represent a position. It contains two simple attributes to start (which I will improve later).

  • description: how the position is described in the narrative text produced by the program.
  • tag: how the position is recognized by the parser.

Code generator

So far, the adventure has 10 objects. Each object consists of 5 attributes (the fields in the struct OBJECT). It is likely that a real-life adventure has hundreds, even thousands of objects, and the number of attributes per object increases. Keeping such a large list of objects and attributes in its current form would be difficult.

For example, when I added the wallField and wallCave objects previously, I had to do it in three different places: once in object.h (like #define) and twice in object.c (an element in the array objs, and a separate matrix for the tags). This is clumsy and error-prone.

Instead of keeping object.h and object.c by hand, I will start generating the files from a single source that best suits my needs. This new source file could be in any language I prefer (usually a domain-specific language), as long as I have the tools to convert it back to C. The following is a simple example. Consider the following layout to organize objects:

+----------------------------------------------------+
|                                                    |
|   +--------------------------------------------+   |
|   |                                            |   |
|   |    Raw C code (declarations)               |   |
|   |                                            |   |
|   +--------------------------------------------+   |
|   +--------------------------------------------+   |
|   |                                            |   |
|   |                                            |   |
|   |    - ObjectName                            |   |
|   |          AttributeName AttributeValue      |   |
|   |          AttributeName AttributeValue      |   |
|   |          ...                               |   |
|   |    - ObjectName                            |   |
|   |          AttributeName AttributeValue      |   |
|   |          AttributeName AttributeValue      |   |
|   |          ...                               |   |
|   |    - ...                                   |   |
|   |                                            |   |
|   +--------------------------------------------+   |
|                                                    |
+----------------------------------------------------+

Based on the objects I've collected so far, I can build the following source file. The file name does not matter much; I simply called object.txt, to make it clear that it is related to object.h and object.c.

+------------+
| object.txt |
+------------+
#include <stdio.h>
#include "object.h"

typedef struct object {
   const char    *description;
   const char   **tags;
   struct object *location;
   struct object *destination;
} OBJECT;

extern OBJECT objs[];

- field
     description "an open field"
     tags        "field"

- cave
     description "a little cave"
     tags        "cave"

- silver
     description "a silver coin"
     tags        "silver", "coin", "silver coin"
     location    field

- gold
     description "a gold coin"
     tags        "gold", "coin", "gold coin"
     location    cave

- guard
     description "a burly guard"
     tags        "guard", "burly guard"
     location    field

- player
     description "yourself"
     tags        "yourself"
     location    field

- intoCave
     description "a cave entrance to the east"
     tags        "east", "entrance"
     location    field
     destination cave

- exitCave
     description "a way out to the west"
     tags        "west", "out"
     location    cave
     destination field

- wallField
     description "dense forest all around"
     tags        "west", "north", "south", "forest"
     location    field

- wallCave
     description "solid rock all around"
     tags        "east", "north", "south", "rock"
     location    cave

I made up the syntax myself, so it is safe to assume there are no standard tools to translate it to C. We will have to write our own code generator! Since this code generator will be a separate program, completely independent of our adventure program, we can write it in any language we like - not necessarily C. Here is one possible implementation, written in AWK:

+------------+
| object.awk |
+------------+
BEGIN {
   count = 0;
   obj = "";
   if (pass == "c2")
   {
      print "\nOBJECT objs[] = {";
   }
}

/^- / {
   outputRecord(",");
   obj = $2;
   prop["description"] = "NULL";
   prop["tags"]        = "";
   prop["location"]    = "NULL";
   prop["destination"] = "NULL";
}

obj && /^[ \t]+[a-z]/ {
   name = $1;
   $1 = "";
   if (name in prop)
   {
      prop[name] = $0;
   }
   else if (pass == "c2")
   {
      print "#error \"" FILENAME " line " NR ": unknown attribute '" name "'\"";
   }
}

!obj && pass == (/^#include/ ? "c1" : "h") {
   print;
}

END {
   outputRecord("\n};");
   if (pass == "h")
   {
      print "\n#define endOfObjs\t(objs + " count ")";
   }
}

function outputRecord(separator)
{
   if (obj)
   {
      if (pass == "h")
      {
         print "#define " obj "\t(objs + " count ")";
      }
      else if (pass == "c1")
      {
         print "static const char *tags" count "[] = {" prop["tags"] ", NULL};";
      }
      else if (pass == "c2")
      {
         print "\t{\t/* " count " = " obj " */";
         print "\t\t" prop["description"] ",";
         print "\t\ttags" count ",";
         print "\t\t" prop["location"] ",";
         print "\t\t" prop["destination"];
         print "\t}" separator;
         delete prop;
      }
      count++;
   }
}

We actually need to call this AWK script three times to generate the C sources:

awk -v pass=h  -f object.awk object.txt >  ../include/object.h
awk -v pass=c1 -f object.awk object.txt >  ../src/object.c
awk -v pass=c2 -f object.awk object.txt >> ../src/object.c

This will generate a new object.h and object.c, which should be identical (save for layout).

object.c is generated in two steps; for object.h, a single pass is sufficient. I could have created three separate AWK scripts, one for each pass, but instead I created one big script that combined all three, which seemed like the right thing to do considering the many similarities.

The code generation script is very simple; does not check the syntax on attribute values. Most typos made in object.txt pass through the generator without errors. This is not a problem: the syntax checks made later by the C compiler are sufficient. When compiling fails, the trick is to recognize errors in the C code, then find and correct the original source in object.txt. To make this task a little easier, the least I could do was to let the code generator add some comments in the generated C code (see object.awk).

The AWK script can also pass errors to the C compiler, issuing a #error directive as part of the generated code.

Notes:

  • Important: at the time, it did not make any manual changes to object.h and object.c; these would only be
    discarded by the code generation process.

  • This combination of languages ​​(C, AWK and a domain-specific language) may seem initially confusing. Moreover,
    this is still relatively simple compared to the combination of server-side and client-side techniques with
    which the average web developer is confronted.

  • Because object.txt is converted to simple C code, compiled and linked with the other modules, all of its data
    will be part of the final executable. Like any source file, object.txt should not be present when the
    executable is running (for example, when someone is playing). This, of course, is just a matter of choice. It
    is very well possible to keep object.txt regardless of the executable and make the executable import data from
    object.txt at runtime. This is particularly interesting when you build an adventures development system. Keep
    in mind that it will make the code slightly more complicated; requires more effort in analyzing object.txt,
    since there will not be any C compiler to support me.

Visualization

When it comes to choosing a domain-specific language, keep in mind that code generation is not its only advantage. A simple AWK script, similar to the one above, can be used to display a virtual world map by drawing a chart.

Currently under development...

More attributes

There are many possible reasons for expanding the ‘object’ structure.

When I introduced the objects, they only had three attributes. Later, I added a fourth attribute. This is more or less the absolute minimum. To put more details into the adventure, I need more attributes.

  1. The command looks around offers a global description of the player's position, including a list of objects, people and other objects present. Many adventures require the player to examine these objects, or to reveal some clues needed to make progress in the game, or simply to improve the atmosphere of the game. I want to add the details of an attribute that contains a detailed description of each object, plus an attribute contents that is used with objects that contain other objects.

  2. When the player follows a passage, the answer is invariably "OK" followed by a description of the new position. This is a bit boring; it would be much nicer to give your personalized message to each passage. I will add a textGo attribute to preserve this message.

  3. Some steps have a "turn"; they do not go where the player expects them to go. For example, a forest road could hide a trap. While the passage seems to be directed from position A to position B, in reality the end point is position C, ie the bottom of a pit. The most common "twists" are "blocked" passages: a closed net, a broken bridge, a narrow slot. Suppose that our entry into the cave is blocked by the guard. Any attempt to enter the cave will fail; instead the player will remain in his original position, ie the field. I can simply change the destination of the passage in the field (or NULL), but this would result in an unwanted response to commands such as going to the cave and watching caves: "You do not see any cave here." I need separate attributes for the real and the apparent end point of a passage. I will introduce an attribute prospect to represent the latter; the attribute destination, introduced earlier, still maintains the actual end point. In most cases, the two will be the same, so I will have object.awk that generates an appropriate default value; the perspective must be specified only in object.txt when it differs from the destination.

  4. In many adventures, the player, as well as other people in the game, are limited to the weight of the objects they can carry. Give each object a weight; the combined weight of all objects in a person's inventory should not exceed that person's capacity. Give an object a very high weight to make it immobile (a tree, a house, a mountain).

  5. RPG-style adventure games will need a whole range of attributes for persons (both player and non-player), for example health. Objects with zero health are either dead, or they are not a person at all.

I define seven new attributes in object.txt.

All this will make the project more complete, giving a much deeper and more flexible narrative and explorative depth.

Passages (draw a map with the game rooms and implement it).

It's time to draw a map - and implement it!

The best tools for drawing a map will always be: a pencil and a piece of paper. A basic map consists of locations (the rectangles), connected by passages (the arrows).

Here is a representation of how the game map should be:

+--------------------------------------------------------------+
|                                                              |
|                                                              |
|                                       +----------------+     |
|                                       |                |     |
|                                       |                |     |
|             +-----------------------> |     ROOM 1     |     |
|             |                         |                |     |
|             |                         |                |     |
|             |                         +----------------+     |
|             |                                 |              |
|             |                                 |              |
|             |                                 |              |
|     +----------------+                        |              |
|     |                |                        |              |
|     |                |                        |              |
|     |     ROOM 1     |                        |              |
|     |                |                        |              |
|     |                |                        |              |
|     +----------------+                        |              |
|                                               |              |
|                                               |              |
|                                               v              |
|                                       +----------------+     |
|                                       |                |     |
|                                       |                |     |
|                                       |     ROOM 2     |     |
|                                       |                |     |
|                                       |                |     |
|                                       +----------------+     |
|                                                              |
|                                                              |
+--------------------------------------------------------------+

Create the main.c

We start off with just 10 lines of code; it is little more than a trivial “Hello World” program.

North, East, South, West

                                           N
                                           ^        
                                           |        
                                           |        
                                   O <-----|-----> E
                                           |        
                                           |        
                                           v        
                                           S

Traditional text adventures use compass directions to navigate. For example, on the map I drew previously, the player may want to go east to move from the field to the cave. We can implement it by giving the passage to Cave the tag "est". However, there are two problems we must solve first. 1. We may still want to refer to the passage as "entry" and "east". But at the moment, an object can only have one tag. 2. On a larger map, with multiple locations and steps, the "east" tag will be defined multiple times. Currently, our parser can not handle it: the tags are considered as unique (see the parseObject function in the misc.c module). These problems also apply to other objects, not just to steps. In our adventure, we have a silver coin and a gold coin. On the one hand, it would be foolish not to accept receiving coins in a place where only one of the coins is present. On the other hand, it should be possible to use a silver coin in case both coins are present in the same position. This immediately brings us to a third problem with our parser: 1. A tag can only be a single word; "silver coin" would never be recognized. All three problems I solved them, starting from problem no. 1. It is resolved by assigning to each object a list of tags, rather than just a single tag.

Improved error handling

So far, our error handling has been very basic. For example, command get just returns “You can't” in all possible situations that prevent the player from picking up something. Such a reply is dull and not very helpful. It neglects an important aspect of any computer game; in fact, an essential part of life itself: the player has to learn from his mistakes. It is OK for an adventure to be difficult, even frustratingly difficult. But when the player has the feeling he is not making any progress at all, or when the only way to make progress is a brute-force attack on all verb-noun combinations, then even the most hardened player will lose interest and eventually give up. The least an adventure game should do, is explain why the player's command cannot be completed: “You can't do that, because...” This helps to make the virtual world more convincing, the story more credible, and the game more enjoyable.

Most commands operate on one or more objects, for example:

  • The player takes an object, then gives it to another person.
  • The player follows a pass to another position.

The first thing to check (after the obvious typing errors captured by the parser) is the presence of these objects; failure should result in something like "There is not ... here" or "You do not see any ..." In this commit, I will construct a generic function that can be used by every command to find out if an object is within reach of the player's hand.

One can think that we only have to distinguish two cases: either the object is here, or it is not. But many commands require more gradients than "here" and "not here". Examples:

  • To use a weapon or an instrument, the player must keep it; its mere presence on the scene is not enough.
  • To delete an object, you must keep it; to take an object, you do not have to keep it.
  • Another person holding an object may prevent the player from obtaining that object; getting objects is generally easier when the object is simply laid out here.

This is because the term 'here' can mean a lot of things:

Function Description Code
distPlayer The object is the player object == player
distHeld The player is holding the object object->location == player
distHeldContained The player is holding another object (for example a bag) containing the object object->location != NULL && object->location->location == player
distLocation The object is the player's location object == player->location
distHere The object is present at the player's location object->location == player->location
distHereContained Another object (either a person or a ‘container’), present at the player's location, is holding (but not hiding) the object object->location != NULL && object->location->location == player->location
distOverthere The object is a nearby location getPassageTo(object) != NULL

The first case (object is player) may seem trivial, but it is important nonetheless. For example, the command "examine yourself" should not return "There is no yourself here."

I tried to follow a logical order: nearby things are at the top, further down below they become more distant. We can continue the list, to cover objects that are even further away:

Function Description Code
distNotHere The object is (or appears to be) not here
distUnknownObject The parser did not recognize the noun entered (we will use this in a later chapter) object == NULL
distNoObjectSpecified The player entered a command with no noun object == NULL

Notice we have seven different cases of 'here', but only one for 'not here'. This is because typically, the game only needs to provide information about things that can be perceived by the player. If it's not here, then there's nothing more to tell.

In the leftmost column, I proposed a symbolic name for each case. We will gather these names in an enum named DISTANCE.

Inventory

Now it is allowed to "hold" the objects.

Previously, I created a matrix to store all objects, including the player himself. Considering the player as an object, we make the player an integral part of the game, instead of a spectator looking at the virtual world from a safe distance.

The main loop

We bring some basic interactions in our program!

The basic principle of a text adventure is simple:

  • The player inserts a command.
  • The program analyzes and executes the command.
  • Repeat steps 1 and 2 until the player decides to exit.
  • The following code example consists of three functions, one for each step:
  • getInput function
  • function parseAndExecute
  • the main function takes care of repeatedly calling the other two functions

This program, however brief it may be, already responds to commands like looking around, going north and going out. But obviously it's not much. It has no positions, no objects; all that is there is darkness. So in the next commit, I will start adding the positions.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.