RPGDXThe center of Indie-RPG gaming
Not logged in. [log in] [register]
 
 
Post new topic Reply to topic Goto page 1, 2  Next 
View previous topic - View next topic  
Author Message
XMark
Guitar playin' black mage


Joined: 30 May 2002
Posts: 870
Location: New Westminster, BC, Canada

PostPosted: Fri Dec 10, 2004 6:04 am    Post subject: Database library for game dev? [quote]

I'm not sure if anyone has released something similar but the idea just occured to me when I was thinking of the many problems with storing information such as weapon/enemy/magic spells in data files.

The problem is that after making the data structure for a weapon, suddenly you decide that you want to add different types of weapon damage like crushing, slicing, etc. Unfortunately you have 100 weapons made already so you have to write a "one off" program to update your weapon data file to add the new attribute. Then in your game itself you have to change the weapon loading code. This could end up being a major headache if you have to do it every time.

That's where the idea of databases come in. I think it would be neat for all the game data to be stored in a database format, so you would use some kind of management program to make your tables, specify the relations between them, and edit the data.

The other portion would be a library that you would use in your program to access the data. The most-used function would be something like getValue(string Tablename, int keyvalue, string Attributename)

Code:

int CalculateDamage(int current_player, int current_enemy) {
  int damage;
  damage = myDB.getvalue("Player", current_player, "WeaponStrength");
  damage -= myDB.getvalue("Enemy", current_enemy, "ArmorStrength");
  return damage;
}


What this would do is look in the table "Player" and find the row according to the primary key #current_player, and then get the WeaponStrength value from that player. Basically like an SQL select statement but executed in a much simpler fashion.

Anyone who knows databases probably just thought "Dude, Mark, you did a crappy normalization job there." So if all weapon information was in a different table then it would just search for the WeaponStrength field through Player's foreign keys. Of course, this could take a long time so it would be a good idea for the database to store paths to different tables' attributes through foreign keys.

So anyways, it wouldn't just be that. There would also be search functions. For example:

myDB.find("Player", "Name", "Bokk")

would return the primary key value for the player named Bokk, and

myDB.findattribute("Player", "Name", "Bokk", "Level")

would do the same search, but find Bokk's level instead of the primary key value. Hmm.. come to think of it, it could just be the same function name overloaded.

Does something like this exist already? (other than linking your program with a real database)
_________________
Mark Hall
Abstract Productions
I PLAYS THE MUSIC THAT MAKES THE PEOPLES FALL DOWN!
Back to top  
Rainer Deyke
Demon Hunter


Joined: 05 Jun 2002
Posts: 672

PostPosted: Fri Dec 10, 2004 6:20 am    Post subject: [quote]

That sounds like an overcomplicated solution to a non-problem. Why not just make your data files a bit more flexible instead?
Back to top  
XMark
Guitar playin' black mage


Joined: 30 May 2002
Posts: 870
Location: New Westminster, BC, Canada

PostPosted: Fri Dec 10, 2004 6:47 am    Post subject: [quote]

Well, it wasn't specifically for that problem, it was just the thing that got me started thinking about it.

It's meant more as a portable system to easily create, edit, and associate your data structures outside of the program's code, and provide a simple way to access that data.
_________________
Mark Hall
Abstract Productions
I PLAYS THE MUSIC THAT MAKES THE PEOPLES FALL DOWN!
Back to top  
Bjorn
Demon Hunter


Joined: 29 May 2002
Posts: 1425
Location: Germany

PostPosted: Fri Dec 10, 2004 7:51 am    Post subject: [quote]

I agree with Rainer Deyke.

The problem of extendability is easily solved using XML, and it also pretty much takes care of being able to access the data structures outside of the program's code.

The idea is not at all non-interesting though. Especially for the MMORPG kind, using a database would probably be a good idea. This would prevent having to store all the game data in memory all the time but still allow you to access it pretty quickly on demand. It would also take care of the persistency of your data as you'll want updated data to live through a server restart, and it might make it easier to make dynamic updates (say with an editor tool) while the game is live.
Back to top  
Mandrake
elementry school minded asshole


Joined: 28 May 2002
Posts: 1341
Location: GNARR!

PostPosted: Fri Dec 10, 2004 1:02 pm    Post subject: [quote]

Quote:

That's where the idea of databases come in. I think it would be neat for all the game data to be stored in a database format, so you would use some kind of management program to make your tables, specify the relations between them, and edit the data.

The other portion would be a library that you would use in your program to access the data. The most-used function would be something like getValue(string Tablename, int keyvalue, string Attributename)


Databases are used quite a bit commercially. I don't see it as a non-problem, because unlike Reiner there, I realize that what you are suggesting is a more flexible way to hold and do data-types.

So far, I think, the best way to do this would be to either use SQL-lite or XML. XML would probably be better because it is slowly become standard. But, SQL can make doing some crazy stuff uber-simple. Like, updating every weapon in the database (and adding a field) can be done with 1 SQL statement. Moving weapons, sorting them, etc. It all depends on how much work you want to do on your end.
_________________
"Well, last time I flicked on a lighter, I'm pretty sure I didn't create a black hole."-
Xmark

http://pauljessup.com
Back to top  
Hajo
Demon Hunter


Joined: 30 Sep 2003
Posts: 779
Location: Between chair and keyboard.

PostPosted: Fri Dec 10, 2004 1:22 pm    Post subject: [quote]

"If a hammer is all you have, everything looks like a nail"

Databases may or may not be the proper tool for your project. I'd say small projects run better without, while big projects will need one. I agree with Björn that a MMORPG most likely will benefit from a real database.
Back to top  
biggerUniverse
Mage


Joined: 18 Nov 2003
Posts: 326
Location: A small, b/g planet in the unfashionable arm of the galaxy

PostPosted: Fri Dec 10, 2004 2:48 pm    Post subject: [quote]

Learn the way of the Tuplespace, young acolyte.

EDIT:
disclaimer: Useful especially for parallel-computing applications (read: MMOG), and especially powerful in languages that have serialization/deserialization API (Java, etc.).

http://www.dancres.org/blitz/index.html
_________________
We are on the outer reaches of someone else's universe.
Back to top  
Hajo
Demon Hunter


Joined: 30 Sep 2003
Posts: 779
Location: Between chair and keyboard.

PostPosted: Fri Dec 10, 2004 3:42 pm    Post subject: [quote]

A tuplespace is cool, but I had SQL in mind when I wrote database. I mean the data querying and manipulation powers that come with a SQL database are very handy - yet the size and complexity of most database products limits the usefulness for small games.
Back to top  
biggerUniverse
Mage


Joined: 18 Nov 2003
Posts: 326
Location: A small, b/g planet in the unfashionable arm of the galaxy

PostPosted: Fri Dec 10, 2004 3:56 pm    Post subject: [quote]

I agree, and recommend XML data files for small projects. I have even thought, when designing schema, that it would be interesting to have an "Independent XML DTD for Computer Game Datasets" standard.
_________________
We are on the outer reaches of someone else's universe.
Back to top  
biggerUniverse
Mage


Joined: 18 Nov 2003
Posts: 326
Location: A small, b/g planet in the unfashionable arm of the galaxy

PostPosted: Fri Dec 10, 2004 4:02 pm    Post subject: [quote]

Actually, while thinking a little more about it, it occurred to me that Berkeley DB might a viable, embedded solution...
_________________
We are on the outer reaches of someone else's universe.
Back to top  
janus
Mage


Joined: 29 Jun 2002
Posts: 464
Location: Issaquah, WA

PostPosted: Fri Dec 10, 2004 11:52 pm    Post subject: [quote]

biggerUniverse wrote:
Actually, while thinking a little more about it, it occurred to me that Berkeley DB might a viable, embedded solution...

