Grayvatar

http://aaron.maenpaa.ca/grayvatar/634329486bd326c395c8b3539c035139?s=196&d=identicon&r=PG

The week before last I got to spend some time working on a prototype as part of a sekrit project at work. In order to give the prototype the air of not-quite-done, I did the whole thing in black and white. This worked great up until I added in Identicons to identify users, which added a bunch of brightly colored triangles and ruined the whole black-and-white aesthetic. I eventually ended up pulling a bunch of Identicons from Gravatar with wget and desaturated them with ImageMagik. It worked, but it was kind of gross.

Teh Codez

It occurred to me that it should be pretty straightforward to build a web app that implemented the Gravatar API and desaturate real Gravatars, so I took a crack at it:

import StringIO
import urllib, urllib2

import cherrypy
import Image

from paver.easy import path

URL = "http://www.gravatar.com/avatar/%s?%s"

class Grayvatar(object):
    @cherrypy.expose
    def default(self, *bits, **kwargs):
        if bits:
            cherrypy.response.headers["Content-Type"] = "image/png"
            url = URL % (bits[0], urllib.urlencode(kwargs))
            img = get_img(url).convert("L")
            return img_as_str(img)

        pwd = path(__file__).abspath().parent
        return (pwd / "how_it_works.html").text()

def get_img(url):
    sock = urllib2.urlopen(url)
    return Image.open(StringIO.StringIO(sock.read()))

def img_as_str(img):
    sock = StringIO.StringIO()
    img.save(sock, "png")
    return sock.getvalue()

if __name__ == "__main__":
    cherrypy.quickstart(Grayvatar())

I think it's awesome that, thanks to CherrPy and PIL, I could put together a web app that does all that 30 odd lines (including imports), but it's less awesome that so many of those lines are shuffling data in and out of a StringIO.

mod_wsgi

I set up an instance at: http://aaron.maenpaa.ca/grayvatar using Apache and mod_wsgi. I used a lightly adapted version of the the wsgi config from the CherryPy wiki.

import sys
sys.stdout = sys.stderr

import atexit
import threading
import cherrypy

sys.path = ['/home/aaron/code/grayvatar'] + sys.path

import grayvatar

cherrypy.config.update({
    'environment': 'embedded',
    'log.screen': True,
})

if cherrypy.__version__.startswith('3.0') and cherrypy.engine.state == 0:
    cherrypy.engine.start(blocking=False)
    atexit.register(cherrypy.engine.stop)

application = cherrypy.Application(grayvatar.Grayvatar(), script_name=None)

The most important change was adding 'log.screen': True to the config. This sends errors to stderr, which eventually makes it's way to /var/log/apache2/error.log which makes it possible to figure out what's going on when things don't go right.

How It Works

Just like Gravatar. The base URL is http://aaron.maenpaa.ca/grayvatar/. You calculate the MD5 hash of your email address, append it to the base URL and then add any query parameters that Gravatar supports. That gets you a URL like this: http://aaron.maenpaa.ca/grayvatar/634329486bd326c395c8b3539c035139?s=256&d=identicon&r=PG

It fetchs the appropriate Gravatar, desaturates and returns it. Keep in mind, this is a toy: It doesn't actually respect the cache headers Gravatar sends and worse, it doesn't even pass them along to the client.

Baneslayer-Lifelink

In my post about my Pyromancer's Ascension deck, I mentioned that I was playing against my Baneslayer-Lifelink deck and I thought I'd share the decklist:

This deck came into being as a way to find a home for the Baneslayer Angels that I purchased on the basis that they were too awesome for me to not have a playset. The deck ends up being unified mechanically by lifelink (that's the "Lifelink" part of Baneslayer-Lifelink) even though, thematically, it's a bit unfocused. Angels, and priests, and vampires! Oh my!

It turns out that the Vampire Nighthawks are surprisingly effective. The combination of deathtouch, flying and lifelink make them difficult to block and, conversely, great blockers. They provide great early game attackers to dishearten your opponent as your life total starts going in the "wrong" direction. One of the Grave Titans and the Wurmcoil Engine actually came out of boosters (Shocking, I know) and made their way in to provide some late game heavies.

Pyromancer's Ascension

I got to play some Magic: The Gathering with one of my co-workers for the first time in a while. He had forgotten to bring his cards, so I lent him some of my decks and the match-ups were: Red-Green Stompy vs. Aggro-Infect, Vampires vs. Red Deck Wins and Baneslayer-Lifelink vs. Pyromancer's Ascension.

The Baneslayer-Lifelink vs. Pyromancer's Ascension turned out to be the most interesting. I was piloting the Ascension deck and in the first game it played oddly aggro. I dropped a pair of Kiln Fiends, cleared the board with a Staggershock and then attacked for massive damage. This worked out just fine, but it's not really the kind of play you would expect from a deck with four creatures in it.

The second game was where things really started to heat up. Other that getting a Pyromancer Ascension down early, my opponent really dominated the early game. I tried to thin the field with some burn spells, but it got to the point where I didn't really have anything other than the Ascention and some land while my opponent had a pair of Vampire Nighthawks stairing at my throat: Not looking good.

I used Foresee to dig for a Staggershock to match the one in my hand. I bet on triggering the the Ascension, cast the Staggershock, and passed the turn. My dropped a Baneslayer Angel and swung. Ouch. I rebounded the Staggershock, cast the other one, pumped the Ascention and passed the turn. My oponent swung again. More ouch. I rebounded the Staggershock, and put that second counter on the Ascention. I was at five life, with no creatures. My opponent had thirty-something life, a Baneslayer and a pair of Nighthawks. Still not looking good, but I did have a fully charged Ascension. I cast Preordain betting that in four cards and two draws I could pull an Island to replace the one I tapped. I pulled a Lightning Bolt and an Island. I bolted the Nighthawks and killed the Baneslayer with a kicked Burst Lightning. I passed the turn, and it came back to me: I wasn't dead yet! The turns that followed played out as some combination of Call to Mind, Kiln Fiend and Lighting Bolt.

Moral of the story: Spells are better when you get to cast them twice. I clawed my way back from a pretty big deficit and it was terrifyingly fun to cast the same Lightning Bolt over, and over again. The darkhorse MVPs were Kiln Fiend and Staggershock. In the first game, Staggershock kept the board clear and stimulated my Kiln Fiends and in the second, it was key to triggering the Ascension; however, in both games, the Kiln Fiends proved instrumental in actually chipping away at my opponent's life total while my burn spells kept the board clear.

Here's the decklist (because this kind of post is screaming for one):

The more wealthy of our readers, will notice that Scalding Tarn is a drop-in replacement for the Evolving Wilds. Other than that, it's not too shabby for a deck with a grand total of eight rares. The Chandras Ablaze kind of felt like they were dead cards, but it will take more than two games to determine whether they need to be subbed out for something else.

Portal is Awesome!

Ever since Portal 2 was announced I've been thinking about playing Portal again and I finally got around to it this weekend. Verdict: Still Awesome.

It was lots of fun. If you havene't played it yet, you really have to. It's sublime. Now I'm even more excited for Portal 2... I mean, the prepulsion gel alone!

Krypto and 24

Daily WTF inspired me to implement a Krypto and 24 solver:

import functools
import itertools
import operator

class Node:
        def evaluate(self): raise NotImplemted()
        def print(self): raise NotImplemted()

        def __add__(self, other): return Add(self, other)
        def __sub__(self, other): return Sub(self, other)
        def __mul__(self, other): return Mul(self, other)
        def __truediv__(self, other): return Div(self, other)

        def __str__(self):
                return "<Node: {}>".format(self.print())

        def __repr__(self):
                return str(self)

class Binary(Node):
        def __init__(self, left, right):
                self.left = left
                self.right = right

        def evaluate(self):
                try:
                        return self.op(
                                self.left.evaluate(),
                                self.right.evaluate(),
                        )
                except ZeroDivisionError:
                        return float("NaN")

        def print(self):
                return "({} {} {})".format(
                                self.left.print(),
                                self.symbol(),
                                self.right.print()
                )

        def op(self, left, rigth): raise NotImplemted()
        def symbol(self): raise NotImplemted()

class Unary(Node):
        def __init__(self, op, val):
                self.op = op
                self.val = val

        def evaluate(self):
                return self.op(self.val)

        def print(self):
                return str(self.evaluate())

def BinOp(op, symbol, commutative = True):
        class inner(Binary):
                def op(self, left, right):
                        return op(left, right)

                def symbol(self):
                        return symbol

                @classmethod
                def iscommutative(cls):
                        return commutative

        return inner

Add = BinOp(operator.add, "+")
Sub = BinOp(operator.sub, "-", commutative = False)
Mul = BinOp(operator.mul, "*")
Div = BinOp(operator.truediv, "/", commutative = False)
Lit = functools.partial(Unary, lambda x: x)

OPS = [Add, Sub, Mul, Div]

def generate_expressions(nodes):
        if len(nodes) == 1:
                yield first(nodes)

        for x, y, op in itertools.product(nodes, nodes, OPS):
                isnew = not op.iscommutative() or id(x) > id(y)
                if x is not y and isnew:
                        yield generate_expressions((nodes - set([x, y])) | set([op(x, y)]))

def flatten(xs):
        for x in xs:
                try:
                        for y in flatten(x):
                                yield y
                except TypeError:
                        yield x

def first(xs):
        for x in xs:
                return x

def krypto(nums, solution):
        def issolution(expr):
                return expr.evaluate() == solution

        nodes = set(map(Lit, nums))
        expr = first(filter(issolution, flatten(generate_expressions(nodes))))
        print(expr.print(), "=", expr.evaluate())

if __name__ == "__main__":
        import sys
        args = list(map(int, sys.argv[1:]))
        krypto(args[:-1], args[-1])

My favourite part is that the core of the algorithm is 6 lines:

def generate_expressions(nodes):
        if len(nodes) == 1:
                yield first(nodes)

        for x, y, op in itertools.product(nodes, nodes, OPS):
                isnew = not op.iscommutative() or id(x) > id(y)
                if x is not y and isnew:
                        yield generate_expressions((nodes - set([x, y])) | set([op(x, y)]))

The parametric classes are also a reminder of just how badass python is:

def BinOp(op, symbol, commutative = True):
        class inner(Binary):
                def op(self, left, right):
                        return op(left, right)

                def symbol(self):
                        return symbol

                @classmethod
                def iscommutative(cls):
                        return commutative

        return inner

Edit

2010-05-16: Apparently Python comes with an implementation of cross product.