What you’re asking for is essentially a domain-specific language—a small programming language for a narrow purpose, in this case defining P&P RPG rules. Designing a language is in principle not difficult, but there is a considerable amount of up-front knowledge that you must gain in order to be at all productive. Unfortunately, there is no central reference for this stuff—you’ve got to pick it up through trial, error, and lots of research.
First, find a set of primitive operations whereby other operations can be implemented. For example:
Get or set a property of the player, an NPC, or a monster
Get the result of a die roll
Evaluate arithmetic expressions
Evaluate conditional expressions
Perform conditional branching
Design a syntax that expresses your primitives. How will you represent numbers? What does a statement look like? Are statements semicolon-terminated? Newline-terminated? Is there block structure? How will you indicate it: through symbols or indentation? Are there variables? What constitutes a legal variable name? Are variables mutable? How will you access properties of objects? Are objects first-class? Can you create them yourself?
Write a parser that turns your program into an abstract syntax tree (AST). Learn about parsing statements with a recursive descent parser. Learn about how parsing arithmetic expressions with recursive descent is annoying, and a top-down operator precedence parser (Pratt parser) can make your life easier and your code shorter.
Write an interpreter that evaluates your AST. It can simply read each node in the tree and do what it says: a = b
becomes new Assignment("a", "b")
becomes vars["a"] = vars["b"];
. If it makes your life easier, convert the AST into a simpler form before evaluation.
I recommend designing the simplest thing that will work and remain readable. Here’s an example of what a language might look like. Your design will necessarily differ based on your specific needs and preferences.
ATK = D20
if ATK >= player.ATK
DEF = D20
if DEF < monster.DEF
monster.HP -= ATK
if monster.HP < 0
monster.ALIVE = 0
end
end
end
Alternatively, learn how to embed an existing scripting language such as Python or Lua into your application, and use that. The downside of using a general-purpose language for a domain-specific task is that the abstraction is leaky: all the features and gotchas of the language are still present. The upside is you don’t have to implement it yourself—and that is a significant upside. Consider it.
I'm for 2, but with slight modification. I wouldn't use enum
. You can just as fine use GetType()
. If you were to add enum
to the mix, it would duplicate the type information contained within the object.
On top of that, I would create an Visitor for the Choice
class. This will remove the need for reflection and give UI type-safe way to create textual representation for each choice. It will also ensure that when you add new choice type, the compiler will force you to implement all related code, like creating a textual representation for it. And I don't see how having many classes is bad. It is actually good thing, as long as those classes are cohesive.
And I don't think those objects will contain only data. They can very well be objects doing the actual action. Just put Execute()
method on them, and they can be the ones drawing the cards from decks or allocating points to buckets.
Best Answer
You just need a virtual member function in your superclass:
And implementations in your subclasses:
Now when you do your battles, you could do something like this (edited to put in class):
This will cause the appropriate member function defined in the subclass to be called on each of the combatant objects.
Edit:
To populate your combatant list and call the
doBattle()
function, you can do something like this:It is important to use pointers here. Only pointers and references can be used to provide polymorphism. You cannot have variables of type
Superclass
because it cannot be instantiated, you can only have references and pointers of typeSuperclass
. Smart pointers should probably be used but I thought I'd keep it simple.