History
What does the HuntBot do
Design Phase
Implementation Phase
The GUI
Tricks
Improvements
Statistics
How to organize a Hunt?
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:
class TNameQueue : public TMyQueueThis 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.
{
public:
char *pszName;
int iType;
}
class TObjQueue : public TMyQueueTObjQueue is the exact description of the target objects scattered around the bot's visibility range..
{
public:
char *pszModel,*pszDescription,*pszAction;
int iX,iY,iZ,iYaw,iObjectNumber,iOwner,iTimestamp,iCellx,iCellz;
}
class TAvatar : public TMyQueueAll 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.
{
public:
int iSession,iOldSession,iCitNumber;
int iClickCount,iTotalTime;
char *pszName;
int iIP;
int iNumobj;
bool bVerbose,bCrackedBrowser;
TObjQueue *ObjHash[HASH_SIZE];
}
class TBot : public TAvatarPlease 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.
{
private:
void *instance;
int iSequence[3][3]; // total zone sequence value
bool bUpToDate;
public:
int iSector_x, iSector_z;
int iCell_x, iCell_z;
}
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);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:
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 );
-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"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.
"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 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.
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:
In this section I would like to tell a few tricks which made the
bot even better:
Total objects found :2270As 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?
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
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:
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.
| 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 |
FIRST THINGS FIRST:
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!!!
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 :)