[Home] [Downloads] [Search] [Help/forum]

Gammon Software Solutions forum

See www.mushclient.com/spam for dealing with forum spam. Please read the MUSHclient FAQ!

[Folder]  Entire forum
-> [Folder]  Programming
. -> [Folder]  STL
. . -> [Subject]  Newer, Better, Cooler Bitmask-Flags

Home  |  Users  |  Search  |  FAQ
Username:
Register forum user name
Password:
Forgotten password?
(New message)
Subject: Newer, Better, Cooler Bitmask-Flags
Name:
Your forum user name.
Register forum user name
Password:
Your forum password.
Forgotten password?
Message:
Message to be posted (in English, please).
Forum codes:
Check this if your message uses 'forum codes' or templates (auto-detected for new posts).
Forum codes Templates

Save this message ...


Subject review (reverse sequence)

Pages: 1  2 

Posted by Nick Gammon   Australia  (18,770 posts)  [Biography] bio   Forum Administrator
Date Wed 07 Jul 2004 12:23 AM (UTC)  quote  ]

Amended on Wed 07 Jul 2004 12:30 AM (UTC) by Nick Gammon

Message
Now that you are using STL and C++, it might be a good idea to look at moving away from static buffers for strings.

Depending on what MSL is defined as (4096 in my version) then you are either:


  • Using too much memory, and wasting (say) 4,000 bytes of RAM which is never used; or

  • Not allowing enough, in which case one day it will overflow and crash


The version below takes the same arguments as yours, and still returns a const char * string, but uses a string internally, which can never overflow. I have declared it as static so it doesn't go out of scope too soon ...



#ifdef WIN32
  #pragma warning( disable : 4786)
#endif

#include <bitset>
#include <string>
#include <iostream>

#include <stdio.h>

using namespace std;

char *  const   npc_race        [] =
{
"human", "elf", "dwarf", "halfling", "pixie", "vampire", "half-ogre",
"half-orc", "half-troll", "half-elf", "gith", "drow", "sea-elf",
"lizardman", "gnome", "r5", "r6", "r7", "r8", "troll",
"ant", "ape", "baboon", "bat", "bear", "bee",
"beetle", "boar", "bugbear", "cat", "dog", "dragon", "ferret", "fly",
"gargoyle", "gelatin", "ghoul", "gnoll", "gnome", "goblin", "golem",
"gorgon", "harpy", "hobgoblin", "kobold", "lizardman", "locust",
"lycanthrope", "minotaur", "mold", "mule", "neanderthal", "ooze", "orc",
"rat", "rustmonster", "shadow", "shapeshifter", "shrew", "shrieker",
"skeleton", "slime", "snake", "spider", "stirge", "thoul", "troglodyte",
"undead", "wight", "wolf", "worm", "zombie", "bovine", "canine", "feline",
"porcine", "mammal", "rodent", "avis", "reptile", "amphibian", "fish",
"crustacean", "insect", "spirit", "magical", "horse", "animal", "humanoid",
"monster", "god"
};


template <class T>
const char * bitset_to_string (T bits, char *const flagarray[] )
  {

  static string s;

  s.clear ();

  for (size_t i = 0; i < bits.size (); i++)
    if (bits [i])
      {
      if (!s.empty ())
        s += " ";  // separate multiple ones by a space
      s += flagarray [i];
      }

  return s.c_str ();
  } // end of bitset_to_string

int main (void)
  {

  bitset<100> race_flags;

  race_flags.set (5);
  race_flags.set (6);
  race_flags.set (70);

  printf ("results = '%s'\n", bitset_to_string (race_flags, npc_race) );

  }



The only problem with the code above is that it doesn't handle spaces inside your constants, which I think your code does. The version below which introduces a couple more routines that I wrote to trim spaces, will handle spaces at either end of the constants (see the spaces I added to "worm" below) ...


#ifdef WIN32
  #pragma warning( disable : 4786)
#endif

#include <bitset>
#include <string>
#include <iostream>

#include <stdio.h>

using namespace std;

