It is currently 24 Apr 2024, 08:45
   
Text Size

PT counters

Moderators: timmermac, Blacksmith, KrazyTheFox, Agetian, friarsol, CCGHQ Admins

PT counters

Postby DennisBergkamp » 22 Jan 2009, 04:36

I was thinking of implementing some cards with +1/+1, -1/-1 and 0/-1 counters. But, how to do this the best way? Cards like Korlash, Heir to Blackblade and Nightmare make things more difficult than just using the setAttack/getAttack and setDefense/getDefense methods.

Any ideas?
User avatar
DennisBergkamp
AI Programmer
 
Posts: 2602
Joined: 09 Sep 2008, 15:46
Has thanked: 0 time
Been thanked: 0 time

Re: PT counters

Postby Rob Cashwalker » 22 Jan 2009, 05:14

As I suggested in the original counters thread, P/T counters opens a huge can of worms.

It starts with needing to modify the whole definition of getAttack and getDefense to take the counters into account, which is the easy part. The hard part is that every instance in the code where get/setAttack and get/setDefense is used, needs to be modified as well. New methods and card object properties would need to be implemented.

If a card needs the getAttack value in order to calculate a setAttack, then it should be seeking a baseAttack value, and setting the base value to the new value. ie: Giant Growth.

If a card needs the getAttack value to calculate some other effect, then it needs to query the overall getAttack, including the P/T counters. ie: a "fling" effect - sac a creature to deal damage equal to its power.... Combat just cares what the current total is.

Then of course you need to add a state-based effect to iterate through each card's counters list to annihilate the +1/+1 and -1/-1 counters.

At this rate, we might as well modify the concept of pump spells a little as well - instead of modifying the base values, the base value should be the printed value, never modified by external code. (unless an effect says that they change permanently, or at least until EOT, like snakeform) Then a new list should be implemented in the card object to track pumps - continuous ones like glorious anthem, or temporary ones like Giant Growth. So instead of setAttack(+1) on resolve and setAttack(-1) on EOT, a pump is added to the array on resolve and removed at EOT. In the case of glorious anthem, the state-based effect it constantly loops through can be removed - the pump gets applied to a creature once, then iterate through all cards to remove the pump when glorious anthem is destroyed; not forgetting that it would need a single new piece of code to respond to new creatures coming into play, their pump array would need to be updated just the once.

Then we're still only a few steps away from implementing the entire layer model of P/T calculation.

On the other hand.....

In the end, if you think about it, changing the attack and defense is just as permanent as a counter is... Same with that P/T boosting enchantment we have - it's implemented as a sorcery that modifies the P/T. It could have been modeled after glorious anthem, but instead of iterating through every card, it just constantly modifies the enchanted creature. For all intents and purposes the boosts from counters or enchantments stay with the creature until it dies, as long as MTGForge doesn't have counter removal spells, or destroy enchantments....
The Force will be with you, Always.
User avatar
Rob Cashwalker
Programmer
 
Posts: 2167
Joined: 09 Sep 2008, 15:09
Location: New York
Has thanked: 5 times
Been thanked: 40 times

Re: PT counters

Postby Incantus » 22 Jan 2009, 14:03

Rob Cashwalker wrote:At this rate, we might as well modify the concept of pump spells a little as well - instead of modifying the base values, the base value should be the printed value, never modified by external code. (unless an effect says that they change permanently, or at least until EOT, like snakeform) Then a new list should be implemented in the card object to track pumps - continuous ones like glorious anthem, or temporary ones like Giant Growth. So instead of setAttack(+1) on resolve and setAttack(-1) on EOT, a pump is added to the array on resolve and removed at EOT. In the case of glorious anthem, the state-based effect it constantly loops through can be removed - the pump gets applied to a creature once, then iterate through all cards to remove the pump when glorious anthem is destroyed; not forgetting that it would need a single new piece of code to respond to new creatures coming into play, their pump array would need to be updated just the once.
This is exactly what Incantus does, although it extends the same idea to all the characteristics (name, color, cost, supertypes, types, subtypes, power, toughness and loyalty). Then in the get* function, it calculates the current value using the layers algorithm.

