The nazbot agent is an extension to the open-source nazghul game engine. The extension provides an API for writing an agent to control the "player".
***
Getting and starting the agent:
0. First, you need to install the SDL and SDL_image libraries available from
libsdl.org (I believe they're already installed on onyx) 1. Get the source from http://myweb.cableone.net/gmcnutt/nazbot-0.1.3.tar.gz 2. Once downloaded, then under linux or cygwin:
$ tar -xzf nazbot-0.3.0.tar.gz
$ cd nazbot-0.3.0
$ ./configure --prefix=$HOME
$ make
$ make install
!!! Note: the --prefix option to configure should refer to your home
directory. If you don't use this option then the make install step will
require you to be root.
3. To run the agent:
$ cd worlds/haxima-1.001
$ nazghul --bot map-1.scm
Besides map-1.scm, there is also map-2.scm and map-3.scm. Each map illustrates a different quest and a different situation to challenge an agent.
Some other tips:
- You can download the windows binary at
http://myweb.cableone.net/gmcnutt/nazbot-win32.tar.gz. Untar it the same,
but you don't need to build it. Just open a command window, cd to the
directory, and run the game as:
DOS> nazghul --bot map-1.scm
- You can invoke the game without the --bot option to play it as a human. To toggle between agent mode and human mode use the semicolon (;) key.
- There is a User's Guide at http://myweb.cableone.net/gmcnutt/USERS_GUIDE.TXT which has more instructions on how to play the game, which will be important to understand the agent API. The ORIENTATION section of the manual assumes you use the haxima.scm file instead of atf.scm as the startup world.
- For agent development you will only have one place (or map, or level) to work in, so you don't have to worry about portals or map edges leading to other places.
- Also, you will only have one character in your "party", so your agent will only control one actor.
***
Writing an agent
The interface used between the agent and the kernel is specified in src/agent.h. I've tried to comment it extensively. When you write an agent, you should not need to include any other nazghul header file. In fact, I encourage you not to.
agent.h specifies both the interface which your agent presents to the nazghul kernel as well as the interface which your agent uses to access the kernel.
In a file called src/beowulf.c you will find a sample implementation of an agent. It isn't meant to be a good agent, instead it's meant to provide examples of how to use the functions outlined in agent.h. Hopefully its pitiful performance will inspire you to prove that you can write one better than a professional programmer. That's my excuse anyway.
In particular, I do not want you to use a function called actor_get_path(). It's there, like the Tree in the Garden to tempt you, but do not use it. This function promises to do your pathfinding for you. And it will. But it cheats, and furthermore will punish you in hidden ways. Feel free to look at the underlying implementation in astar.c, and you may even use astar.c if you like, but avoid actor_get_path().
To write your own agent the simplest thing to do is gut beowulf.c down to some stubs which implement the agent interface and start filling them in. If you just want to start with beowulf.c and change it knock yourself out, but get rid of the calls to actor_get_path().
beowulf.c is not that well documented. Tough. You need to get used to reading code. Being able to read other people's grotty code is an important part of being a pro.
BTW, I will encourage Dr Andersen to award an unspecified amount of extra credit to anyone who can guess why the file is named beowulf.c. I personally will buy a coke for the first person who can tell me what the literal meaning of "beowulf" is. This is a really easy question, and Google probably makes it easier, so I should be losing 60 cents here pretty quick.
The smart ones will be able to see pretty quickly that the present architecture only permits one agent at a time, and it must control the player. However, if you are confident of your scheme programming skills you may try to write an NPC agent. See examples in worlds/haxima-1.001/spider.scm, troll.scm and ai.scm. The API provided to these agents is in kern.c and it allows them to cheat like clairvoyants at a casino. Eternal fame awaits anyone who can present me with a new monster AI written along these lines. Surely someone can do better than what I have done.
The session will end when your agent dies or calls agent_quit(). Upon exit the program returns the agent's score. So, for example, in UNIX you can retrieve this via:
$ nazghul --bot map-1.scm
$ echo $?
***
Agent-writing FAQ
Q: If the goal of a map is to escape the dungeon, how do I find the exit? A: As a human player the exit is marked with a ladder. As an agent use the
loc_is_exit() call to test locations.
Q: How can my agent tell if a terrain is a door? A: Technically, doors aren't terrain. They're mechanisms. And, in the agent
maps, it just so happens that doors are called "doors". So, this generally works to tell you if a door is at a location:
OBJ mech = loc_mechanism(loc);
if (mech && ! strcmp(obj_name(mech), "door"))
/* yep, it's a door */;
else
/* probably not a door */;
I say "probably not" because if somebody ever adds a new type of door and decides to name it "steel orifice" then the above check will incorrectly decide that a steel orifice is not a door.
A more robust but more challenging way is for the agent to "learn" what constitutes a door. An agent can discover this by experimenting. I mean, what really is a door? A door is a mechanism that, when handled, makes its location passable to the agent.
Q: How can my agent tell if a terrain is a wall? A: Unlike a door, a wall really is terrain. You can try the name hack:
TERRAIN t = loc_terrain(loc);
if (t && ! strcmp(terrain_name(t), "wall"))
/* yep, it's a wall */;
But again, what if the wall is named "rock wall" or "log palisade"? And again, it doesn't really matter what it's name is - a wall is impassable, opaque terrain. Your agent can learn the names of wall terrains by experimenting.
Q: When I look at the map that is dumped after the agent can no longer find a
path. Some of the places that are out of line of sight are showing up as unaccessible rather than unexplored. A: First of all, you're using agent_find_path() instead of writing your own
pathfinding. I know it's tempting but IT CHEATS. What you are seeing here is an example of that. The locations are out of line-of-sight, but the CHEATING PATHFINDING CODE looks at them anyway.
Q: I noticed in the source a function called is_valid_location()... can I use
this?
A: Knock yourself out but it's pretty useless on the agent maps. The function
is used on the big, wrapping world-level maps used in the main game. Agent maps never wrap, so you can tell if a location is off-map by checking its (x, y) coords as compared to the current map's width and height.
Q: Why aren't the hit points of the agent shown on the main screen? A: They are. In the upper right hand window near the top line you see your
agent's name, Hiro Protagonist (credit Neal Stephenson with the pun, viz. Snow Crash)? Right next to it you see a number and a "G" as in 30G? That number "30" is your agent's hit points. The "G" doesn't mean "gold" (that's the next window down), it means "good", as in not poisoned (P), not dead (D), not sleeping (S) and nor paralyzed (Z). Sorry for the obscure UI but remember this is based on a twenty year-old game and I kind of like the honest crankiness of the interface.
Q: How can I tell how much damage weapons do? A: As a player, you have to experiment. But your agent can use the
armament_average_damage() function to get a hint.
Q: Can my agent cast spells?
A: Alas no, not generally. You can mix spells that produce potions and use
them, but most spells require interactive targeting with the user and there's no solution to that yet in the agent extension.
Q: I make changes to the source and rebuild, but they don't seem to take
affect!
A: Be sure and do a "make install" so that the new binary is posted to your
$HOME/bin directory.
***
Making maps.
Making maps is so much fun I really should charge you money for it. The simplest way to explain it is with a question-and-answer session. I'll take your questions now. Let's start with you there, in the glasses.
Q: Uh, how do I make a map?
A: See the examples files: map-1.scm, map-2.scm, etc.
Q: Ok. But what do they do?
A: They each call four scheme functions. First, they call (load "atf.scm") to
load a bunch of definitions. Then they call (mk-map ...) to make a map and populate it with terrain and stuff (this is most of the file). Then they call one of the three set-goal functions to give the agent a goal. Then they call start-at to tell the game where to start the agent.
Q: Do I have to call (load "atf.scm")?
A: Yes, and you must call it first thing.
Q: What's all that funny "rr rr rr ..." stuff in mk-map? A: That's the terrain map. Note that the first argument to mk-map is the result
of calling (list ...) with a bunch of strings. Each string represents a row of terrain. Each two-character word in the string represents a single tile of terrain. You can edit maps in a text editor. There is another way to do it in-game, but it involves some extra steps to retrieve the results. Let me know if you want to learn how to do it. Sometimes I just use Gimp to pixel-edit a map then save it as an xpm and use search-and-replace to convert it. Sorry, the tools suck, noted for future refinement.
Q: Does my map have to be 32x32?
A: Yes. Unless you are feeling adventerous and care to peek under the covers at
what mk-map actually does. You can find it in atf.scm.
Q: How do I know what the terrain codes like "rr" mean? A: You can find them in terrain_palette.scm, which maps these codes to terrain
types. "rr" maps to rock wall.
Q: I've noticed that terrain has different properties. Like walls block
line-of-sight and movement. Trees just block movement. And some terrain
hurts my agent if he steps on them. How can I find out what properties a
terrain has?
A: All the terrain types are defined in terrain.scm. It's pretty
well-documented so I think a smart person like you can figure it out.
Q: Can I add my own terrain type?
A: Feel free.
Q: Cool! Can I give it its own picture? A: Yes. See sprites.scm and sprite-sets.scm. If you can't figure them out let
me know and I'll give you a rundown.
Q: How do I put stuff on the map?
A: Do it in mk-map. After the terrain list you can provide any number of
(add-object <obj> x y) calls to put stuff on the map. That second argument must ultimately be the result of a call to (kern-mk-obj <type> <quant>). For example, to make a 4x4 map of grass and put a short sword at tile (0, 0):
(mk-map (list ".. .."
".. ..")
(add-obj (kern-mk-obj short-sword 1) 0 0))
Except you can't make a 4x4 map with mk-map -- it always has to be 32x32.
Q: What stuff can I put on the map?
A: Check out arms.scm, items.scm and anyplace else that calls (kern-mk-obj-type
...) or it's wrapper, (mk-obj-type ...). All of those calls make object types. To make an object from a type use (kern-mk-obj <type> <quant>) and this will create an instance of the type. Use the (add-obj <obj> x y) call discussed above within mk-map to put the object on the map.
Q: How do I set a goal for the map?
A: The third function in your file should be one of the (set-goal-*) procedures
to give the agent a goal. There are three:
(set-goal-escape-dungeion <x> <y>)
The agent must reach the tile at (x, y) to complete the goal.
(set-goal-kill-all-monsters)
The agent must kill all hostile monsters to complete the goal. Not all
non-player character's are hostile!
(set-goal-steal-item <x> <y>)
The agent must find and get the "quest item" to complete the goal.
Q: How do I tell the game where to start the agent: A: The final thing you call is (start-at <x> <y>), Which sets the agent's
starting point.
Q: The game crashes/errors/calls me names when I try to load my map. What's
wrong?
A: I don't know. Send me your map file and I'll have a look.
As always with a new language or API, start with the minimum necessary (or a working example) and build it up one small step at a time until you're confident. Unfortunately, the warnings (and asserts!) you get from a map with an error in it can be maddening to figure out. These errors pretty much always appear when starting up, so if you can get that far you're probably home free.
And of course give me your questions and comments at
