So I've been thinking about the best way to structure this skill given that it needs a massive refactor anyway to catch up with the last year's worth of development in opsdroid. Here are my current thoughts on how the code should be divided up, as a starting point for further discussion.
Examples use D&D 5e as a starting point but the intention is for this structure to work for any games we want to implement.
Player interaction layer
Skill
This would be the skill through which the players actually play the game. It will receive instructions from the chat service (so these will be almost if not entirely Messages) and convert these into more abstract game-based Events. This layer should be as rules-agnostic as possible, so these events would be high-level generic concepts like Attack, Move, Hide or whatever.
Example:
class PlayerInteraction(Skill):
@match_regex("I hit it with my axe")
def make_attack(self, msg):
opsdroid.send(AttackEvent(attacker=msg.sender, defender="it", weapon=msg.sender.weapons["axe"]))
General Rules layer
Skill or Connector (Obviously as a connector it would be pretty abstract but skill also seems a little weird considering the user wouldn't ever interact with it directly.)
An object to define game logic and store relevant game state. This object would construct the game rules by chaining together events appropriately. Relevant output would be reported back to the player as Messages but otherwise the user shouldn't interact with this layer. Again the rules here will be high-level, with the actual crunch of resolving those rules being dealt with elsewhere (see below).
Example:
class GameRules(Skill):
@match_event(AttackEvent)
def attack_made(self, attack):
hit = attack.attacker.make_attack(target=attack.defender)
if hit:
opsdroid.send(DamageEvent(victim=attack.defender))
else:
opsdroid.send(Message("You missed!")
Character Rules layer
Non-opsdroid class
A class to handle the actual number-crunchy resolution of game actions. This would be called by the GameRules class when necessary and return sensible outputs from resolving actions, eg if an attack hits or misses. Since most of the rules in many RPGs centre around things the characters can do, this would likely be the only such class needed, but if the game calls for it then you could make additional classes for different kinds of crunching (e.g. a Ship class for Star Trek Adventures). Subclasses would allow for different types of character (e.g. Fighter and Wizard would both inherit basic rules like attacking from Character but would have their own additional rules).
Example:
class Character():
def make_attack(self, target):
if dice_roll("d20") >= target.ac:
return True
An important consideration in making the separation like this is that many RPGs have a few high rules with many exceptions - D&D is a good example, where there is one check resolution method that's used for everything with different numbers, but character abilities can tweak the odds or add opportunities for actions during the check resolution, and so on. The intention, then, is to separate these broad rules from character-specific rules and abilities.
So I've been thinking about the best way to structure this skill given that it needs a massive refactor anyway to catch up with the last year's worth of development in opsdroid. Here are my current thoughts on how the code should be divided up, as a starting point for further discussion.
Examples use D&D 5e as a starting point but the intention is for this structure to work for any games we want to implement.
Player interaction layer
Skill
This would be the skill through which the players actually play the game. It will receive instructions from the chat service (so these will be almost if not entirely
Message
s) and convert these into more abstract game-basedEvents
. This layer should be as rules-agnostic as possible, so these events would be high-level generic concepts likeAttack
,Move
,Hide
or whatever.Example:
General Rules layer
Skill or Connector (Obviously as a connector it would be pretty abstract but skill also seems a little weird considering the user wouldn't ever interact with it directly.)
An object to define game logic and store relevant game state. This object would construct the game rules by chaining together events appropriately. Relevant output would be reported back to the player as
Message
s but otherwise the user shouldn't interact with this layer. Again the rules here will be high-level, with the actual crunch of resolving those rules being dealt with elsewhere (see below).Example:
Character Rules layer
Non-opsdroid class
A class to handle the actual number-crunchy resolution of game actions. This would be called by the GameRules class when necessary and return sensible outputs from resolving actions, eg if an attack hits or misses. Since most of the rules in many RPGs centre around things the characters can do, this would likely be the only such class needed, but if the game calls for it then you could make additional classes for different kinds of crunching (e.g. a
Ship
class for Star Trek Adventures). Subclasses would allow for different types of character (e.g.Fighter
andWizard
would both inherit basic rules like attacking fromCharacter
but would have their own additional rules).Example:
An important consideration in making the separation like this is that many RPGs have a few high rules with many exceptions - D&D is a good example, where there is one check resolution method that's used for everything with different numbers, but character abilities can tweak the odds or add opportunities for actions during the check resolution, and so on. The intention, then, is to separate these broad rules from character-specific rules and abilities.