About once a year I go off with about 30 of my friends and go and play role playing games and board games for a week in a big house out in the country. This is kinda awesome, but as we’ve increased the numbers we’ve been having fun with the scheduling. Namely, we need to decide when the different games are happening (especially for the 6+ hour board games…), and a week quite frankly almost isn’t enough to cram everything in. Couple that with the actual scheduling being done literally at the last minute, and last year we ran into the limits of what can sensibly be done by hand.
But wait! Last year I did some work with OptaPlanner, which is a constraint satisfaction solver explicitly designed for this sort of problem. However, that would require writing a bunch of Java code, and I’m not that much of a fan of having to do that if I don’t have to. Although, I could try writing this in Clojure instead and leverage the magic of gen-class to do this right? Turns out the answer to that is: mostly.
Enter Herder, a web-based tool for doing just this. You define a convention over some set of days, add time slots per day, some people, some events (and say who’s coming along on those events), and it generates a schedule. You can also add some options, allowing people to only attend some days of the convention, or define multi-day events (we had a use case for something that runs for 5 sessions on separate days), or even have a preferred time slot. It tries to generate a “perfect” schedule i.e. no clashes, everything nicely spaced out, but also gives you feedback on what the problems are if some of the constraints had to be violated.
Behind the scenes, it’s a single page app using Reagent and ClojureScript for the frontend, talking over a mix of REST HTTP and websockets. This talks to the API section of the app in Clojure, which is mostly a CRUD app (using Korma for the SQL bits). When changes are made that could potentially alter the solution (fixing the spelling of someone’s name doesn’t change things, but adding a new event does), the API section tells the solver to generate a new solution (and then feed that back into the database for use by the CRUD app). The solver itself is almost entirely Clojure, except for two tiny little Java classes that define an abstract implementation of a generic interface, which apparently you can’t do in gen-class. I almost got stuck in a situation where I had a Clojure class that would be referenced by the Java code, which would then be in turn referenced by other Clojure code, but for once Java’s type erasure turns out to be useful, and I could just define the ref in the Java class as Object and everything was pretty much fine.
Early beta testing of this with last years data is now done, and I’m about to unleash it on the people going to this year’s event for further testing before potential live use later this year!