Thread: Text adventure engine idea. thoughts?

  1. #16
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    As for messaging I leave that to you to figure out.

    As for the text string problems:

    Instead of storing the text descriptions of rooms as lines of text, there is another way.
    To use this method you will need to create an editor to create the text file and the text file information.

    Store the text descriptions in one huge array. Load it at startup. To find the description for the current room, you need a starting index and ending index in the array. Print from start to end and wallah...description - and fast too.

    Code:
    struct RoomDescripInfo
    {
      DWORD dwRoomID;
      DWORD dwStartIndex;
      DWORD dwEndIndex;
    };
    So your text descrip file is a huge file full of these structs. The first DWORD of the file would be how many structs (or rooms) there are. Then read in that many structs. Then read in the entire text file containing the room descriptions and stick it into a buffer. Using the information in the map, you retrieve the ID for the current room, then you look in the array of structs and find out the starting and ending index into the description buffer or array. Print the description starting at dwStartIndex and stop printing at dwEndIndex.

    This way all descriptions are in memory and you are simply indexing into a huge array to get them. With tons of memory at your disposal I see no reason not to load everything at load time and then access it as you need it.

    You decide on how to interpret new line and carriage return characters.

    Code:
    
    #define MAP_ERROR  0xFFFFFFFF
    
    #pragma pack(1)
    class CMapHeader
    {
      DWORD dwWidth;
      DWORD dwHeight;
      DWORD dwChunkSize;
    };
    #pragma
    
    class CMap
    {
       friend class CMapContainer;
       DWORD *m_pMap;
    
       DWORD  m_dwWidth;
       DWORD m_dwHeight;
       DWORD m_dwMaxOffset;
    
       bool m_bInitFlag;
    
       CMap(void):m_pMap(NULL),m_dwWidth(0),m_dwHeight(0),
       m_bInitFlag(false) {}
       virtual ~CMap(void)
       {
          if (m_pMap)           //MSCV bug for delete on NULL pointers
          {
             delete [] m_pMap;
             m_pMap=NULL;
          }
       }
       
       bool Create(DWORD dwWidth,DWORD dwHeight)
       {
          //Can't init more than once
          if (m_bInitFlag) return true;
    
         //Setup class data
         m_dwMaxOffset=dwWidth*dwHeight;
         m_dwWidth=dwWidth;
         m_dwHeight=dwHeight;
         m_pMap=new DWORD[m_dwMaxOffset];
        
         //Bail on allocation failure
         if (!m_pMap) return true;
    
         //Class is ok
         m_bInitFlag=true;
    
         //Function success
         return false;
    
       }
    
       DWORD SetMapValue(DWORD dwOffset,DWORD dwValue)
       {
          if (!m_bInitFlag) return MAP_ERROR;
    
          if (dwOffset<m_dwMaxOffset)
          {
             DWORD dwPrevValue=m_pMap[dwOffset];
             m_pMap[dwOffset]=dwValue;
             return dwPrevValue;
          } else return MAP_ERROR;
       }   
       
       DWORD GetMapValue(DWORD dwOffset)
       {
         if (!m_bInitFlag) return MAP_ERROR;
    
         if (dwOffset<m_dwMaxOffset)
         {
           return m_pMap[dwOffset];
         } else return MAP_ERROR;
       }
    
       bool SaveToHandle(int iHandle)
       {
          if (!m_bInitFlag) return true;
    
          CMapHeader hdr;
          hdr.dwWidth=m_dwWidth;
          hdr.dwHeight=m_dwHeight;
          hdr.dwChunkSize=m_dwMaxOffset;
    
          //Write out header      
          _write(iHandle,&hdr,sizeof(CMapHeader));
    
          //Write out map data
          _write(iHandle,m_pMap,m_dwMaxOffset);
       }
       
       bool LoadFromHandle(int iHandle)
       {
          CMapHeader hdr;
          //Read header
          _read(iHandle,&hdr,sizeof(CMapHeader));
          
          //Check for existing map and delete
          if (m_pMap) delete [] m_pMap;
    
          //Setup class data
          m_dwMaxOffset=hdr.dwChunkSize;
          m_dwWidth=hdr.dwWidth;
          m_dwHeight=hdr.dwHeight;
           
          //Create map
          m_pMap=new DWORD[m_dwMaxOffset];
    
          //Bail on error
          if (!m_pMap) return true;
    
          //Class is ok
          m_bInitFlag=true;
          
         }
    
         //Read in map data 
          _read(iHandle,m_pMap,m_dwMaxOffset*sizeof(DWORD))
    
         //Function success
         return false;
             
       }
    
       CMap &operator = (const CMap &map)
       {
          //Assign class data 
          m_dwMaxOffset=map.m_dwMaxOffset;
          m_dwWidth=map.m_dwWidth;
          m_dwHeight=map.m_dwHeight;
        
          //Ensure passed object is valid
          if (map.m_pMap && map.m_bInitFlag)
          {
             //Setup pointer to passed object
             DWORD *ptrMap=map.m_pMap;
    
             //Check for existing map and delete
             if (m_pMap) delete [] m_pMap;
    
             //Create our map
             m_pMap=new DWORD[m_dwMaxOffset];
          
             //Only way out is an exception
             if (!m_pMap) throw CEngineException("Out of Memory") ;
              
             //Setup pointers and temp data for asm routine
             DWORD *ptrClassMap=m_pMap;
             DWORD tempMaxOffset=m_dwMaxOffset;
    
             //32-bit copy from [DS]:ESI to ES:EDI
             _asm {
               mov  esi,[ptrMap]
               mov  edi,[ptrClassMap]
               mov  ecx,[tempMaxOffset]
               rep   movsd
               } 
           } else throw CEngineException("NULL pointer assignment");
           
            //Class is ok
            m_bInitFlag=true;
        
          }   
        //Return this object        
        return *this;
      } 
    };
    
    enum Directions {
     UP=0x01,
     DOWN=0x02,
     WEST=0x04,
     EAST=0x08,
     NORTH=0x10,
     SOUTH=0x20
    };
    
       
    struct CRoomDescripInfo
    {
      DWORD dwRoomID;
      DWORD dwStartIndex;
      DWORD dwEndIndex;
    };
    
    class CRoom
    {
      WORD  m_dwID;
      CRoomDescripInfo  m_RoomInfo;
      WORD  m_dwExits;
      WORD  m_dwDoors;
      ....
    };
    
    class CRoomContainer
    {
       std::vector<CRoom *> m_vRooms;
       ...
       public:
         ...
         void ShowDescription(void);
    
    };
    This is the very beginning of a text adventure game. The map class would have a map container that controlled all access to and from the map. Each map could be of varying sizes If the player moves up, he moves down in the vector and down is up in the vector. Doors can be coded by using the Directions enum as well as directional data for each room.

    I leave the rest to you.

    For the message system, think about how Windows does it.

    I wrote this code while sitting here so I'm sure there are bugs in it. This system needs to completely implement the following classes.

    CMapContainer

    CRoom
    CRoomContainer

    CObject
    CObjectContainer

    CCharacter
    CCharaterContainer

    CParser

    CMessageSystem

    CMessageSystem 'could' send a message to the CObjectContainer and/or CCharacterContainer which would then route the message to the correct MessageProc of the CObject class or CCharacter class. The message is then processed by the class. This means that the central message system is only responsible for sending messages, not for responding to them. The bulk of the work would be done in each class's MessageProc.
    You don't have to worry about function pointers because if the MessageProc is inside of the class and is public, then all CMessageSystem needs is a vector or array of pointers to all objects/characters in the game.

    CMessageSystem->CObjectContainer->CObject->CObject::MessageProc()

    Like that.
    Last edited by VirtualAce; 01-15-2006 at 01:46 AM.

  2. #17
    Registered User
    Join Date
    Jan 2005
    Posts
    106
    This is what I'm doing for the room handling now, BTW. Keep in mind that it's sort of sloppy, so the variables probably look a bit funky and there's no bounds checking. This is mostly a proof of concept thing. I've already written half of these functions in the main project, and they're somewhat better.

    Code:
    #include <fstream>
    #include <iostream>
    #include <sstream>
    #include <string>
    #include <vector>
    
    using namespace std;
    
    int main()
    {
      int x = 0;
      
      ifstream file("rooms2.txt", ios::in);
      vector<string>rooms;
      
      string search = "bedroom";
      string read_in;
      vector<string>current_room;
      
      search = "<" + search;
      
      for(;;) //read-in file to vector
      {     
        getline(file, read_in);    
        if(read_in == "#") {break;}
        rooms.push_back(read_in);
      }
      
      x = 0;
      
      while(rooms[x] != search) //search function -- would return X.
      {                                         
        x++;
      }      
    
      int y = 0;
      
      while(rooms[x] != ">") // loads search sector.
      {
        //f(current_room[y] != ">") {break;}
        current_room.push_back(rooms[x] + "\n");  
        x++;
      }   
      
      y = 0;
      
      for(y; y < current_room.size(); y++) // y should be a size_t -- Also, this prints the room data
      {
        cout << current_room[y];
      }
      
      cin >> search;
      return 0;
    }
    Basically, it loads an ifstream into a vector. It then searches through the vector for some search string (searching for the last word in the vector, in a 1300~ odd entry vector, produced no slowdown at all. I'm assuming because x++ is the only operation being done?) Anyway, it then runs this x value (a position inthe vector, of course) through another while statement, which loads lines into another vector till some delimiting char (>, in this case. It's not put in the resultant vector. That might change later if I need it to.)

    Search, in this function, would be a string read in from the room definition file*. There are several methods to do this. I've outlined one below.

    I'm sort of shying away from using a class for this. The concept of a room is rather fuzzy, in the scope of this project. It's a general place that holds items. As such, a room could be an inventory, or some sort of interface, with the directional parameters being menu options or something. Assuming it is a literal room, then by leaving it how it is now, I could also change the number of directional parameters each room takes by altering the data textfiles. Making it a class limits it, unless I leave the class extremely vague, at... which point I no longer need the class. Furthermore, by leaving it as a vector of strings, I can process it with a generic function that loads stuff from and fstream into a vector.

    Also, your method seems to produce a gridded map. That's not entirely desirable.

    *
    <name
    desc
    #
    to north
    to south
    to east
    to west
    to up
    to down
    >

    which line does which will be either stored in a header or an external data file. to get a directional line, I run the search function on #, return the X position, and basically just plug the x value into search=vectorname[x].

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 6
    Last Post: 01-29-2008, 04:40 AM
  2. Text adventure game GUI
    By VirtualAce in forum Game Programming
    Replies: 11
    Last Post: 11-07-2007, 06:34 PM
  3. Outputting String arrays in windows
    By Xterria in forum Game Programming
    Replies: 11
    Last Post: 11-13-2001, 07:35 PM
  4. Simple Text Adventure: Need Help
    By Unregistered in forum Game Programming
    Replies: 19
    Last Post: 10-28-2001, 07:22 PM
  5. text adventure
    By BuRtAiNiAn FlY in forum C Programming
    Replies: 1
    Last Post: 09-05-2001, 09:39 PM