char *  const   npc_race        [] =
{
"human", "elf", "dwarf", "halfling", "pixie", "vampire", "half-ogre",
"half-orc", "half-troll", "half-elf", "gith", "drow", "sea-elf",
"lizardman", "gnome", "r5", "r6", "r7", "r8", "troll",
"ant", "ape", "baboon", "bat", "bear", "bee",
"beetle", "boar", "bugbear", "cat", "dog", "dragon", "ferret", "fly",
"gargoyle", "gelatin", "ghoul", "gnoll", "gnome", "goblin", "golem",
"gorgon", "harpy", "hobgoblin", "kobold", "lizardman", "locust",
"lycanthrope", "minotaur", "mold", "mule", "neanderthal", "ooze", "orc",
"rat", "rustmonster", "shadow", "shapeshifter", "shrew", "shrieker",
"skeleton", "slime", "snake", "spider", "stirge", "thoul", "troglodyte",
"undead", "wight", "wolf", " worm ", "zombie", "bovine", "canine", "feline",
"porcine", "mammal", "rodent", "avis", "reptile", "amphibian", "fish",
"crustacean", "insect", "spirit", "magical", "horse", "animal", "humanoid",
"monster", "god"
};

#define SPACES " \t\r\n"  // default "spaces" for trimming strings

// trim spaces from right (you can define what spaces are)
string trim_right (const string & s, const string & t = SPACES);
// trim spaces from left
string trim_left (const string & s, const string & t = SPACES);
// trim spaces from both sides
string trim (const string & s, const string & t = SPACES);

inline string trim_right (const string & s, const string & t)
  {
  string d (s);
  string::size_type i (d.find_last_not_of (t));
  if (i == string::npos)
    return "";
  else
   return d.erase (d.find_last_not_of (t) + 1) ;
  }  // end of trim_right

inline string trim_left (const string & s, const string & t)
  {
  string d (s);
  return d.erase (0, s.find_first_not_of (t)) ;
  }  // end of trim_left

inline string trim (const string & s, const string & t)
  {
  string d (s);
  return trim_left (trim_right (d, t), t) ;
  }  // end of trim

template <class T>
const char * bitset_to_string (T bits, char *const flagarray[] )
  {

  static string s;

  s.clear ();

  for (size_t i = 0; i < bits.size (); i++)
    if (bits [i])
      s += trim (flagarray [i]) + " ";

  s = trim_right (s);  // get rid of final space

  return s.c_str ();
  } // end of bitset_to_string

int main (void)
  {

  bitset<100> race_flags;

  race_flags.set (5);
  race_flags.set (6);
  race_flags.set (70);

  printf ("results = '%s'\n", bitset_to_string (race_flags, npc_race) );

  }



Output

results = 'vampire half-ogre worm'




In the second example above, the implementations of trim, trim_left and trim_right *can* be moved into a cpp file (not a header file) because they are straight functions, not templated functions.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Samson   USA  (683 posts)  [Biography] bio
Date Tue 06 Jul 2004 11:11 PM (UTC)  quote  ]
Message
Ah, sweetness. That fixed it up nicely. No kidding on the not expecting it part. I sure didn't think it needed to be in a header file. But I'm still feeling my way through this for the most part. Thanks for the heads up :)

SmaugMuds.org: http://www.smaugmuds.org - The Smaug MUDs Community Center

"The past was erased, the erasure was forgotten, the lie became truth." -- George Orwell, 1984
[Go to top] top

Posted by Nick Gammon   Australia  (18,770 posts)  [Biography] bio   Forum Administrator
Date Tue 06 Jul 2004 10:52 PM (UTC)  quote  ]
Message
I was caught by this a while ago, and the answer can be a bit obscure when you are not expecting it. :)

The way templates work is that the compiler generates the code required when it sees an instance of the templated code (not the template declaration).

Thus, you cannot prototype the function like you have done - the compiler is accepting the prototype, but is not aware to generate the *instance* required for each of the 5 cases.

The correct, and only, solution is to put the entire templated function into a header file, and include that in the places where it is wanted. This is how STL does it. If you have this:

#include <map>

(for example)

