#programming
2026
- Android Auto on my HR-V's Android 4.0.4 head unit
I recently bought a 2018 Honda HR-V as our family car. It has a 7” touchscreen running Android 4.0.4, a version so old it predates Android Auto by about three years. It runs Android Auto now anyway, thanks to a small app I built over the course of a week.
- Rebuilding and analysing 4 years of Wordle stats from WhatsApp chat logs
Yes, I’m still playing Wordle in 2026. According to Wordle, I’ve played 1,559 games, I win 99% of the time, my current streak is 107 days and my longest is 278. Four numbers, and they’re mostly correct. What I don’t get is anything behind them. No per-game history. No way to compare a Tuesday to a Sunday. No sense of whether I’ve actually got better at this over four years. Each day I’ve played, I’ve also shared the result to WhatsApp. Just a habit, not a data strategy, but it turns out that habit quietly built a far more detailed record than Wordle itself ever keeps. One I can finally ask my own questions of. So I looked into rebuilding my own copy of the data.
- What's the most popular number in Hacker News titles?
Two consecutive titles on the HN front page yesterday had a 6 in them. This means nothing. But it’s the sort of nothing that lodges in your brain until you do something about it, so what is the most popular number in Hacker News titles?
- Enriching webmentions from third-party platforms
I keep finding new gaps in my webmention implementation. Mentions coming in from Bluesky, Lemmy and Lobsters were showing up with no author at all, just a blank avatar and an empty name. Reddit and Hacker News bookmarks fared a bit better, with a username attached, but got flattened into a generic “bookmarked” icon with no indication of where the post had actually been shared. Lobsters mentions also carried the wrong published date. None of these felt like they were pulling their weight compared to a proper webmention from a blog or Mastodon.
- Sending webmentions on deploy
I’ve spent a fair bit of time on the receiving end of webmentions, from adding support for them in the first place through to fetching them at build time and rendering them server-side so the discussion section doesn’t flicker in over JS. What I’d never actually done was send the other half. Every link I’ve ever made out to another IndieWeb site has just sat there, silently not telling anyone I’d linked to them.
- Merge Commits Are Just Noise
Run git log --graph on most team projects and you’ll see the problem. Parallel lines everywhere, merge commits that just say “Merge branch ‘feature/x’ into develop”. None of it useful. It’s noise. I’ve been using a rebase-based workflow for years to avoid this. The history stays linear and the diffs stay readable. This post covers both the argument and the specific branch structure I use for team feature work.
- Baking webmentions into the build
Back in 2022 I added webmention support to this blog. The implementation worked but it was entirely client-side, so the page would load, JS would fire, fetch from webmention.io, and render the results into the DOM. With JS disabled or on a slow connection, the discussion section was just empty. It worked well enough that I didn’t touch it for three years, but the rate-limit situation had been silently catching visitors out and eventually I started pulling at the thread.
- Reducing commit log noise caused by a GitHub Action
I have a “now playing” widget on my /now page that shows the last track scrobbled to Last.fm. It’s powered by a GitHub Actions workflow that polls the Last.fm API every hour and commits the result to the repo. It worked fine, but quietly polluted my commit history.
- Plugins instead of patches
I’ve been running Known as my social posting hub for a while now. It’s a solid IndieWeb-friendly CMS that handles status updates, photos, and syndication to various silos. Recently I upgraded my instance for the first time in about four years, which meant dealing with all the little tweaks I’d made to core templates over that time. Previously I’d just edited core files directly, which meant upgrades would wipe my changes. After four years I had no idea what I’d even modified (I’ll go into the archaeology of that in another post). This time I decided to do it properly, and ended up writing four plugins.
- Building an emoji-only chat app
My daughter finds emoji hilarious. She’s 6 and will scroll through an emoji picker for ages. My wife and I started by letting her use WhatsApp’s emoji picker to send us messages, but that quickly polluted our chat history with emoji spam. We tried a few dedicated emoji-only apps like Emojichat, but there was this uncertainty about how they handled data. Most of them were also 1:1 only. I wanted the three of us in the same room.
- Building a Balatro-lite game with vanilla web tech
I’ve spent countless hours playing Balatro, and I’ve always wanted to create my own game with similar mechanics. When TrinketOS announced a game jam, it was the perfect excuse to make it happen.
2022
- A Bit of a Bodge To Make MASSIVE Emoji
I’ve been fiddling with an instance of Known over on social.omgmog.net. As part of my fiddling I’ve been trying out some of the plugins for Known. I came across the “Render Emoji Unicode” plugin but it was using an old library, and referencing an old version of Twemoji, so I updated it to use the latest Twemoji library.
- Adding support for Webmentions
I’ve added Webmention support to the posts on this blog. Webmentions are a method for websites to know that they’ve been linked to (or mentioned) from elsewhere on the web.
2021
- The Raspberry Pi Pico and the Pimoroni Pico RGB Keypad
In January the Raspberry Pi Pico was announced. The Pico is a RP2040 microcontroller featuring a dual-core Arm Cortex-M0+ processor with 264KB internal RAM and support for up to 16MB of off-chip Flash. It’s comparable to certain Arduino or ESP32 boards and at less than £4 per board it’s a well-priced alternative.
2019
- Approaches for a simple state system
There are lots of ways to achieve even the most simple goal in programming, and Pico-8 – even with it’s limited nature – is no different to any other system. Today I’m looking at state systems. There are lots of approaches to handling state out there, and each has their own pros and cons. In Pico-8 the main concern is keeping the token count low so that you can add more features to your game. Beginner state system This first state system uses a state variable and an enum table of available states to decide which state we’re currently in. The state variable is updated by user input (button press, or death) and it’s contents controls which functions are executed in the _update() and _draw() functions. If you’ve got a huge game the conditions in the _update() and _draw() functions can quickly become unweildly. The main benefit here is that you can control what to execute in the _update() and _draw() functions from one place. This approach is quite token hungry. function _init() t = 0 -- create timer state = 1 states = { menu = 1, game = 2, gameover = 3 } hp = 100 end function _update() t += 1 -- increment timer if state == states.menu then update_menu() elseif state == states.game then update_game() else update_gameover() end end function _draw() if state == states.menu then draw_menu() elseif state == states.game then draw_game() else draw_gameover() end end function update_menu() if btn(4) and btn(5) then state = states.game end end function draw_menu() print("Press z and x to start") end function update_game() if hp < 1 then state = states.gameover end end function draw_game() print("HP: "..hp) end function update_gameover() if btn(4) and btn(5) then state = states.game end end function draw_gameover() print("Press z and x to start over") end Improved state system This is the approach I prefer, the _update() and _draw() functions call a variable that points to another function. To change state you simply update this variable. There is no variable or enum needed to keep track of state, but you do need to explicitly redeclare the _upd and _drw variables each time you change state. This is approach is quite light on the token count. -- core init/update/draw functions function _init() t = 0 -- create timer hp = 100 _upd = upd_menu _drw = drw_menu end function _update() t += 1 -- increment timer _upd() end function _draw() cls() _drw() end -- menu update/draw functions function upd_menu() if btn(4) and btn(5) then _upd = upd_game _drw = drw_game end end function drw_menu() print("Press z and x to start") end -- game update/draw functions function upd_game() if hp < 1 then _upd = upd_gameover _drw = drw_gameover end end function drw_game() print("HP: "..hp) end -- gameover update/draw functions function upd_gameover() if btn(4) and btn(5) then _upd = upd_game _drw = drw_game end end function drw_gameover() print("Press z and x to start over") end
- Outlining text and sprites
It’s pretty simple to create an outlined text effect with a simple function, such as: function printo(text,x,y,text_color,border_color) local text_color = text_color or 7 local border_color = border_color or 0 -- render the "outline" in each direction for _x=-1,1 do for _y=-1,1 do print(text,x+_x,y+_y,border_color) end end -- print the foreground text print(text,x,y,text_color) end -- usage: printo("Hello world!",64,64,12,8) The process for outlining a sprite is very similar, but it uses the pal() function to change colors and works best of all if your sprite is a single color… -- where spr_table is like: -- {spr_index, base_color} function spro(spr_table,x,y,spr_color,border_color) local spr_color = spr_color or 7 local border_color = border_color or 0 -- render the "outline" in each direction for _x=-1,1 do for _y=-1,1 do -- replace the base_color with new border_color pal(spr_table[2],border_color) -- draw the sprite with the new color spr(spr_table[1],x+_x,y+_y) end end -- replace the base_color with the new spr_color pal(spr_table[2],spr_color) -- render the recolored sprite spr(spr_table[1],x,y) -- reset the rendering palette pal() end -- usage: spro({1,7},64,64,12,8) And obviously you can set this spro function up to handle more of the properties available to spr, such as number of tiles in x/y, and whether to flip or not. As a final solution if you’re going to be doing this a lot you could combine the previous two functions to one mega outline function: function outline(what,x,y,fc,bc) if typeof(what) == "string" then printo(what,x,y,fc,bc) else spro(what,x,y,fc,bc) end end And here’s how the final effect looks when applied to a 1-color sprite and some text, with random colors:
- A menu and state system
I’ve been playing with state systems lately, and decided to build a menu system to play with going between each state. First of all, my helper functions… I’m using this to print outlined text: -- print text with an outline function printo(str,x,y,c1,c2) for xoff=-1,1 do for yoff=-1,1 do print(str,x+xoff,y+yoff,c1) end end print(str,x,y,c2) end The menu function has two modes, one for running in _update() and one for running in _draw(), you control this by passing "u" or "d" as the second argument. The first agument takes a table containing a menu definition. I’ll talk more about this with an example after the wall of code: function menu(m, mode) local ic=#m.items if mode == "u" then if btnp(2) then -- up m.selection -= 1 if m.selection < 1 then m.selection = 1 end end if btnp(3) then -- down m.selection += 1 if m.selection > ic then m.selection = ic end end if btnp(5) then -- x if m.items[m.selection][2] then m.items[m.selection][2]() end end end if mode == "d" then local padding={4,4,0,6}-- t/r/b/l local lineheight=8 -- find longest string local widest_str=0 for i,item in pairs(m.items) do if #item[1] > widest_str then widest_str = #item[1] end end local width = (widest_str*4)+padding[2]+padding[4] local height = (ic*lineheight)+padding[1]+padding[3] local xoff = 64 - (width/2) local yoff = 64 - (height/2) local x0 = xoff local y0 = yoff local x1 = xoff + width local y1 = yoff + height -- box rect(x0-2,y0+1,x1+2,y1+2, 5) rect(x0-1,y0-1,x1+1,y1+1,0) rectfill(x0,y0,x1,y1,1) rect(x0,y0,x1,y1,7) -- aditional text if m.guide then local _w = #m.guide*4 local _x = 64 - (_w/2) local _y = y1+padding[1]+2 printo(m.guide,_x,_y, 0, 7) end -- items for i,item in pairs(m.items) do if i == m.selection then -- selection highlight printo( item[1], xoff+padding[4], yoff+padding[1]+((i-1)*lineheight), 0,7 ) if m.items[m.selection][2] then palt(0,false) palt(1,true) local xadj=(5 - time()%3) spr(1,x0-xadj,y0+padding[1]+((i-1)*lineheight) - 1) pal() end else -- normal item printo( item[1], xoff+padding[4], yoff+padding[1]+((i-1)*lineheight), 0,6 ) end end end end There’s a lot going on there, but it’s basically maintain a counter for the user’s current selection, and then if there is a function associated with a menu item call it when the x button is pressed. Here’s how the menu definitions look _menu_start = { selection = 1, items = { {"new game", game}, {"new game", game}, {"new game", game}, {"new game", game}, {"credits", credits} }, guide="❎ to select" } _menu_game = { selection = 1, items = { {"game over", die}, {"congratulations", win}, {"---"}, {"back", start} } } _menu_credits = { selection = 1, items = { {"this sub menu contains lots of"}, {"strings without actions bound "}, {"to them, so you can just show "}, {"multiple lines of text."}, {"30 characters is a rough limit"}, {"---"}, {"back", start} }, guide="enjoy ♥" } You can go between menus using a state system. Here’s the barebones of a state system. We assign a new function to _upd() and _drw() each time the state changes. function _init() start() -- defined later end --update function _update() _upd() end function update_start() menu(_menu_start, "u") end function update_game() menu(_menu_game, "u") end function update_credits() menu(_menu_credits, "u") end --draw function _draw() cls(13) _drw() end function draw_start() menu(_menu_start, "d") end function draw_game() menu(_menu_game, "d") end function draw_credits() menu(_menu_credits, "d") end -- functions that are called directly -- through menu selections function start() _upd=update_start _drw=draw_start end function game() _upd=update_game _drw=draw_game end function credits() _upd=update_credits _drw=draw_credits end And here’s how it looks in action. I’ve got a custom sprite for the selection hand drawn in sprite slot 1:
2018
- A custom font
Out of the box Pico-8 has one font. It’s the utilitarian answer to most problems. 3px by 5px and fairly legible. This font isn’t always good enough. How about making your own font? I’ve seen lots of approaches for creating fonts in Pico-8 – mostly using sprites for each glyph of the font, but then you’re limited to 8x8px and a have to mess with pal modes if you want to adjust colors on-the-fly. My approach uses the raw pixel data encoded in strings that are rendered using calls to pset, falling back to the built-in font for any missing glyphs. First, here are my helper functions… -- merge tables function mt(t1,t2) local new = {} for k,v in pairs(t1) do new[k] = v end for k,v in pairs(t2) do new[k] = v end return new end -- less verbose way to do ternary operations function ternary(cond ,t ,f ) if cond then return t else return f end end I had to come up with a compact way to represent the glyphs. Initially I used something like: { glyph = "o", pixels = { 0,1,1,1,0, 1,0,0,0,1, 1,0,0,0,1, 1,0,0,0,1, 1,0,0,0,1, 1,0,0,0,1, 0,1,1,1,0, }} But that contains a lot of tokens and lots of repeated/dead values to represent the empty pixels in a glyph. Eventually I moved to something like: {"o", "0.1.3.1.1.3.2.3.2.3.2.3.2.3.1.1.3"} That’s the same pixel data, but encoded in to a much shorter string. The first number indicates our starting parity – this can be 0 or 1 and tells us if the top/left pixel is empty or filled. Each time we have a . the parity flips, so with the example of our “o” glyph it starts with 0 for 1 count, then 1 for 3 counts, 0 for 1 count, 1 for 1 count, etc. When the remaining pixels in a glyph are 0 we can just omit them from the glyph data as they’re not drawn. The real wins are in the more simple/empty glyphs, such as “.” for example. Before: { glyph = ".", pixels = { 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,1,0,0, }} After: {".", "0.32.1"} Making this change took me from an initial token count of roughly 2800 to a much more manageable count of 560-ish. So with that technical talk out of the way, here’s the complete glyph set in code, and the function to render text. I’ll give some usage examples after. global_y = 0 function write(str,opts) opts = opts or {} _opts = { x=0, y=global_y, c=7 } opts = mt(_opts,opts) font = { w=5, h=7, -- first num indicates starting value, 0 or 1 -- each number indicates number of 0 or 1 in a row -- flips at each . glyphs = { {"0", "0.1.3.1.1.3.2.3.2.3.2.3.2.3.1.1.3"}, {"1", "0.2.1.3.2.2.1.1.1.4.1.4.1.4.1.2.5"}, {"2", "0.1.3.1.1.3.1.4.1.3.1.3.1.3.1.3.5"}, {"3", "0.1.3.1.1.3.1.4.1.2.2.5.2.3.1.1.3"}, {"4", "0.3.2.2.1.1.1.1.1.2.2.3.6.4.1.4.1"}, {"5", "1.6.4.1.5.3.5.2.3.1.1.3"}, {"6", "0.1.3.1.1.3.2.4.4.1.1.3.2.3.1.1.3"}, {"7", "1.5.4.1.4.1.3.1.3.1.3.1.3.1"}, {"8", "0.1.3.1.1.3.2.3.1.1.3.1.1.3.2.3.1.1.3"}, {"9", "0.1.3.1.1.3.2.3.1.1.4.4.2.3.1.1.3"}, {"a", "0.1.3.1.1.3.2.3.7.3.2.3.2.3.1"}, {"b", "1.4.1.1.3.2.3.5.1.1.3.2.3.5"}, {"c", "0.1.3.1.1.3.2.4.1.4.1.4.1.3.1.1.3.1"}, {"d", "1.4.1.1.3.2.3.2.3.2.3.2.3.5"}, {"e", "1.6.4.1.4.4.1.1.4.1.4.5"}, {"f", "1.6.4.1.4.4.1.1.4.1.4.1"}, {"g", "0.1.3.1.1.3.2.3.2.4.1.1.4.3.1.1.3"}, {"h", "1.1.3.2.3.2.3.7.3.2.3.2.3.1"}, {"i", "1.5.2.1.4.1.4.1.4.1.4.1.2.5"}, {"j", "1.5.2.1.4.1.4.1.2.1.1.1.2.1.1.1.3.2"}, {"k", "1.1.3.2.2.1.1.1.1.1.2.2.3.1.1.1.2.1.2.1.1.1.3.1"}, {"l", "1.1.4.1.4.1.4.1.4.1.4.1.4.5"}, {"m", "1.1.3.3.1.3.1.1.1.2.3.2.3.2.3.2.3.1"}, {"n", "1.1.3.3.2.2.1.1.1.2.2.3.3.2.3.2.3.1"}, {"o", "0.1.3.1.1.3.2.3.2.3.2.3.2.3.1.1.3"}, {"p", "1.4.1.1.3.2.3.5.1.1.4.1.4.1"}, {"q", "0.1.3.1.1.3.2.3.2.3.2.3.1.1.3.5.1"}, {"r", "1.4.1.1.3.2.3.5.1.1.1.1.2.1.2.1.1.1.3.1"}, {"s", "0.1.3.1.1.3.2.5.3.5.2.3.1.1.3"}, {"t", "1.5.2.1.4.1.4.1.4.1.4.1.4.1"}, {"u", "1.1.3.2.3.2.3.2.3.2.3.2.3.1.1.3"}, {"v", "1.1.3.2.3.1.1.1.1.1.2.1.1.1.2.1.1.1.3.1.4.1"}, {"w", "1.1.3.2.3.2.3.2.1.1.1.2.1.1.1.2.1.1.1.1.1.1.1.1"}, {"x", "1.1.3.2.3.1.1.1.1.1.3.1.3.1.1.1.1.1.3.2.3.1"}, {"y", "1.1.3.1.1.1.1.1.3.1.4.1.4.1.4.1.4.1"}, {"z", "1.5.4.1.3.1.3.1.3.1.3.1.4.5"}, {" ", "0.35"}, {".", "0.32.1"}, {",", "0.27.1.3.1"}, {"!", "0.2.1.4.1.4.1.4.1.4.1.9.1"}, {"?", "0.1.3.1.1.3.1.4.1.3.1.3.1.9.1"}, {"'", "0.3.1.3.1"}, {"\"", "0.1.1.1.1.1.1.1.1"}, {":", "0.12.1.14.1"}, {";", "0.12.1.14.1.3.1"}, {"(", "0.3.1.3.1.3.1.4.1.4.1.5.1.5.1"}, {")", "0.1.1.5.1.5.1.4.1.4.1.3.1.3.1"}, {"[", "0.1.3.2.1.4.1.4.1.4.1.4.1.4.3"}, {"]", "0.1.3.4.1.4.1.4.1.4.1.4.1.2.3"}, {"{", "0.2.2.3.1.4.1.3.2.4.1.4.1.4.2"}, {"}", "0.1.2.4.1.4.1.4.2.3.1.4.1.3.2"}, {"<", "0.3.1.3.1.3.1.3.1.5.1.5.1.5.1"}, {">", "0.1.1.5.1.5.1.5.1.3.1.3.1.3.1"}, {"-", "0.16.3"}, {"+", "0.12.1.3.3.3.1"}, {"/", "0.4.1.3.1.4.1.3.1.3.1.4.1.3.1"}, {"\\", "1.1.5.1.4.1.5.1.5.1.4.1.5.1"}, } } _x = opts.x _y = opts.y letters = {} for i=1,#str do letter = sub(str,i,i) add(letters,letter) end for k,v in pairs(letters) do letter = nil for _glyph in all(font.glyphs) do if _glyph[1] == v then letter = _glyph[2] end end local _sx = _x local _sy = _y if letter != nil then local _px = _sx local _py = _sy pixels = {} local _p = sub(letter,1,1) local prev = nil for i=2,#letter do cur = sub(letter,i,i) if type(prev) == "number" and cur != "." then cur = sub(letter, i-1, i) end if cur == "." then _p = ternary(_p == "0", "1", "0") else for j=1,tonum(cur) do add(pixels, _p) end end prev = tonum(cur) end for i,p in pairs(pixels) do i = i - 1 if i % font.w == 0 then _py += 1 _sx = _x - (font.w * (i / font.w)) end _px = _sx + i if p == "0" then pset(_px, _py, opts.c) end end else font.w = 8 print(v, _sx, _y + ((font.h - 2)/2), opts.c) end _x += font.w + 1 end global_y += font.h + 2 end If you’re still with me, congratulations! Here is how you use the function: function _draw() cls() global_y = 0 -- reset on each draw call write("hello world") -- passing a color is possible! write("this is max 🐱", {c=12}) write("my font brings all") write("the devs to the yard") write("0123456789") write("abcdefghijklm") write("nopqrstuvwxyz") write(".,?!:;()[]{}-+/\\<>") write("yes, this is a lot") write("nicer to read! ♥") write("") write("█▒🐱⬇️░✽●♥☉웃⌂⬅️😐") write("♪🅾️◆…➡️★⧗⬆️ˇ∧❎▤▥") write("@#$%^&*") end And this is how it looks:
- A Dialog System
This is a flexible dialog system I’ve come up with. It has the following features: automatically wrap long lines of text automatically page super long text to additional dialogs delay control per-letter display First, here are some of the helper functions that I’m using… -- draw a box with a frame -- arguments: -- x,y position -- w,h dimensions -- p padding -- fg,bg foreground and background colors function box(x,y,w,h,p,fg,bg) rectfill(x,y,x+w,y+h,bg) rect(x+p,y+p,x+w-p-1,y+h-p-1,fg) end -- joins two tables function jt(table1,table2) local new = {} for k,v in pairs(table1) do new[k] = v end for k,v in pairs(table2) do new[k] = v end return new end -- sleep for a set number of seconds (using 30fps mode) function sleep(s) for i=1,s*30 do flip() end end And lastly, the meat of the dialog system. I’ve scattered some comments through-out the code to try to explain what I’m doing. I’ll provide some initialisation examples after… function dialog(d) d = d or {} -- defaults _d = { a = true, bg = 5, c = false, dl = 5, fg = 7, p = 8, t = true, ts = 0.05, txt = "", w = 128, y = 96 } -- slightly dynamic defaults _d.x = (128 / 2) - (_d.w / 2) _d.h = 128 - _d.y -- merge passed options with default _d = jt(_d, d) -- text _ch = 6 -- char height _wi = _d.w - (_d.p * 2) _wrap = (_wi / 4) - 1 -- 27 local lines = {} local limit = ceil(#_d.txt / _wrap) local _tb = _d.txt while limit > 0 do local substr = sub(_tb, 1, _wrap) -- grab first bit -- ensure line doesn't have a space at the start if sub(substr,1,1) == " " then substr = sub(substr, 2) end add(lines, substr) -- stick it in our lines table _tb = sub(_tb, _wrap + 1, -1) -- get rest of text limit -= 1 end -- lines limit _ll = flr((_d.h - _d.p - _ch) / _ch) if _d.t == true then -- type it out box(_d.x, _d.y, _d.w, _d.h, _d.p / 2, _d.fg, _d.bg) for l=1,_ll do local _l = lines[l] or "" -- vertical alignment local va = _d.y + _d.p + ((l - 1) * 6) local _lld = _ll while _lld > 1 do if #lines < _lld then va += (_ch / 2) end _lld -= 1 end -- center it if necessary local ha = _d.x + _d.p if _d.c == true then ha = _d.x + (_d.w / 2) - (#_l * 2) end local chars = {} local limit = #_l local ci = 0 -- split line in to chars while limit > 0 do local _ch = sub(_l, 1, ci + 1) add(chars, _ch) ci += 1 limit -= 1 end for c=1,#chars do print(chars[c], ha, va, _d.fg) if _d.a == true then sfx(0) end sleep(_d.ts) -- tiny delay end end else -- print normally box(_d.x, _d.y, _d.w, _d.h, _d.p / 2, _d.fg, _d.bg) for l=1,_ll do local _l = lines[l] or "" -- vertical alignment local va = _d.y + _d.p + ((l - 1) * 6) local _lld = _ll while _lld > 1 do if #lines < _lld then va += (_ch / 2) end _lld -= 1 end -- center it if necessary local ha = _d.x + _d.p if _d.c == true then ha = _d.x + (_d.w / 2) - (#_l * 2) end print(_l, ha, va, _d.fg) if _d.a == true then sfx(0) end end end if #lines > _ll then local _nl = lines -- delete strings we've used for i=1,_ll do del(_nl,lines[1]) end -- join thr remaining local str = "" if #_nl > 1 then for i=1,#_nl do str = str .. _nl[i] end else str = _nl[1] end sleep(_d.dl) -- delay before next -- call recursive local n = jt(_d, {txt=str}) dialog(n) else sleep(_d.dl) -- delay before next end end Whew that was a lot of code. Here’s how you can use it: -- short, centered, delay of 2 dialog({ txt = "hello,", c = true, dl = 2 }) -- short, centered dialog({ txt = "my name is inigo montoya", c = true }) -- short, centered dialog({ txt = "you killed my father", c = true }) -- short, centered, not typed dialog({ txt = "prepare to die!", c = true, t= false }) -- long, typed, multiple message boxes dialog({txt="this is a lot of text that will wrap on to multiple lines and hopefully use a recursive message box in order to show all of the text ♥"}) You can see how this all looks below:
- Extra small extra macro keys v2.0
Well I took another look at the design of my macro keys project sooner than I expected. I started over with the design of the 3D-printed part of this project, and then moved the buttons and guts over from the previous version.
2017
- Useful bash aliases for VPS
Some useful bash aliases that I use on my VPS’: www=/srv/www conf=/etc/apache2/sites-enabled alias ra='/etc/init.d/apache2 restart' alias ownit='chown -R www-data:www-data .' Create an Apache conf and directory for a new domain: function makeconf { primary=${1} conffile="${conf}/${primary}.conf" site_path="/srv/www/${primary}" mkdir -p ${site_path} echo "<VirtualHost *:80>" >> "${conffile}" echo " ServerName ${primary}" >> "${conffile}" echo " ServerAlias www.${primary}" >> "${conffile}" shift for i; do cat >> "${conffile}" <<EOL ServerAlias ${i} ServerAlias www.${i} EOL done echo " DocumentRoot ${site_path}" >> "${conffile}" echo "</VirtualHost>" >> "${conffile}" } Grab the latest Wordpress tar and extract it to the current directory: function wordpressitup { wget --no-check-certificate https://wordpress.org/latest.tar.gz tar xfz latest.tar.gz cd wordpress cp -R * .. cd .. rm -r wordpress echo "Wordpress installed" } Some shortcuts to go to common directories: function dwww { cd /srv/www } function dcache { cd /var/cache/mod_pagespeed/v2 } function dconf { cd /etc/apache2/sites-enabled/ } See which directories and files are using up all of the space: function fsizes { du -h --max-depth=1 | sort -hr df -BG }
2016
- Summer of Hacks: Game Dev Day
As part of the JS Oxford-organised Summer of Hacks, I co-organised the “Game Dev Day” hack day in Oxford. This is the second time I’ve held an event during the Summer of Hacks (the previous event being SusHack #3 last year!).
2013
- SusHack #1: Building the foundations of the Oxford Flood Network Stack
This past weekend I held my first hack day. This is a recollection of why/how SusHack came to be, and how the day went.
- Awesome Bash arrays
Just some examples of how to do things with Bash arrays, and their analogous examples in Javascript.
2012
- The woes of trailing commas in IE
If you leave a trailing comma in your JavaScript code, you’re a terrible person. It’s right up there with omitting semicolons as far as I’m concerned.
- Getting to grips with git log
git is a powerful tool. After using it for the past couple of years I’m still finding new/powerful things that can be done with it.
- Using jQuery to create a custom thumbnail for an embedded video
Recently Jenny asked me for a way to embed a YouTube video with a custom thumbnail that would play the video when clicked.
- Updates to my 'install all firefox' bash script for Mac OS X
I wrote briefly about my install-all-firefox shell script back in December last year. Since then the script has received a complete rewrite, and some new features.
2011
- Bash script to install all major versions of Firefox on OS X
I wrote a nice bash script that automatically downloads and installs the major versions of Firefox, sets up profiles for each and modifies the .app launchers so that they start with their designated profiles.
- Trying coffeescript
Recently decided to give coffeescript a go, it’s nice and syntactically similar to other languages such as Python. From the coffeescript developers: CoffeeScript is a little language that compiles into JavaScript. Underneath all of those embarrassing braces and semicolons, JavaScript has always had a gorgeous object model at its heart. CoffeeScript is an attempt to expose the good parts of JavaScript in a simple way. Here’s my first test with it, first of all a golfjs solution I wrote, using obfuscated (read: in as few bytes as possible) JavaScript: i=input,v=i.match(/[aeiou]/gi),y=i.match(/y/gi), output=(v?v.length:0)+(y?y.length/2:0) And here’s the same code in coffeescript: v = /[AEIOU]{1}/gi y = /Y{1}/gi a = if input.match(vow) then input.match(vow).length else 0 b = if input.match(y) then input.match(y).length*0.5 else 0 output = a+b And the JavaScript coffeescript compiles to: var a, b, output, v, y; v = /[AEIOU]{1}/gi; y = /Y{1}/gi; a = input.match(vow) ? input.match(vow).length : 0; b = input.match(y) ? input.match(y).length * 0.5 : 0; output = a + b;
- My solution to the JavaScript challenge 07/03/11
Okay so this solution is a bit later than I planned, but a busy working week and lack of motivation toward the end kind of got the better of me. Anyway, here’s my solution to the challenge. I’m sure Tomas will post his up soon.
- Javascript function to group repeated values of an array with totals
Something that took a little while to pull of well while working on the JS challenge this week, thought I’d throw it up here as it has its uses practically for grouping like array values with totals:
- Javascript/jQuery challenge of the week 07/03/11
Okay well this is something Tomas and I have been planning to do for ages, and in fact we did something like this a year ago, but it didn’t amount to a lot.
- Building JavaScript arrays from HTML using regular expressions
Bit of idea ping-pong with Tomas this evening, to provide a solution to something he is working on. He had some text in a table cell that needed to be split and put into an array, separating each line into two parts.
2010
- A simple solution for a jQuery animation loop memory leak
I’m probably stating something very obvious to everybody, but this is something that caused me a couple of hours of confusion. When creating a new slightly-fancier page to be displayed on omgmog.net I had a problem in that after a certain amount of time the browser would become unresponsive (or in Google Chrome the page would die - but I love how Google Chrome handles this). The problem I soon realised was in my animation loop. I’ve got a couple of lines of jQuery that move some elements on the page around randomly, to give a floating effect. The original code was as follows: $("h1 span").not("#h1p3").everyTime(0,function(){ $(this).animate({'margin-left':rand_no(),'margin-top':rand_no()}); }); It turns out the problem with this code is that the animation was looping before it had completed, so the browser presumably is stacking up all of these $().animate(); functions, which in turn caused the browser to become unresponsive. To remedy this, I specified a time for the $().animation(); function, and set the $().everyTime(); timeout to slightly longer than this: $("h1 span").not("#h1p3").everyTime(500,function(){ $(this).animate({'margin-left':rand_no(),'margin-top':rand_no()},490); }); With these small changes in place, it seems my problem is solved! I don’t have any “lessons learned” speech to close the post with, it’s just something that I found interesting :)
- Using Greasemonkey scripts with Google Chrome
This isn’t new news, but it’s something I’ve found quite useful. You can use userscripts created for Firefox/Greasemonkey in Google Chrome. Many of the scripts that you can find over on userscripts.org can be used effortlessly by Google Chrome, and once installed can be disabled/uninstalled from the Extensions tab.
- Waka Waka - Pacman Moleskine! and some Objective-C
Today my Pacman Moleskine arrived - I’m pretty excited, but I won’t be using it until I fill my regular lined Moleskine. I’ve been learning Objective-C along with @tmayr, so far covering just basic things such as processing user input and displaying output, using Cocoa/Interface Builder. Most recently we’ve been looking at filesystem things. I’ll probably post some of the haphazard things I’ve put together here eventually. Some pictures of the Pacman Moleskine… Unable to recover the image :(
- Using jQuery to supply and clear default input values
This is a repost of a solution I provided over at the smashing magazine forums. I’m posting it here as it’s a pretty useful solution in itself. The original question is as follows: I’m building a new website and on this one page I need to use a huge form, instead of using labels I want to enter the “label” text in the value attribute and then use JS to clear the field on mouseclick. The problem is that every field needs to be filled out. I don’t want users hitting the submit button with some of the standard values still in place (easy to miss certain stuff) because this will mess things up. I can ofcourse check for empty fields but to do this I need to somehow check if all the fields have been changed on form submit. So I whipped up a quick implementation of how I would do it using jQuery.