I thought of that too. Subversion uses it for repositories and it seems to work pretty good.
Back to top  
XMark
Guitar playin' black mage


Joined: 30 May 2002
Posts: 870
Location: New Westminster, BC, Canada

PostPosted: Thu Dec 16, 2004 10:11 pm    Post subject: [quote]

Interesting. I think I should check out XML some time. Anyone know a good beginners' tutorial online?
_________________
Mark Hall
Abstract Productions
I PLAYS THE MUSIC THAT MAKES THE PEOPLES FALL DOWN!
Back to top  
Bjorn
Demon Hunter


Joined: 29 May 2002
Posts: 1425
Location: Germany

PostPosted: Fri Dec 17, 2004 10:36 am    Post subject: [quote]

The number one Google result for "xml tutorial" looks as good as any, haven't read it myself though.

http://www.w3schools.com/xml/default.asp
Back to top  
omega_alpha
Fluffy Bunny of Doom


Joined: 07 Jun 2003
Posts: 16
Location: California

PostPosted: Sat Dec 18, 2004 8:15 am    Post subject: [quote]

The database idea is an interesting one, and in fact it makes alot of sense. Storing them all in one, I'm assuming global, class would make it much easier to access and modify player attributes.
Back to top  
Mandrake
elementry school minded asshole


Joined: 28 May 2002
Posts: 1341
Location: GNARR!

PostPosted: Sun Dec 19, 2004 12:43 am    Post subject: [quote]

Here's something I wrote two years ago, for my book on programming an RPG in Allegro. It writes/reads a human redable XML-like format, and acts like a database.

Cut and paste into a db.hpp or something and include it.

Code:

#ifndef _DB
#define _DB


#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <string>
#include <time.h>

using namespace std;


class simple_field_rules_obj
{
public:
   char data_type;
   string fieldname;
   simple_field_rules_obj(string s, char d)
   {
      fieldname=s;
      data_type=d;
   };
};

typedef map <string, string> db_x_record_primitive_object;
typedef vector < simple_field_rules_obj> db_x_fieldlist;

ifstream db_xml_tmpin;
ofstream db_xml_tmpout;


string db_xml_remove_brackets(string s)
{
 string f;
 for(int i=1; i!=s.length()-1; i++)
 {
    f+=s[i];
 }
 return f;
};

string db_xml_add_brackets(string s)
{
   s="<"+s+">";
   return s;
};

string db_xml_add_ending_brackets(string s)
{
   s="</"+s+">";
   return s;
};

class db_xml_fields_wrapper
{
public:
   int total_fields;
   db_x_fieldlist dbx_fields;
   db_xml_fields_wrapper()
   {
      total_fields=0;
      dbx_fields.clear();
   };
   void add_field(string s, char data_type)
   {
      dbx_fields.push_back(simple_field_rules_obj(s, data_type));
      total_fields++;
   };
   void remove_field(string s)
   {
      //???
   };
   void change_field(string s, char data_type)
   {
      for(int i=0; i!=total_fields; i++)
      {
         if(dbx_fields[i].fieldname==s)
         {
            dbx_fields[i].data_type=data_type;
         }
      }
   };
   friend ostream & operator << (ostream &in, db_xml_fields_wrapper &tmp)
   {
     in << "<fields>" << endl << "\t";
      for(int i=0; i!=tmp.total_fields; i++)
      {
         in << tmp.dbx_fields[i].fieldname << " " << tmp.dbx_fields[i].data_type << " ";
      }
      in << "\n" << "</fields>" << endl;
      return in;
   };
   friend istream & operator >> (istream &in, db_xml_fields_wrapper &tmp)
   {
      tmp.dbx_fields.clear();
      tmp.total_fields =0;
      bool eof=false;
      while(!eof)
      {
         if(in.eof()) eof=true;
         string sbuff;
         in >> sbuff;
         if(sbuff== "<fields>")
         {
            while(sbuff!= "</fields>")
            {
               in >> sbuff;
               if(sbuff!= "</fields>")
               {
                  char data_type;
                  in >> data_type;
                  tmp.add_field(sbuff, data_type);
               }
               if(sbuff=="</fields>")
               {
                  eof=true;
               }
               if(in.eof()) eof=true;
            }
         }
      }
      return in;
   };
};



class dbx_recordset_object 
{
   db_x_fieldlist dbx_fields ;
   int total_fields;
public:
   db_x_record_primitive_object data;   
   void open(db_xml_fields_wrapper dbf)
   {
      dbx_fields.clear();
      data.clear();
      total_fields=dbf.total_fields;
      for(int i=0; i!=dbf.total_fields; i++)
      {
         dbx_fields.push_back(dbf.dbx_fields[i]);
         //data[dbf.dbx_fields[i].fieldname]=db_records[dbf.dbx_fields[i].fieldname];
      }
   };
   
   inline dbx_recordset_object(db_xml_fields_wrapper dbf) { open(dbf); };
   inline dbx_recordset_object() { total_fields=0; };

   friend ostream & operator << (ostream &in, dbx_recordset_object &tmp)
   {
     in << "<rec>" << endl;
      for(int i=0; i!=tmp.total_fields; i++)
      {
         in << "\t" << db_xml_add_brackets(tmp.dbx_fields[i].fieldname) << endl << "\t" << "\t";
         in << tmp.data[tmp.dbx_fields[i].fieldname] << endl;
         in << "\t" << db_xml_add_ending_brackets(tmp.dbx_fields[i].fieldname) << endl;
      }
      in << "</rec>" << endl;
      return in;
   };
   friend istream & operator >> (istream &in, dbx_recordset_object &tmp)
   {
     if(!in.eof())
     {
      bool eof=false;
         string sbuff="";
         in >> sbuff;
         if(sbuff== "<rec>")
         {
            tmp.data.clear();
            while(sbuff!= "</rec>")
            {
               in >> sbuff;
               if(sbuff!= "</rec>")
               {
                  string mbuff;
                  sbuff=db_xml_remove_brackets(sbuff);            
                  tmp.data[sbuff]="";
                  while(mbuff!=db_xml_add_ending_brackets(sbuff))
                  {
                     in >> mbuff;
                     if(mbuff!=db_xml_add_ending_brackets(sbuff))
                     {
                        tmp.data[sbuff]+=mbuff;
                        tmp.data[sbuff]+=" ";
                     }
                  }
               }   
           }
      }
      }
      return in;
   };
    inline char field_data_type(string var)
    {
      for(int i=0; i!=total_fields; i++)
      {
         if(dbx_fields[i].fieldname==var)
         {
            return dbx_fields[i].data_type;
         }
      }
    };
};


typedef struct
{
   int last_found;
   string var;
   string equals;
   char data_type;;
}DB_SRCH;

//superclass, yeah!  use this bastard to create interesting filters later.

class dbx_conversion_filters
{
public:
    dbx_conversion_filters()
    {
       //stub.
    };
    ~dbx_conversion_filters()
    {
       //stub
    };
    inline int to_int(string s){ return atoi(s.c_str()); };
    inline string to_string(string s) { return s; };
    inline string from_int(int i) {char m[5]; itoa(i, m, 10); return m; };
    inline string from_string(string s){ return s; };
};

class dbx_object
{
   string table_name;

   db_xml_fields_wrapper fields;
//private function:
   void get_last_id()
   {
      ifstream table(table_name.c_str());
      db_xml_fields_wrapper tmp1;
      dbx_recordset_object tmp2;
      table >> tmp1;
      while(!table.eof())
      {
         table >> tmp2;
         last_id=atoi(tmp2.data["ID"].c_str());
      }
      table.close();
   };
    inline int dbx2i(string field){ return atoi(field.c_str()); };
   inline string i2dbx(int i) { char m[5]; itoa(i, m, 10); return m; };

public:
   int last_id;