Then that has the entire code for generating maps inside the include file, and that has to be in the front of any module that wants to use it. And, there is no map.cpp file. It is all in the header.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Samson   USA  (683 posts)  [Biography] bio
Date Tue 06 Jul 2004 08:28 AM (UTC)  quote  ]
Message
Going by what you've posted above, I went on to modify my bitset_string function as follows:


template <class T>
char *bitset_string( T bits, char *const flagarray[] )
{
   static char buf[MSL];
   size_t x;

   buf[0] = '\0';
   for( x = 0; x < bits.size(); x++ )
   {
      if( bits.test(x) )
	{
	   mudstrlcat( buf, flagarray[x], MSL );
         /* don't catenate a blank if the last char is blank  --Gorog */
         if( buf[0] != '\0' && ' ' != buf[strlen(buf)-1] )
            mudstrlcat( buf, " ", MSL );
	}
   }

   if( ( x = strlen( buf ) ) > 0 )
      buf[--x] = '\0';

   return buf;
}


This has a prototype in mud.h as such:


template <class T>
char *bitset_string( T bits, char *const flagarray[] );


Called as such, for various different bitsets I want to turn into strings for the purpose of saving/displaying their contents:


	fprintf( fpout, "%s~\n", bitset_string( pMobIndex->actflags, act_flags ) );
      fprintf( fpout, "%s~\n", bitset_string( pMobIndex->affected_by, a_flags ) );


I was rather excited watching the results, it was compiling along just fine until it hit the linking stage where I got spammed silly with dozens of these:


o/act_info.o(.text+0x277c): In function `do_look':
/usr/include/c++/3.3.3/bitset:1064: undefined reference to `char* bitset_string<std::bitset<(unsigned)12> >(std::bitset<(unsigned)12>, char* const*)'
o/act_info.o(.text+0x27a4): In function `do_look':
/home/samson/Alsherok/src/act_info.c:1061: undefined reference to `char* bitset_string<std::bitset<(unsigned)42> >(std::bitset<(unsigned)42>, char* const*)'
o/act_wiz.o(.text+0x13e0): In function `do_rstat':
/home/samson/Alsherok/src/act_wiz.c:953: undefined reference to `char* bitset_string<std::bitset<(unsigned)42> >(std::bitset<(unsigned)42>, char* const*)'


Now, obviously I'm doing something wrong. I can't seriously expect that the compiler wants me to make 5 dozen different copies of bitset_string just in case. It seems to me all I'm missing is how to properly pass a std::bitset as an argument to a general function. There has to be away, but none of the web references I've found will tell me.

SmaugMuds.org: http://www.smaugmuds.org - The Smaug MUDs Community Center

"The past was erased, the erasure was forgotten, the lie became truth." -- George Orwell, 1984
[Go to top] top

Posted by Nick Gammon   Australia  (18,770 posts)  [Biography] bio   Forum Administrator
Date Mon 28 Jun 2004 10:52 PM (UTC)  quote  ]

Amended on Mon 28 Jun 2004 10:53 PM (UTC) by Nick Gammon

Message
Ah, I see. Converting a bitset to a string of names. Well, maybe 3 lines. :)

This still looks neater:


#ifdef WIN32
  #pragma warning( disable : 4786)
#endif

#include <bitset>
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>

using namespace std;

char *  const   npc_race        [] =
{
"human", "elf", "dwarf", "halfling", "pixie", "vampire", "half-ogre",
"half-orc", "half-troll", "half-elf", "gith", "drow", "sea-elf",
"lizardman", "gnome", "r5", "r6", "r7", "r8", "troll",
"ant", "ape", "baboon", "bat", "bear", "bee",
"beetle", "boar", "bugbear", "cat", "dog", "dragon", "ferret", "fly",
"gargoyle", "gelatin", "ghoul", "gnoll", "gnome", "goblin", "golem",
"gorgon", "harpy", "hobgoblin", "kobold", "lizardman", "locust",
"lycanthrope", "minotaur", "mold", "mule", "neanderthal", "ooze", "orc",
"rat", "rustmonster", "shadow", "shapeshifter", "shrew", "shrieker",
"skeleton", "slime", "snake", "spider", "stirge", "thoul", "troglodyte",
"undead", "wight", "wolf", "worm", "zombie", "bovine", "canine", "feline",
"porcine", "mammal", "rodent", "avis", "reptile", "amphibian", "fish",
"crustacean", "insect", "spirit", "magical", "horse", "animal", "humanoid",
"monster", "god"
};


