Skip to content
This repository has been archived by the owner on Dec 21, 2018. It is now read-only.

Add keyword abilities for cards #285

Closed
dkniffin opened this issue Jan 30, 2017 · 13 comments
Closed

Add keyword abilities for cards #285

dkniffin opened this issue Jan 30, 2017 · 13 comments
Assignees
Labels

Comments

@dkniffin
Copy link

dkniffin commented Jan 30, 2017

It'd be nice if the json for each card included an array with the keyword abilities for the card. I'm writing a program that uses this data, and right now, there's no reliable way to determine if a card has a certain ability, other than using a regex against the card text, which isn't too reliable. For example, if I want to see if a card has Unearth, I'd have to do something like /unearth/.test(cardText), but that would also match something like Sedris, the traitor king

I'm not sure how this data is compiled, but if it's manual, it might also be worth adding costs for the abilities at the same time. Something like this maybe?

{
  "name": "Rotting Rats",
  ...
  "keywordAbilities": [
    {
      "name": "Unearth",
      "cost": "{1}{B}"
    }
  ],
  ...
}

(Edit: I changed the json example from Sedris to Rotting Rats. I meant to do that initially, because Rotting Rats has the actual static ability, whereas Sedris has a conditional ability.)

@Nightfirecat
Copy link

Seems like a big change for what isn't a very general use case.

In your program, couldn't you just use a smarter regex, such as /^Unearth.+/m? That would avoid the edge-case of the text appearing midway through a card text's line.

@dkniffin
Copy link
Author

Yea, I could probably do that. Another use case I thought of is if you want to search for cards with cheap abilities, but since it's not something I'm looking for right now, I'm fine closing this.

@Garbee
Copy link
Contributor

Garbee commented Jan 30, 2017

Focusing in on the problem of providing static ability keywords.

Take Akroma of Fury for example. "Begins with" matching will fail to catch her protection and trample abilities.

This is possible to implement, but it can get tricky with the edge-cases. Basic logic...

  1. Get list of known abilities (manually compiled list from the rulebook)
  2. Look a line that starts with any of the provided abilities with either a \n or , following after them.
  3. Take those lines and split it by ,
  4. With the resulting arrays, loop over each and do whatever formatting voodoo is needed to make sensible objects.
  5. Combine the resulting array of keywords into one big keyword object list.

I'm certain Wizards has a few quirky edge-cases that will show up when working on this code, but that's the basics to handle it.

We should not include conditional keywords, as they are non-static and far less useful than quickly finding any thing with a given static keyword. For example, the provided Sedris example would not have any keyword abilities listed. As he does not statically have it, but he provides it to other cards conditionally. If you're trying to find cards that provide abilities to others, the filtering for pre-computed values is far more extreme and imo we shouldn't be tackling that just yet.

@dkniffin
Copy link
Author

Yea, I was thinking of that case this morning, where it's a list of static abilities. I think the solution @Garbee described might work. In the particular application I'm writing, I think I'm going to lean towards writing a function that returns the right set of abilities for each card, because I have a small set of them. In a larger app, that wouldn't be feasible unless they were in this data set, which is why I made this issue.

I completely agree about the comment about conditional keywords. It would be much harder to compile that data, and not very useful.

@Garbee
Copy link
Contributor

Garbee commented Jan 30, 2017

That data is actually extremely useful to provide users a search function to filter by cards that will provide the effect they are looking to make a combo out of. But, it's far lesser-done and due to the difference in language used card-to-card much harder to make a decision based on static analysis of the card text.

@PAK90
Copy link

PAK90 commented Jan 30, 2017

Funny this should come up; I'm actually working on a project to tackle this problem. It's a ways off completion for now, but I'll figure it out eventually.

For purely extracting static evergreen abilities, regex should work fine. However anything beyond those would be extraordinarily tricky with regex alone.

@dkniffin
Copy link
Author

dkniffin commented Feb 13, 2017

I just got around to updating my app (which can be found here now, btw) to use @Garbee's method for abstracting the abilities from a card's text. Here's the resulting javascript code, with some test cases at the bottom:

