Wednesday, February 11, 2015

Ordering of replacement effects

Handling of multiple replacement effects is intricate, but well described in the comprehensive rules, so I'll link you to my favorite online version: rule 616

The code I presented in monday doesn't handle any of that, but it wouldn't be too hard to do. In my quick mockup, effects are stored in a list and applied in that order. For example:

Boon reflection: If you would gain life, you gain twice that much life instead.
Nefarious Lich: If you would gain life, draw that many cards instead. (and other abilities)

Clearly a life gain could be replaced by either, but only one of the resulting events is a life gain in turn. So, if I gained two life, depending on the order, I'd draw either two or four cards, and it should be my choice.

So how would that be written? Almost exactly like the comprehensive rules are formulated: Starting with the set of all replacement effects,
  1. filter the set for those applicable to the event
  2. if the set is empty, we're done
  3. filter further
    • for self-replacement effects
    • if the result is empty, for "under whose control an object would enter the battlefield" replacement effects instead
    • if the result is empty, for "cause an object to become a copy of another object as it enters the battlefield" replacement effects instead
    • if the result is empty, don't filter at all
  4. if there's more than one effect, let the applicable player choose one effect
  5. replace the event and remove the effect from the unfiltered set
  6. repeat
a partial function already has isDefinedAt, and filtering sets is easy in scala, e.g. effects.filter { _.isDefinedAt(event) }. I can even use groupBy to partition the effects into their four types in one line. What needs thought is how to ask the players for ordering effects.

 Edit: here is my new vesion for replace:

//applies all effects to the event.
@tailrec
def replace(event: Event, effects: Set[ReplacementEffect]): Event = {
  val filtered = effects.filter { _.isDefinedAt(event) }
  //TODO here I need to know what type an effect is
  //0... self-replacement, 1... control, 2... copy, 3... regular
  val byType = filtered.groupBy { effect => 3 }
  val sorted = byType.toSeq.sortBy { case (k, _) => k }
  val choices = sorted.collectFirst { case (_, v) if !v.isEmpty => v }
  choices match {
    case None => event
    case Some(effects) =>
      val effect =
        if (effects.size == 1) effects.head
        else {
          //TODO let player choose one effect
          effects.head
        }
      //only applicable effects here, so use apply directly
      replace(effect.apply(event), effects - effect)
  }
}

The only change to my description is that I don't do step 2, but instead do it in step 4. The collectFirst call returns the first nonempty collection, or None if they're all empty, so I get that check here for free anyway. Also note that I don't use a loop, but instead use recursion, which is preferred in functional programming. Some types of recursion, like this, can be optimized into a loop, so here it's just a matter of style. In fact, by adding the @tailrec annotation, the compiler would issue an error if this couldn't be optimized into a loop.

No comments: