Monday, December 28, 2009

How to Conquer the Complexity of Rules - Part 1

Programming Magic's tidal wave of rules is a huge challenge. I did my best with Forge version 1. I got the program "out the door" and working but it only implements a few of the rules. I'm slowly but thoroughly working on Forge version 2 and I'm trying to figure out how to program a few simple rules now and allow for more complex rules implementation later.

My idea is to put all of the rules into one large object which I call the RulesEngine. (An object is just code statements that are related.) You give all of the information to the RulesEngine and let it handle the rules internally. This way you are separating the rules from the rules implementation. In programming it is often useful to separate "tightly knit" systems like separating the user interface from the back-end business logic using the model, view, controller (MVC) pattern.

At the heart of Forge version 1 is the method nextPhase(), which advances the game to the next phase. (And although I know that there is technically a difference between phases and steps, I call everything a "phase".) I plan to use nextPhase() in Forge version 2 also. At first nextPhase() will just advance to the next phase but later on it could do anything number of things: empty the mana pool, move duplicate legendary and planeswalker cards to the graveyard, implement upkeep effects like Juzam Djinn, and check for other state effects.

The idea is to start with a "simple" implementation of Magic's rules and later on you can update your implementation as needed since all of the rules are in one place. Next time I examine more of the pros and cons of this idea.

