Thursday, February 12, 2015

What is a card?

This post is about ambiguity, but this post is not about Ambiguity. (Don't forget to look at the card topsy turvy as well. And don't forget to look at the card Topsy Turvy as well)

Okay, enough with the puns... at least if I don't find any others while writing... what I really want to write about is how overloaded the term "Card" is in Magic. Let's see how many different meanings I can come up with.

Cards are those things that Wizards designs. For example, Grizzly Bears is a card, and Llanowar Elves is as well.

What about two Grizzly Bears? A card can also mean the physical object, sold in boosters and put in decks. But you can't even simply say that the physical card is "a Grizzly Bears", because there's a few different ones. And there's foil as well, and probably other things I'm forgetting. So the physical card is actually not an instance of what Wizards R&D designed, but of one translation of one printing of such a card, either in foil or not.

Getting to the actual game, cards are a kind of object, in contrast with tokens and copies of cards, which are not cards but serve almost the same purpose.

Noncard objects exist in a few different forms: emblems are always in the command zone; tokens can only exist on the battlefield; ability objects exist on the stack; copies of spells exist on the stack; casting a copy of a card creates that copy in the original card's zone, which in then put onto the stack as a spell while casting.

Permanents are objects that can be either cards or tokens, so another thing a card can be. Compared to other objects, permanents have state:  tapped/untapped, flipped/unflipped, face up/face down, and phased in/phased out.

The comprehensive rules on spells are worded a little strangely: "A spell is a card on the stack", "A copy of a spell is also a spell", "if the player does [cast a copy of a card], that copy is a spell as well". It sounds contradictory at first, but it's manageable.

So what have we got here:
  • What Oracle says a card is, identified by the English name of the card.
  • A printing of such a card, identified by English name together with an expansion. This extends the Oracle card by rarity, artist, illustration, collector number, card frame style, guild symbols and similar in the text box, ...
  • A translation of a printing, identified by a multiverse ID, or alternately by English name, expansion and language; I wouldn't trust that the translated name is unique. The translation adds obviously anything language specific about a card. This includes printed, non-oracle wording including reminder text, as well as flavor text.
  • A piece of cardboard that has a Magic card printed on it. To my understanding, this interpretation of "card" can be uniquely identified by a function defined on an interval of the timeline that maps every instant in that interval to the volume of space that is occupied by the card at that time. Or, a card is a card.
  • A digital representation of such can also be considered a card in that sense.
  • A card is an object that can be in a zone in a game of Magic.
  • Some permanents are (represented by) cards.
  • Some spells are (represented by) cards.
You can imagine that all this ambiguity makes it quite hard to come up with a proper software model for a game of Magic!

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.

Tuesday, February 10, 2015

Replacement effects with partial functions

This is a very crude mockup of how replacement effects could look in Laterna Magica. Instead of any impressive effect, I'm just modifying a variable that represents a player's life total, but it does quite a lot for only being about 30 lines of code:

//a replaceable event
trait Event { def execute(): Unit }
//an effect that replaces an event
type ReplacementEffect = PartialFunction[Event, Event]

//applies all effects to the event. If a replacement doesn't match, the
//event remains unchanged
def replace(event: Event, effects: ReplacementEffect*) =
  effects.foldLeft(event) { case (event, effect) => effect.applyOrElse(event, identity[Event] _) }

var activeEffects: Seq[ReplacementEffect] = Nil
def execute(event: Event) = replace(event, activeEffects: _*).execute()

//example: A player's life points
var _life = 20
def life = _life

def gainLife(life: Int) = execute(GainLife(life))

case class GainLife(life: Int) extends Event {
  def execute() = _life += life
}

//execution:

//gain three life
gainLife(3)
println(life) //23

//gain twice as much life
activeEffects = List({ case GainLife(x) => GainLife(x * 2) })

//gain six life
gainLife(3)
println(life) //29


The trait Event is used to represent replaceable events, and effects are simply "partial functions": functions that can only be applied to some of the values that its parameter type would allow. For example, at the bottom there's the partial function:

{ case GainLife(x) => GainLife(x * 2) }

