Sunday, July 31, 2011

A Better Hex Coordinate System

When I started working on Hexwar, I used the simple approach to displaying hexagons from the article Pixel Coordinates to Hexagonal Coordinates. The problem with this coordinate system is that it's difficult to perform functions for distance on it. I ended up walking the entire grid from the starting hex and making an array of distances. This proved faulty (due to my faulty walking algorithm) and I decided to move to the coordinate system similar to Clark Verbrugge’s Hex Grids. The major difference being that I want my hexes to point horizontally instead of vertically. (I considered using the three coordinate hex space model, but that'd require a change to a lot of my code.)

Explanation of Coordinate Systems

Understanding this, one must see there are three different coordinate systems at work here.

Hex Space

First comes the hex space. This is the game world's coordinate system. It is the one which all the game mechanics rely upon. It looks like :
Hex Space
                      __   5
                   __/D \__   4
                __/  \__/  \__   3      "Y" coord
             __/  \__/  \__/  \__   2
          __/A \__/  \__/  \__/  \__   1
       __/  \__/  \__/E \__/B \__/  \__   0
      /  \__/G \__/  \__/  \__/F \__/C \
      \__/  \__/  \__/  \__/  \__/  \__/
         \__/  \__/  \__/  \__/  \__/   5
            \__/  \__/  \__/  \__/   4
               \__/  \__/  \__/    3
                  \__/  \__/    2     "X" coord
                     \__/    1
                         0  
Such that A =(2,5); B =(4,2); C =(5,0); D =(5,5); E =(3,3); F =(4,1); G =(1,4)  

Array Space

Next is the array space. This is a translation of the hex space into an orthogonal space. I've made it the same as the original hex space I was using. It begins with (0,0) in the top left and extends positively to the right and down. It looks like :
Array Space

 __    __    __    __
/M \__/O \__/  \__/  \__
\__/N \__/  \__/  \__/  \
/  \__/  \__/  \__/  \__/
\__/  \__/  \__/  \__/  \
/P \__/R \__/  \__/  \__/
\__/Q \__/  \__/  \__/  \
/  \__/  \__/  \__/  \__/
\__/  \__/  \__/  \__/

Such that M =(0,0); N =(1,0); O =(2,0); P =(0,3); Q =(1,3); R =(2,3)
The reason for this space is that computer screens are square, not diamond shaped. The hex space as described above would be displayed such that a large percentage of the screen would be unused, or a large percentage of world would be unseen.

Screen Space

Finally is the screen space. This represents the pixels on the screen. This could also be a viewport space if I need to implement scrolling, but I currently want the entire map to appear on screen at once. This starts at (0,0) for the pixel in the top left and proceeds positively to the right and down.

Converting Between Coordinate Systems


Between Array and Hex Coordinates



Between Screen and Array Coords



Calculating the distance between two hexes

Finally for the thing that I did the switch for. This hex coordinate system allows a quick (compared to walking the whole map) calculation of distance between two hexes. It also allows for some Line-of-Site calculations, but I don't currently need those. Anyways, the code :


Oh well, that's it for now!

Thursday, July 28, 2011

Why JOIN ... USING clauses can be a bad habit

So first, let me say that I do like the JOIN ... USING clauses. They make straight forward joins look a lot better. The problem is if you get into a habit of using them and doing further join filtering in the WHERE clause. Most of the time, this is perfectly okay because the relational calculus handles things nicely. But what if your SQL becomes more complicated based on certain options? Let's look at an example. (This story is based on a subtle defect I had to work on today.)

Given two tables :
users
user_idSERIAL
some_flagBOOLEAN

user_attributes
user_attribute_idSERIAL
user_idFORIEGN KEY
nameVARCHAR
valueVARCHAR

Such that users have 0..n user_attributes (don't complain about this schema design, it's just for example).

You need a report that shows all users with a certain attribute, easy :


Bam! Done. Oh wait, the product owner wants an option to display all users that don't have that attribute set. Still easy :



...and done again! You're a master programmer. Of course, product owners are never happy. Now they want another option to display all users and that have that attribute or have some_flag set. That's easy too :



And done! I'm going home.
...
...
...
What do you mean the report isn't working?

So, because of "(name='CLOWN_ATTRIBUTE' OR some_flag = TRUE)" things aren't quite working right. The OR condition allows it to connect to any row in the user_attributes table that belongs to a user with some_flag set. While one can probably use some fancy SQL logic to make this still work with the JOIN ... USING clause, I find it much easier to :



Oh well, I know to look out for one more thing now.