*Author

Offline HyroenTopic starter

  • Legendary Member
  • ******
  • Posts: 3556
  • Country: ca
  • Reputation Power: 52
  • Hyroen brings all the vitality and activity of a Life Nymph.Hyroen brings all the vitality and activity of a Life Nymph.Hyroen brings all the vitality and activity of a Life Nymph.Hyroen brings all the vitality and activity of a Life Nymph.Hyroen brings all the vitality and activity of a Life Nymph.Hyroen brings all the vitality and activity of a Life Nymph.Hyroen brings all the vitality and activity of a Life Nymph.Hyroen brings all the vitality and activity of a Life Nymph.Hyroen brings all the vitality and activity of a Life Nymph.Hyroen brings all the vitality and activity of a Life Nymph.
  • like. follow. tweet.
  • Awards: Slice of Elements 8th Birthday CakeWar #10 Winner - Team AirSlice of Elements 7th Birthday CakeSilver DonorSlice of Elements 4th Birthday CakeSkill of the Elemental : Mark of ProtectionWeekly Tournament WinnerSlice of Elements 3rd Birthday Cake
Coding Difficulty https://elementscommunity.org/forum/index.php?topic=61029.msg1220887#msg1220887
« on: January 22, 2016, 03:33:41 am »
Card design should always consider how a card is coded. Some individuals may have more coding experience than others and thus, coding difficulty may be hard to assess.

In this thread we can discuss relative coding difficulty of cards that are already a part of EtG to have a better notion of how difficult a card design may be to code.

Naturally, this would only be a guide, CI&A seeks new mechanics and always challenges designers to come up with new and innovative designs, and thus, not all mechanics may have a good comparison, as such, this thread may also be a good place to ask how difficult a card idea would be to code.



If you are an experienced coder:
  • Please discuss the coding of 1 or more cards in EtG, and rate coding difficulty from 1-10.
  • When possible, respond to coding difficulty questions by card designers.
WAR X - TEAM :air AIR

   [EtG Council]   |   [Card Ideas & Art]   |   [Guilds]

Offline UTAlan

  • Hero Member
  • *****
  • Posts: 1802
  • Reputation Power: 58
  • UTAlan is truly a Titan, worthy of respect and acknowledgement.UTAlan is truly a Titan, worthy of respect and acknowledgement.UTAlan is truly a Titan, worthy of respect and acknowledgement.UTAlan is truly a Titan, worthy of respect and acknowledgement.UTAlan is truly a Titan, worthy of respect and acknowledgement.UTAlan is truly a Titan, worthy of respect and acknowledgement.UTAlan is truly a Titan, worthy of respect and acknowledgement.UTAlan is truly a Titan, worthy of respect and acknowledgement.UTAlan is truly a Titan, worthy of respect and acknowledgement.UTAlan is truly a Titan, worthy of respect and acknowledgement.UTAlan is truly a Titan, worthy of respect and acknowledgement.
  • Immortally Aether
  • Awards: Slice of Elements 9th Birthday CakeSlice of Elements 8th Birthday CakeSlice of Elements 7th Birthday CakeWeekly Tournament WinnerSlice of Elements 6th Birthday CakeReviver of the WikiWar #6 Winner - Team AetherSlice of Elements 3rd Birthday CakeSecond Budosei of BudokanSlice of Elements 2nd Birthday CakeWeekly Tournament Winner
Re: Coding Difficulty https://elementscommunity.org/forum/index.php?topic=61029.msg1220892#msg1220892
« Reply #1 on: January 22, 2016, 05:08:36 am »
I'll start with an easy one: Abomination | Micro-Abomination.

Spoiler for Hidden:
Difficulty: 1
Just adding some text to a single file. Nothing special or difficult at all.

Here is the template for creatures:
Code: [Select]
["Name","Code","Cost","Active","Attack","Health","Status","Tier"]
NameSelf explanatory.
CodeUnique code for the card. Unupgraded starts with 5, upgraded starts with 7.
CostHow much it costs.
ActiveActive abilities, like "pillar", "rewind", or "lobotomize".
AttackDuh.
HealthSee above.
StatusThings like Airborne and Nocturnal. Nothing special for this guy, though.
TierRarity, I think? Fippe or serprex might be able to correct me if that is not the case.