   dbx_object()
   {
      last_id=0;
      table_name="";
   };
   dbx_object(dbx_recordset_object &recset, string s)
   {
      last_id=0;
      table_name="";
      connect(recset, s);
   };
   void connect(dbx_recordset_object &recset, string s)
   {
      table_name=s;
      recset.open(fields);
      last_id=0;
   };
   void disconnect()
   {
      table_name="";
      gia_del_file("burn.tmp");
   };
   void new_table(string s)
   {
      table_name=s;
      ofstream table(table_name.c_str());
      table.close();
   };
   inline void output_fields(){  ofstream table(table_name.c_str()); table << fields; table.close(); }
   inline void add_field(string s, char data_type){   fields.add_field(s, data_type);   output_fields(); };
   inline void change_field(string s, char data_type){ change_field(s, data_type); output_fields(); };
   void refresh(dbx_recordset_object &recset)
   {
      ifstream burnfile("burn.tmp");
      ofstream table(table_name.c_str());
      burnfile >> fields;
      table << fields;
      while(!burnfile.eof())
      {
         burnfile >> recset;
         if(!burnfile.eof()) table << recset;
      }
      table.close();
      burnfile.close();
   };
   void insert_record(dbx_recordset_object &recset)
   {
      get_last_id();
      last_id++;
      recset.data["ID"]=i2dbx(last_id);
      ofstream burnfile(table_name.c_str(), ios::app);
      burnfile << recset;
      burnfile.close();
   };
   void delete_record(dbx_recordset_object &recset)
   {
      last_id=dbx2i(recset.data["ID"]);
      ofstream burnfile("burn.tmp");
      ifstream table(table_name.c_str());
      dbx_recordset_object tmp2;
      tmp2.open(fields);
      table >> fields;
      burnfile << fields;
      while(!table.eof())
      {
         table >> tmp2;
         if(!table.eof())
         {
            if(dbx2i(tmp2.data["ID"])!=last_id)
            {
               burnfile << tmp2;
            }
         }
      }
      table.close();
      burnfile.close();
      refresh(recset);
   };
   void update_record(dbx_recordset_object &recset)
   {
      last_id=dbx2i(recset.data["ID"]);
      ofstream burnfile("burn.tmp");
      ifstream table(table_name.c_str());
      db_xml_fields_wrapper tmp1;
      dbx_recordset_object tmp2;
      table >> tmp1;
      tmp2.open(tmp1);
      burnfile << fields;
      while(!table.eof())
      {
         table >> tmp2;
         if(!table.eof())
         {
            if(dbx2i(tmp2.data["ID"])!=last_id)
            {
                cout << tmp2;
               burnfile << tmp2;
            }
            else
            {
               cout << recset;
               burnfile << recset;
            }
         }
      }
      table.close();
      burnfile.close();
      refresh(recset);
   };
   void move_first(dbx_recordset_object &recset)
   {
      ifstream table(table_name.c_str());
      table >> fields;
      table >> recset;
      last_id=dbx2i(recset.data["ID"]);
      table.close();      
   };
   int move_next(dbx_recordset_object &recset)
   {
      ifstream table(table_name.c_str());
      table >> fields;
      while(!table.eof())
      {
         table >> recset;
         if(dbx2i(recset.data["ID"]) == (last_id+1))
         {
            last_id++;
            table.close();
            return 1;
         }
      }
      table.close();
   };
   int move_prev(dbx_recordset_object &recset)
   {
      ifstream table(table_name.c_str());
      table >> fields;
      while(!table.eof())
      {
         table >> recset;
         if(dbx2i(recset.data["ID"]) == (last_id-1))
         {
            last_id--;
            table.close();
            return 1;
         }
      }
      table.close();
   };
   void move_last(dbx_recordset_object &recset)
   {
      ifstream table(table_name.c_str());
      table >> fields;
      while(!table.eof())
      {
         table >> recset;
         last_id=dbx2i(recset.data["ID"]);
      }
      table.close();
   };

