10 Oct 2025
For the past couple of years I have been running a music league at work. All of the hard work is done by the music league app, all I have to do is come up with 10 themes and set up the timing schedule for players to submit songs and vote. Then, each week, players have two days to think of and submit a song for the weeks theme (e.g. song with a colour in the title), and five days to vote on the playlist of songs. Players get 10 upvotes and 3 downvotes to to assign to the songs as they see fit, although these numbers can be configured by the league admin. The rounds in the latest league were:
- 🤝 Best song with feat.
- 🐯 Song with an animal in the title
- 👩🤝🧑 Bands with siblings song
- 🥫 Best product placement/use of a brand name
- 🍦 Answer the question with a title: Why are you sticky?
- 🪦 Least appropriate for a funeral
- ⛵ Sea themed
- 💃 Just dance: songs that instruct the listener to dance
- ♻️ “In the style of” (cover that’s not in the original genre)
- 💎 Deep cut - lesser known song from a big band
At the end of 10 weeks the winner is declared, and they receive the pass the aux cable trophy:

I also crunch a load of stats for the music league. The code for the stats can be found here, and includes voting correlation matrices, genre correlation between players, song ages etc…

08 Oct 2025
I recently came across the excellent work done by the National Bass Directory. A pdf of pubs serving Bass, updated monthly with tip offs from regular drinkers (Bassketeers). However, no one had gone to the trouble of putting them in a Google map layer for easy navigation. This was fairly easily remedied. You can find the map here, and the code that made it here.
The code runs every morning at ~9am (Github cron jobs are dependent on when a runner becomes available) and tags me in a comment if there are any changes to the pub locations csvs. If there are, I upload them manually to the google map layer. A future job may be to automate this entirely, but I think I will need to host the data somewhere myself.
The Bass directory adds several useful bits of information, whether it is guest or permanent, when it was last sighted, the pouring method (banked bass🤤) and any other notes. Visually the map separates permanent and guest locations.
Permanent bass symbol |
Guest bass symbol |
 |
 |
