Stop Coding and Start Drawing

The Elm #gamedev community on Slack has started having monthly game jams. That sounded fun so I built a pirate-themed tower defense game for the February jam.

This being my first real foray into gamedev, I got stuck on multiple occasions. Figuring out how to properly do the coordinate math was particularly frustrating. Normally when I’m stuck on a big problem, I try to break it down into smaller sub-problems. I was still stuck, so I stopped coding and tried to represent my problems visually.

Coordinate systems and vector math

A quick aside on vectors. Any point in a coordinate system can be described as a combination of direction and distance from (0,0). These “vectors” are usually drawn as arrows on a graph. They can be added, and subtracted by drawing them head to tail. Check out this vector tutorial to learn more.

problem solving with vectors

I had some path-finding logic that generated a series of waypoints, giving me a path that safely navigated around the islands to get from a pirates current position to the target destination.

The hard work is done right? Now all I need to do is to keep moving the pirate to the next point in the path. Turns out it isn’t quite so simple. Consider the following scenario:

  1. I have a pirate at point A
  2. The pirate is moving towards point B
  3. The pirate is not moving fast enough to reach B in this tick
  4. The pirate can only move distance towards B this tick.

The question is “Where is the pirate at the end of the tick?”.

After drawing out the initial two vectors A and B, I realized that travelling the full distance between both points was equal to A - B. What I really wanted was a vector C whose direction was the same as A - B, but whose magnitude was distance.

Now I’d managed to calculate this value before. However once I drew it, I realized that C alone pointed to an obviously wrong position. What I really wanted was A + C. Problem solved!

Neighboring tiles

Another problem I ran into was finding out which map tiles were adjacent to each other. I allowed movement in 8 directions: up, down, right, left, and diagonally. Thus, most tiles have 8 neighbors. However, edge and corner tiles have fewer because the map does not wrap around. Which neighbors does each kind of tile have?

Time to go to the drawing board!

neighbors for each class of tile

Turns out there are 9 possible scenarios for neighbors:

  • 4 corners, each with 3 different neighbors
  • 4 non-corner edges, each with 5 different neighbors
  • All other tiles who have the full 8 neighbors

Based on this information I was able to naively calculate a tile’s neighbors.

Maps and tile systems

I used Tiled to turn this image into a tile set. I built a map out of those tiles which Tiled exported as a list of tile ids. I modeled my Map type in a similar fashion, each layer (water, land) was stored as a list of tiles.

type alias Map =
    { land : List Tile
    , sea : List Tile
    , width : Int
    , sheet : TileSheet
    }

As long as I know the width (in tiles) of the map, I can then use math to position any tile. It turns out the math is really simple. Given a tiles position in the list (tileNumber) and the mapWidth in tiles, X and Y positions can be found via modulo (%) and integer division (//) respectively.

  • x = tileNumber % mapWidth
  • y = tileNumber // mapWidth

To convert those numbers into pixels, just multiply by the side of a tile (in pixels). In my case, 64.

However, my math wasn’t quite right. I was having to add or subtract 1, seemingly arbitrarily, in various calculations to get things to show up in the right place on the screen.

Again I was stuck. So I stopped coding and started drawing.

one-based-map

Looking at the (X, Y) pairs, I noticed some weird patterns:

  1. In each row, the X values went 1, 2, 0
  2. In each column, the Y values went 0, 1, 2
  3. In the last column, the Y values went 1, 2, 3

Something was off. I played around with the formula for finding X and Y and eventually found that subtracting 1 from the initial tile number before doing the divide or modulo operation gave me much more reasonable coordinates. Hmmmm… 💡💡💡 then the lightbulbs turn on! My list of tile numbers starts at 1. If I subtract 1 from all those numbers, I’d get a zero-based list. I start drawing again

zero-based-map

Look at the symmetry! Now positions go from (0,0) to (2,2). X and Y values stay consistent within a given row or column. I make a few changes in the game and now the math works out perfectly without needing to mess around with offsets. Zero-based indexing FTW!

Conclusion

When you’re stuck on a programming problem, the best way to get unstuck is often to try to express the problem in a different medium. Even just switching from coding a problem to verbally explaining can be a huge help, hence the imfamous rubber duck debugging technique.

Using a visual medium can yield even better results. This can be via good old-fashioned paper and pencil or in some digital drawing tool. I often use draw.io. The key thing is that you are representing your problem visually instead of in code structures.

So the next time you get stuck, move away from that editor, stop coding and start drawing!