Current edition: Vol.5, No.5, May 2002
 

Active Worlds News


Megapath Object Set
Bot of the Month
Worlds to Watch
Tech Support Tip
Hot Spots to Visit
AWEC Calendar
Cy Awards Fashion
Festival 2002
AWTeen Map
Seattle Reunion
AW Loses a Friend

Resources

Building Links
Community Links
AWCOM Links

 

 

icon

 

icon

 

icon

 

 

Bot Of The Month Club

As is traditional each year, this month we talk with Andras again about the Easter Egg "Hunt Bot." The Easter Egg hunts in Storage (and other worlds) are legendary in ActiveWorlds. Andras (one of the originators of this tradition) tells us all about the bot, what it does, and even how it works! Thanks, Andras, for such a great yearly tradition :-)


Hamfon & The Hambots

Know of a bot that you'd like to see featured as the Bot of the Month? If so, contact us at newsletter@activeworlds.com.

HuntBot - the answer to how to make an object hunt easy for the hunters AND to the host.


History
What does the HuntBot do
Design Phase
Implementation Phase
The GUI
Tricks
Improvements
Statistics
How to organize a Hunt?

History:

We started to make egg hunts in Storage world 5 years ago after the very first Egg Hunt in Winter then added Christmas Present hunts as a Storage world event the same year. The first few hunts were based on manual registration and tedious recording of who found how many objects. Even the count of the findings were handled manually. Then I decided a bot can do this task much quicker than a human being, so I started to create the HuntBot. The bot is written in C++ under Borland's CBuilder 5.
 

What does the HuntBot do:

The basic role of the bot is to query the world for the objects which are the target of the hunt (eggs, gifts, etc.). It keeps track of all visitors entering and leaving. When a hunter clicks on an object, the bot checks if this is a hunt target and if the hunter is clicking on it the first time. It announces to the hunter that a new object found and tallies it's findings for him.
 

Design phase:

As some probably know, I tend to design all of my programs before the actual coding. It gives me the advantage of having a full view of the idea and I lay down the specification based on all the requirements. Every improvement or addition that comes later can nicely fit into the design when I've done a careful specification at the beginning.
 

Ok, what are the goals we want to accomplish?

  1. Lets make the bot so the user can use the mouse instead of typing, freeing up his keyboard for chatting. This is extremely important because the hunt is a social gathering and people like to talk to each other. Why force them to change the keyboard focus causing strange messages to everyone?
  1. Let the bot keep track of each individual's tally. Hunters like to see where are they in the hunt!

  2.  
  3. Let the bot allow the users to log out of Active Worlds (some of you still need some sleep, do you?) and continue the hunt where they left off.

  4.  
  5. Make the bot "Error proof"! The hunters will probably kill you if the bot crashes and forgets all the eggs they found!!

  6.  
  7. Try to create a bot which is hard to cheat. Some people still like to get "free dinner". Don't let them!

  8.  
  9. Keep logs! More logs! Even more!!!! The only way to find cheaters is if you can analyze the logs. The more data you have the more info you can gather about the hunt.

  10.  
  11. What if the bot crashes? Do the hunters have enough patience to wait until you reload the bot (if you are not asleep at the keyboard).   Will they wait for the bot to do the world query again to get all the object's locations?   Unlikely, so save all info immediately after it is collected and reload it when the bot restarts. (A reload from your HD is much faster than from the net.)  Have the bot check itself to see if it is still running/connected to the world. If not, force the restart programmatically so no human intervention needed.

  12.  
  13. Try to minimize the network traffic. Only the bare minimum of signals should be collected from the server to avoid the server's (and the bot's) computer overloading.

  14.  
  15. Optimize the bot's action to make it more responsive to the users. This phase can be done later, not during the design phase, because you don't know where the bottleneck will be up front.

  16.  
  17. Finally, have a tool built-in to provide immediate scoring for announcing the winners. The faster you satisfy their curiosity, the happier the hunters are. What we learned during the hunts is that it is better to keep the top scores secret until the end of the game. It keeps lower scoring hunters staying in the game longer (and at least enjoying the chit-chat).

Implementation phase: (for those who are not interested in technical details can skip this section)


The first thing is always the data structure! If you screw that up you'll have a hard time accomplishing your goals.  When you design them, it's better to add more information to them than less. It will pay later and the computer memory is free nowadays <oopsie> (well, pretty cheap anyway!).

Our environment calls for dynamic data structure. You can't decide up front how many visitors/players will be there or the maximum amount of clicks/players.

I started with a basic bi-directional link class which is the parent class to all data structures:

class TMyQueue
{
private:
 TMyQueue *predecessor,*successor;
public:
 __property TMyQueue *succ = {read=successor, write=successor};
 __property TMyQueue *pred = {read=predecessor, write=predecessor};
 __fastcall TMyQueue () { predecessor=successor=this; };
 void __fastcall TMyQueue::Insert(TMyQueue *after);
 TMyQueue * __fastcall TMyQueue::Remove();
 TMyQueue * __fastcall TMyQueue::First();
 TMyQueue * __fastcall TMyQueue::Next();
 bool __fastcall TMyQueue::Finished(TMyQueue *q);
};


There are 4 descendant of this class:
 

1, Name list:

class TNameQueue : public TMyQueue
{
public:
 char *pszName;
 int iType;
}

This class  is used to keep the generic names of the hunt object (like egg1.rwx, rjegg27.rwx, etc.). The program uses this queue when it does the query of the world to select the target objects from the world database. Don't forget to delete the char string in the destructor! This frequently forgotten action results memory leaks.
 

2, Object descriptor:

class TObjQueue : public TMyQueue
{
 public:
 char *pszModel,*pszDescription,*pszAction;
 int iX,iY,iZ,iYaw,iObjectNumber,iOwner, iTimestamp,iCellx,iCellz;
}

TObjQueue is the exact description of the target objects scattered around the bot's visibility range..
 

3, Avatar descriptor:

class TAvatar : public TMyQueue
{
public:
 int iSession,iOldSession,iCitNumber;
 int iClickCount,iTotalTime;
 char *pszName;
 int iIP;
 int iNumobj;
 bool bVerbose,bCrackedBrowser;
 TObjQueue *ObjHash[HASH_SIZE];
}

All visitors get a TAvatar object assigned from the beginning of the hunt. The avatar has an object queue to hold those targets which the hunter already clicked. That object queue is carved up into a hash table which I'll describe in the Tricks section.
 

4, Bot descriptor:

class TBot : public TAvatar
{
private:
 void *instance;
 int iSequence[3][3];  // total zone sequence value
 bool bUpToDate;
 public:
 int iSector_x, iSector_z;
 int iCell_x, iCell_z;
}

Please note that the TBot is a descendant of the TAvatar class. All properties of the avatar are inherited by the bot but used slightly differently. There is no use for a few variables (like bVerbose or iNumobj) but I was too lazy to create two independent classes. The object hash table is holding all the target objects which the bot covers. The bot's sector and cell address is pre calculated to avoid lengthy mathematical operations.
Based on the user's selection, there can be more than one bot instance to cover the whole hunting territory. With a single bot one can conduct a hunt within a 400x400m area which is a single bot's maximum visibility range. We had 2000x2000m to cover, so in our hunt we used 25 bot instances!

Now a few world about the program's structure:

The program has all the necessary callbacks installed for the job and it had a 100 ms timer to call aw_wait(0) regularly.

 aw_event_set (AW_EVENT_AVATAR_ADD, handle_avatar_add);
 aw_event_set (AW_EVENT_AVATAR_DELETE, handle_avatar_delete);
 aw_event_set (AW_EVENT_AVATAR_CHANGE, handle_avatar_change);
 aw_callback_set (AW_CALLBACK_ADDRESS, handle_IP_address);
 aw_event_set (AW_EVENT_CHAT, handle_chat);
 aw_event_set (AW_EVENT_OBJECT_CLICK, handle_object_click);
 aw_event_set (AW_EVENT_WORLD_DISCONNECT,handle_world_disconnect);
 aw_callback_set (AW_CALLBACK_QUERY, handle_query);
 aw_event_set (AW_EVENT_CELL_BEGIN, handle_cell_begin);
 aw_event_set (AW_EVENT_CELL_OBJECT, handle_cell_object);
 aw_callback_set (AW_CALLBACK_CELL_RESULT, handle_cell_result );

I started the coding itself with a "Top down" coding method so I could debug while creating it. First I made the world query part work. When the query was operating as I wanted, I created a database from the query, so later I don't have to query the world again. That saved a lot of time for me! Each bot simply dumps its object queue to a separate file after the query finished (huntobj000.txt, huntobj001.txt ...). At the restart, the user can select a check box to "AutoLoad" those queues from files instead of doing the query again. The object file format is similar to the standard propfile format, except it has the object number embedded too:

-392249089 0 976710621 -60475 20 -58900 900 11 0 0 prpeg44.rwx

The next obvious step was to teach the bot how to handle the clicking. I did some cheating so the bot can receive the AW_EVENT_OBJECT_CLICK message without any queries. When the hunter clicks on an object the callback method gives us the avatar session number, the object number and the cell's x and z coordinates the object is residing in. The bot then checks if this object number is in it's object queue. If it is not found there (was not a target object!) it simply discards the message but increments the corresponding TAvatar's click counter. When the object is found in it's queue, the bot searches the corresponding avatar's object queue to see if the object is residing there. If the object is not found, it is then placed in the avatar's object queue, the object counter is incremented and a message sent to the hunter if he did not disable the bot whispers. (There is a special case when the object is not counted as a new one but that is described in detail in the Improvement section.)  Each new target click is recorded in the click.log file for archiving and retrieval purpose later. When the bot is restarted, it reads this click.log file and fills up the corresponding queues with the information. The hunter's data is preserved!!

Bots should have names but I was too lazy to implement a feature to assign individual names for all bot instances. I used one name for the center bot and another name for all the other instances which I concatenated a simple sequence number to. They are the "helper" bots for the center bot.   e.g.: [EasterBunny], [LittleBunny1], [LittleBunny2], etc.

I implemented a chat log to help with debugging the program so each bot instance has it's own log file and the program has it's chatlog file under the name of the bot instance.

While I was at the chat management, I decided it would be good to control the bot with whispers. I created "Authorized PS" accounts within the bot: a name list which holds the citizen name. Each citizen on this list can control the bot with whisper if he/she is a PS in the world and is wearing one of the "Special" avatars. The bot INI file should hold at least the owner's citizen name, so he/she can add another user to this list with whispers. There is a detailed help message sent to the AuthorizedPS if he/she whispers help to the bot.

Things you can control from whisper contains:
 

 "TOURIST:ENABLE/DISABLE"
 "AUTOSTART:ENABLE/DISABLE"
 "STARTHUNT: - enables click logging"
 "STOPHUNT: - disables click logging"
 "CHATLOG:ENABLE/DISABLE"
 "NOCHEATERS:ENABLE/DISABLE - disable them from the score list"
 "CHEATWARNING:ENABLE/DISABLE"
 "ADDAUTHORIZEDPS:citizenname - to add another citizen to the bot control list."
 "DELETEAUTHORIZEDPS:citizenname - to remove a citizen from the bot control list."
 "LISTAUTHORIZEDPS: - to list authorized citizens"
 "TOP:   whispers the top 10 hunters"
 "ANNOUNCERESULT:# broadcasts the #-th hunter's results"

The next step was to create an INI file to hold all the controls for the bot. Sometimes it's faster to fill the INI file with data than do the one by one fill on the GUI. There are two hidden fields from the GUI and they are accessible only within the INI file. The 2 fields are the Universe URL and Universe Port. The program defaults to the Active Worlds parameters.

The last step was the error recovery process. The bot detects if it loses connection to the server and it tries to restart itself after 60 seconds. A successful restart reloads all the saved data (like the clicks for the avatars and the objects for the bots) and the hunt continues.

With all of those implemented features I had a fairly good working program which was used for the next egg hunt in Storage world. Running for 24 hours and surviving several crashes, I collected enough real data to improve the program. Even with the occasional crashes the hunt was a success.
 

The GUI

A few pictures are worth a thousand words. I don't consider myself a good GUI designer, so this program's look and feel leaves something to be desired. :) Fortunately the interface is only visible by the bot's user, not to the hunters.
This is how the program looks before the hunt:

When adding new objects to the objet name list you'll get the following popup window:

While in the initial phase - you want to modify the default greeting messages:

After you start the bot, each bot will have it's own Tab field containing it's log, position and communication fields:

During the hunt you can check the current scores either inworld by whispering to the bot or in the GUI:

When the hunt is finished, you can announce the winners by either whispering or with double clicking on the score line (Don't forget to check the "DoubleClick Announce" check box!)

Then it comes to generating the appropriate http document(s) for your web page:


 
 

Tricks


In this section I would like to tell a few tricks which made the bot even better:

  •  I found that if you send a lot of messages from the bot to the hunter (like the help messages), it can stop responding to the clicks for a while so I used the 100 ms timer to send the long messages in pieces - line by line.
  • Another problem I found was that while flushing the RichEdit content to the disk (logging!) the bot could lose the connection due to some slow disk I/O operation. The solution was to flush it every time the length reached 2K bytes so the disk I/O could be fast enough. The only synchronous disk write was the click logging because I did not want to lose any clicks if the bot crashed.
  • How to cheat the SDK so that the bot has an up to date query table, so it starts to receive click events? Remember - I have all the object data in memory, loaded from a previous query, so I did not really want to requery the world. The bot saves all the sequences to the huntseq.txt file too, so I reload those from disk and when I invoke the aw_query() the bot is already up to date! It turned out later that if you fill the bot's sequence with -1 - no query will happen but the bot is up to date with the world (it's sequence number exceeds the current sequence number).
  • One more thing I found out in a hard way: When you flush the RichEdit object (the logs space holder) to the disk, you have to be careful with the amount you write at once. The lengthy file write operation can cause messages lost from the world server. After some experiments I decided to flush the log files when their buffer reaches 4K Bytes.

Improvements:

We had a few hunts behind us already and I learned that the program needs improvements. As the number of eggs we hid inworld increased, the search for the object in the bot and the avatar's queue started to grow too long. I had to do something to make the search faster. Imagine, in our last hunt we had in excess of 19,000 eggs! That means searching 19,000 objects to find the one they clicked! Well - it just won't work :)
The solution was to introduce hash tables for the avatars and for the bots. Each hash table has 256 queues. I used the random number AW assigns to the object to select within the queues with simply masking down the lower 4 bits forming an integer index to the hash table. I hoped the random numbers have even distribution and it wasn't too bad. At last year's hunt I used only a 16 element hash table and the most dense area had the following queue lengths:
 

Total objects found :2270
 0. Hash queue length= 146
 1. Hash queue length= 151
 2. Hash queue length= 151
 3. Hash queue length= 142
 4. Hash queue length= 155
 5. Hash queue length= 142
 6. Hash queue length= 129
 7. Hash queue length= 146
 8. Hash queue length= 145
 9. Hash queue length= 147
10. Hash queue length= 159
11. Hash queue length= 128
12. Hash queue length= 124
13. Hash queue length= 132
14. Hash queue length= 125
15. Hash queue length= 148

As you can see the distribution was pretty even. The queue length is still too large this is why I finally made it to 8 bits (256 queues) which reduced the length to an average 9 objects. With this trick I was able to reduce the CPU load by %90!!!! Tremendous savings, isn't it?

During the hunt we found the need to communicate to all of our hunters but they were spread around the P100 world. I did not want to use the world's Welcome Message because not every hunt will be hold in a world where you can change it so I implemented a broadcast feature. You can whisper the message to the bot and it will relay it through all the bots in the world effectively reaching all hunters.

We had to find ways to avoid cheating. In the past several people were using "cracked" browser, so they can override the world's features. There are two features built into the bot:

  1. If someone uses a "cracked" browser so it can select an object event the "Object Selection" is off - triggers a flag that that hunter is cheating.
  2. We hid some eggs inside other objects (like a small black box just barely larger than the egg itself) which has a "cheat" word in the action field. This method helped us to discover those who replaced the OP with an OP on their disk so only the egg objects are visible. If someone clicked on those objects it triggered the cheating flag too. One warning though: It can happen that a person on a slower machine will get the surrounding box rendered much later than the egg itself, so try to place those objects to the area where the visitors first land in the world - effectively caching them. When such cheats found do some checking in the logs to see if it happened too frequently or not. The bot's owner can turn the cheat warnings on or off of course.

