5
\$\begingroup\$

I am currently creating a browser based game, and was wondering what was the standard approach in making diverse conditions and status bonuses database wise. Currently considering two cases.

  • Event Conditions
    1. Needs min 1000 gold
    2. Needs min Lv 10
    3. Needs certain item.
    4. Needs fulfillment of another event
  • Status Bonus
    1. Reduces damage by 20%
    2. +100 attack points
    3. Deflects certain type of attack

I wish to be able to continually change these parameters during the process of production and operation, so having them hard-coded isn't the best way.

All I could come up with are the following two methods:

  1. Create a table that contains each conditions with needed attributes

    Have a model named conditions with all the attributes it would need to set them:

    conditions
      condition_type (level, money_min, money_max item, event_aquired)
      condition_amount
      prerequisite_condition_id
      prerequisite_item_id
    
  2. Write it in a DSL form that could be interpreted later in the code

    Perhaps something like yaml, have a text area in the setting form and have the code interpret it.

    condition_foo:
      condition_type :level
        min_level: 10
      condition_type :item
        item_id: 2
    

Method 2 looks to be more practical and flexible for future changes, trade off being that all the flex must be done on the code side.

Not to sure how this is supposed to be done, is it supposed to be hard coded? separate config file? Any help would be appreciated.

This will be implemented with Ruby on Rails.

\$\endgroup\$
1
\$\begingroup\$

You won't be able to store the conditions (logic-wise) in the database (unless you decide to store scripting code directly in the database). Instead you could establish a construction where each condition and bonus is associated with a unique key. You can then in turn store these keys in the database.

For example to describe the event conditions required to unlock status bonus 1 you would have tables resembling the following:

   [StatusBonusTable]
 StatusID StatusBonusKey ParameterN
   1     "reduceDamage"      20       

[Event Conditions Table]
 StatusBonusId EventConditionKey ParameterN
     1           "goldRequired"      1000
     1           "levelRequired"      10

I would then suggest using a factory to load this data from the database and creating instances of the event condition classes passing that parameters stored in the database and likewise with the status bonuses:

if (eventConditionKey == "goldRequired")
 goldRequired = parameter[0]
 return new GoldRequiredCondition(goldRequired);

The advantages of using this approach is that it will not require changes to the database when you need new conditions and status bonuses. When you want a new condition or bonus the only place you will need to make changes is in your code. The disadvantage is the lack of automatic verification of the data you insert into the database. You will be able to insert keys that have no match in code, and set it's parameters to values that make no sense.

\$\endgroup\$
1
\$\begingroup\$

I think this is one case that would benefit from a document-database solution (i.e a "NoSql" database).

If you're set on using a SQL database then @Qua's answer is probably best.

The 2nd method you mentioned would fit perfectly with the document model. I'm currently looking at implementing something similar with MongoDb, which from what I've seen essentially stores and allows easy querying of JSON objects. See this answer for some more info.

Also keep in mind, that you can have more than one type of database. I'm using SQL for stuff that fits nicely into a relational model and Mongo for stuff that doesn't.

\$\endgroup\$
  • \$\begingroup\$ Thanks for the info, I've tried stuffing objects into mysql once on rails, it worked but wasn't the prettiest of sights, having a clean way would come in handy. \$\endgroup\$ – Saifis Mar 5 '11 at 10:09
1
\$\begingroup\$

Method 2 is not bad, it is versatile, but hard to manage the logic and more importantly, not platform independent unless you use something like XML or JSON. The data still stays application dependent - that might be a problem when you want to create another (non-web based) interface for your game.

Method 1 would work for an SQL database, but if you want to keep it fast when your app is under a heavy load, you might want to think again about having to join tables or do 3 consecutive queries for a single item.

If you knew that you were not going to have more than 64 statuses and 64 conditions and not have any single item have more than x number of statuses and y number of conditions, then you could solve this with the bitwise comparison of two 64 bit integers and x + y numeric fields in the same record where values would be given in the order appearance as a set bit.