While ReplacementEffect is defined for all Events, this one only accepts GainLife instances. Using PartialFunction.applyOrElse, I handle the cases where a replacement effect does not apply to an event.

The example is self-contained, so you should be able to run it yourself. In any case, the output is next to the print statements: The first time, you only get 3 life, but after registering the effect, the amount is indeed doubled. In reality, execute (and replace?) needs to be a bit smarter to recognize the right effects to apply, but otherwise this could stay almost as it is.

Monday, February 2, 2015

Scala revisited: so much shorter!

I didn't even realize that I wrote about Scala before, until I logged into my blog and saw that it's apparently one of my most popular posts. My earlier flirt with Scala ebbed out rather quickly, and I think the reasons were twofold.

Firstly, back in 2010, IDE integration for Scala wasn't that great. While there's still much room for improvement, the situation is much better now. I only tried the Eclipse Scala IDE, and from what I heard, IntelliJ integration is nice as well.

The second reason is simply my lack of understanding of functional programming. I would say that I had a good concept of how functional programming is supposed to work back then, but I was surely lacking practice. My interest recently led me to a FP/Haskell class at university, and while it was only introductory level, I think it helped a lot.

As far as I know, the language itself has evolved quite a bit as well. There was a big cleanup of the collection API, and much more - it has been five years, after all. How time flies!

Anyway, I'm in love with that language! In the last three weeks, I converted Harmonic (and other projects) from Java to Scala - and from Maven to Gradle, by the way - and got from 1027 to 870 lines of code, about 15% less. Granted, that's not exclusively the language switch but also a partial redesign - hence the three weeks of work - but I don't think that lessens the message. It gets more impressive when I add that the Scala code includes about 300 lines of freshly written unit tests!

Scala gets rid of a lot of boilerplate code that bloats the line count of Java. For example, a typical property takes 7 lines of Java:

private int property;
public void setProperty(int property) {
    this.property = property;
}
public int getProperty() {
    return this.property;
}

Converted to Scala idioms, the code would look like this:

private var _property: Int = _
def property = _property
def property_=(property: Int) = _property = property

Well, that is a little shorter. Also note how def property omits the parentheses, and that methods ending in _= are called as if they were assignments. Now compare incrementing that property:

//Java
x.setProperty(x.getProperty() + 1);
//Scala
x.property = x.property + 1


That's just one example, I could go on: Instead of static members, Scala has objects, but they are actually singletons and can extend other classes and traits. Traits are like interfaces, can have method implementations like in Java 8, but are actually inherited as mixins. Case classes allow pattern matching, comparing that to switch is almost insulting given how much more flexible pattern matching is. Implicits allow the compiler to do even more of the work for you: implicit conversions are like user-defined autoboxing (another insult!), implicit parameters save you typing when a context variable is needed over and over in method calls. Need to pass a Game to every other method? Make it implicit!

I bet there are many more gems hidden in Scala, but I only had three weeks to look so far :P

Okay, one more thing. I won't even describe it, just show you code. This is how unit tests can look in Scala:

class EngineSpec extends FlatSpec with Matchers with GivenWhenThen {
  behavior of "An engine"

  it should "handle branches properly" in {
    Given("an engine")
    implicit val engine = new Engine()

    And("a PolybufIO for a custom action")
    engine.addIO(MyAction)

    {
      When("creating a branch at HEAD")
      val head = engine.head
      val branch = engine.Branches.createBranchHere("branch1")

      Then("the branch's tip should be the head")
      branch.tip should be(head)

      When("executing an action")
      engine.execute(new MyAction())

      Then("the branch's tip should still be the old head")
      branch.tip should be(head)
    }

    {
      When("creating another branch at HEAD")
      val head = engine.head
      val branch = engine.Branches.createBranchHere("branch2")

      Then("the branch's tip should be the head")
      branch.tip should be(head)

      When("making that branch current")
      engine.currentBranch = branch

      And("executing an action")
      engine.execute(new MyAction())

      Then("the branch's tip should be the new head")
      branch.tip should be(engine.head)

      When("moving that branch's tip")
      branch.tip = head

      Then("the new head should be the branch's tip")
      engine.head should be(branch.tip)
    }
  }
}