Rob Cashwalker wrote:In the end, if you think about it, changing the attack and defense is just as permanent as a counter is... Same with that P/T boosting enchantment we have - it's implemented as a sorcery that modifies the P/T. It could have been modeled after glorious anthem, but instead of iterating through every card, it just constantly modifies the enchanted creature. For all intents and purposes the boosts from counters or enchantments stay with the creature until it dies, as long as MTGForge doesn't have counter removal spells, or destroy enchantments....
You don't want to go down this route. Otherwise you'll find yourself adding more and more hacks as you start implementing more interesting cards (like Allay and Arcbound Fiend). Do the painful thing now and implement the idea you describe above.
Incantus
DEVELOPER
 
Posts: 267
Joined: 29 May 2008, 15:53
Has thanked: 0 time
Been thanked: 3 times

Re: PT counters

Postby DennisBergkamp » 22 Jan 2009, 16:53

Guys,

Thanks for the responses. I was thinking something similar, to keep track the base attack and defense values in the card object, then to have a separate method return the "actual" or "net" value. I realize now though, that cards like Glorious Anthem will need a big big rewrite :(

Looks like this is more of a long-term project, once I have more time I may attempt this.
User avatar
DennisBergkamp
AI Programmer
 
Posts: 2602
Joined: 09 Sep 2008, 15:46
Has thanked: 0 time
Been thanked: 0 time

Re: PT counters

Postby Rob Cashwalker » 22 Jan 2009, 18:31

DennisBergkamp wrote:Guys,

Thanks for the responses. I was thinking something similar, to keep track the base attack and defense values in the card object, then to have a separate method return the "actual" or "net" value. I realize now though, that cards like Glorious Anthem will need a big big rewrite :(

Looks like this is more of a long-term project, once I have more time I may attempt this.
It's cool that your an active coder, always working on adding the splashy cards.... I'm a keyworder... I like to program repeatable effects, though I haven't been very active on it, trying to work on the Cardset editor.....

But what we need for this part of the project is to stop adding stuff, and instead focus on revising the structure... not as fun as making new cards, but think of how much better the system will be and how many new cards the revision will support!
The Force will be with you, Always.
User avatar
Rob Cashwalker
Programmer
 
Posts: 2167
Joined: 09 Sep 2008, 15:09
Location: New York
Has thanked: 5 times
Been thanked: 40 times

Re: PT counters

Postby DennisBergkamp » 22 Jan 2009, 19:49

It's cool that your an active coder, always working on adding the splashy cards.... I'm a keyworder... I like to program repeatable effects, though I haven't been very active on it, trying to work on the Cardset editor.....

But what we need for this part of the project is to stop adding stuff, and instead focus on revising the structure... not as fun as making new cards, but think of how much better the system will be and how many new cards the revision will support!
Yes, you're probably right, even though this is quite a tricky task. I'm not much of an architect, maybe someone (you, jpb, Orpheu or forge?) could lay out some kind of design/plan of how to do such a revision exactly? And what parts of the current structure should be revised? Probably many, many parts. That way we could discuss and (hopefully) agree on a good design.

I suppose a lot of us were counting on V2 to fix issues like these, but then again V2 might never come out.
User avatar
DennisBergkamp
AI Programmer
 
Posts: 2602
Joined: 09 Sep 2008, 15:46
Has thanked: 0 time
Been thanked: 0 time

Re: PT counters

Postby Incantus » 22 Jan 2009, 22:16

DennisBergkamp wrote:Yes, you're probably right, even though this is quite a tricky task. I'm not much of an architect, maybe someone (you, jpb, Orpheu or forge?) could lay out some kind of design/plan of how to do such a revision exactly? And what parts of the current structure should be revised? Probably many, many parts. That way we could discuss and (hopefully) agree on a good design.

I suppose a lot of us were counting on V2 to fix issues like these, but then again V2 might never come out.
Here's what I do when stuck on a particular design - just review the comp rules for that section of the design. Usually they are so detailed, they almost spell out an implementation that will be rules correct. For example, the layering system screams "use a list to store all relevant continuous effects, so they can be easily combined as necessary". I basically use what I call a stacked_variable object, which basically acts as a simple variable. However, behind the scenes, this object actually implements a list of all relevant continuous effects. So for example, say you have a permanent that's red.

card.color == "R" will return True

color is a stacked_variable which overrides the equality symbol, to make it equivalent to set membership. So the base value of the characteristics is stored as a set, and the stacked_variable contains a list of additions/overrides that can either add to or remove from the base set (a set in this case is a container that mimics a mathematical set). So if you have a red and white creature, the following are true:

card.color == "R"
card.color == "W"

Then you play a card that sets it's color to blue:

card.color.set("U")

this basically adds a characteristic setting object to the list (which basically clears the set and adds the overriden characteristic - compare with cards that say - "Target is blue in addition to its colors", which simply adds a new value to the set). So now, card.color == "R" returns False, while card.color == "U", but internally, the color object looks like:

List(Characteristics("R", W"), Characteristics("U"))

So each stacked_characteristic object supports the following functions:

card.color.set() - replaces the current value
card.color.set_copy() -- for the copy layer, it sticks it into the list in the right position (basically after the last inserted copy effect)
card.color.add() - adds another value
card.color.remove() - removes a value (i haven't seen any cards that really use this, but you could imagine - "Target red creature is no longer red", some kind of hybrid busting card)
card.color.add_all() - not really necessary for color, but think of creature types and changeling

also, implementing it as a set means you can do intersection as well (for hybrid, or a lot of the cards from the Lorwyn block):
say card is an Elf Warrior:

card.types.intersects(("Elf", "Wizard")) returns True, while card.types.intersects(("Goblin", "Wizard")) returns False.

I believe Java has a basic Set class, so this architecture could be easily ported. I also do this for the controller (even though it's not a characteristic) to make it easy to change controllers.

I hope this makes sense.
Incantus
DEVELOPER
 
Posts: 267
Joined: 29 May 2008, 15:53
Has thanked: 0 time
Been thanked: 3 times

Re: PT counters

Postby GandoTheBard » 23 Jan 2009, 02:27

I hope this makes sense.
Yep it illuminates the concepts quite nicely. Thanks. Hopefully the java programmers here will pick it up and run with it. :D
visit my personal homepage here: http://outofthebrokensky.com

Listen to my podcast with famed AJ_Impy "Freed from the Real" on http://puremtgo.com
User avatar
GandoTheBard
Tester
 
Posts: 1043
Joined: 06 Sep 2008, 18:43
Has thanked: 0 time
Been thanked: 0 time

Re: PT counters

Postby Rob Cashwalker » 23 Jan 2009, 04:57

incantus' solution looks complex, but it's absolutely the best description.

P/T is a dynamic value. At any given time it can be different. So incantus is correct, it screams "LIST!"

Some suggested architecture?

Card object:
Code: Select all
// base P/T
Integer mPower
Integer mToughness

// Creature becomes p/t, evaluated in timestamp order of when each spell or ability created the modification
// PTMod object consists of an ID field and a value field
// the effect that created the modification may need to reference the ID later, to remove the modification
// ie EOT, or when the originating card leaves play
// some modifications become more permanent "this effect doesn't end at EOT"
PTMod[] mPowerMods
PTMod[]mToughnessMods

// Temorary and semi-permanent boosts
// Boost object consists of a ID field and a value field
// the effect that created the boost will need to reference the ID later, to remove the boost
// ie EOT, or when the originating card leaves play
Boost[] mPowerBoosts
Boost[] mToughnessBoosts

// already present
Counters[] mCounters
A set, get and remove method will need to be created for each except mPower and mToughness, these are the printed values, straight from cards.txt, so only a get is required.

Lignify would actually have to stay in play (not implemented as sorcery), and add a modification object to the card. During the card's destruction, remove the modification from the card.

Snakeform would simply add a modification object to the card, and in its EOT command, remove the modification object.

Glorious anthem would simply add a Boost object to each card in play, and any new cards that come into play. (and actually filter it for only creatures...) The Destroy command, executed when it is destroyed, will remove the boost object from all cards.

Giant growth would simply add a boost object, then remove it at EOT.

And counters added by various effects.

during Combat, or any other effect seeking the current net P/T:
Code: Select all
getNetPower()
Integer nP
np = mPower

for i = 0 to mPowerMods.length
   np = mPowerMods[i].Value

for i = 0 to mPowerBoosts.length
   np = np + mPowerBoosts[i].Value    // negative is still a "boost", and adding a negative is the same as subtraction

for i = 0 to Counters[i].length
   if Counters[i].type == PT
      np = np + Counters[i].Power      // pseudo code, obviously

return np
And the same for the Toughness.

We could even morph getAttack to do the above, and then we don't have to touch the combat code, or anything that wants the net value. But all cards that manipulate the P/T, do have to be updated.
The Force will be with you, Always.
User avatar
Rob Cashwalker
Programmer
 
Posts: 2167
Joined: 09 Sep 2008, 15:09
Location: New York
Has thanked: 5 times
Been thanked: 40 times

Re: PT counters

Postby Incantus » 23 Jan 2009, 05:29

Rob Cashwalker wrote:Lignify would actually have to stay in play (not implemented as sorcery), and add a modification object to the card. During the card's destruction, remove the modification from the card.

Snakeform would simply add a modification object to the card, and in its EOT command, remove the modification object.

Glorious anthem would simply add a Boost object to each card in play, and any new cards that come into play. (and actually filter it for only creatures...) The Destroy command, executed when it is destroyed, will remove the boost object from all cards.

Giant growth would simply add a boost object, then remove it at EOT.

And counters added by various effects.
One thing nice about python is that it let's you treat functions as first class objects, so basically all my characteristic modifying functions return a function that reverses the change (which can be used to remove it at end of turn, etc). For example:

Code: Select all
reversal = card.augment_power_toughness(1,1)
until_end_of_turn(reversal)
It looks a little awkward, and is normally written this way: until_end_of_turn(card.augment_power_toughness(1, 1)), but basically the until_end_of_turn function sets up a callback to call whatever function is passed in as an argument at the end of the turn.

I think this is one of the ideas I had that simplified a lot of things. For example, I can also use this in static abilities, since basically i store the reversal function until the static ability is no longer valid, and then call it (and since everything is packed nicely into a function because of closures, i don't even need to know the original card that was modified).

I'm not sure if this is possible in java, but there's probably some similar way.

during Combat, or any other effect seeking the current net P/T:
Code: Select all
getNetPower()
Integer nP
np = mPower

for i = 0 to mPowerMods.length
   np = mPowerMods[i].Value

for i = 0 to mPowerBoosts.length
   np = np + mPowerBoosts[i].Value    // negative is still a "boost", and adding a negative is the same as subtraction

for i = 0 to Counters[i].length
   if Counters[i].type == PT
      np = np + Counters[i].Power      // pseudo code, obviously

return np
And the same for the Toughness.

We could even morph getAttack to do the above, and then we don't have to touch the combat code, or anything that wants the net value. But all cards that manipulate the P/T, do have to be updated.
This looks almost identical to what i have. One small point - the first loop is unnecessary, since you overwrite each value you just need to use the one at the end of the list.

Also, don't use getAttack - put this code in some function called getPower/getToughness in the actual card object (or creature object - i'm not sure how MTGForge is structured), since you'll need this calculation whenever you need to reference the P/T of a card (state-based effects, cards like Proper Burial, etc). Also, beware that because of cards like Dwarven Thaumaturgist you'll need to calculate both P/T every time (just in case they can be switched - remember layer 6!).

Of course, I have no idea how this will affect the AI.
Incantus
DEVELOPER
 
Posts: 267
Joined: 29 May 2008, 15:53
Has thanked: 0 time
Been thanked: 3 times

Re: PT counters

Postby jpb » 23 Jan 2009, 07:22

I would like to see the PT stuff abstracted into objects themselves, so a card would simply have a Power object and a Toughness object. I think this would eliminate bugs and make things easier for our brains to understand.

So far the info on this subject is great. Good progress.
jpb
 
Posts: 132
Joined: 05 Sep 2008, 13:12
Has thanked: 0 time
Been thanked: 0 time

Re: PT counters

Postby DennisBergkamp » 25 Jan 2009, 04:03

I've been looking at your design a bit Rob, and I think I'm understanding most of it.

One problem we would still have though is when cards like Glorious Anthem "leave" play. Currently this is not really implemented yet either, which still cause problems for cards like Faceless Butcher, Meadowboon, etc. when they are not destroyed, but bounced / removed from game.
User avatar
DennisBergkamp
AI Programmer
 
Posts: 2602
Joined: 09 Sep 2008, 15:46
Has thanked: 0 time
Been thanked: 0 time

Re: PT counters

Postby Rob Cashwalker » 25 Jan 2009, 05:53

My only thoughts on the leaves play is that the individual cases of cards that remove another from play, have to reset the card back to default - clear the counters, clear the pumps, clear the mods, etc. Returning the card to play would miss out on the effects....

The shorter solution is to forget about the set-and-forget method, and continue with the constant updating... but instead of explicit PT values, just add and remove the pump to all cards.

Another idea is that the state-based-effects code could generically check for new cards, and if there are, execute the effects either to all cards, (remove and replace like its done now) or somehow specifically the new cards. State-based-effects runs constantly, so if there's anywhere we can stick a check for "leaves play" and "comes into play", this is it.

Don't forget that the pumps need to have a way of including keywords... and then getKeywords() needs to take the layering into account too.
The Force will be with you, Always.
User avatar
Rob Cashwalker
Programmer
 
Posts: 2167
Joined: 09 Sep 2008, 15:09
Location: New York
Has thanked: 5 times
Been thanked: 40 times

Re: PT counters

Postby Almost_Clever » 25 Jan 2009, 11:53

Something that will need to be accounted for is that the current play timing for things like Oblivion Ring are not correct (cast Oblivion Ring, select target, target is removed from game, then Oblivion Ring is put into play. Should be cast Oblivion Ring, Oblivion Ring is put into play, select target, target is removed from game). There is not the opportunity to bounce Oblivion Ring in response to the "remove from game" effect to keep the target permanently removed from the game. Note, Faceless Butcher is currently implemented correctly so that I'm able to properly abuse the "return to play" effect prior to the "remove from play" effect without abusing an implementation bug.
A woman came up to me and said / "I'd like to poison your mind / With wrong ideas that appeal to you / Though I am not unkind."
User avatar
Almost_Clever
Tester
 
Posts: 345
Joined: 15 Jan 2009, 01:46
Has thanked: 0 time
Been thanked: 0 time

Re: PT counters

Postby jpb » 26 Jan 2009, 19:46

DennisBergkamp wrote:One problem we would still have though is when cards like Glorious Anthem "leave" play.
Here is a possible solution. If power and toughness are objects then you can have the counters on these be associated to a source card. When the source card needs to remove it's counters it will search all cards in play and remove the counters it put on them. This can be abstracted in to methods. I am thinking either put the methods directly in Card as such

Code: Select all
Card.hasPowerCounterFrom(srcCard)
Card.hasToughnessCounterFrom(srcCard)
Card.removePowerCounterFrom(srcCard)
Card.removeToughnessCounterFrom(srcCard)
or leave it in the power and toughness object as such

Code: Select all
Card.getPower().hasCounterFrom(srcCard)
Card.getToughness().hasCounterFrom(srcCard)
Card.getPower().removeCounterFrom(srcCard)
Card.getToughness().removeCounterFrom(srcCard)
Then abstract it in the card for the more heavier operations, like

Code: Select all
Card.hasCountersFrom(srcCard)
Card.removeAllCountersFrom(srcCard)
I tend to favor leaving it in the power and toughness objects. This is just for removing counters. It might be best to add the equivalent for adding as well, else how will you know what the srcCard was =)
jpb
 
Posts: 132
Joined: 05 Sep 2008, 13:12
Has thanked: 0 time
Been thanked: 0 time

Next

Return to Forge

Who is online

Users browsing this forum: No registered users and 85 guests


Who is online

In total there are 85 users online :: 0 registered, 0 hidden and 85 guests (based on users active over the past 10 minutes)
Most users ever online was 4143 on 23 Jan 2024, 08:21

Users browsing this forum: No registered users and 85 guests

Login Form