It might not be the most beautiful solution and somewhat limited, but it's very fast if you expect the db to be hit hard. I used to operate a web based browser game with a 50k+ user base, and it taught me the usefulness of this approach.

\$\endgroup\$
  • \$\begingroup\$ Bit comparison sounds like a good idea, the limitations might not always be a bad thing either, it would be a nice limit to keep the game system form becoming overly complex. \$\endgroup\$ – Saifis Mar 5 '11 at 10:06
  • \$\begingroup\$ Perhaps it would be even better if the values would have key pairs that indicate which bit they offer a value for. That way you could filter for condition and status values as well with a single query. Good luck! \$\endgroup\$ – Konrad K. Mar 5 '11 at 10:43
1
\$\begingroup\$

I'd prefer Method 2. It's flexible and can be implemented fairly easy.

The idea is to split the complex condition into a tree of simple ones. Each node of the tree is a comparison function and the leaves are either references to the user data or threshold values.

So your "Need min 100 gold" are going to look like

  greater
  +-gold
  +-999

or in JSON

"greater":{"attribute":"gold", "value":999}

but, using keys as function references have it's downsides: for example, you can't make an array of conditions. Therefore it would be better to make a condition object, like this:

{
  "type": "greater",
  "arguments": {
    "attr":"gold"
    "value":999
  }
}

using structures like this, you can build complex conditions just by introducing functions "every" and "some", which will return true when every or some conditions are true accordingly (and that is where we need an array of conditions).

So your initial condition may be rewritten as

const enough_gold = {
  "type": "greater",
  "args": {"attr": "gold","value": 999}
}
const lvl_ten_plus = {
  "type": "greater",
  "args": {"attr": "level","value": 9}
}
const have_item = {
  "type": "have",
  "args": {"attr": "item","value": "item_name"}
}
const compl_event = {
  "type": "have",
  "args": {"attr": "event","value": "event_name"}
}

const new_event = {
  "type": "all",
  "args": [
    enough_gold,
    lvl_ten_plus,
    have_item,
    compl_event
  ]
}

the data for testing this condition may look like

{
  "gold": 8350,
  "level": 11,
  "item": ["item1", "item3"],
  "event": ["some_event", "another_event"]
}

I switched to js to make it more readable and to show how you can nest conditions. I also introduced "have" function - this will iterate an array-attribute and return true if it includes provided value.

You can store those condition sets in file or database. In the latter case, it would be better to store each condition as a separate record and reference from complex ones to the simple ones. This will allow you to reuse some conditions and make balancing and patching easier.

I have no experience with MongoDB, but from what I read it will be perfect for this job.

Oh, and once you've extracted condition tree, you will know exactly which player attributes you'll need for the test, so you won't have to extract everything. Just in case.

Not that long ago I wrote a small condition-solver lib just for fun and maybe cases like this. It allows for creating nested conditions and testing them against some data set. Solver class is about 50 lines long (indents, comments and debug method included) and pretty simple, so I guess, there would be no problems porting it to ruby or PHP or any other language as long as there are hash tables and function pointers. The idea is simple as well: it will recursively call specified function for the specified arguments. And it's realy simple to use, here's an example

// Creating a solver with some initial data
const sol = new Solver({
  money: 30,
  fame: 25
})

// Creating some conditions
// have more than 50 money
const rich = cnd(
  'gt',
  arg(
    'money',
    50
  )
)

// have more than 15 fame
const good = cnd(
  'gt',
  arg(
    'fame',
    15
  )
)

// have at least something
const well = cnd(
  'any',
  [
    rich,
    good
  ]
)

// let's try it
sol.is(rich) // false: "money" is not > 50 (30)
sol.is(good) // true: "fame" is > 15 (25)
sol.is(well) // true: he's not rich, but he's good at least

Here's the source code link if it's relevant.

As for status bonuses, try looking at this answer it shows how you can implement them with MongoDB (and likes), and this one uses more traditional RMDBS approach.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.