2010-01-31

Do the Wave...

My first abortive attempt at a Google Wave bot was an IRC <-> Wave bot that would let me look at IRC over Wave (or, more likely, Wave over IRC).

The second bot I've attempted actually works, though, so naturally I'm more proud of that one. Inspired in part by an Ars Technica article, my D&D group decided to investigate gaming over Wave. I still think that OpenRPG is a better overall solution, but in a pinch, Wave could work. So we added the bot that they used for the article and played around with it a little bit.

For those not familiar with how D&D die rolls work, I recommend D&D for Dummies, but the basics should suffice for this example: NdM indicates rolling N M-sided dice. For monopoly, you use 2d6 (two, six-sided dice). For D&D, the standard roll is 1d20, plus or minus some modifiers. From there, you build up more and more complicated expressions until you're a level 10 Rogue/Warlock with 22 Dex waving your +3 Dagger at the zombie and you roll up 1d20+5+6+1+3+1+3+1 vs Reflex (and yet still miss!).

But I digress. All those modifiers you add to a die should be cumulative and per total roll: 2d6+3 is two, six-sided dice, plus 3. The die rolling wave bot linked by Ars Technica treats the modifier as modifying each die, so you get two, six-sided-dice-plus-three, effectively doubling the modifier (1d6+3 + 1d6+3).

Well, I had a dice-roller before that kind of worked (from my IRC adventures), so I decided to take my work on version 2 of that and port it to Java. Why Java? Because I hate Python. And for a Wave robot, those are the only choices.

So, I loaded up my least favorite IDE, reminded myself why I hate Eclipse, switched to NetBeans, lost a hard drive (which means I owe the internet the obligatory "Back Up Your Data" blog post), forgot all about it for a couple days, installed NetBeans on a different computer, and settled in to write myself a bot. Ah, Java. Good times.

Normally, I'd use a Linux as my IDE of choice, but Netbeans and Eclipse both have plugins for deploying a project to Google App Engine (where all Wave bots call home), and I didn't want to have to worry about that part. Plus, Java is a library language, so integrated docs for the libraries is rather nice.

A wave robot is pretty simple, you register for events, and then write a handler for them. The handler in this case is a little compiler (yes, it lexes and parses the die roll into a syntax tree; I'm a bit of a masochist, apparently), and an "interpreter" that actually rolls the result. Borrowing from OpenRPG, it looks for a roll inside square braces and replaces the contents with the result:

[1d20+5+6+1+3+1+3+1] becomes [1d20+5+6+1+3+1+3+1 = (37)] (I rolled well). There's always more to do with it, but this is a good start for a weekend. To play with the bot, add die-roller@appspot.com to your Wave contacts and then invite it to your favorite dragon-slaying waves. The author asks only a modest share of the XP from any dragon slayed with the help of this tool.

Now for the ugly:

NOWHERE on the internet could I find this little "feature," so I'm going to warn people here. When replacing text in the Java API (TextView.replace()), Annotations get "shifted" by the difference in text length you're inserting. As do Range objects which completely enclose the area. For example:

Range r = new Range(0, 10); // a range from 0 to 1
textview.replace(new Range(1,9), "a bunch more text");
// r is now the range [0, 18] because 8 more characters were inserted

Unfortunately, this happens on Annotations that have already been added (in this jsonrpc call) as well, but incorrectly. They get shifted twice (or one-and-a-half times, or something...they always end up beyond where they should), so this code acts very strangely indeed:

while (!stack.empty()) {
  Range r = stack.pop();
  ...
  textview.replace(r, a_string);
  textview.setAnnotation(r, "style/color", "#336699");
  ...
}

The "correct" way to do this is to keep track of an offset and then shift the Annotations yourself:

int offset;

for(Range r: stack) {
  ...
  textview.replace(new Range(r.getStart()+offset, r.getEnd()+offset), a_string);
  textview.setAnnotation(new Range(r.getStart()+offset, r.getEnd()+offset), "style/color", "#336699");
  offset += a_string.length() - (r.getEnd() - r.getStart());
  ...
}

Obviously, the more Java way of doing this would involve several dozen more Factorys, but at least Google embraces Python and so doesn't do things the "Java way."

Oh, and back up your data.

:wq

No comments:

Post a Comment