const ABILITIES = [
  'Deathtouch', 'Defender', 'Double Strike', 'Enchant', 'Equip', 'First Strike', 'Flash', 'Flying', 'Haste', 'Hexproof', 'Indestructible', 'Lifelink', 'Menace', 'Prowess', 'Reach', 'Trample', 'Vigilance', 'Absorb', 'Affinity', 'Amplify', 'Annihilator', 'Aura Swap', 'Awaken', 'Banding', 'Battle Cry', 'Bestow', 'Bloodthirst', 'Bushido', 'Buyback', 'Cascade', 'Champion', 'Changeling', 'Cipher', 'Conspire', 'Convoke', 'Cumulative Upkeep', 'Cycling', 'Dash', 'Delve', 'Dethrone', 'Devoid', 'Devour', 'Dredge', 'Echo', 'Entwine', 'Epic', 'Evoke', 'Evolve', 'Exalted', 'Exploit', 'Extort', 'Fading', 'Fear', 'Flanking', 'Flashback', 'Forecast', 'Fortif', 'Frenzy', 'Fuse', 'Graft', 'Gravestorm', 'Haunt', 'Hidden Agenda', 'Hideaway', 'Horsemanship', 'Infect', 'Ingest', 'Intimidate', 'Kicker', 'Landhome', 'Landwalk', 'Level Up', 'Living Weapon', 'Madness', 'Megamorph', 'Miracle', 'Modular', 'Morph', 'Myriad', 'Ninjutsu', 'Offering', 'Outlast', 'Overload', 'Persist', 'Phasing', 'Poisonous', 'Protection', 'Provoke', 'Prowl', 'Rampage', 'Rebound', 'Recover', 'Reinforce', 'Renown', 'Replicate', 'Retrace', 'Ripple', 'Scavenge', 'Shadow', 'Shroud', 'Soulbond', 'Soulshift', 'Splice', 'Split Second', 'Storm', 'Substance', 'Sunburst', 'Surge', 'Suspend', 'Totem Armor', 'Transfigure', 'Transmute', 'Tribute', 'Undying', 'Unearth', 'Unleash', 'Vanishing', 'Wither'
]

function abilities(cardText) {
  function cleanupAbility(text) {
    return text.replace(/\(.*\)/, '') // Remove reminder text
               .replace(/\{.*\}/, '') // Remove the cost
               .trim() // Remove whitespace from start and end
  }
  const text = cardText;
  const lines = text.split("\n")
  const keywordLines = lines.filter((line) => {
    const firstPossibleAbility = cleanupAbility(line.split(/,/)[0])
    return ABILITIES.includes(firstPossibleAbility)
  })
  return keywordLines
          .map((line) => line.split(/,(\{.+\})?\s?/))
          .reduce( ( acc, cur ) => acc.concat(cur), [])
          .map((rawAbilityText) => cleanupAbility(rawAbilityText));
}

abilities("First Strike")
abilities("Unearth, First Strike, Vigilance")
abilities("Vigilance, Trample")
abilities("Unearth")
abilities("{1}{B}: Regenerate target Zombie.\nMorph {2}{B} (You may cast this card face down as a 2/2 creature for {3}. Turn it face up any time for its morph cost.)")
abilities("Skeleton creatures you control and other Zombie creatures you control get +1/+1 and have deathtouch.")
abilities("When Rotting Rats enters the battlefield, each player discards a card.\nUnearth {1}{B} ({1}{B}: Return this card from your graveyard to the battlefield. It gains haste. Exile it at the beginning of the next end step or if it would leave the battlefield. Unearth only as a **sorcery.)")**

A couple thoughts:

@lsmoura
Copy link
Contributor

lsmoura commented Feb 13, 2017

@dkniffin I like this code. A lot.

I'll implement it in March, after I move.

@adampatterson
Copy link

For whatever its worth, I am a newer player and don't know cards by name but I know what I might want to add to a deck. So I would look up Green Dinosaurs with Trample + Haste.

@lincolnthree
Copy link

Isn't that achievable by searching the card text for the most part?

@adampatterson
Copy link

It would be, but it would be a better experience to include it as a possible search facet so that you can reflect matching card counts as you make other selections.

@lincolnthree
Copy link

No disagreement there :)

@ZeldaZach
Copy link
Member

If someone would like to reopen this to restart a discussion on V4, we can talk about it there. Otherwise, unfortunately I am closing this ticket

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

10 participants