April updates: hex grid guide, iteration

One of my goals this year is to iterate more. I have a tendency to "launch" a page and then move on to the next topic. Instead, for the first few months of the year I decided I would go through my existing pages to:

  1. figure out how the content could be better (explanations, diagrams, user interface, little details)
  2. figure out how the tech could be better (load time, animation code, diagramming code, code structure)

Over the past 6 weeks I've been working on my guide to hexagonal grids. I first worked on the tech, switching from an imperative style to a declarative style, and also switching to a library with automatic dependency tracking. Then I worked on content updates, some of which I described in my previous blog post. In the past week I've been going through my list on trello and implementing more content updates:

Labels: ,

April updates: hex grid guide

“We can rebuild it—we have the technology.”

Over the past few weeks I've been reimplementing my guide to hexagonal grids. I'm generally not a fan of rewrites that have no end-user benefits but there are lots of improvements I want to make to the page, and the convoluted code was making it harder to make the changes I wanted. I ended up spending 60 hours on this, reduced the number of lines of code from 2400 to 1400, and reduced the total Javascript sent to the browser (gzipped) from 85k to 54k. A large part of this was rewriting the diagrams to use Vue.js instead of D3.js. As much as I love D3, there's not much on the hexagon guide that benefits from it, and I ended up using it as a nicer JQuery. In a previous blog post I described wanting dependency tracking. That's why Vue.js worked well for this page. I think React would've been a reasonable choice as well, but on this page Vue fit my needs better.

Labels: ,

March updates: lots of small things

One of my goals this year is to publish in smaller chunks instead of trying to do everything in "one big launch". This is what I've been working on over the past few weeks:

Using Vue with Canvas

In my last post I had said I was trying out some libraries to automatically track dependencies for me. The one I'm playing with right now is Vue.js. It can track dependencies between data and the HTML/SVG elements. For example if I write SVG:

<circle :cx="center.x" :cy="center.y" r="30"/>

and I have it hooked up to data:

data: {
  center: {x: 100, y: 150}
}

then Vue will remember that the circle depends on those x and y properties. If I modify the data with center.x = 75; Vue will detect this and update the circle. This is quite convenient!

Labels: , ,

How I implement my interactive diagrams, part 2

In the last post I described how I structure my interactive diagrams and gave some examples. In this post I'll describe what I'd like to do for future diagrams.

Flow diagram: controls → input → algorithm → output → visualization

When I started writing interactive diagrams I used d3.js, which is a great visualization toolkit. At the time I thought data visualization was the hardest part of what I was doing, and I needed a library for that. Since then, I've seen other libraries — React, Riot, Ember, Mithril, Intercooler, Marko, Vue, Aurelia, Ractive, Rax, Svelte, MobX, Moon, Dio, Etch, Hyperapp, S/Surplus, Preact, Polymer, lit-html, Elm, and many more — that have given me different ways to look at the problem. I've realized that the visualization isn't the hardest part of what I'm trying to do. There are two two big problems I want a library to help me with:

  • How do I update the visualization?
  • Which algorithms do I need to run again?

Labels: ,

How I implement my interactive diagrams, part 1

I have an interactive tutorial about making interactive tutorials, showing how I made my line drawing tutorial. I wanted to describe how I made interactive diagrams in several other pages. The flow is:

Flow diagram: controls → input → algorithm → output → visualization

The controls and the visualization are what the user sees. The input, algorithm, and output are running behind the scenes.

Labels:

Updating C++ code on the A* implementation page

In my articles I have to make a tradeoff between making things approachable and including all the little details you might need in a real project. I tend to err on the side of making things approachable, but that means people can get frustrated when trying to translate these topics into real code.

For my A* pages I have a “theory” page that describes the main concepts but doesn't go into detail with code, and then I have a separate “implementation” page that shows more of the code. I included some C++ code that is abstract and reusable, but I've been unhappy with it. I decided to simplify it a little bit:

  • I dropped the template parameter for SimpleGraph because it's only ever used with char.
  • Even though Location is a property of the Graph, the templated code for this is verbose and distracting so I made Location a separate template parameter.
  • I switched from unordered_set and unordered_map to set and map; in practice you might prefer a hash table or an array, depending on the location type, but it's hard to pick one of these that's best for all cases.
  • I dropped some of the reference and const parameters.
  • I switched from set.count() to set.find() to test membership.
  • I wrote out explicit types instead of using auto.
  • I used std::xyz instead of using to omit the std:: namespace.

I have mixed feelings about most of these, and went back and forth a few times before deciding I'm overthinking it and just going with my gut.

Before:

template<typename Graph>
void dijkstra_search
  (const Graph& graph,
   typename Graph::Location start,
   typename Graph::Location goal,
   unordered_map<typename Graph::Location, typename Graph::Location>& came_from,
   unordered_map<typename Graph::Location, double>& cost_so_far)
{
  typedef typename Graph::Location Location;
  PriorityQueue<Location, double> frontier;
  frontier.put(start, 0);

  came_from[start] = start;
  cost_so_far[start] = 0;
  
  while (!frontier.empty()) {
    auto current = frontier.get();

    if (current == goal) {
      break;
    }

    for (auto& next : graph.neighbors(current)) {
      double new_cost = cost_so_far[current] + graph.cost(current, next);
      if (!cost_so_far.count(next) || new_cost < cost_so_far[next]) {
        cost_so_far[next] = new_cost;
        came_from[next] = current;
        frontier.put(next, new_cost);
      }
    }
  }
}

After:

template<typename Location, typename Graph>
void dijkstra_search
  (Graph graph,
   Location start,
   Location goal,
   /* out */ std::map<Location, Location>& came_from,
   /* out */ std::map<Location, double>& cost_so_far)
{
  PriorityQueue<Location, double> frontier;
  frontier.put(start, 0);

  came_from[start] = start;
  cost_so_far[start] = 0;
  
  while (!frontier.empty()) {
    Location current = frontier.get();

    if (current == goal) {
      break;
    }

    for (Location next : graph.neighbors(current)) {
      double new_cost = cost_so_far[current] + graph.cost(current, next);
      if (cost_so_far.find(next) == cost_so_far.end()
          || new_cost < cost_so_far[next]) {
        cost_so_far[next] = new_cost;
        came_from[next] = current;
        frontier.put(next, new_cost);
      }
    }
  }
}

The code isn't as fast but I hope it's a little easier to read. I added a section at the end with the faster code.

My goal is to show how the algorithms work, and not to write reusable library code. That's not what I'm good at. Whenever I try to show code, I get into these messes. I hope the code I have is a reasonable starting point for anyone who's writing their own instead of using a library.

This year I'm trying to iterate more and publish things more often. Take a look at the updated A* implementation page and let me know what you think. I'll add suggestions to the Trello page.

Labels:

Updating mapgen2

Back in 2010 when I wrote a procedural map generator based on Voronoi polygons instead of Perlin noise, the goal was to produce volcanic islands with a variety of monsters. I had started with Perlin-based height maps but got too much variety in the maps. I decided to use Perlin noise for deciding land vs water, but use other algorithms for everything else. I wanted the players to start on the beach and walk uphill, meeting other players who started elsewhere on the beach and were also walking uphill. I wanted to always produce islands with mountains in the center, so that the players on any beach would travel the same distance to get to the top. The maps were intentionally unrealistic but they were designed to satisfy the game design constraints. I made a Flash demo to accompany the article.

Labels:

Thinking about writing

I'm looking again at traffic to my site and how I write. A few years ago I realized I had a "ratchet" problem. My first articles were less polished, and I felt good publishing them. When I got better at writing, my newer articles were more polished, but I would hesitate to publish. I didn't want to publish until I got everything just right. And as a result I've been publishing less and less the last few years.

Why didn't I want to publish until I got everything right? I didn't want a traffic spike to occur and for lots of people to see my unpolished page:

Pageviews for my hexagonal grids page

The trouble is that all the metrics shown in the charts make those traffic spikes look very important. But this cumulative sum chart makes those traffic spikes look unimportant:

Cumulative pageviews for my hexagonal grid page

The spikes feel good. HEY I got on reddit! But they don't seem that important in the grand scheme of things. Most of my visitors come from day to day visits, not from the spikes. And that means my trying to make sure everything's polished before I put it up may be unnecessary. A friend of mine says I should treat my pages like wikipedia. The cumulative traffic graph supports that.

A few years ago I gave myself an “out” by having "x" pages that were less polished. They were blog entries. They were “fire and forget” pages that I would publish, unpolished, and not update. It worked. I published a lot more. I really liked publishing every few weeks instead of every few months.

The trouble was that it was too easy of an out. I ended up publishing almost everything as "x" pages the last two years, and I think it took away from my regular pages. In particular, the "x" pages let me get away with not iterating. I didn't update them after I posted. And they didn't get better. And then they got very little traffic.

If my pages are like wikipedia I should be putting up incomplete unpolished pages earlier and then refining them over time.

So this year I want to publish more often but more importantly iterate more after I publish. I can treat the early viewers as beta testers. It's the “launch early” philosophy from startup culture (minimum viable product, etc.). I tell other people to do this but I haven't been practicing what I preached.

Take a look at my Trello page to see the kinds of things I want to work on.

Labels: