⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 items.htm

📁 物件管理系统源代码
💻 HTM
📖 第 1 页 / 共 2 页
字号:
    </blockquote>
  </blockquote>
</blockquote>
<p>Also, there are the functions for combining two piles together. The + operator 
  uses the += operator (because it is easier to add objects from one pile to another 
  pile, than adding two piles together into a new one).</p>
<blockquote> 
  <blockquote> 
    <blockquote> 
      <blockquote> 
        <p> <font face="Courier New, Courier, mono" size="-1">cItemPack & cItemPack::operator+=( 
          const cItemPack & o ) { </font></p>
        <blockquote> 
          <p><font face="Courier New, Courier, mono" size="-1">std::map< cItem,unsigned 
            long >::const_iterator i;<br>
            for( i = o.contents.begin( ); i != o.contents.end( ); ++i ) { </font></p>
          <blockquote> 
            <p><font face="Courier New, Courier, mono" size="-1">add( i->first, 
              i->second ); </font></p>
          </blockquote>
          <p><font face="Courier New, Courier, mono" size="-1">} <br>
            return( *this ); </font></p>
        </blockquote>
        <p><font face="Courier New, Courier, mono" size="-1">} </font></p>
        <p><font face="Courier New, Courier, mono" size="-1">cItemPack cItemPack::operator+( 
          const cItemPack & o ) const { </font></p>
        <blockquote> 
          <p><font face="Courier New, Courier, mono" size="-1">return( cItemPack(*this) 
            += o ); </font></p>
        </blockquote>
        <p><font face="Courier New, Courier, mono" size="-1">}</font></p>
      </blockquote>
    </blockquote>
  </blockquote>
</blockquote>
<p>And finally, in order not to restrict ourselves to these functions as far as 
  reading through the pile goes, I have added a final function that returns a 
  const instance of the map, which can then be used to access directly the data 
  about the various objects (namely through iterators).</p>
<blockquote> 
  <blockquote> 
    <blockquote> 
      <blockquote> 
        <p><font size="-1" face="Courier New, Courier, mono">const std::map< cItem,unsigned 
          long > & cItemPack::getItems( void ) { return( contents ); }</font></p>
      </blockquote>
    </blockquote>
  </blockquote>
  <p><b><i>The Item database</i></b></p>
</blockquote>
<p>The database will of course be dependent on the data to be stored for each 
  item. For the purposes of this article, the information stored will be the name 
  of the object, a short description of it, its value and its weight. They will 
  be stored into structures, one structure per item:</p>
<blockquote> 
  <blockquote> 
    <blockquote> 
      <blockquote> 
        <p><font size="-1" face="Courier New, Courier, mono">struct sItemData 
          { </font></p>
        <blockquote> 
          <p><font size="-1" face="Courier New, Courier, mono">std::string name, 
            description; <br>
            unsigned long value, weight; </font></p>
        </blockquote>
        <p><font size="-1" face="Courier New, Courier, mono">};</font></p>
      </blockquote>
    </blockquote>
  </blockquote>
</blockquote>
<p>The item database at its core should be an easy means of accessing information 
  about an object based on its type. This leads to the following properties:</p>
<blockquote> 
  <p></p>
</blockquote>
<ul>
  <ul>
    <ul>
      <ul>
        <ul>
          <li>Returning the item data for any given item</li>
          <li>Creating an item corresponding to a given item name</li>
        </ul>
      </ul>
    </ul>
  </ul>
</ul>
<p>To represent the data manager, I've chosen to use a monostate object. It is 
  an object that can only exist in one instance at a time, but unlike a singleton, 
  that instance is not directly accessible. However, it could become useful at 
  a point or another to re-load the object, in which case the new version will 
  simply replace the previous one. As a monostate, the object should have:</p>
<blockquote>
  <p></p>
</blockquote>
<ul>
  <ul>
    <ul>
      <ul>
        <ul>
          <li>Functions to initialize it (when the program starts)</li>
          <li>Functions to delete it (when the program exits)</li>
        </ul>
      </ul>
    </ul>
  </ul>
</ul>
<p> There is no actual need to turn this object into a class, a namespace will 
  do:</p>
