Explanation
Let’s say we have multiple quests that rewards a player upon completing something. The below examples show the user quests:
Name: Get a high score of 100
Requirement: Score of 100
Reward: 10 Coins
Name: Get a high score of 5000
Requirement: Score of 5000
Reward: 10 Diamonds
Name: Destroy 20 ships
Requirement: Destroy 20 ships
Reward: 5 Diamonds
Name: Reach level 5
Requirement: Level 5
Reward: 20 Coins, 10 Diamonds
Then I create the following logic:
Create a superclass named “Quest”. Create a subclass for each quest; inheriting the “Quest” superclass. Using polymorphism, we call the “CheckForCompletion” method to check if the quest requirement has been met. If so, the user get rewarded.
Question
My question is do I create a database table named "Quests" and store each of the above quests in the table?
Quests table data example: Name: Destroy 20 ships Requirement: {“Destroy”, “20”} Reward: {“Diamonds”, “5”} If so, what is the best way to load each quest into their appropriate class?
OR
Do I create a class for each quest without creating a quest table? If so, how will I know when the user has completed the quest so that the user is not awarded the reward twice?
I know I can create a single table and store the necessary requirements. Then have a class with a bunch of if statements to execute the right code, but that does not seem right to me. What I am asking is; is there a better way to do it?
Thanks
Edit 1:
Each quest can have multiple requirements. For example: Destroy 5 Ships, Kill 10 units, Score of 1000 etc.
To explain further want I want is to eliminate if statements. I don't want to do if statements like these;
if (score == 100)
//process
else if (ships_destroyed == 5)
//process
else if (level == 5)
//process
else if (ships_destroyed == 5 && units_killed == 10 && score == 1000)
//process
I wish to eliminate this process as I could have hundreds of quests and I don't see it expanding easily enough. So is there a better way?
I had the similar requirements: adding achievements/badges in the game.
You should think about:
Preconditions: if precondition is simple, like 'destroy 5 ships' that it is easy to store it to db. If it is complex, like 'destroy 5 ships and 10 units', it is not flat structure of preconditions.
So, i can suggest you
How to checking each condition is also problem. Specially, if you are going to have a lot of them. One of the possible solutions, add some triggers definitions. Like 'user just destroyed ship', 'user just win battle'. And define for each quest which type of trigger can trigger it. That info also can be stored in separate table
UPD: to check each precondition there should be one java class per each type.
interface Precondition {
String type()
boolean check(Map<String, Object> config, Long userId);
}
class DestroyShipPrecondition{
public String type() {
return "destroy_ship"
}
public boolean check(Map<String, Object> config, Long userId){
Long expectdAmount = config.get("amount");
Long realAmount = getDestroyedShipsByUser(userId);
return expectedAmount<=realAmount;
}
}
Some your service should have the Map of all preconditions by their type. So, when you need to check quest, you load its preconditions and then:
public boolean checkQuest(Quest quest, Long userId){
for (PreconditionConfig preconditionConfig : quest.getPrecondidtionConfigs() ){
Precondition precondition = precondititons.get(preconditionConfig.type);
if (!precondition.check(preconditionConfig.getContext(), userId)){
return false;
}
}
return true;
}
Your question has a couple of related aspects - and as usual, the answer is "it depends".
Firstly, I'd recommend not subclassing your "Quest" class - this is not black and white, of course, but in general, I favour composition over inheritance
If you're confident that the game mechanics won't change, I'd not bother with a database table - I would use constants in your programming language (Java or PHP). Looking up static data in the database is expensive from a development and performance point of view.
If you think this data may need to change as you're tuning the game, that might be too limiting - doing a full build every time you want to tweak the rewards for a quest will make it hard to get the balance right. In this case, I'd use a configuration file - the data does change, but only when you start the game, not while it's running.
If your game mechanics require the rewards to change during the game, and it's a multi-player game, then you may want to use a database.
Finally, most game engines include a scripting language for this kind of requirement - maybe overkill for what you're planning, but worth considering.
TL;DR store your logic as lambdas in enums and use your game state as an input.
A really blunt solution would be to use enums... Use an enum and load it up with lambda's as outlined in the code sample below.
For sake of density I put it all in one class, but you can probably see where I'm going with it...
Later on you can isolate triggers and actions into a separate enum, add an identifier to the enum, and then use those to link them together in a database. Add parameter strings to define the exact score required and you've probably got all the flexibility you need..
I chose to let the conditions be checked every single gameloop. But it's more efficient to attach the check to certain changes in your game state for example at the end of a setPlayerScore method.
public class Game {
public static class GameState {
public int playerscore = 1750;
}
public enum Quest {
FIVE_HUNDRED_PT(
(GameState gamestate) -> gamestate.playerscore > 500,
() -> Game.trigger500pointHurray()
),
FIVE_THOUSAND_PT(
(GameState gamestate) -> gamestate.playerscore > 5000,
() -> Game.trigger5000pointHurray()
);
private final Function<GameState, Boolean> trigger;
private final Runnable action;
Quest(Function<GameState, Boolean> trigger, Runnable action) {
this.trigger = trigger;
this.action = action;
}
}
public static void gameloop(GameState state) {
// Do all game logic.
for (Quest q : Quest.values())
if (q.trigger.apply(state))
q.action.run();
}
public static void trigger500pointHurray() {
System.out.println("Hurray x500");
}
public static void trigger5000pointHurray() {
System.out.println("Hurray x5000");
}
public static void main(String[] args) {
gameloop(new GameState());
}
}