#define NUMITEMS(arg) (sizeof (arg) / sizeof (arg [0]))


template <class T>
string bitset_to_string (T bits, vector<string> names)
  {

  string s;

  for (size_t i = 0; i < bits.size (); i++)
    if (bits [i])
      s += names [i] + " ";
  

  return s;
  } // end of bitset_to_string

int main (void)
  {

  vector<string> npc_race_vector;
  bitset<NUMITEMS (npc_race)> race_flags;

  race_flags.set (5);
  race_flags.set (6);
  race_flags.set (70);

  copy (npc_race,
        &npc_race [ NUMITEMS (npc_race) ],
        back_inserter (npc_race_vector));

  cout << "result = " << bitset_to_string (race_flags, npc_race_vector) << endl;

  }



Output

result = vampire half-ogre worm




The important 3 lines are in bold.

What I have done here is use the NUMITEMS macro (to find how many flags are in the array), and convert the char * array into a vector (for simplicity, although this probably isn't necessary).

Then I used a templated function to save having to pass down the number of bits in the array.

Also by using:

bitset<NUMITEMS (npc_race)> race_flags;

I could let the bitset adjust itself to the number of flags actually needed, although there may be reasons to not do that.

Finally the bitset_to_string function scans the array of bits, and for each one set, copies the appropriate name into the string, appending a space.

The only problem is you will have a trailing space (unless no bits match) but you can always do a test and shorten the resulting string by one if necessary.


- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Samson   USA  (683 posts)  [Biography] bio
Date Mon 28 Jun 2004 12:12 PM (UTC)  quote  ]
Message
Yes. It's purpose is to take a std::bitset and turn it into a string of names to represent the bits which are set. Much like flag_string does for the BVxx type, and ext_flag_string does for the EXT_BV types. Used for display in stats screens, identify spells, and file formats etc.

If it can be done in 2 lines I'd be rather impressed with that.

SmaugMuds.org: http://www.smaugmuds.org - The Smaug MUDs Community Center

"The past was erased, the erasure was forgotten, the lie became truth." -- George Orwell, 1984
[Go to top] top

Posted by Nick Gammon   Australia  (18,770 posts)  [Biography] bio   Forum Administrator
Date Mon 28 Jun 2004 05:01 AM (UTC)  quote  ]
Message
OK, good. Now what does this routine do exactly? Turn a bitset into a string? Can't help thinking it can be replaced by one or two lines.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Samson   USA  (683 posts)  [Biography] bio
Date Mon 28 Jun 2004 03:58 AM (UTC)  quote  ]
Message

char *bitset_string( std::basic_string< char, std::char_traits<char>, std::allocator<char> > flags, int flagmax, char *const flagarray[] )
{
   static char buf[MSL];
   int x;

   reverse( flags.begin(), flags.end() );

   buf[0] = '\0';
   for( x = 0; x < flagmax; x++ )
	if( flags[x] == '1' )
	{
	   mudstrlcat( buf, flagarray[x], MSL );
         /* don't catenate a blank if the last char is blank  --Gorog */
         if( buf[0] != '\0' && ' ' != buf[strlen(buf)-1] )
            mudstrlcat( buf, " ", MSL );
	}

   if( ( x = strlen( buf ) ) > 0 )
      buf[--x] = '\0';

   return buf;
}


Mainly..... because I don't know that much about C++ yet. I'm delving into it bit by bit ( no pun intended! ) so every little thing helps. Didn't know about the reverse algorithm. Certainly made that particular portion of things much easier to work with though.

SmaugMuds.org: http://www.smaugmuds.org - The Smaug MUDs Community Center

"The past was erased, the erasure was forgotten, the lie became truth." -- George Orwell, 1984
[Go to top] top

Posted by Nick Gammon   Australia  (18,770 posts)  [Biography] bio   Forum Administrator
Date Sun 27 Jun 2004 11:39 PM (UTC)  quote  ]
Message
Er, yes, it does look a bit of a mess. Why do you need all that complication? To reverse the bits, you can use the reverse algorithm on the string, like this:


#include <bitset>
#include <string>
#include <sstream>
#include <iostream>
#include <algorithm>

using namespace std;

// this define lets you create a string on-the-fly
// you can use output modifiers in the string, eg.
// string mystr = MAKE_STRING (
//    "hello " << boolalpha << true << hex << 1234 << octal << 3456 << dec
//    << 123456 << scientific << 123.45 << fixed << 1234.567 " );

#define MAKE_STRING(msg) \
   (((ostringstream&) (ostringstream() << boolalpha << msg)).str())


int main (void)
  {
  bitset<50> mybits;

  mybits.set (1);
  mybits.set (5);
  mybits.set (40);

string s;

  s = MAKE_STRING (mybits);

  cout << "bits = " << s << endl;
  reverse (s.begin (), s.end ());
  cout << "reversed bits = " << s << endl;

  }



Output

bits = 00000000010000000000000000000000000000000000100010
reversed bits = 01000100000000000000000000000000000000001000000000


- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Samson   USA  (683 posts)  [Biography] bio
Date Sun 27 Jun 2004 10:45 PM (UTC)  quote  ]

Amended on Sun 27 Jun 2004 10:47 PM (UTC) by Samson

Message
Well, all very good suggestions. After prodding a few things, I came up with this:


char *bitset_string( std::basic_string< char, std::char_traits<char>, std::allocator<char> > flags, int flagmax, char *const flagarray[] )
{
   static char buf[MSL];
   char newflags[MSL];
   int x;

   mudstrlcpy( newflags, flags.c_str(), MSL );

   invert( newflags, log_buf );
   mudstrlcpy( newflags, log_buf, MSL );

   buf[0] = '\0';
   for( x = 0; x < flagmax; x++ )
	if( newflags[x] == '1' )
	{
	   mudstrlcat( buf, flagarray[x], MSL );
         /* don't catenate a blank if the last char is blank  --Gorog */
         if( buf[0] != '\0' && ' ' != buf[strlen(buf)-1] )
            mudstrlcat( buf, " ", MSL );
	}

   if( ( x = strlen( buf ) ) > 0 )
      buf[--x] = '\0';

   return buf;
}


That's in build.c, right next to ext_flag_string and such. The first argument uses this macro:


#define BITSTRING( arg ) (arg).to_string< char, std::char_traits<char>, std::allocator<char> > ()


So basically now all I have to do is call it like so:

bitset_string( BITSTRING( channel->flags ), CHAN_MAXFLAG, chan_flags )

Yes, it's a bit of a hackish looking mess, but it's getting the job done and has already proven to work on a bitset with 65 members, which is far beyond what a ulong can hold.

The invert function was used because the normal bitset string is backward for this purpose. The lowest bits are at the rightmost portion of the string. The inversion made walking the string trivial at that point.

SmaugMuds.org: http://www.smaugmuds.org - The Smaug MUDs Community Center

"The past was erased, the erasure was forgotten, the lie became truth." -- George Orwell, 1984
[Go to top] top

Posted by Nick Gammon   Australia  (18,770 posts)  [Biography] bio   Forum Administrator
Date Sun 27 Jun 2004 10:17 PM (UTC)  quote  ]
Message
Once you have got that far, you can use named flags, which make it easier to program in the first place, and easier to read the area files, like this:


#include <map>
#include <string>
#include <iostream>

using namespace std;

int main (void)
  {
  map <string, bool> flags;

  flags ["dark"] = true;
  flags ["inside"] = true;
  flags ["nomob"] = true;

  for (map <string, bool>::const_iterator i = flags.begin ();
       i != flags.end ();
       i++)
    cout << "bit " << i->first << " = " << i->second << endl;
  }



Output

bit dark = 1
bit inside = 1
bit nomob = 1


- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Nick Gammon   Australia  (18,770 posts)  [Biography] bio   Forum Administrator
Date Sun 27 Jun 2004 10:13 PM (UTC)  quote  ]

Amended on Sun 27 Jun 2004 10:14 PM (UTC) by Nick Gammon

Message
Another approach, rather than using bitset, which limits you to deciding how many bits you want, in advance, is to use a map of bools (or a vector of bools for that matter). Here is one way:


#include <map>
#include <string>
#include <iostream>

using namespace std;

int main (void)
  {
  map <int, bool> flags;

  flags [1] = true;
  flags [5] = true;
  flags [40] = true;

  for (map <int, bool>::const_iterator i = flags.begin ();
       i != flags.end ();
       i++)
    cout << "bit " << i->first << " = " << i->second << endl;
  }



Output

bit 1 = 1
bit 5 = 1
bit 40 = 1


This system lets you use *any* flag numbers (eg. 10000) because the map simply stores the keys it is given.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Nick Gammon   Australia  (18,770 posts)  [Biography] bio   Forum Administrator
Date Sun 27 Jun 2004 08:27 AM (UTC)  quote  ]

Amended on Sun 27 Jun 2004 11:36 PM (UTC) by Nick Gammon

Message
The example below illustrates reading the bitset back in. I certainly wouldn't use a long to handle the bits, that defeats the whole purpose of being able to use more than 32 of them.


#include <bitset>
#include <string>
#include <sstream>
#include <iostream>

using namespace std;

#define MAKE_STRING(msg) \
   (((ostringstream&) (ostringstream() << boolalpha << msg)).str())

int main (void)
  {
  bitset<50> mybits;

  mybits.set (1);
  mybits.set (5);
  mybits.set (40);

string s;

  s = MAKE_STRING (mybits);

  cout << "bits = " << s << endl;

// now read it back in

  bitset<50> newbits;

  istringstream str (s);
  str >> newbits;
  if (str.fail ())
    {
    cout << "Invalid bits" << endl;
    return 1;
    }

  cout << "new  = " << newbits << endl;
  }

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Nick Gammon   Australia  (18,770 posts)  [Biography] bio   Forum Administrator
Date Sun 27 Jun 2004 08:13 AM (UTC)  quote  ]

Amended on Sun 27 Jun 2004 11:36 PM (UTC) by Nick Gammon

Message
Another approach again, if you want a string and not to go direct to cout, is to use a MAKE_STRING macro, like this:


#include <bitset>
#include <string>
#include <sstream>
#include <iostream>

using namespace std;

// this define lets you create a string on-the-fly
// you can use output modifiers in the string, eg.
// string mystr = MAKE_STRING (
//    "hello " << boolalpha << true << hex << 1234 << octal << 3456 << dec
//    << 123456 << scientific << 123.45 << fixed << 1234.567 " );

#define MAKE_STRING(msg) \
   (((ostringstream&) (ostringstream() << boolalpha << msg)).str())


int main (void)
  {
  bitset<50> mybits;

  mybits.set (1);
  mybits.set (5);
  mybits.set (40);

string s;

  s = MAKE_STRING (mybits);

  cout << "bits = " << s << endl;

  }

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Nick Gammon   Australia  (18,770 posts)  [Biography] bio   Forum Administrator
Date Sun 27 Jun 2004 08:09 AM (UTC)  quote  ]
Message
Another, simpler, approach is to just use the output stream. This works and gives the same results:


#include <bitset>
#include <iostream>

using namespace std;

int main (void)
  {
  bitset<50> mybits;

  mybits.set (1);
  mybits.set (5);
  mybits.set (40);

  cout << "bits = " << mybits << endl;

  }



- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


9,668 views.

This is page 2, subject is 2 pages long:  [Previous page]  1  2 

[Reply to this subject]  Reply to this subject   [New subject]  Start a new subject   [Refresh] Refresh page

Go to topic:           Search the forum


[Go to top] top

[Home]

Written by Nick Gammon - 5K

Comments to: Gammon Software support
[RH click to get RSS URL] Forum RSS feed ( http://www.gammon.com.au/rss/forum.xml )

[Best viewed with any browser - 2K]    [Internet Contents Rating Association (ICRA) - 2K]    [Web site powered by FutureQuest.Net]