<blockquote> 
  <blockquote> 
    <blockquote> 
      <blockquote> 
        <p><font face="Courier New, Courier, mono" size="-1">namespace cItemDatabase 
          { </font></p>
        <blockquote> 
          <p><font face="Courier New, Courier, mono" size="-1">const sItemData 
            &amp; getData( const cItem & ); <br>
            cItem create( const std::string & ); <br>
            void initialize( const std::string & ); <br>
            void unload( void ); </font></p>
        </blockquote>
        <p><font face="Courier New, Courier, mono" size="-1">};</font></p>
      </blockquote>
    </blockquote>
  </blockquote>
</blockquote>
<p>There is an additional concern: exceptions. There are three possible errors 
  in the above functions. First, all functions that require the object to be intialized 
  beforehand cannot fail silently because they need to return something (and it 
  would be a bad idea to fail silently because this can cause bugs). Next, the 
  create function may not find an item corresponding to its argument, it must 
  then have a way to alert the user. And finally, the user might try to get the 
  data for an object that does not exist in the database. This will go through 
  this enum type:</p>
<blockquote> 
  <blockquote> 
    <blockquote> 
      <blockquote> 
        <p><font size="-1" face="Courier New, Courier, mono">enum eDatabaseError 
          { IDBERR_NOT_INITIALIZED, IDBERR_INVALID_NAME, IDBERR_INVALID_ITEM };</font></p>
      </blockquote>
    </blockquote>
  </blockquote>
</blockquote>
<p>More exceptions may come later on, depending on new behaviors we will implement 
  into the database (such as an <font face="Courier New, Courier, mono">IDBERR_OUT_OF_COFFEE</font> 
  if we ever implement coffeemaking solutions).</p>
<p>First, the variables required for the implementation :</p>
<blockquote> 
  <blockquote> 
    <blockquote> 
      <blockquote> 
        <p><font face="Courier New, Courier, mono" size="-1">namespace cItemDatabase 
          { </font></p>
        <blockquote> 
          <p><font face="Courier New, Courier, mono" size="-1">std::deque< sItemData 
            > item_database_entries; <br>
            bool item_database_initialized = false; </font></p>
        </blockquote>
        <p><font face="Courier New, Courier, mono" size="-1">};</font></p>
      </blockquote>
    </blockquote>
  </blockquote>
</blockquote>
<p>The above boolean serves as an indicator to tell if the object has already 
  been initialized. I have chosen to store the data as a deque because it allows 
  a <i>O(1)</i> random access without sacrificing memory usage (we need to store 
  all the objects anyway), and because unlike a vector it does not need a contiguous 
  memory space. The initialization and deletion functions are the following:</p>
<blockquote> 
  <blockquote> 
    <blockquote> 
      <blockquote> 
        <p> <font face="Courier New, Courier, mono" size="-1">void cItemDatabase::initialize( 
          const std::string & s ) { </font></p>
        <blockquote> 
          <p><font face="Courier New, Courier, mono" size="-1">item_database_entries.clear( 
            ); <br>
            <i><font color="#999999">//FILE LOADING SEQUENCE</font></i> <br>
            item_database_initialized = true; </font></p>
        </blockquote>
        <p><font face="Courier New, Courier, mono" size="-1">}</font></p>
        <p> <font face="Courier New, Courier, mono" size="-1">void cItemDatabase::unload( 
          void ) { </font></p>
        <blockquote>
          <p><font face="Courier New, Courier, mono" size="-1"> item_database_entries.clear( 
            );<br>
            </font><font face="Courier New, Courier, mono" size="-1">item_database_initialized 
            = false; </font></p>
        </blockquote>
        <p><font face="Courier New, Courier, mono" size="-1">}</font></p>
      </blockquote>
    </blockquote>
  </blockquote>
</blockquote>
<p>I did not write here any actual loading process, because I think it's up to 
  you to choose the one that fits best your style (all of them should be doing 
  the same thing anyway: filling up the deque with the items). The source code 
  provided at the end of the article, however, is fully functional, albeit with 
  the help of a possibly buggy loading system (which is not error-checked at all).</p>
<blockquote> 
  <blockquote> 
    <blockquote> 
      <blockquote> 
        <p><font size="-1" face="Courier New, Courier, mono">const sItemData &amp; 
          cItemDatabase::getData( const cItem & i ) { </font></p>
        <blockquote> 
          <p><font size="-1" face="Courier New, Courier, mono">if( item_database_initialized 
            ) { </font></p>
          <blockquote> 
            <p><font size="-1" face="Courier New, Courier, mono">unsigned long 
              type = i.getID( ); <br>
              if( type >= item_database_entries.size( ) ) { throw IDBERR_INVALID_ITEM; 
              }<br>
              else { return( item_database_entries[type] ); } </font></p>
          </blockquote>
          <p><font size="-1" face="Courier New, Courier, mono">} <br>
            else { throw IDBERR_NOT_INITIALIZED; } </font></p>
        </blockquote>
        <p><font size="-1" face="Courier New, Courier, mono">}</font></p>
      </blockquote>
    </blockquote>
  </blockquote>
</blockquote>
<p>The <font face="Courier New, Courier, mono">getData</font> function extracts 
  the type identifier of the object and checks if it is withing the bounds of 
  the database, which causes it to either return the correct object, or throw 
  an invalid item exception. Since item descriptions can get fairly large, it 
  is best to return a constant reference of an existing description instead of 
  returning a copy. It is both natural and necessary for the reference to be constant: 
  modifying it would cause bugs in other areas of the code, and the description 
  of an item type is not something that should not be modified from inside the 
  game anyway.</p>
<blockquote>
  <blockquote> 
    <blockquote> 
      <blockquote> 
        <p><font size="-1" face="Courier New, Courier, mono">cItem cItemDatabase::create( 
          const std::string & s ) { </font></p>
        <blockquote> 
          <p><font size="-1" face="Courier New, Courier, mono">if( instance == 
            0 ) { throw IDBERR_NOT_INITIALIZED; } <br>
            unsigned long i; <br>
            for( i = 0; i != instance->data.size( ); ++i ) { </font></p>
          <blockquote> 
            <p><font size="-1" face="Courier New, Courier, mono">if( i->name == 
              s ) { return( i ); } </font></p>
          </blockquote>
          <p><font size="-1" face="Courier New, Courier, mono">} <br>
            throw IDBERR_INVALID_NAME; </font></p>
        </blockquote>
        <p><font size="-1" face="Courier New, Courier, mono">}</font></p>
      </blockquote>
    </blockquote>
  </blockquote>
</blockquote>
<p>The creation function, as simple as possible : it checks that there is an instance 
  somewhere, then runs through the entire map looking for an object with the same 
  name as it. Naturally, it's a time-consuming <i>O(n)</i> process, so I suggest 
  using it sparingly (it is not meant for mainstream use anyway, only for debugging/cheating/scripting 
  purposes, as name-to-identifier correspondence can usually be evaluated at release 
  time). </p>
<blockquote> 
  <blockquote> 
    <blockquote> 
      <blockquote> 
        <p><font face="Courier New, Courier, mono" size="-1">cItem cItemDatabase::create( 
          const std::string & s ) { </font></p>
        <blockquote> 
          <p><font face="Courier New, Courier, mono" size="-1">if( !item_database_initialized 
            ) { throw IDBERR_NOT_INITIALIZED; } <br>
            long i; <br>
            for( i = item_database_entries.size( )-1; i >= 0; --i ) {</font></p>
          <blockquote> 
            <p><font face="Courier New, Courier, mono" size="-1">if( item_database_entries[i].name 
              == s ) { return( cItem(i) ); }</font></p>
          </blockquote>
          <p><font face="Courier New, Courier, mono" size="-1">} <br>
            throw IDBERR_INVALID_NAME; </font></p>
        </blockquote>
        <p><font face="Courier New, Courier, mono" size="-1">}</font></p>
      </blockquote>
    </blockquote>
  </blockquote>
</blockquote>
<p>The implementation of the above function is quite straightforward: check if 
  the data has been initialized, then run through the entire vector until a match 
  is found, and if there is none, throw an exception.</p>
<blockquote> 
  <p><b><i>Conclusion</i></b></p>
</blockquote>
<p>This six-file coding spree has left us with a basic, yet efficient item system. 
  Applications using this system will be moving around cItem instances without 
  knowing what's inside, and whenever some data is needed, a quick call to the 
  database will bring up everything. Item piles are even more useful than items 
  themselves because they can represent the player's inventory, items on the ground, 
  or any kind of item stack anywhere.</p>
<p>The way the system works is demonstrated in the zip file that comes with the 
  article (see below). The archive contains all the source code in a VC++6 project/workspace, 
  along with two additional files that provide a testing scaffold. The associated 
  executable is a dos console application that loads an object list from a file, 
  and allows you to manage an inventory by adding, removing and looking at things 
  inside.</p>
<p align="center">(<a href="code.zip">Download the source code here</a>)</p>
</body>
</html>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -