+ Ents - Simplified Chatter Scenario
Further Information Sources
- copy the commented interface of the needed modules so a place available for the students
Configuring and Running the Simulation
World Configuration
A world configuration is stored in a file with extension .zad and describes:
- how many rooms are there in the world, what are their dimensions in tiles (rooms are rectangular)
- where are the doors located and what rooms do the doors connect
- where are the objects (including furniture) located and what are their properties
- how many participants are to join the simulation, what are their properties and their types (ent/human). For computer controlled ents, a string argument can be supplied, this argument is then passed to the participant when it joins the simulation, so it can read appropried file of scripts/lexikons etc.
Preparing a Simulation
To prepare a new simulation, the best way is to make a copy of the sample configuration directory ("the world package") and modify the world configuration file, "svet.zad".
Before launching the simulation and always after modifying the world configuration file, use the command "make" in the world package. This will prepare the configuration stored in file svet.zad for running, or as we say, "instantiate the world".
Within one world package, you can prepare several configuration files, such as kitchen.zad, full_house.zad etc. Before starting the simulation, use "make kitchen.inst" or "make full_house.inst" to instantiate one of the worlds. (As expected, "make" and "make svet.inst" are equal.)
Launching a Simulation
Once the configuration is ready and a world has been instantiated, one can launch the simulation.
Always launch the world server and the browser from the same configuration directory (the world package). Only the "chatter" can be launched anywhere, as long as it doesn't need any extra configuration files (as long as the students do not add any such files).
- First launch the world server: "entiserver start"
- The server loads the instantiated world and will expect the participants to join. Ents/Browsers are expected to connect in fixed order, the order given in the configuration file.
- As requested, launch and connect browsers ("entiprohlizec") or ents ("chatter").
The world server expects the participants to connect to the default port number 24444. If you need to launch server on a differrent port, such as 12345, use the command:
entiserver start -p 12345
Remember, that all the participants must learn on what computer and what port is the server running. For the browser, specify the host and port in the Connect dialog box. The chatter connects as the default to the 'localhost' and the default port number 24444. If you wish to change this, use:
chatter -h another_host -p another_port
Chatter - The Lazy Ent
For purposes of experiments with natural language processing only, a simplified version of ent was prepared, the chatter.
The chatter does not contain any interpreter of scripts (a "planner") or the full Czech linguistic module. The chatter contains only the network part to communicate with the world server and the memory (the "database") to store the positions and properties of objects and ents.
The most important part of the chatter is the module
chatting.m.shan
, the chatting module. This is the only module of the sample chatter you need to modify in order to teach the chatter new (linguistic) skills.
The sample chatting module of the chatter is equipped with a very simple reacting behaviour:
- As default, he does nothing (i.e. he sends "nop"s). Being idle for too long, he requests all the ent to speak to him.
- If somebody speaks to him, he analyses the input and immediately answers. (As a demonstration, he can only add 1 to a number he hears. If he hears something that is not a number, he responds with an error message.)
- If he hears somebody speaking to a third ent, he requests the speaker to speak to him. (He ignores the case when he hears himself, as it wouldn't be a good idea to request oneself to speak to oneself -- by the way, this would start an infinite loop.)
Because the chatter is too lazy to move etc., after a longer simulation, he'll become hungry, thirsty etc. Finally, he might even die (and so will the user, if he doesn't take care of himself). The world server treats death in a simplified way - for a certain period of time, the dead ent is excluded from participating the simulation (he doesn't receive any update of the room, nor is asked for atomic instructions), later, he rejoins the simulation again. However the death is not tested properly, so the users are kindly requested to avoid dying.
Calls to the Chatting Module
The main loop of the chatter calls the chatting module after every update received. The memory (database) has been already updated so the facts about objects are fresh.
The module
chatting.m
must export a predicate:
% After every AI received, the main module calls this predicate.
% The pointer to the main database of the ent is always given, so that
% the chatter can observe its neighbourhood.
% Given the list of input utterances and the result of our last atomic
% instruction we are expected to output an atomic instruction.
% For this can we can utilize and update out chatting__memory
% and also we can touch the io.
:- pred chat(db_mint__db, utterances, ai_result, ai, memory, memory, io, io).
:- mode chat(in, in, in, out, in, out, di, uo) is det.
After every AI received, the main module calls this predicate.
The pointer to the main database of the ent is always given as the first parameter, so that
the chatter can observe its neighbourhood (see the chapter Database below).
Given the list of input utterances (empty, if nothing was uttered) and the result of our last atomic
instruction the predicate is expected to output an atomic instruction.
For this can it can utilize and update its chatting__memory
and also it can touch the io.
On output, this predicate is expected to respond with an atomic instruction (ai), either an explicit silence (that is to perform a Nop), or an utterance at another ent or possibly an action. Also, the chatter produces a new chatting memory.
The data types utterances, ai_result and ai are defined in the module
communicator.m
of the core ent library. (To be described.)
The Chatting Memory
The chatting memory can be used to store arbitrary information for the chatting module. You can redefine the type of the chatting memory, so that it will contain whatever you wish.
The chatting memory is initialized once, after receiving the first update from the world server. The module
chatting.m
must export a constructor for the chatter memory, that is the predicate:
:- pred init_memory(db_mint__db, maybe(string), chatting__memory, io, io).
:- mode init_memory(in, in, out, di, uo) is det.
This predicate can already access the main database, and also receives a parameter specified in the world configuration file. (This way, several different chatters can be distinguished if they join the same simulation. Ignore the parameter, if you do not need it.)
After initialization, the main loop stores the pointer to the chatting memory and passes the pointer to every call of the chatting module. The chatter can always update the memory and return a new version.
Console Mode of the Chatter
For purposes of programming and debugging, the chatter is ready for a server-less mode of operation. To run the chatter in this mode use:
chatter --static=fixed_database_file
The fixed_database_file is a snapshot of the chatters main database, i.e. the snapshot of the chatter's neighbourhood. Such a snapshot can be created in the regular mode of running by issuing a special command to the sample chatter: when in the simulation is running, tell "save filename" to the chatter. It should respond with something like "the snapshot was saved".
In the console mode, the world server is not contacted (no need to run the world server at all) and the chatter responds to messages typed directly on the command line. Here is a sample discussion:
klokan:chatter$./chatter --static=foo
Loading the database dump file: foo
Starting to chat on the console, the user is the ent id XXX
Say something:
> Hello.
Received: Hello.
Got Utterances: [utterance(268369920, 0, "Hello.")]
Got Last RetVal: 0
I say: Sorry, I understand only plain numbers.
> 1234
Received: 1234
Got Utterances: [utterance(268369920, 0, "1234")]
Got Last RetVal: 0
I say: 1234 plus 1 is 1235
> Chatter finished.
Use Ctrl-D to stop the conversation.
Basics of the Project The Ents
This chapter describes the core ideas of the project The Ents you need to understand
The Ato-protocol
The world server and the participants of the simulation (be they ents or browsers) communicate with so called "ato-protocol". The world server describes with this protocol all the changes that have just happened in the current room (incl. what was uttered), and the participants express an atomic instruction, what they want to do now.
The network module of the chatter knows the ato-protocol and updates all the information stored in chatter's database. The network module also simplifies the utterance protocol, i.e. the encoding of uttered sentences.
In the simplified chatter scenario, you don't need to know anything about the ato-protocol or updating the memory. You just need to know, when is the chatting module called.
Handles, Symbolic Handles and Preprocessing
Handles
Everything in the world of ents has a unique identification number, so-called
handle. Handles are assigned not just to objects and ents, but also to all
the possible values of all the properties, to all the tiles in the rooms, all
the actions that can be performed and all the moments in time. All the
components of simulation (World Server, Browser, Ent or Chatter) use the same
set of handles. Handles are the "common dialect".
Type, Token, Subtype, Main Type
Every handle is a four-byte unsigned integer. For simplicity, handles have an
inner structure. The first two bytes specify the
type of the thing (the type
of the object, the room, where the tile is). The second two bytes specify the
token (id of the object of the given type, the coordinates of the tile
within the given room). The
type of handles is further divided into
main_type (first half byte, specifies the kind of the handle -- handle of an
object, handle of a tile, handle of time) and
subtype (the type of the object
etc.).
Usually, the token part equal to zero means "any object of the given type". The
handle of both type and token equal to zero is used to represent any handle at
all, it is a "free variable".
All the routines for manipulating handles are stored in the module
handle.m
.
This module also defines the most useful handles as constants/functions, such
as handle__self, handle__ent (for any ent) etc. See the module for the details.
Symbolic Handles
For simplicity and consistence,
no handle should be entered in the code as
the number. All the important handles have a string alias name, so called
symbolic handle.
For example the name HanyH is used for the value 0x0. The name HOpenH is used
for an *o*bject of the type pen and the name HXopenH is used for the atomic
instruction of opening a thing. Generally, all the symbolic handles are
enclosed in H...H and the first character after the H is used to distinguish
main types.
Instead of preparing a source file
foo.m
and using numeric handles in it,
create
foo.m.shan
("shan" for symbolic handles) and use symbolic handles.
Then run the script
dohandles.pl foo.m.shan
to obtain the file
foo.m
.
(Usually this type of automatic preprocessing is set up in your M(m)akefile, so
you just need to remember to modify the files *.m.shan and not the *.m.)
The full list of symbolic handles is available in the file
$ENTIROOT/conf/handles
. The script dohandles.pl uses this handles register to
assign the numbers.
Compilation Caveat
You may have already noticed the main problem with using (symbolic) handles:
once anybody changes the file
$ENTIROOT/conf/handles
,
all the sources of
all the components (Entiserver, Entiprohlizec, etc., including your code) have
to be passed to dohandles.pl and recompiled again. Or the other way round: if
the components are compiled and installed for you in a global way, you are not
allowed to add any new handles. The good news is that you should never need to
add new symbolic handles.
The Memory of the Ent (the "Database")
The ent (and the chatter) has built in a simple memory, also called the
"database". This memory stores information about observed objects, their
location and other properties. The information of all the visible objects is
updated in every turn of the simulation. For invisible objects, the database
stores the most recent information. In other words, no
history of object
properties is stored. To log this type of information, your code must
explicitly make a copy of the properties of interest.
The Structure of the Database
The structure of the database is fairy simple: it is a list of n-tuples of
handles. The lists are indexed by special set of handles, so called "senses"
(or meanings), such as HShavecolor_obj_colorH, HSposition_obj_tile_sinceH.
Within a list, all the n-tuples are of the same length (the same
n, number of
arguments). The number and meaning of the arguments is arbitrary but fixed for
a given sense. The name of the sense indicates the number of arguments and
their meaning quite clearly: under the sense HSposition_obj_loc_sinceH, the
n-tuples are triples {handle of the object, handle of its position, handle of
the time since when is the object there}.
Using the Database
Upon startup (and contacting the world server) the database is inited and
updated for you. You obtain a pointer to the database. This pointer is needed
when accessing the database.
The predicates for querying and manipulating the database are stored in the
module
db_mint.m.shan
. (The name of the module comes from
database-mercury-interface, as for historical reasons the database is
implemented in C.)
To query the database, use the predicate:
:- pred db_mint__dbq(db, handle, list(handle), list(handle)).
:- mode db_mint__dbq(in, in, in, out) is nondet.
Given the pointer to the database, the sense handle and the arguments "sample",
this predicate consecutively returns all the matching tuples stored in the
database (or immediately fails if no matching tuple is found). For example the
call:
dbq(Database, HShavecolor_obj_colorH, [HOpenH, HPcolor-redH], FoundArgs)
will return all the known red pens (in fact tuples [pen handle, HPcolor-redH]).
A predicate for modifying the database is available too. In general, you should
never want to modify the database, so I will not describe it. Use the custom
chatting__memory to store any kind of information.
--
OndrejBojar - 04 Apr 2003