I am intending this for both programmers and non-programmers, so some of this will be review. Program architecture describes how you divide things up into classes, like a paragraph in a novel, and how all the classes (paragraphs) work together. The architecture was very homegrown; I basically programmed it and saw if it works. Finally the architecture was stable enough to implement Magic cards. I programmed using Java 1.2 with no packages.
The goal of any programmer is to program real-world objects. If I was building a program that simulated a car, I would program a tire class. A non-programmer would understand most of the tire class code, because it relates to a real-world object. Every program includes many classes that don’t relate to real-world objects, but are required in order to make the program work.
So MTG Forge has a Card class as well as a SpellAbility class, that implements the functionality of all spells and abilities. A Card class has at least 1 SpellAbility class, because every card is at least a spell, lands are the exception I guess. Lands are just put into play and not actually played. If a card like Elvish Piper has an activated ability, the associated Card has a SpellAbility added that does the Piper’s ability.
Every SpellAbility has an abstract resolve() method that every Magic card and ability overrides. The SpellAbility that implements the Elvish Piper ability lets you put a creature card from your hand into play, all that code goes into resolve() method. All of the zones like cards in play, graveyard, hand, are global variables, so the resolve() method has access to them. Even though MTG Forge only has about 118 classes, it has at least 400+ anonymous (unnamed) classes. Every card and ability is implemented using an anonymous class. I just as easily could have used a regular, named class for each card, and I plan to do that in version 2.
Global variables are generally bad, but they are a trade-off. You can potentially have huge problems because you cannot track down that specific line of code that changes a global variable. Non-global variables have less code that modifies them, so if you have a problem you don’t have to look through your whole program.