Suggestions for improvements on the map/production process welcome! If you have a Bass sighting near you that’s not on the list report it on the Facebook group or in the comments section of the latest Bass directory.
11 Jul 2021
It is common for the Guardian cryptic crossword to include a theme, sometimes clearly referred to, sometimes waiting to be discovered as the answers are filled in. For good examples see Maskarade. When I decided I wanted to set some themed crosswords I didn’t fancy spending ages filling out a grid with theme words by hand, let alone checking that once a theme word was in place, the rest of the grid could still be filled. As I couldn’t find a program to achieve this goal I wrote one.
It is written in Python, with a TKinter GUI to help with filling out the rest of the grid once as many theme words as possible have been inserted. The best way to look at my current method is through an example. Take a theme of chocolate bars (in no particular order):
MARS |
DOUBLEDECKER |
DAIM |
BOUNTY |
MILKYWAY |
YORKIE |
GALAXY |
TWIRL |
AERO |
BOURNEVILLE |
FUDGE |
LION |
TOPIC |
PICNIC |
PENGUIN |
BOOST |
CHOMP |
TOFFEE |
CRISP |
CRUNCH |
NUTS |
SNICKERS |
We will refer to these as the theme dictionary from now on. How many can be fitted into a crossword grid, while still allowing the rest of the grid to be filled out with words.
Grid Choice
The first question is which grid? It is tempting to think a custom grid could be made for any given set to optimise the number of fitted theme words. However, there are constraints on what a grid can look like. They must have some form of symmetry, at least two-fold rotational. The reason for symmetry is historical and uncertain, but a symmetrical grid is certainly more pleasing to look at. In fact, the Guardian uses a set number of grids (~60 when not reducing for handedness/90 degree rotational symmetry).
Limiting ourselves to these ~60 grids, it makes sense to only consider grids which have the most spaces matching the theme dictionary word lengths. Let’s re-arrange the theme dictionary by word length using a defaultdict
.
4 |
MARS, DAIM, AERO, LION, NUTS |
5 |
TWIRL, FUDGE, TOPIC, BOOST, CHOMP, CRISP |
6 |
BOUNTY, YORKIE, GALAXY, PICNIC, TOFFEE, CRUNCH |
7 |
PENGUIN |
8 |
MILKYWAY, SNICKERS |
11 |
BOURNEVILLE |
12 |
DOUBLEDECKER |
Then score each grid by multiplying the number of positions with length n by the number of words of length n in the theme dictionary. This is an easy way to find the grid with the most ways to fit the theme words in, increasing the chances of finding theme words which interlock with each other, increasing the density. A good match is shown below, with plenty of spaces for 4, 5, and 6 letter words.
Although in the actual method it is good to look at the top 10 grids, otherwise the trend will always be towards grids with the most possible spaces.
Permutations
How many possible ways are there to fit the theme dictionary into the grid? We can use permutations from combinatorics, nPr. As an example, let’s say we have two theme words which can fit into four possible positions in the grid. There are 4P2=12 permutations if both positions are filled, plus 4P1=4 permutations if only one positions is filled, plus the possibility of no positions being filled, for a total of 17 permutations. That last permutation may sound trivial, but it is relevant when filling out a whole grid, although one can be subtracted at the end, as an entirely empty grid is definitely not of interest. Also, if the above example were reversed and there were four possible theme words and two possible spaces the number of permutations remains the same. If we have a words and b positions of a given length, then the total number of permutations for that length is
perms = 0
for i in range(min(a, b) + 1):
perms += nPr(max(a, b), i)
However, when we iterate over all of the length words/positions which are available the permutations are multiplied together instead of added. For the previous example grid this gives
length | words | pos | combs
-------+--------+--------+--------
4 | 5 | 6 | 1237
5 | 6 | 8 | 28961
6 | 6 | 4 | 517
7 | 1 | 4 | 5
8 | 2 | 4 | 17
-------+--------+--------+--------
TOTAL | 1574318946365
There are ~1.6 trillion possible permutations. Of course, many of these would be invalid due to letter clashes, however a first approach would require checking them to find these clashes. This doesn’t take into account checking if the grid is still fillable from a much larger dictionary of words/phrases.
Fitting
Programs to fill crossword grids can work by letter or by word. Working by letter makes sense when using a large dictionary, as individual letters at intersecting points determine the number of possible words which will fit. However, in the case of a small dictionary it makes the most sense to fit by word. Although, optimisations can be made by caching which letters have already been tried at intersecting positions.
10 Jul 2021
ALTA is a device for freeze/thaw cycling samples. In my case, usually water and a suspension of K-feldspar, a mineral dust which is an efficient ice nucleator. It is a cheap and simple device to make, and requires no human interaction once it has been set going, making it an ideal device for ice nucleation experiments.
Code, a parts list and instructions on how to put it together can be found on the (now defunct) github page.
09 Jul 2021
The pH of the medium a plant is grown in can have a large impact on its growth. This device is a simple pH controller for a hydroponic plant set up.
The brain of the operation is a PyBoard microcontroller. Any microcontroller would do, but I prefer them to your typical arduino uno/nano for several reasons. First I am most comfortable in python, and can get more done quickly, second it has a built in SD card for data storage, third, and most importantly, it has a REPL. The REPL allows instance access to the peripherals, making prototyping, testing and bug-hunting much easier.
Parts List
- PyBoard 1.1
- Peristaltic Pumps x2
- pH-meter
- L298N Stepper motor driver
- Wires
- Power supply (~10V)
- LCD screen (optional)
- Buttons (optional)
- DHT11 Temperature and humidity sensor (optional)
I found all of these parts on ebay for a total less than £50. I have avoided exact part names here because they don't matter, but feel free to send me an email for further guidance. To keep things neat you will also want a box to put them in, I 3D printed one, but any box you can cut some holes in will do.
The screen and buttons aren't essential, however, they are useful for checking it is working as intended, and allowing users who can't code to keep the pH meter calibrated. I also included a cheap and cheerful temperature/humidity sensor, so the data can also be tracked along with the pH.
The L298N stepper motor driver is an absolute workhorse. It allows the whole set-up to be powered from one supply, as it will step down 10V to 5V to run the pyBoard, as well as allowing control of the higher voltage peristaltic pumps. In terms of putting the whole thing together I would suggest an incremental approach. Add each component one at a time, and use the REPL to check they are working as expected. As can be seen below the wiring can turn into a bit of a rats nest, so adding them one at a time ensures no crossed wires. The pin outs can all be found in main.py
.
The code in the github link above should be used more as a guideline for your particular set-up. Test everything thoroughly before you actually use it! One final note: I would recommend putting freewheeling diodes across your peristaltic pumps.
Calibration
You can calibrate the pH meter with just two points, however I wanted to do three to confirm the response was linear in the region of interest. The result is shown in the graph below (sorry there are no error bars). Fitting the data points gives a gradient of 157 and a y-intercept of 1185. The "units" on the y-axis are just the straight analog read value, a number between 0 and 4095 (12 bits of resolution). Re-arranging to make x the subject of the equation provides the pH of the reservoir as a function of the measured voltage.
Future Improvements
- Currently the feedback always provides the same amount of fluid from each reservoir at each test interval. However, PID control could be implemented to maintain a better pH balance.
- Using a time triggered call back instead of a continuous counting loop would allow the device to run more efficiently power-wise.