Friday, August 29, 2014

Android Rect

While working on a performance-related redesign of Idamu Caverns, I came across some disappointing issues with the design of (issues which are shared with RectF in the same package).

If you're not familiar with the package, it contains a number of classes related to 2D drawing.  With Idamu Caverns being a 2D game, it makes heavy use of the classes in that package.

To condense the story into its essential parts: the Rect class is used extensively to communicate drawing information to other classes and methods for the purposes of 2D rendering.  For that reason, Rect objects are peppered throughout the drawing code of Idamu Caverns.

The first issue I came across was a subtle bug in my code that took me far longer than it should have to isolate and resolve.  In the end, it turned out that I was accidentally inverting the top and bottom of a Rect object used to draw the parts that weren't working right.

The Rect object is fussy about it's parameters.  This is only slightly hinted at by the documentation, which states that it doesn't check them for validity (i.e. that top is a smaller value than bottom).  What is hidden behind that comment is the fact that Rect will allow you to set invalid parameters without any errors or warnings.  But furthermore, when you try to use that invalid Rect anywhere, instead of generating an error or warning, you'll just get the wrong result.

The first issue is less of a bug and more of a missed opportunity.  While it's understandable that Rect is designed to work with the coordinate system native to the screen, it would not have been that difficult to make it work with other coordinate systems, such as the Cartesian coordinate system that's rather popular.

The second issue, however, I'd classify as a bug.  If you have a set of parameters that have an important relationship between them (such as the top/bottom situation) it's expected that the internal workings of the class will enforce those relationships, or at least give you a clear warning when you've violated them and things aren't going to work right.

I was pretty upset when I finally identified the problem.  Upset because I trusted the Rect class to be reliable, and had spent a lot of time looking everywhere else in the code on the assumption that if the parameters were invalid I would have gotten an exception.

But my disappointment in the design didn't stop there.  I decided to do the right thing and subclass Rect into a ScreenRect class that would validate it's parameters and provide the type of sanity checking that would make my development more efficient.  Since ScreenRect would subclass Rect, I could use it in any of the android methods that required a Rect.  Object oriented programming at it's finest, right?

Except that the Rect class is final and therefore can't be subclassed.  I spent far too much time trying to understand why anyone would make the Rect class final, as I can think of a number of theoretical reasons to subclass it.

As a result, I had to make the ScreenRect class do all of the work I wanted, while providing a getRect() method to extract a Rect from it when the underlying API requires it.  Feels like really hacky programming to me.

All said, it feels like Rect wasn't really designed so much as thrown together without any real thought.  If anyone knows a valid reason why Rect has all these shortcomings, I'd be interested to hear it.

Monday, August 25, 2014

== equals()

I saw this in a code example somewhere earlier this morning and after thinking about it for a while decided to write a short post on it.

if (stringVar1 == stringVar2 || stringVar1.equals(stringVar2)) {

I've seen this other places, by other developers.  Discussing this is interesting, because depending on your level of expertise, you might have many reactions to this code snippet:

The inexperienced Java coder will wonder why you'd use the .equals() at all ...

Someone with at least a little experience knows that two String variables can be different and yet represent the same sequence of characters, which is the purpose of .equals().  That programmer might be thoroughly confused as to why you'd ever use == when it's not guaranteed to work.

A slightly more experienced programmer will probably assume that the == is there as a performance tweak, to avoid the overhead of the call to .equals() if the two variables happen to point to the same String object.

But the really experienced programmer will note that the whole thing is just stupid.  Why?  Because the first thing that String.equals() does is test to see if the two variables point to the same object, so the alleged performance tweak is insignificant.  Additionally, since the actual chance that both of those variables point to the same object is very slim in most use cases, this construct is probably making the code slower most of the time.

Programmers should know this.  Beyond simply the String implementation, the Java documentation specifically recommends that the first thing that an equals() implementation should do is test for the same object reference.  It's widely accepted best practice ... and most IDEs will generate that code for you if you right-click and then hit whatever button automatically generates equals() and hashcode().  So why do people still do this?  I can't really be sure.

This isn't a terribly common construct, but it's common enough that it makes me worry about how much young coders "code by example" and how dangerous it is that there are so many bad examples out there to copy from (despite the number of good examples also extant).

The reason it stuck in my head is that shortly after seeing it, I saw another bad code example in the Android API docs.  The docs for the Android TestCase class recommend that you use assertTrue(result == 5.0) instead of using assertEquals(5.0, result).  Of course, anyone who's done much with unit testing knows that the former will provide a cryptic, unhelpful message in the event of a failure; whereas the latter will at least tell you what was expected and what was actually achieved, which starts you down the road to solving the problem.  What's an even greater irony in this case is that the example is supposed to be of assertTrue(String, boolean), thus hammering home the point that unit test failures should be verbose enough to be helpful.

Monday, August 18, 2014

Challenges with Idamu Caverns and iterative development

I knew, going in, that not everyone was going to understand quick iterative development.  In fact, when I started Idamu Caverns, I listed the ability to communicate what I was doing as one of the biggest challenges.

Things seldom turn out the way I expect.  For starters, the number of people who understand the "work in progress" side of things seems to be just about everyone, and the number of people tolerant of it, and even enthusiastic is much higher than I anticipated.

It seems, however, that explaining is still the biggest challenge, just not in the way that I expected.  I guess what's actually happening is logical, and I should have expected it.

People are looking at the graphics and their response is generally, "I understand that the graphics aren't very good because the game is a work in progress."  But when faced with missing pieces of functionality, people seem to get confused, or assume that there is a bug.

My earlier post about swords in backpacks is a good example.  Part of the problem is that the inventory system, like everything else in the game, is incomplete.  I expect that at some point I'll have inventory slots for a belt and scabbard so that you have a logical place to put long blades, but the work to create those bits just hasn't been done yet.

There are other challenges in this same vein.  People have complained that there aren't enough monsters, aren't enough weapons and treasure, etc, etc.  All of these complaints are completely valid, but are not really problems with the game so much as additional indications that it's still a work in progress.

Overall, I guess the answer is to simply keep communicating that fact to people.

Of course, because I'm me, I have to look deeper into things.  I have to see if I can extract some meaningful life lesson from things like this.

I spent last week at Gen Con.  I'm not the most serious RPGer in existence, but I enjoy occasionally putting my fate in the hands of a terrible accent and a good die roll.  I managed to get myself into a couple of Timewatch sessions, and watching the other players got me thinking about how gamers have changed over the years.

It seems that not too long ago, sitting down with gamers resulted in them constantly trying to find a way to do whatever they wanted.  "What do you mean kicking dirt in the Orc's face only gives him a -1?  He should be completely blind!"

Timewatch allows (and actually encourages) you to pull Bill and Ted-style shenanigans, such as "Oh?  My character is about to die?  Except that a future version of myself comes back to save her at the last minute!"  And it has game rules to determine whether such a stunt will succeed (hint, it usually does, that's the point of the game!)

I was disappointed in how seldom players took advantage of that ability, and I include myself in that generalization.  Looking back, it felt like doing that sort of thing would be against the rules.  Is that what the other players were thinking as well?  Or was it just a case of new players taking a bit of time to warm up to the system?

Either answer is interesting, but when I look at the lack of feedback and involvement by Idamu Caverns players, I don't know if it matters.  Whether they're unsure of the rules, or simply not warmed up to the idea, it's my responsibility to encourage feedback from them.  I think that's going to take a greater role in game development over the next few weeks.

Tuesday, August 12, 2014

A bug or not a bug?

It's happening in a manner that could be described as "periodic."  Someone contacts me about Idamu Caverns for Android to tell me they've found a bug: they can't put their dagger in their backpack!

"Bug" is in interesting word, as it has different meanings to different people, and the exact definition is (in my experience) cause for much strife programmers and users.

  • In the programming sense, a bug is when software is behaving differently than designed.
  • To most users, a bug is when software is not behaving in a way that they expect or understand.

It's a subtle but important difference.  In the case of "daggers don't fit in backpacks," there is no bug in the programming sense.  Think about it.  Take a real-life backpack, then stuff a naked blade the length of your forearm into it.  What's going to happen?  If you're lucky, it will only tear a single hole and you'll notice before you lose anything, or before the blade dangling out of your pack slices you in some way.  So, yes, the behavior was completely intentional.

However, it is absolutely a bug in the second sense.  Why?  Because every other game out there allows ridiculous stuff to go into backpacks.  Open a typical virtual backpack and you find full-length swords, battle axes, even entire suits of armor!  None of this is liable to fit inside a real backpack at all, let alone leave room for anything else or be light enough to carry.

But the industry has set an expectation, and deviating from that expectation is considered a bug.

There are logical reasons for this: Games are not real life, and trying to make them too much like real life causes them to be tedious and unpleasant in all the ways real life is tedious and unpleasant.  Having an unreasonably large backpack allows for additional game dynamics (what I call inventory grinding).  And, in general, not having a practical way of carrying something frustrates gamers and makes the game less fun.

The thing that is amazing to me is that the game behavior is the expectation, so much so that assuming that the game might, in any way, mimic reality isn't really considered.  Instead, the gamer assumes that the programmer made an error.  It's an interesting psychological observation.

I did make an error.  Whether allowing daggers to go in backpacks is an error is a topic for debate, but that's not the error I'm talking about.  The error was in providing a generic message to the gamer, "You have nowhere to put this."  From a software perspective, that's good design, any time the inventory code can't accommodate you, it provides this message (special cases in software create complex and error-prone code and should be avoided).  From a usability standpoint, it's terrible design, as generic error messages make users believe that something happened that wasn't intended to.

Really, there are three possible solutions to this problem:

  1. Just allow the player to store naked blades in the backpack.
  2. Provide a more specific failure message: "You can't do this because putting a naked blade in a backpack will tear and destroy it."
  3. Let the user put the blade in the backpack, then when they take a step, have the backpack slice open and all their stuff spill out.
#3 sounds hilarious, but in reality it would just frustrate players.

It got me thinking, though ... if I took the code for Idamu Caverns and frameworkized it (is that a word?) I could then make another game in which silly behavior is rewarded with silly results.  In addition to #3, if you put crazy stuff (like a full suite of plate mail) into your pack, it would work fine; except that a few steps later your back would give out and you'd have to pay 5000 gold to a chiropractor to get rid of your -1 penalty on all future actions ...