xenonlab-studio / dungeon_adventure Goto Github PK
View Code? Open in Web Editor NEWText adventure (Interactive Fiction) inspired by the classic "Colossal Cave Adventure" of 1976.
License: GNU General Public License v3.0
Text adventure (Interactive Fiction) inspired by the classic "Colossal Cave Adventure" of 1976.
License: GNU General Public License v3.0
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:
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".
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).
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.
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...
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.
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.
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.
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.
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).
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.
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 | | | | | | | | | | | +----------------+ | | | | | +--------------------------------------------------------------+
We start off with just 10 lines of code; it is little more than a trivial “Hello World” program.
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.
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 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:
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.
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.
We bring some basic interactions in our program!
The basic principle of a text adventure is simple:
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.