Design Feedback
Posted: 09 Jun 2009, 21:01
So as I mentioned in my introductory post, I've been going for a while on a Magic engine of my own, and I thought I'd ask you more seasoned Magic programmers for some feedback on the design of the program.
The way it is structured is that there is a Game singleton, that holds everything. It has a list of Player objects, a Stack object, a Layers object and a phase counter.Each Player object has a Deck,Hand,Table,Graveyard and Removed object representing the zones. All of those contain card lists, which all inherits at some level from the CardBase class.
Almost all the rule-enforcement is done by the cards themselves, when inheriting from CardBase, they must override the methods FillOutInfo(),SubscribeToEvents() and CanBePlayed()(Not *must* for SubscribeToEvents(),unfortunately, but they really should)
In FillOutInfo(), the card fills out data about itself, such as Name,Color,Cost,Types.
In SubscribeToEvents(), the card attaches itself as listener to whatever game events it wants to know about from the Game object, which currently has feeds for CardPlayed,CardTapped,CardUnTapped,CardSentToGraveyard(Same as discarded,right?),CardDrawn,PhaseChanged,CardActivated(Think "Clicked on" in say Shandalar). The card provides it's own handler methods for the events, in which it itself has to add an Action to the stack or insert itself into the Layers object for effect processing.
In CanBePlayed(), of course, it determines if it can be played and returns that. It itself has to determine if the player has enough mana for it here (which also led to the unsightly "feature" that you have to tap for enough mana before playing any card..)
Other than those methods, cards have access to the full capabilities of C#, of course.
What remains is creature combat,actually responding to some keywords,finishing the stack and layers and fixing the user interface so that I can actually test what I have in practice >_>
As an example here's the code for Mobile Fort.
The way it is structured is that there is a Game singleton, that holds everything. It has a list of Player objects, a Stack object, a Layers object and a phase counter.Each Player object has a Deck,Hand,Table,Graveyard and Removed object representing the zones. All of those contain card lists, which all inherits at some level from the CardBase class.
Almost all the rule-enforcement is done by the cards themselves, when inheriting from CardBase, they must override the methods FillOutInfo(),SubscribeToEvents() and CanBePlayed()(Not *must* for SubscribeToEvents(),unfortunately, but they really should)
In FillOutInfo(), the card fills out data about itself, such as Name,Color,Cost,Types.
In SubscribeToEvents(), the card attaches itself as listener to whatever game events it wants to know about from the Game object, which currently has feeds for CardPlayed,CardTapped,CardUnTapped,CardSentToGraveyard(Same as discarded,right?),CardDrawn,PhaseChanged,CardActivated(Think "Clicked on" in say Shandalar). The card provides it's own handler methods for the events, in which it itself has to add an Action to the stack or insert itself into the Layers object for effect processing.
In CanBePlayed(), of course, it determines if it can be played and returns that. It itself has to determine if the player has enough mana for it here (which also led to the unsightly "feature" that you have to tap for enough mana before playing any card..)
Other than those methods, cards have access to the full capabilities of C#, of course.
What remains is creature combat,actually responding to some keywords,finishing the stack and layers and fixing the user interface so that I can actually test what I have in practice >_>
As an example here's the code for Mobile Fort.
- Code: Select all
class Mobile_Fort : CardBase
{
private bool HasUsedAbilityThisTurn;
protected override void FillOutInfo()
{
Name = "Mobile Fort";
Color = CardColor.Artifact;
Cost = new ManaPool(0, 0, 0, 0, 0, 4);
Types = "Artifact Creature";
CurPower = OrigPower = 0;
OrigPower = OrigToughness = 6;
HasUsedAbilityThisTurn = false;
Instructions = "Defender (This creature can't attack.)\n3: Mobile Fort gets +3/-1 until end of turn and can attack this turn as though it didn't have defender. Play this ability only once each turn.";
ShouldUntapAutomatically = true;
Keywords.Add("Defender");
}
protected override void SubscribeToEvents()
{
base.SubscribeToEvents();
Game.Instance.CardActivated += new CardActivatedEventHandler(CardActivated);
Game.Instance.PhaseChanged += new PhaseChangedEventHandler(PhaseChanged);
}
void PhaseChanged(object sender, global::MTGClientSDL.EventArgClasses.PhaseChangedEventHandler e)
{
//Remove the powerup effect when we reach end of turn
if (e.NewPhase == "EndTurn")
{
if (HasUsedAbilityThisTurn)
{
Keywords.Add("Defender");
HasUsedAbilityThisTurn = false;
CurPower -= 3;
CurToughness += 1;
}
}
}
void CardActivated(object sender, global::MTGClientSDL.EventArgClasses.CardEventArgs e)
{
//We don't care yet.
if (e.Card != this || Location != CardLocation.Table)
{
return;
}
//Controller can't afford ability
if (!Controller.MyMana.CanPay(new ManaPool(0, 0, 0, 0, 0, 3)))
{
Interface.Instance.Alert("Not enough mana. Tap 3 first!");
return;
}
//Controller has already done this once this turn
if (HasUsedAbilityThisTurn)
{
Interface.Instance.Alert("You have already activated this ability this turn.");
return;
}
//Do ability
Controller.MyMana.Pay(Cost);
//An Action is just a struct bundling some info together; Description, Function, SourceCard, Type.
Game.Instance.TheStack.Actions.Push(new Action("Activate Mobile Fort.", new VoidNoParameters(MyStackAction), this, ActionType.Activate));
HasUsedAbilityThisTurn = true;
}
public void MyStackAction()
{
//This method will be called by the Stack object in order with other effects and reactions
CurPower += 3;
CurToughness -= 1;
Keywords.Remove("Defender");
}
public override bool CanBePlayed()
{
return (Owner.MyMana.CanPay(Cost) && (Game.Instance.GetPhase() == "Main1" || Game.Instance.GetPhase() == "Main2"));
}
}