Dwemthy’s Array in Java -
Refactored
By
Clinton
Begin June 15,
2008
Dwemthy's
Array is text based adventure game with a build-in coding challenge that is
particularly suited to implementation with a dynamic language such as Ruby. What
caught my attention was
Adrian
Kuhn's implementation in Java. Despite my love-hate (or like-hate)
relationship with Java, I'm always up for a coding challenge, especially when
it's Java vs. Ruby.
For the record, I'm curently a full-time Ruby on Rails developer and loving
every minute of it. But I've been a Java developer for 10 years as well, and so
I find it compelling to compare the two languages.
Now with MOAR Meta!
Java is not known for its meta-programming capabilities, nor is it a dynamic
language. In fact, Java is one of the most strictly typed and type safe
languages there is (despite its poor implementation of generics). Java is also
known to be a bit more verbose and "chunky" than most other languages. That
said, I think that much of this has to do with habits that are known as "best
practices" in the Java community. While there are some things that we can't
avoid (like checked exceptions), there are a lot we can do away with (JavaBeans
and a bunch of unnecessary design patterns). With some of the dogma removed,
Java can be quite terse and feature rich. Adrian demonstrated this in his
implementation.
While I think Adrian did a good job in general, I think there's a lot more
opportunity to be "more meta"...did I just say that? Oh well, let me explain.
The magic that Ruby has (that Java doesn't) is a little feature called
"method_missing". This is quite simply an impossible feature for Java to ever
have, as it is a statically typed and [fully] compiled language. There will
never be a way to call someObject.someMethodThatDoesNotExist() in Java, as
there's simply no interface (be it a class or an interface) to compile to. Ruby
on the other hand is dynamic. You can call whatever you want on any object (i.e.
send any message you want to any object) and Ruby will accept it. By default its
catch-all implementation of method_missing behaves much like Java -- it throws
an exception. However, when overridden, this method becomes powerful and allows
for some interesting behavior.
In the case of Dwemthy's Array, it basically allows an Array to behave as though
it were one of the items it contains. This is not easily accomplished with
Java.... at least not if you approach it as if Java were Ruby.
In his implementation, Adrian sort of side stepped the issue. He found a common
hook point that was conveniently called exactly when it needed to be to yield
the same behavior as the ruby implementation. This happened to be the alive()
method. Note that the Ruby implementation didn't have an alive() method,
it just checked "life <= 0" directly.
#Ruby def method_missing( meth, *args ) answer = first.send( meth, *args ) if first.life <= 0 shift if empty? puts "[Whoa. You decimated Dwemthy's Array!]" else puts "[Get ready. #{ first.class } has emerged.]" end end answer || 0 end
|
//Java
-- not meta programming, just
programming.
public boolean alive()
{
if (life
> 0) return true;
if
(array.length == 0) {
puts( "[Whoa. You decimated Dwemthy's Array!]"
);
return
false;
} else
{
life =
array[0].life;
strength =
array[0].strength;
charisma =
array[0].charisma;
weapon =
array[0].weapon;
name =
array[0].name;
array = Arrays.copyOfRange(array, 1,
array.length);
puts( "[Get ready. %s has emerged.]", this.name
);
return
true;
}
}
|
While this was convenient for this particular application, not every application
would roll over for a belly scratch so easily. So what I'm going to demonstrate
here, is how to implement this in Java, by making excessive use of strong,
static typing. I also wasn't terribly excited about Adrian's use of
Strings and Maps where Classes could be used, so I've made changes to make it
more type safe and object oriented.
The result will be closer to the Ruby implementation and a more generally
applicable design overall.
Creature Features
Java isn't dynamic. So what? Ruby isn't static! :-P So how can we leverage
the features of Java to allow it to more closely resemble the design of the Ruby
implementation? How can it yield benefits similar to that of the Ruby program
design? How do we not skirt around the fact that Java doesn't have the
method_missing feature?
Specifically, we're going to make use of:
* Annotations,
* Dynamic Proxies,
* Interfaces, and
* Inner classes
Let's start from the highest level, the Creature class -- the central class of
Dwemthy's Array. I think the goal of this exercise should not be to cut lines of
code or show off obscure features of the language (although it may do that). I
think the goal should be to build an API (or perhaps a DSL, to use the term
loosely) for creating a Creatures. You see, in a nutshell, Dwemthy's Array is
basically Pokemon for Programmers. You instantiate two or more Creatures and
have them duke it out. You generally control one creature, while the other
creature responds programmatically. Afer a round or two of character stats plus
some random numbers banging around, a victor emerges.
The star of the show is the Rabbit. He's your creature. Love him. Pet him. Look
at his code in Ruby and Java...
#Ruby class Rabbit < Creature traits :bombs
life 10 strength 2 charisma 44 weapon 4 bombs 3
# little boomerang def ^( enemy ) end
# the hero's sword is unlimited!! def /( enemy ) end
# lettuce will build your strength and extra ruffage # will fly in the face of your opponent!! def %( enemy ) end
# bombs, but you only have three!! def *( enemy ) end end
|
//Java
public class
Rabbit
extends Creature.Base
{
{
bombs =
3;
life =
10;
strength =
2;
charisma =
44;
weapon =
4;
}
private int
bombs;
@Trait public int
bombs() {
return
bombs;
}
// little
boomerang
@Command('^') public
void boomerang(Creature enemy)
{
}
// the hero's sword is
unlimited!!
@Command('/') public
void sword(Creature enemy)
{
}
// lettuce will build
your strength and extra
ruffage
// will fly in the face
of your opponent!!
@Command('%') public
void lettuce(Creature enemy)
{
}
// bombs, but you only
have three!!
@Command('*') public
void bomb(Creature enemy)
{
}
}
|
The classes here look almost identical. I deleted the method bodies, as
they were identical. Skeptics can download each version and check, but
it's not what we're interested in here.
I think both do a good job of describing the Rabbit in a fairly concise and
descriptive way. While the Java code is noisier, I think it documents
itself better. Indeed, the comments are mostly redundant in the Java code,
while the Ruby code would leave the reader guessing if the comments were
deleted. Notice the approach to implementing "traits" in each.
Also notice how the command keys are implemented. The commands are: ^/%*
These are the characters you enter on the command line to instruct your Rabbit
on how you would like him to maim the opponent.
Speaking of Opponents
Above you saw advanced definitions of creatures, including user key commands and
custom traits. Simple creatures, like our opponents
("oh
snap!"), should be simple to define. Here you can see a few:
#Ruby class IndustrialRaverMonkey < Creature life 46 strength 35 charisma 91 weapon 2 end
class DwarvenAngel < Creature life 540 strength 6 charisma 144 weapon 50 end
|
//Java
private static class
IndustrialRaverMonkey
extends
Creature.Base {
{
life =
46;
strength =
35;
charisma =
91;
weapon =
2;
}
}
private static class
DwarvenAngel
extends
Creature.Base {
{
life =
540;
strength =
6;
charisma =
144;
weapon =
50;
}
}
|
The noise is a little more apparent here, but it's not outrageous. It's
Java.
Reversing the Advantage
So far, other than some squiggly brackets, everything looks very similar and
this is becoming a really long and boring trudge through tedium. So where
do we see the difference? When do we show that Ruby sucks and Java
rules? Well, there is no sucking here. There's only different
approaches. The differences lie in the Creature base classes themselves,
and in the Array. The point to take away here is that these
implementations are very different, but yield very similar resulting API as
we've seen in the consumer examples above.
So here we go. The following is the Creature base class/interface.
#Ruby class Creature def self.metaclass; class << self; self; end; end
def self.traits( *arr ) return @traits if arr.empty? attr_accessor *arr arr.each do |a| metaclass.instance_eval do define_method( a ) do |val| @traits ||= {} @traits[a] = val end end end
class_eval do define_method( :initialize ) do self.class.traits.each do |k,v| instance_variable_set("@#{k}", v) end end end
end
traits :life, :strength, :charisma, :weapon
def hit( damage ) #...game logic end
# This method takes one turn in a fight. def fight( enemy, weapon ) #...game logic end
end
|
//Java
public interface Creature {
@Trait int life();
@Trait int strength();
@Trait int charisma();
@Trait int weapon();
void hit(int damage);
void fight(Creature enemy, int weapon);
void command(Character op, Object... args);
public static class Base implements Creature {
protected int life, strength, charisma, weapon;
protected Map<Character, Method> COMMANDS = new
HashMap<Character, Method>();
public Base() {
Class type = this.getClass();
for (Method m : type.getMethods()) {
Command command =
m.getAnnotation(Command.class);
if (command != null) {
COMMANDS.put(command.value(), m);
}
}
}
@Trait public int life() {
return life;
}
@Trait public int strength() {
return strength;
}
@Trait public int charisma() {
return charisma;
}
@Trait public int weapon() {
return weapon;
}
public void hit(int damage) {
//...
}
public void fight(Creature enemy, int weapon) {
//...
}
public void command(Character op, Object... args) {
try {
COMMANDS.get(op).invoke(this,
args);
} catch (Exception e) { throw new
RuntimeException(e); }
}
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Trait {
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Command {
char value();
}
}
|
The Java creature class is a lot longer, but
most of the length
difference can be credited to Ruby's shortcuts for defining properties (or
attributes as Ruby calls them). The Java code actually ends up
defining:
two annotations, an
interface and a base class. Thus the excessive typing
I promised you earlier. Despite its length, I think it's actually quite readable
and you get a sense of where we're going with some of the annotations and
meta-programming here. Whereas the Ruby class spends its time dynamically
defining classes, the Java class spends finding all of the Command annotations
and sets them up in a Map so that we can look up their corresponding
methods. Again, while the code is longer, the advantage over the Ruby
program is that the method name and the command are separated, which is both
more readable and a more universally applicable design. In a larger
application, nobody would suggest writing a Ruby app this way. But in the
case of the Java app, we're not really breaking any horrible rules (except maybe
for stuffing all of these entities into a single class definition for the sake
of this example -- the design stands though). The biggest disappointment
to me was that @Trait needs to be repeated because of the way that some of the
reflection works here. Lastly, you'll notice that we had to implement our
own command() method to act as equivalent to Ruby's built-in send() method.
Disclaimer: I did leave out a couple of Java methods like
Adrian's awesome toString() method and my autoCommand() runner which were added
strictly to make the Java game fun to play. They are not pertinent to the
discussion, nor part of the original design. And I also left out the game
logic in the fight() and hit() methods, as they are quite identical, and again
not what we're interested in here.
Enter the Dragon... into the
array.
With our creatures well described, we now come to the actuall challenge part of
the application. This is where the original Ruby authors challenged the
rest of the world to implement Dwemthy's Array, knowing full well that
method_missing was an ace up their sleeve. But we pulled a
fast one, and turned strong typing into an advantage. While we can't
intercept "missing methods", we can intercept ALL methods. And by tagging
our Traits we're able to intercept specifically the access of the traits of our
Creature.
#Ruby class DwemthysArray < Array alias _inspect inspect def inspect; "#<#{ self.class }#{ _inspect }>"; end
# This space intentionally left blank to line up the methods
def method_missing( meth, *args ) answer = first.send( meth, *args ) if first.life <= 0 shift if empty? puts "[Whoa. You decimated Dwemthy's Array!]" else puts "[Get ready. #{ first.class } has emerged.]" end end answer || 0 end end
|
//Java
public class DwemthysArray
extends ArrayList<Creature>
implements InvocationHandler {
private Creature current;
private DwemthysArray(Creature... creatures) {
addAll(Arrays.asList(creatures));
nextCreature();
}
public static Creature asCreature(Creature... creatures) {
Creature creature = (Creature)
Proxy.newProxyInstance(
Creature.class.getClassLoader(), new Class[]{Creature.class}, new
DwemthysArray(creatures));
return creature;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
try {
return method.invoke(current, args);
} finally {
Creature.Trait trait =
method.getAnnotation(Creature.Trait.class);
if (trait != null &&
current.life() <= 0) {
nextCreature();
}
}
}
private void nextCreature() {
if (size() == 0) {
puts("[Whoa. You decimated
Dwemthy's Array!]");
} else {
current = remove(0);
Kernel.puts("[Get ready. %s has
emerged.]", current.getClass().getSimpleName());
}
}
}
|
Once again we see some Java noise, but it's mostly construction logic, which is
the price we pay for using a Dynamic Proxy in Java. But that's a fixed
cost. Once implemented, if you compare the actual logic between the Ruby
code and the Java code, while quite different (almost opposite), they are about
the same length and achieve a very similar result.
The way the Java version works is that it proxies the Creature interface, then
delegates all method calls to the first object in its collection. It also
catches any Trait methods and implements the special event logic that manages
the death of creatures and possibly the ultimate demise of
Dwemthy's
Array!
And finally, we can see how the game is played...
#Ruby r = Rabbit.new
dwarr = DwemthysArray[IndustrialRaverMonkey.new, DwarvenAngel.new, AssistantViceTentacleAndOmbudsman.new, TeethDeer.new, IntrepidDecomposedCyclist.new, Dragon.new]
# Ruby's awesome, so it # can act like an operator!
r ^ dwarr
|
//Java
Creature rabbit = new Rabbit();
Creature dwemthy = DwemthysArray.newInstance(
new IndustrialRaverMonkey(),
new DwarvenAngel(),
new AssistantViceTentacleAndOmbudsman(),
new TeethDeer(),
new IntrepidDecomposedCyclist(),
new Dragon()
);
// Java uses command line stdio
while (rabbit.life() > 0 && dwemthy.life() > 0) {
puts(rabbit);
rabbit.command(getChar(), dwemthy);
}
|
Just so you're not surprised, you won't win. Your Rabbit will die over and
over while you enjoy the twisted darkness of it all. :-)
So now what?
I'm not sure really. This was quiet possibly the biggest waste of time in
my coding career, but it was ridiculously fun! I'm an RPG fan and this is
probably the closest I'll ever come to actually writing a game... so for that
I'm grateful. I hope in your case you take away a little creative insight
into how Java can look when it's not weighed down by a GoF book and a JEE
Spec.
So now go get the code and play the game! I've included a few sample
battle classes, the two main battles are
BattleOfDwemthysArray
and
BattleOfGrottoOfSausageSmells. But to demonstrate the
flexibility of the design, I've included
BattleOfTwoRabbits and
within each I've included a couple of comments to show you how to alter the
behavior, including letting
1000 Rabbits take on
Dwemthy's Array and how you can play as
Scuba
Argentine.
References and Thanks:
Cheers,