14 comments:

  1. seriously, I don't think this is a good Idea. it's good to separate the rules from the rest, but having one single object handle all the rules is bad. you can separate one rule from another without merging the rules with the rest.

    this way, you can write code that concentrates on the layering system; then other code that concentrates on turns, phases, steps and priority; then some code for state-based effects; then something for triggered abilities...

    magic is too complex to put everything in one class; you're creating a lot of unmaintainable code this way

    ReplyDelete
  2. Ouch, that's going to be one hell of a spaghetti class

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. Rares isn't too far off... the rules start with the turn structure.

    Here's my view on how it should be implemented, based on concepts Forge is built on, however, it would not be practical to rewrite Forge to do it as such.

    As the "nextPhase()" method iterates through the turn structure, each phase and step has a set of rules to handle effects for the beginning, middle and end of that phase/step. These "rules" are implemented by a generic handler that should iterate through a list of Commands that have been registered to occur at this point.
    Some of these Commands are "built-in", for gameplay input, (like declaring attackers) while other Commands are added dynamically by card effects ("At the beginning of your upkeep, do ___".
    In between executing each of the commands, State Based Effects are executed, which is just another loop through a list of Commands registered by the rules code, or card code.

    Then finally, the phase/step is turned over to the player. At which point, everything the player does causes the State Based Effects rule processor to run. Switching priority is also handled in this manner.

    Another rule processor would be made available to handle lists of replacement effects ("when this card would be put in a graveyard, do ___ instead") or other triggered abilities. ("whenever a card goes to the graveyard, do ___")

    Handling card property layers, like type and power/toughness is part of the current Card object, and I think it is fine where it is. Though it can be enhanced to handle more complex layering.

    This approach allows code for many of the various game rules to be located in one file for editing purposes. However, the overall implementation is more generic, without hard-coded, card-specific rules or modifiers in the middle of it all.

    ReplyDelete
  5. This comment has been removed by the author.

    ReplyDelete
  6. I'm interested in seeing more of Rob Cashwalker's comments on this engine. Is there a blog I may read with more of his thoughts?

    ReplyDelete
  7. As Homer sadi "Your ideas are intriguing to me and I wish to subscribe to your newsletter."

    ReplyDelete
  8. I agree with Moxdev and Silly Freak, and, to some extent, with rob's suggestion here.

    Having all the rules in one file (or even worse, one class) is not going to work with MTG.

    That's the mistake you did in Forge1, that's the mistake I did in my first implementations of Wagic: one big class to rule them all.

    It makes the code unreadable and unmaintainable.

    you'd rather look for something "modular".

    For example you could consider rules as "plug ins" that talk to each other through a messaging system.

    Your big "rules class" should actually just be holding a list of rules currently running, and help them talk to each other.

    Consider manaburn:
    If manaburn is an independant rule that somehow "registers" in the system, removing it from the rules is just removing the line that says: "game.addRule(New ManaBurn())" or something.
    But if you have nextPhase contain code that does that, it's another story... you have to dig in the code to find where it gets activated...

    Here's something else: In MTG, theoretically a card can change any rule. So you have to make sure that any single "core" rule in the game is defined with the same level of flexibility as the code of your cards. Basically, the core rules and the rules described on cards should inherit from the same class.

    Another thing that you can take into consideration while working on the core: "If I was asked to change my game into a Pokemon or LOTR card game, how much work would that require?" I am convinced that the more flexible and abstract you go with the rules, the closer you get to have some kind of "universal" engine. I'm not saying it is something we want to reach, but since the rules of Magic always evolve, I'm thinking that an engine that could support flawlessly several CTCG would have no issue supporting any future change in the MTG cards.

    I'm far from that goal myself, but recently realized that the core rules are nothing more than rules you could see on a standard card, and moved some of the rules outside of the engine. I now have invisible cards in game that have abilities such as: at each draw step, you draw a card, OR when this card comes into play, shuffle your library, draw 7 cards, and your life becomes 20.
    The "invisible" cards come into play just before the game starts, of course.
    And since it reuses the code from the cards, you could imagine reuse the keywords introduced by Rob, and create alternate rules quite easily...

    CheckThis file

    ReplyDelete
  9. In the core, the game runs on the priority cycle:

    1. player input
    2. put triggered abilities on the stack
    3. handle state-based effects (repeat until nothing happens)
    4. goto 1

    If a player passes, next player in the turn structure gets priority. If all players pass in succession, top object on stack resolves, then triggered abilities and state-based effects are handled again. If all players pass in succession AND stack is empty, you advance the game to next step or phase (and, of course, check triggered abilities and state-based effects).

    This (plus a handful of special actions like selection of attackers and blockers) is the core of Magic. This says, that at the very minimum you need method for passing basic player actions (casting a spell, activating an ability, playing a land, passing) in a way the engine understands. You need a system to handle triggered abilities (ability can trigger at any time, even in the middle of announcement or resolution of another ability, but everything that triggers is collected and put on stack at once after the announcement/resolution is complete). You need a "sweeper" that will apply state-based effects. And you need a turn structure system that is flexible enough to implement:

    Time Stretch
    Fatespinner
    Paradox Haze
    Savage Beating
    Savor the Moment
    Final Fortune

    Another important part is the last known information rule. This rule states that if something wants to know a characteristic of an object that no longer exists, characteristic from last time it existed is used.

    At the very least, this means that it's not correct to simply give every card an unique ID. Every card, IN EVERY ZONE must have an unique identifier, and if the same physical card leaves a zone and comes back, it should have different ID now. For most practical purposes, the card ID is enough to identify them, but internally, it should be more precise. Also, the LKI rule could be implemented in two ways: either by making dead objects into "ghosts" that persist until nothing can refer to them anymore (awkward with things like Ertai's Meddling or epic spells), or by having a robust history system that allows the rules engine to look back in any moment of the game and check the characteristic of any object at any point of time. This might be a better approach, given a number of cards in Magic that care about events that happened in the past (Storm spells, Khabal Ghoul, Sengir Vampire etc.).

    ReplyDelete
  10. Sorry Ben, I don't "blog". I participate in a community discussion about the further development of Forge.

    ReplyDelete
  11. If I could give one word of advice (other than the no to one single rules class) it would be to implement the "vanilla" Magic rules, and then make just about anything that might want to be changed by the card rules be hook-able.

    Doing that gave me a good foundation and I was able to implement everything *except* for handling choosing the order of multiple replacement effects (which I am working on). I have maybe about 15 or so classes that handle the core engine (not counting undo etc).

    As wololo mentioned, modular is the way to go. There is a mechanic called Venom in my game (similar to Poison) and it is implemented as a standalone class that just sorta injects itself at runtime if there are any pieces that have that ability. This keeps the core engine pretty tight and I can add stuff as I need to without changing core stuff.

    ~telengard

    ReplyDelete
  12. \0/ wow, so many briliant ideas here;)
    my two cents about the flexibility of plug-in structure: not long ago I was playing with it and created test program that has nothing in itself except working with plugged sub-systems. It had Fifefox like interface that could update plugins and download new ones.

    Just imagine the moment when user runs a MTG client (e.g. Forge), then presses "search for updates" and gets notification "Install Exalted plugin" or "Update Combat plugin". And after that he can use exalted keyword or new combat system.

    ReplyDelete
  13. I knew that the "one rules object" would get from flak but that is ok. In Forge version 1 the rules are scattered everywhere and it is hard to change anything. Also Forge 1.0 it only has about 4 events.

    For Forge 2.0 I want to have a ton of events.

    And for putting the rules into one object, basically the big "rules engine" object will be composed of other objects that do the real work. The "rules engine" object is just a big "bag" that passes the info onto other objects.

    I view the big rules object like the monolithic Linux core. It isn't modular and although modular looks better on paper but it doesn't really work.

    Time will tell how well this idea works.

    ReplyDelete
  14. First, i wanna say that this forum is both enlightning and understandable. That´s a rare thing.

    I'm not a programmer, but a Magic Player/Amateur Game designer who's looking for insight on making a computer version of my game.

    I recently had an idea of how rules should be managed. I'd make layers so Card rules and "floor rules" are the same, for starters. The communication, for me, should be like:

    Event --> Rule --> PhysAction.

    So obviously Event refers to playing a card, or triggering something. Then, you'd go to the rules world, wich may or not be a black box that reckons a state of play, process rules (more than one, sequential or parallel by some criteria) and then make a resolution (a change to another state), wich obviously triggers all the PhysAction required to get there.

    Physaction would be very simple things, like moving cards, tapping cards, etc...

    That way you can actually reflect (on code) a playground and not a strange rulesengine that does thing a different way from reality, and comes with more than a few inevitable bugs.

    Am i crazy? Is this impossible? Or that's mainly what you were doing up till now in better, more detailed ways?

    I am learning a LOT from this site, keep up the good work.

    ReplyDelete

Note: Only a member of this blog may post a comment.