Hunters wanted results IMMEDIATELY! To do a manual recount or sorting would take too long (remember the 2000 election?). I added a new feature to the bot that it can announce the winners from the sorted Score list by whispering the proper command to it.

Another tedious work was to create a web page which holds the list of the winners and (possibly) all other participants data. In the past Daphne manually created/filled out those tables! I added another feature now that the bot can generate the html tables for a web page. All you have to do is cut and paste it to your web page.

Statistics from the latest hunt (3/30/2002) during the 36 hours hunt period:

 
Number of eggs hidden 19598
Total visitor number 634
Max. number of egg found 9378
Total number of clicks during the hunt 711477
Maximum number of clicks by one hunter 38992
Total number of eggs found by all hunters 224211
Total "User time" spent in Storage 1178h
Average time a hunter spent in Storage 1h 51'
Average number of eggs found per hunters 353
Average number of clicks per hunter 1122
Average CPU Usage during the hunt (together with the world server) %14
Total CPU time during the 36 hours 1h 28'
Total world server network traffic 2350 MBytes

 

How to organize a Hunt?


FIRST THINGS FIRST:

  • Decide where to hold it and make sure your world's user limit is high enough that potential hunters won't be "locked out".
  • Decide when to hold it and how long it will last. (Don't forget that you have to sleep sometime too!)
  • Decide what to hunt for.  (Having a theme helps create more interest.)
  • If you are going to use any bots, make sure you know how to operate and control them!
  • Decide whether or not to offer prizes and if you are offering prizes, what will they be and in what order they will be awarded; 1st, 2nd, 3rd, etc..?


SETTING UP THE HUNT:
How are you going to hide objects without potential hunters watching you hide them?  Are you going to close your world while you hide the objects or do you have another world with the same build in it that you can set the hunt objects up in then move to the actual hunt world just before the hunt?
Will you need some trusted people to help you hide the objects or will you set up a BuildBot to disperse objects?
What are the "rules" for hiding objects? (inside other objects; below ground level; so high they can't be seen while standing on the ground)    Your hunters will want to have a general idea of where to look and where not to look.
Will you or your helpers need a map of hunt object distribution to be sure there is fairly even coverage?  (If the hiding of objects is going to happen over several days and be done by several people, a map of object distribution can be a big help.)
A web page supporting the event is very helpful.  You can point people to the URL to get complete information about the hunt.   They will know what they need to know in order to make the hunt and it's rules clear to them, plus it makes a host's job easier if they're able to point to a URL..

ADVERTISE, ADVERTISE, ADVERTISE!!!

  • Signs/graphics in your world and your friend's worlds.
  • AW Newsletter
  • AW Community Calendar
  • Newsgroups: AW's and Andras'
  • Supporting web page
  • Word of mouth/telegrams
  • and any other source  you can think of!


AFTER THE HUNT:
Will you be able to log the world or obtain the log so you can check for possible cheating? (cheaters should never win!)
If you are not using a bot to keep track of found objects you will need a method by which your  hunters can prove what objects they've found.   Code words or phrases on each hidden object that the hunter has to list and send to you is one way.  Different filenames for each object in the hunt is another way but again, the hunter has to make a list and send it to you.  You, of course get the job of checking the accuracy of the lists and counting the number of accurate items on them before any winners can be announced.  :o)

Do let your hunters have an idea about how soon after the end of the hunt the winners will be announced.   Hunters are a rather impatient bunch and want to know as soon as possible. :o)

Signs at GZ of who the winners are is always welcome by people that participated.  A web page listing the tallies of all that hunted (or tallies over a certain amount) is also welcome to so ppl can compare how they did against other hunters.

Don't forget to thank your helpers!

The hunt is not fully complete till the winners have their prizes.

REMEMBER:
Holding a hunt is mostly a terrific social event; the hunting is actually pretty secondary for many.  Having a host at Ground Zero to answer questions and say hello helps the sociability a lot.  Even hunters that don't win a prize usually come away from the event feeling very good about the time spent.  :o)   (Don't YOU forget to enjoy the hunt and your visitors too!)
 

Courtesy of Andras and Daphne :)
 

If you're interested in finding out more about how to use the SDK, visit the SDK site for information!

 

 
  Active Worlds   |   Newsletter Home   |   Newsletter Archive   |   Contact Us    |   Disclaimer