   DB_SRCH search_start(dbx_recordset_object &recset, string var, string equals)
   {
      DB_SRCH db;
      equals+=" ";
      db.var = var;
      db.equals =equals;
      ifstream table(table_name.c_str());
      table >> fields;
      while(!table.eof())
      {
         table >> recset;
         if(recset.data[var]==equals)
         {
            db.last_found = dbx2i(recset.data["ID"]);
            table.close();
            return db;
         }
      }
      table.close();
      return db;
   };
   bool search_next(dbx_recordset_object &recset, DB_SRCH &db)
   {
      ifstream table(table_name.c_str());
      table >> fields;
      while(!table.eof())
      {
         table >> recset;
         if(recset.data[db.var] == db.equals && dbx2i(recset.data["ID"]) >  db.last_found)
         {
            db.last_found = dbx2i(recset.data["ID"]);
            table.close();
            return false;
         }
      }
      table.close();
      return true;
   };
};

class dbx_records_object
{
    dbx_recordset_object records;
public:
   virtual void connect(dbx_object &db, string s, string var, string find)
   {
      db.connect(records, s);
      db.search_start(records, var, find);
      db.disconnect();
   };
   virtual void disconnect(dbx_object &db, string s)
   {
      dbx_recordset_object tmp;
      db.connect(tmp, s);
      db.update_record(records);
   };
};

#endif



and here is an example on how to use it:

Code:

    dbx_object mydb;
        dbx_conversion_filters dbconv; //use to convert data to string/int etc.
   dbx_recordset_object recset; //recordset

   mydb.new_table("somenew.db"); //create a new table
   mydb.add_field("Hello", 'i');         //Add field Hello with value i
   mydb.add_field("Goodbye", 'f'); //Add field Goodbye with value F
   mydb.add_field("Name", 'p'); //Add field name with value p
   mydb.add_field("ID", 'i');     //ID field, value i.
//Like most databases, updated in real time without the need to save it.

   mydb.connect(recset, "somenew.db");  //reconnect with a recordset to read in the data.

//You can now, with a recordset, read in the data from a hash table, or write to it from a hash table.  Like ADO
   recset.data["Hello"]="HI!";
   recset.data["Goodbye"]="NO!";
   recset.data["Name"]="YOINK!";
// Insert a new record into the table.
   mydb.insert_record(recset);

   recset.data["Hello"]="MYHOUDE";
   recset.data["Goodbye"]="IMA MYHOUDE";
   recset.data["Name"]="monkeybut";
// Insert a new record into the table.
   mydb.insert_record(recset);

   recset.data["Hello"]="MYHOUDE";
   recset.data["Goodbye"]="IMA MYHOUDE";
   recset.data["Name"]="lonelyman";
   mydb.insert_record(recset);

//Move to the last record.
   mydb.move_last(recset);

//A databsse search object
   DB_SRCH srch;
//Search for a match in record field data.
   srch=mydb.search_start(recset, "Hello", "HI!");

//This outputs the results to the screen.
   cout << recset << endl;
   
//Keep on searching.
   while(!mydb.search_next(recset, srch))
   {
      cout << recset.data["Name"] << ":::" << "monkeybut" << endl;
      if(recset.data["Name"]=="monkeybut ")
      {
         mydb.delete_record(recset); //Deletes Monkeybut.
      }
   }
   mydb.disconnect(); //disconnect.




Hope that helps.
_________________
"Well, last time I flicked on a lighter, I'm pretty sure I didn't create a black hole."-
Xmark

http://pauljessup.com
Back to top  
Post new topic Reply to topic Page 1 of 2 All times are GMT
Goto page 1, 2  Next 



Display posts from previous:   
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum