📄 items.htm
字号:
</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
& 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 &
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 + -