For Abomination, I would just fill in the values:
Code: [Select]
["Abomination",5102,5,"",5,5,"",2],["Micro Abomination",7102,1,"",2,4,"",17]

Let's do Bone Pillar | Bone Tower next.

Spoiler for Hidden:
Difficulty: 2
Slightly more complex than Abomination, as we at least have an Active and a Status.

Here is the template for pillars/towers:
Code: [Select]
["Name","Code","Active","Tier","Status"]
NameSelf explanatory.
CodeUnique code for the card. Unupgraded starts with 5, upgraded starts with 7.
ActiveActive abilities, like "pillar", "rewind", or "lobotomize".
TierRarity, I think?
StatusPassive status effects, like "airborne", "nocturnal", or "stackable".

For Bone Pillar, I would start by filling in these values:
Code: [Select]
["Bone Pillar",5200,"pillar",1,"stackable+additive"],["Bone Tower",7200,"pillar+play=pillar",17,"stackable+additive"]
The "pillar" active is defined in a separate file as a javascript function. By stating "pillar", it's an ability that is triggered at the end of each turn. By stating "play=pillar", it's triggered when the card is played. The "+" character is used to allow multiple actives.

Here is the actual code for the "pillar" function:

Code: [Select]
pillar:function(c,t){
if (!t)
c.owner.spend(c.card.element, -c.status.charges * (c.card.element > 0 ? 1 : 3));
else if (c == t)
c.owner.spend(c.card.element, -(c.card.element > 0 ? 1 : 3))
}

The "c" variable represents the current card (the current stack of pillars, in this case), while the "t" represents the target card (not valid for pillars, but it also represents the current card in the case of towers).

We first check if the target ("t") is valid. If not:
    We find the owner of the card and spend quanta. Spending from the card's element. Negative value since we want to reduce the quanta. The # of pillars in the stack is c.status.charges, multiplied by either 1 or 3, depending on the card's element (c.card.element = 0 for Quantum Pillars/Towers).
If "t" is the same as "c", this is triggered when a Tower is played:
    Same as above, but omitting the charges since we only want to generate 1 or 3 quanta, not (1 or 3) * charges.

The statuses we just add to the passives array:

Code: [Select]
var passives = { airborne: true, nocturnal: true, voodoo: true, swarm: true, ranged: true, additive: true, stackable: true, salvage: true, token: true, poisonous: true, singularity: true, siphon: true, mutant: true, bounce: true};
We use the passives array to check if a card is, say, stackable, allowing us to do two different things depending on the answer. For example, here's an excerpt from the destroy() function:

Code: [Select]
if (t.status.stackable){
    // decrement status.charges, and if <= 0, remove
    if(--t.status.charges<=0){
        t.remove();
    }
} else {
    t.remove();
}

Gonna do a spell next. Momentum | Unstoppable.

Spoiler for Hidden:
Difficulty: 3
A little more difficult than pillars, as we have to modify the attack function, too.

Here is the template for spells:
Code: [Select]
["Name","Code","Cost","Active","Tier"]
Not going to explain again. Go to the previous spoilers for explanations of these.

For Momentum, I would start by filling in these values:
Code: [Select]
["Momentum",5306,2,"momentum",2],["Unstoppable",7306,1,"momentum",17]
Our active here is "momentum". Since this is a targeting spell, we need to specify who we're targeting:

Code: [Select]
"momentum":"crea",
Then, in another file, we define what is a valid target for "crea":

Code: [Select]
crea:function(c, t){
    return t.isMaterialInstance(etg.Creature);
},

