|
Bot Of The Month Club
We started to organize Easter EggHunts (and Christmas Gift Hunts) after the success of the EggHunt in the world "Winter" in 1997. Soon we realize that the method they used is just not adequate for the task (you had to write down each descriptions you found on an egg). This experience made me to create the Hunt Helper Bot (HHB for short). What the bot has to do?
First I laid down the specification what do I want to accomplish based on the criteria above. After several 10s of years of programming - I learned that the best practice is to spend most of the time on the specification, some small amount on coding and the rest on a VERY THOROUGH debugging! The criteria were given by hunting (and conducting a few hunts) and learning what is the best for the application. A single bot can cover only 400x400 meter area so based on the size of the world multiple bots spawned within the program but they share the database. In our case we had 25 bots to cover Storage (2000x2000m world). The bot is written in C++ with Borland Cbuilder 5. To create the GUI took only a few hours so I could concentrate on the actual programming. (I know I know, the GUI I made sux but – hey! It does the job :-) )
The bot is very flexible to use in any similar kind of hunt. The object name list is editable and so the welcome/help/tally messages for the users:
1. Identify all hunted objects: The first version of the bot was using aw_query() to get all the objects within the world. With the new SDK I was able to use the aw_enumerate() function, which is very fast if you don’t use the callback method (I was disappointed to see that the callback is just not usable for my purpose!) The bot has it’s own list of target objects for the hunt which can be entered either throughout the GUI or added into the bot’s INI file. First I thought the building will disturb the hunt so I disabled it but it turned out that the already built-in features could survive the build requests (have the AW_OBJECT_ADD, AW_OBJECT_DELETE, etc events properly handled). I used the hiding phase of the egg hunt to exercise/test the bot. The bot records all hunt objects within it’s database and after a successful query/enumeration it saves in local files so there is no need to re-query the world if the target objects are not changed (which is true while doing the hunt since the build is disabled through the duration of the game). 2. Keep all findings up to date: I used the AW_OBJECT_CLICK event to identify a finding. With the left click method the user was still able to chat, explore or whatever she/he wanted to do while doing the hunt. Each avatar entered to the scene has it’s own objects within the program to keep the tally, the citnumber and the user’s IP in a manageable database. I recorded all users’ IP just for the purpose of resolving any dispute arises. The AW_OBJECT_CLICK event gives the target object’s unique number within its cell, the x and z coordinates and the session of the avatar it clicked on. Those 4 datas are just enough to keep the one to one relation between objects and users. First the program checks if the clicked object is a hunt target (i.e. egg or gift) then checks if the user already clicked on this object. If it can’t find in the “clicked” database, it counts as a new find and records the object’s number, x and z coordinates within this particular user’s database. Each new finds is recorded in a file for a retrieval and archiving purpose. 3. Minimize the possibility of cheating: This problem was not related to the bot. The bot tallies only clicks. When we started the game, we disabled almost all build related feature of our world, so CTRL-Right clicks, etc couldn’t happen. All the hunted objects were password protected too. Of course there is still a few way to cheat but I don’t want to reveal those techniques here. :-) With an excess of 400 hunters – we did not encounter significant cheating or the cheater was not even within the top 20 range. 4. Network error resilient: Unfortunately network errors can happen any time, especially if you are running an app, which relies on an undisturbed connection for 24-36 hours. If the bot finds the loss of the connection to the server (hardly can be since it was running on the LAN the server was running) or it lost its connection to the uniserver, it restarts itself with a 1-minute timer till the restart is successful. When the bot restarts it loads the already queried database and the avatar “click log” to reestablish all the internal database. The users are not able to hunt only while the connection is lost – the restart procedure is less than minute with spawning all the bots. 5. Low network traffic: I choose to use the left-click method and I had only one bot per hearing range (400x400m) so each click counted only once on the network. I experimented with a remote (WAN based) bot but the response time was not adequate for the users so I run it on the same LAN the server run. 6. Able to run on the slowest PC you have: Where are the real bottlenecks? Each click involves a search in the main database and another one in the avatars database. We had 17441(!!) hidden objects in our last hunt and our winner had 8041 objects found at the end. One of our previous winner had 27,000+ clicks total! Imagine how long would it take to search those databases!
Here come all the tricks I employed to minimize the time spent on search: Each bot carried all the data of the objects the area it covered. Carefully placing the bots within the world would yield to a single AW_OBJECT_CLICK event to only the bot responsible to the particular object. I implemented a hash table for each bot and each user with 256 entries each. To assign an object to it’s hash location I used the feature that each object has it’s own unique identifier randomly generated. I masked the last eight bit of the random number to select the hash location. The longest search was only 17 elements but the average was around 5. Of course with a more advanced algorithm I probably would able to reduce the longest one to average length but the mathematical calculation to achieve this goal would probably took longer than the search itself J Each hash table entry is a linked list I use almost everywhere within the program if I don’t know the exact amount of data up front. I used all the calculation up front when the bot loads rather keeping them in the memory than recalculating every time I need them. Some words and numbers about our latest hunt. We had 17441 eggs hidden, 400+ hunters and 500+ unique visitors during the hunt. Our top 10 hunters found 57821 eggs total (successful clicks), average 5782 eggs/user for 24 hours. The world server ran on a 166 MHz P1 machine with 96 Mbytes using about %13 of the CPU during the hunt. The bot was running on a 300 MHz PII with 256 MB memory along with several other apps using average %11 of the CPU and at the end occupying 27 MB of memory. The network connection (512K line ) max usage was about 200,000 bits/sec on a 5 minute average (that value were reached at the beginning and when a larger pack of new user came into the world) while regular usage (no new users – just the hunt) was below 120,000 bits/sec. The world server’s log file reached 150Mbytes – the bot’s log files totaled 21 Mbytes. The bot counted 220,610 successful clicks while the world server counted 727,353 clicks (which ALL were transmitted to the bot!) . The total network traffic for the bot were 120 Mbytes while for the world server it was 1,780 Mbytes! Here is the map of Storage (the black areas are unbuilt and we did not hide eggs over there)
and the hidden eggs:
Those maps made by my mapview utility. The success of the hunt (Created by my world monitoring bot):
The world has a 64-user limit, so we did not get more users there. [Editor Note: The X axis on the above graph shows date-and-time. The Y axis shows the number of visitors to the world. - HamFon] class TMyQueue TMyQueue *predecessor,*successor;public: __property TMyQueue *succ = {read=successor, write=successor};}; class TAvatar : public TMyQueue { public: TSheetTable *Table;}; class TBot : public TAvatar { public: void *instance;};
|
Active Worlds | Newsletter Home | Newsletter Archive | Contact Us |