Essentially, this is saying the target has to be a creature (so we can't target ourselves, permanents, or our opponent). Now that that is done, we define the active in the code here:

Code: [Select]
momentum:function(c,t){
    Effect.mkText("Momentum", t);
    t.atk += 1;
    t.buffhp(1);
    t.status.momentum = true;
},

The function Effect.mkText is a graphics thing. Whatever.
Increment the target card's attack by one.
Increment the target card's health by one.
Set the target card's status "momentum" = true.

Finally, we account for momentum in the attack function:

Code: [Select]
if (this.status.momentum || trueatk < 0){
target.dmg(trueatk);
} else {
// deal with shields
}

If momentum is true or the creature's attack is negative, do the damage. Later on in the code we deal with shields, so this bypasses that.

Finally, we add some text to go on the card:

Code: [Select]
momentum:"Target ignores shield effects & gains 1|1",

By request (MeowMeowCat), here is Mind Flayer | Ulitharid:

Spoiler for Hidden:
Difficulty: 3
No statuses or passive abilities. Just one active ability that removes all non-passive abilities from the target, along with a few particular statuses.

Here is the template for creatures:
Code: [Select]
["Name","Code","Cost","Active","Attack","Health","Status","Tier"]
And for this specific creature:
Code: [Select]
["Mind Flayer",5711,2,"1:12=lobotomize",2,2,"",3],["Ulitharid",7711,3,"1:12=lobotomize",3,4,"",17]
The "1:12" means it costs 1 quanta from element 12 (Aether) to use the lobotomize active. This creature has no statuses, so we'll just go straight into lobotomize. First, we specify valid targets:

Code: [Select]
"lobotomize":"crea",
I went over the details of this in Momentum, so read that if you wanna know what's going on here. Here's the actual code for lobotomize:

Code: [Select]
lobotomize:function(c,t){
Effect.mkText("Lobotomize", t);
t.lobo();
delete t.status.momentum;
delete t.status.psion;
delete t.status.mutant;
},

Graphics stuff.
Call the "lobo" function on the target (we'll get there in a second).
Delete the momentum, psion, and mutant statuses from the target.

Okay, now let's see what t.lobo() does:

Code: [Select]
Thing.prototype.lobo = function(){
for (var key in this.active){
if (!(this.active[key].activename[0] in passives)) {
    delete this.active[key];
}
}
}

So we loop through all of the abilities on this card and delete the ones that are not passive.

Finally, let's add the text:

Code: [Select]
lobotomize: "Remove skills from target creature",
« Last Edit: January 22, 2016, 07:29:40 pm by UTAlan »

Offline serprex

  • Administrator
  • ********
  • Posts: 2236
  • Reputation Power: 0
  • serprex hides under a Cloak.
  • Awards: War #12 Winner - Team Darkness
Re: Coding Difficulty https://elementscommunity.org/forum/index.php?topic=61029.msg1220986#msg1220986
« Reply #2 on: January 23, 2016, 01:49:06 pm »
Note that the passives list is used only for marking that:
A status should always be reset when transformed (mutation triggers transform, dragonfly -> frog does not keep airborne, but keeps poison, because airborne is passive & poison is not)
A skill should not be lobotomized
Skills are always removed in transformation & statuses are never removed in lobotomization (NB lobotomize the skill is _not_ only lobotomization the method. Lobo the skill calls the lobo method & then clears momentum/psionic)

This comment is replying to the unclear mentioning of passives in UTA's explanation of pillars, not Mind Flayer

Now I'll document fixing SoR, an open bug since the beginning of oetg-v. I've previously discussed with Fippe some ideas on how to fix it
Currently SoR works as "Reduce cost of active to 0, remove summoning sickness". We've had to nerf Dive to not be exponential because of this but also reduced the cost of SoR by 1. These other changes are not in oetg-v
Spoiler for Hidden:
Code: [Select]
readiness:function(c,t){
Effect.mkText("Ready", t);
if (t.active.cast){
t.cast = 0;
t.usedactive = false;
}
}
We need state tracking because in original you can cast SoR on something & it'll "just work"
Cast SoR on :time thing before they use active -> they can then proceed to activate twice
Cast SoR on :time thing after they use active -> they can then proceed to activate again
Good programming prefers to encapsulate state, or, more relevantly, keep the book keeping logic in the active, rather than leveraging the game engine to book keep everything. So I'm going to modify the active to set a "ready" status which we will-- blah oetg-v doesn't have on-useactive proc like oetg does. So I open etg.js in oetg & remember that I reduced that file to a mere set of constants & adrenaline logic which then leads me to open Thing.js where I scout out useactive but that doesn't have what I want because that's logic for _everything_ reminding me that we have a cast method but cast is a property so /.prototype.cast & vim has scrolled to the castSpell method. I'm going to be lazy & only bring over this.procactive("spell") after this.active.cast(this, t) for now since the nospell parameter is to deal with the fact that we don't want ricochet to ricochet itself in oetg. Now we can bind an active to the event "own spell" & it'll get triggered whenever a creature casts a spell (c will be the thing with the active, t will be the thing that's casting the active)

Quick note: proc 'death' first triggers 'owndeath' on the thing proc'ing before scanning through for anything with a 'death' active

In oetg I renamed procactive to proc so I replace-all procactive to proc in oetg-v to continue my ever long quest to keep these two codebases in agreement. Now back to readiness. Idea is:
Code: [Select]
active cost = 0
if this.ele == time && !this.status.ready {
  this.status.ready = this.usedactive ? 2 : 3
  this.usedactive = false
  add ownspell 'ready'
}
ownspell-ready:
if this.status.ready>1 {
  this.status.ready--
  this.usedactive = false
}
At first I had ready set to 2 or 1 & decremented to 0 but realized that we need to track if something's already readied to avoid SoR stacking (I don't recall the exact stacking behavior of SoR, I'm assuming it's a nop after the first cast)

Now then I've written the pseudocode so I run that through my mental pseudocode-to-javascript compiler &:
Code: [Select]
readiness:function(c,t){
  Effect.mkText("Ready", t);
  if (t.active.cast){
    t.cast = 0;
    if (t.card.element == etg.Time && !t.status.ready){
      t.status.ready = t.usedactive ? 2 : 3;
      t.usedactive = false;
      t.addactive("ownspell", Actives.ready);
    }
  }
},
ready:function(c,t){
  if (c.status.ready > 1){
    c.status.ready--;
    c.usedactive = false;
  }
}
Go to test with a deck that's golden nymphs & wyrms & SoRs & .. it doesn't work. So I manually write in the above code block because yay I can't copy paste & message from the virtual terminal I'm programming in to this post I'm writing. Shit. Hopefully that'll clear my head. My test case was immediately using SoR, where it maintained previous behavior (I had forgotten to run ninja the first time). Anyways set a breakpoint in ready to see if we're even hitting it. Also testing on Wyrm worked so change test deck to nymphs & sors only. Debugger isn't working right, oh well, can see that ready status isn't decrementing. Run git diff to review changes I've made. Whoops I put proc("play") instead of proc("spell") in useactive. Now things are working better, except that I can use an active 3 times by cast & then using SoR. There's nothing in Creature to indicate the difference between cant-cast-because-new & cant-cast-because-already-cast. I'm tempted to default usedactive to 2 & make the JIT cry. So I do & test. Forgot to modify readiness with "t.usedactive == 2 ? 3 : 2". No dice. Should've been ===, not ==. This time pay attention to Ready status in tooltip. Blah need to set ready status to 2 or 1, not 3 or 2. (usedactive is only cleared when ready active's predicate is false)

Now if one uses SoR the turn after a thing is played they can only use the active once. Change to usedactive === true ? 1 : 2

Having implemented the core functionality of SoR, there's one thing left: Fate Egg. This'll be straightforward one-liner in pseudocode: if this.status.ready then PU self after hatching. This may have some incorrect behavior for if we hatch a mutant time creature (if that's an issue someone else can add a !status.mutant to the code)

So test. Success. git diff. git co -am "Fix SoR to be like original". git push. ./pushserver. Refresh github page to get link to commit for this sentence
« Last Edit: January 23, 2016, 03:17:40 pm by serprex »

 

blarg: MeowMeowCat