<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.5">Jekyll</generator><link href="https://blog.lord.geek.nz/feed.xml" rel="self" type="application/atom+xml" /><link href="https://blog.lord.geek.nz/" rel="alternate" type="text/html" /><updated>2024-02-16T05:55:29+00:00</updated><id>https://blog.lord.geek.nz/feed.xml</id><title type="html">Dal Blog</title><subtitle>I'm interested in things.</subtitle><author><name>David Lord</name></author><entry><title type="html">Software I like</title><link href="https://blog.lord.geek.nz/2024/02/11/software-i-like/" rel="alternate" type="text/html" title="Software I like" /><published>2024-02-11T00:00:00+00:00</published><updated>2024-02-11T00:00:00+00:00</updated><id>https://blog.lord.geek.nz/2024/02/11/software-i-like</id><content type="html" xml:base="https://blog.lord.geek.nz/2024/02/11/software-i-like/"><![CDATA[<p>Sometimes I pay for software. Weird, right? There’s a lot of it out there, and it’s famously hard to convince people to hand over money for it.</p>

<p>This is a list of all the stuff I’ve recommended more than once, usually until someone has given me a glassy-eyed stare and agreed to try it. It’s mostly free-as-in-beer, with some paid licences adding more features, or a way to donate.</p>

<p><img src="/assets/images/software_i_like_hero.png" alt="Colourful header with logos of these programs" class="center-image" /></p>

<h1 id="on-macos">On macOS</h1>

<p>I like Apple’s desktop OS, and the software below makes it even better than the default experience.</p>

<hr />

<h2 id="better-window-management">Better window management</h2>

<p>If you ask me, Microsoft got window management right. I’m comfortable with how Mac does it, but these two bring over some of the Windows sparkle, and you’re better off for it.</p>

<h3 id="rectangle"><a href="https://rectangleapp.com/">Rectangle</a></h3>

<p>Makes it easy to move windows around, snap them to an edge, resize them, and move them across monitors. Think of winkey + arrows. Provides key bindings and drag-drop. The shortcuts take some time to learn - I made an <a href="https://apps.ankiweb.net/">Anki</a> deck for this - but now it’s great. I really missed this for the first few years on macOS.</p>

<h3 id="alttab"><a href="https://alt-tab-macos.netlify.app/">AltTab</a></h3>

<p>Does what it says on the box, and quite pretty too. At heart, I use it because it’s better than Apple’s <code class="language-plaintext highlighter-rouge">⌘+`</code> and <code class="language-plaintext highlighter-rouge">⌘+↹</code>, but it’s also very configurable in terms of keys and appearance, and has some extra functionality.</p>

<hr />

<h2 id="utility">Utility</h2>

<h3 id="monitorcontrol"><a href="https://github.com/MonitorControl/MonitorControl">MonitorControl</a></h3>

<p>This does one thing exceptionally well: I use twin Dell monitors connected with USB-C, and this lets you change their brightness just like your laptop screen. <sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">1</a></sup> When writing this blog post I realised I’d never contributed, so chucked them $10.</p>

<h3 id="bettertouchtool"><a href="https://folivora.ai/">BetterTouchTool</a></h3>

<p>Apple’s trackpads are great, but this elevates them to “better than a mouse” for me. For work, anyway. I’ve added some gestures for browsers and file managers: 2-finger swipe up to open a new tab, 2-finger swipe down to close it, and tiptap left/right to change tab. It’s far more powerful than I use it for, and that also makes it fun to play with on long trips without internet service.<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">2</a></sup></p>

<h3 id="alfred"><a href="https://www.alfredapp.com/">Alfred</a></h3>

<p>Great fuzzy finder with a ton of built-in features, plus it’s very programmable, which makes it fun to extend. The native Spotlight has supposedly improved since I started using Alfred, but by now, it’s just too powerful (and customised) to leave. I use it daily for instant lookups like <a href="https://github.com/d-lord/alfred-country-codes">country codes</a>, <a href="https://github.com/d-lord/alfred-http">HTTP status codes</a> and <a href="https://github.com/d-lord/alfred-iata-codes">IATA codes</a>. I also saved a bunch of snippets so it’s easy to type fancy characters like <code class="language-plaintext highlighter-rouge">⌘</code> with <code class="language-plaintext highlighter-rouge">snip cmd</code>. Fallback searches are great too; being able to hit <code class="language-plaintext highlighter-rouge">⌘+space</code> and type <code class="language-plaintext highlighter-rouge">wiki foo</code> to go straight to Wikipedia’s page on Foobar is just lovely.</p>

<h3 id="1password"><a href="https://1password.com/">1Password</a></h3>

<p>Everyone should be using a password manager. Given how many passwords your digital life requires, and how common <a href="https://www.abc.net.au/news/2024-01-19/what-is-credential-stuffing-scams-how-to-prevent-and-protect/103367570">credential stuffing</a> and data breaches are, it’s essential hygiene. I’m not telling you to use 1P necessarily, but it’s been pretty good to me.</p>

<p>Their Families plan is good value for the geek in the family, and I was paying for it for ages. Sometimes, if your employer uses it, the enterprise plans include a free Families licence for you. 😉</p>

<h3 id="camo"><a href="https://reincubate.com/camo/">Camo</a></h3>

<p>Lets you use your phone as a webcam. For a lot of people, your phone is the best camera you have, and far better than whatever your laptop has.</p>

<p>Apple’s <a href="https://support.apple.com/en-au/guide/mac-help/mchl77879b8a/mac">Continuity Camera</a> has largely eaten Camo’s lunch, but I still use it for some extra dials and knobs.</p>

<hr />

<h2 id="programmer-stuff">Programmer stuff</h2>

<h3 id="iterm"><a href="https://iterm2.com/">iTerm</a></h3>

<p>The best terminal I’ve used, and I sponsor it on GitHub. Generally really nice with sensible defaults, good support, and <a href="https://iterm2.com/features.html">powerful features</a>. Heaps of them are useful, like split panes, command-clicking URLs, smart selection, infinite scrollback and shell integration. Heaps are just cool for tinkering, like coprocesses, triggers and showing images. Bonus points for its tmux integration which I don’t use regularly but is amazing when I do - it uses native tabs and panes for tmux’s own.</p>

<h3 id="dash"><a href="https://kapeli.com/dash">Dash</a></h3>

<p>Amazing software, and it probably helped me get hired at Canva. Quick access to all sorts of documentation<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">3</a></sup>, and in a native app. I adore it for Python. The default docsets, and those submitted by the community, are super broad. Not a fan of its new subscription model though, so I’ll probably be using my Dash 6 licence indefinitely.</p>

<hr />

<h1 id="web-browsing-and-social-media">Web browsing and social media</h1>

<h3 id="firefox"><a href="https://getfirefox.com">Firefox</a></h3>

<p>The last browser before Chromium eats everything. Also has <a href="/2019/12/07/tinfoil-firefox/">customisations I value</a>, like separate search and address bars.</p>

<h3 id="ublock-origin"><a href="https://ublockorigin.com/">uBlock Origin</a></h3>

<p>Just a good ad blocker. I think ad blockers in general are crucial to a good internet experience. This also has a good picker interface to let you block anything you like, such as Twitter’s “trending” sidebar.</p>

<h3 id="fb-purity"><a href="https://www.fbpurity.com/">FB Purity</a></h3>

<p>An absolute marvel for Facebook users. You know those chain letters you see about “like and share this post to see your friends’ activity again”? This actually works, and lets you block a lot of the trash injected into the feed and sidebar as well. It surely won’t be around forever, but for now, it does an outsized amount to make the internet a better place for real people.</p>

<h3 id="dearrow"><a href="https://dearrow.ajay.app/">DeArrow</a></h3>

<p>Brings your YouTube experience up a few IQ points, by reducing clickbait in thumbnails and titles.</p>

<p>You know how you turn off your adblocker, and suddenly the web is a garish carnival of adverts willing to use any trick to grab your attention? Viewing YouTube on a client without DeArrow is starting to feel a bit like that.</p>

<p>Makes it super easy to contribute your own improvements, too. I’m a sucker for editorial control.<sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote" rel="footnote">4</a></sup></p>

<hr />

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:3" role="doc-endnote">
      <p>The physical buttons tucked away behind the monitor are annoying to use, and MonitorControl has the same effect. It doesn’t have to fiddle with gamma to fake it. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:1" role="doc-endnote">
      <p>Everyone finds fun in their own way. I know a guy who went on a spree in his home workshop and put everything on wheels. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>It’s not quite “lookups <a href="https://duckduckgo.com/?q=vim+edit+at+the+speed+of+thought">at the speed of thought</a>”, but pretty close. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4" role="doc-endnote">
      <p>The before/after comparison on their homepage is pretty cool too. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>David Lord</name></author><category term="macos" /><category term="software" /><category term="programming" /><summary type="html"><![CDATA[A bunch of stuff I think is worth paying money for, even when you don't have to.]]></summary></entry><entry><title type="html">The flooded road near Warwick</title><link href="https://blog.lord.geek.nz/2023/07/21/fording/" rel="alternate" type="text/html" title="The flooded road near Warwick" /><published>2023-07-21T00:00:00+00:00</published><updated>2023-07-21T00:00:00+00:00</updated><id>https://blog.lord.geek.nz/2023/07/21/fording</id><content type="html" xml:base="https://blog.lord.geek.nz/2023/07/21/fording/"><![CDATA[<p>“If I screw this up,” I said to Char, “I want it on video.”</p>

<p>Our AirBnB host had assured us that the road was only <em>slightly</em> flooded, and we’d also discovered that the only other way there would see us ride in the dark - it needed another 40 minutes on an unsealed road, while watching nervously for kangaroos quite literally jumping out at us.</p>

<p>So: time to try to ford it.</p>

<p><video src="https://lord.geek.nz/f/fording.mp4" style="max-height: 100vh; max-width: 100%; width: 100%;" controls="true"></video></p>

<p>Great trip, even if I wished my bike was actually the <a href="https://en.wikipedia.org/wiki/BMW_GS">BMW GS</a> it dresses up as. We went from Brisbane to Warwick to stay up and watch the <a href="https://astroblogger.blogspot.com/2021/12/geminid-meteor-shower-13-15-december.html">Geminid Meteor Shower</a> with dark skies. Didn’t try for any pics of that though!</p>

<p><img src="/assets/images/2023-07-21-XR-for-two-2021.jpg" alt="A motorbike loaded up for two." /></p>

<h2 id="postscript">Postscript</h2>

<p>Dad dug up a photo from my childhood, probably the one time they let me ride a quad bike, adding “You used not to be so careful…”</p>

<p><img src="/assets/images/2023-07-21-quad-bike-splash.jpg" alt="A child on a quad bike, utterly sending it into a body of water, creating a huge splash." /></p>

<p>Maybe that’s why it was the only time. 🤔</p>]]></content><author><name>David Lord</name></author><category term="motorbikes" /><summary type="html"><![CDATA["If I screw this up," I said to Char, "I want it on video."]]></summary></entry><entry><title type="html">UQCS Panel: “Things I Wish I Knew In First Year”</title><link href="https://blog.lord.geek.nz/2022/03/15/first-year-panel/" rel="alternate" type="text/html" title="UQCS Panel: “Things I Wish I Knew In First Year”" /><published>2022-03-15T00:00:00+00:00</published><updated>2022-03-15T00:00:00+00:00</updated><id>https://blog.lord.geek.nz/2022/03/15/first-year-panel</id><content type="html" xml:base="https://blog.lord.geek.nz/2022/03/15/first-year-panel/"><![CDATA[<p>The <a href="https://uqcs.org">UQ Computing Society</a> run a regular panel called <strong>Things I Wish I Knew In First Year</strong>. I joined the party this time, and given it’d been six years since I graduated, I finally had some useful perspective.</p>

<p>It was a lot of fun! Heaps of questions from the floor. Plus, there was a plush shark on my lap the whole time. #blahajlyfe <img src="/assets/images/blobhaj.webp" alt="Emoji of a soft shark" /></p>

<center><iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/Glq3ksMjtEE?t=224" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe></center>]]></content><author><name>David Lord</name></author><category term="talks" /><category term="uqcs" /><summary type="html"><![CDATA[I was on a panel for the UQ Computing Society, and I had a plush shark on my lap the whole time.]]></summary></entry><entry><title type="html">Some jq tricks</title><link href="https://blog.lord.geek.nz/2022/03/15/jq-tricks/" rel="alternate" type="text/html" title="Some jq tricks" /><published>2022-03-15T00:00:00+00:00</published><updated>2022-03-15T00:00:00+00:00</updated><id>https://blog.lord.geek.nz/2022/03/15/jq-tricks</id><content type="html" xml:base="https://blog.lord.geek.nz/2022/03/15/jq-tricks/"><![CDATA[<p>Learned a few things today, thought I’d post them online as code snippets.</p>

<p>I was using <a href="https://www.terraform.io/">Terraform</a> to manage some infrastructure, and some resource was giving me grief - breaking the program and giving an unbeatably vague error message when I tried to do a plan involving it. Unfortunately the TF state file was over a thousand lines, and so it was hard to track down the resource in question. It was hard to isolate individual resources in such a big file.</p>

<p>Each domain name creates four kinds of resources from each zone, so that meant working with four separate arrays in the JSON.<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> So: I needed to manipulate the 4 kinds of resources created from each zone, directly in tfstate, and remove everything except the broken part to <em>know</em> what’s causing the problem. Processing JSON? Sounds like a task for the sublime <a href="https://stedolan.github.io/jq/">jq</a>.</p>

<h2 id="setup">Setup</h2>

<p>Given a tfstate file like:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="nl">"resources"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
  </span><span class="p">{</span><span class="err">something</span><span class="w"> </span><span class="err">boring</span><span class="p">},</span><span class="w">
  </span><span class="p">{</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"parking_dkim"</span><span class="p">,</span><span class="w">
   </span><span class="nl">"instances"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="p">{</span><span class="nl">"index_key"</span><span class="p">:</span><span class="w"> </span><span class="s2">"lord.geek.nz"</span><span class="p">,</span><span class="w"> </span><span class="err">...</span><span class="p">},</span><span class="w">
      </span><span class="p">{</span><span class="nl">"index_key"</span><span class="p">:</span><span class="w"> </span><span class="s2">"dal-corp.com"</span><span class="p">,</span><span class="w"> </span><span class="err">...</span><span class="p">},</span><span class="w">
      </span><span class="err">...</span><span class="w"> </span><span class="err">lots</span><span class="w"> </span><span class="err">more</span><span class="w"> </span><span class="err">instances</span><span class="w"> </span><span class="err">here</span><span class="w">
    </span><span class="p">]},</span><span class="w">
  </span><span class="p">{</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"parking_dmarc"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"instances"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
       </span><span class="p">{</span><span class="nl">"index_key"</span><span class="p">:</span><span class="w"> </span><span class="s2">"lord.geek.nz"</span><span class="p">,</span><span class="w"> </span><span class="err">...</span><span class="p">},</span><span class="w">
       </span><span class="p">{</span><span class="nl">"index_key"</span><span class="p">:</span><span class="w"> </span><span class="s2">"dal-corp.com"</span><span class="p">,</span><span class="w"> </span><span class="err">...</span><span class="p">},</span><span class="w">
      </span><span class="err">...</span><span class="w"> </span><span class="err">lots</span><span class="w"> </span><span class="err">more</span><span class="w"> </span><span class="err">instances</span><span class="w"> </span><span class="err">here</span><span class="w">
     </span><span class="p">]}</span><span class="w">
</span><span class="err">}</span><span class="w">
</span></code></pre></div></div>

<p>Back up the original file:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cp </span>prod.tfvars prod_full.tfvars
</code></pre></div></div>
<p>and then run the following examples like this:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jq <span class="nt">-f</span> example.jq &lt; prod_full.tfvars <span class="o">&gt;</span> prod.tfvars
</code></pre></div></div>

<p><strong>Let’s get into it</strong>.</p>

<h2 id="binary-search-functions-vs-an-array-of-numbers">Binary search functions vs an array of numbers</h2>

<p>Make your own functions for a binary search by chaining these:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def right_half(addr):
  del(addr[0:(addr|length)/2]);
def left_half(addr):
  del(
    addr[
      ((addr|length)/2 | floor) : ]
  );

[range(1; 18)] |
right_half(.) |
left_half(.)
</code></pre></div></div>
<p>The example starts with the array [1..18], then takes the right half. It slices that up and takes the left half of what’s left, returning [10, 11, 12, 13].</p>

<p>With these functions in hand, you can apply them to do your own binary search in tfstate. Narrow down and see what’s breaking it.</p>

<h2 id="binary-search-on-the-terraform-state">Binary search on the Terraform state</h2>

<p>Try modifying the state by removing half of it, then run Terraform again, and see if it breaks:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def right_half(addr):
  del(addr.instances[0:(addr.instances|length)/2]);
def left_half(addr):
  del(
    addr.instances[
      ((addr.instances|length)/2 | floor) : ]
  );

left_half(.resources[1,2,3,4])
</code></pre></div></div>

<p>If that works fine (no error), try the right half instead:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># declarations as above
right_half(.resources[1,2,3,4])
</code></pre></div></div>

<p>Say that showed the error, then you’d subdivide it:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># declarations as above
right_half(.resources[1,2,3,4]) |
left_half(.resources[1,2,3,4])
</code></pre></div></div>

<p>If that shows the error, great, add another <code class="language-plaintext highlighter-rouge">| left_half</code> to keep narrowing it down. If not, try <code class="language-plaintext highlighter-rouge">| right_half</code> again. Keep doing this until you’ve found one set of resources corresponding to one domain. Tada, you’ve done it.</p>

<p>Once you know what you’re looking for, or only a couple, you can try another approach to only keep specific domains’ resources and discard the rest.</p>

<h2 id="another-approach-filtering-elements-based-on-sub-elements">Another approach: filtering elements based on sub-elements</h2>

<p>This syntax is super useful, using the <a href="https://stedolan.github.io/jq/manual/">update operator</a> with <code class="language-plaintext highlighter-rouge">map</code> and <code class="language-plaintext highlighter-rouge">select</code>.</p>

<p>You can filter members of the resources array by whether their index_key has one of the zones you want.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>["lord.geek.nz", "dal-corp.com", "angry.nerds"] as $targets |
.resources[1,2,3,4].instances |= map(select([.index_key] | inside($targets))) |
</code></pre></div></div>

<h2 id="wrapping-up">Wrapping up</h2>

<p>This is not the best explanation I can write (promise!) but I hope these code samples come in handy for you.</p>

<p>The <a href="https://stedolan.github.io/jq/manual/">jq manual</a> is packed full of useful information. It’s usually a better place to start than StackOverflow.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Hashicorp <a href="https://www.terraform.io/language/state">discourage you from treating it as JSON</a>, but if I respected warning labels, I wouldn’t have eaten all those paint chips as a kid. Lead is an all-natural sweetener and sometimes you’ve just gotta hack some state. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>David Lord</name></author><category term="jq" /><category term="programming" /><summary type="html"><![CDATA[Learned a few things today, thought I'd post them online. Some binary search is involved.]]></summary></entry><entry><title type="html">Shakespearean references and British authors</title><link href="https://blog.lord.geek.nz/2022/01/23/shakespeare/" rel="alternate" type="text/html" title="Shakespearean references and British authors" /><published>2022-01-23T00:00:00+00:00</published><updated>2022-01-23T00:00:00+00:00</updated><id>https://blog.lord.geek.nz/2022/01/23/shakespeare</id><content type="html" xml:base="https://blog.lord.geek.nz/2022/01/23/shakespeare/"><![CDATA[<p>Had a bit of a thought last night after a glass of whiskey and decided it ought to be shared with the world. What better way to practice my own writing than to constrain it to 280 characters?</p>

<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">A British author has 80% of their new novel written, but the last 20% just won&#39;t come. They&#39;re stumped. After weeks, their eyes turn to the Shakespeare section of their home office. A merry wanderer whispers in his ear, &quot;What would just a little hurt? Just to unblock the spring?&quot;</p>&mdash; dal (@dal_geek) <a href="https://twitter.com/dal_geek/status/1484833245560983555?ref_src=twsrc%5Etfw">January 22, 2022</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<p><br /></p>

<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">They resist, but after another week, the serpent cherub&#39;s whisperings lead their frustration to take down the books of the Bard. &quot;Is it plagiarism if it&#39;s out of copyright?&quot; they wonder, and then, &quot;What is plagiarism but a rude name for a tribute? A pastiche? An inspiration?&quot;</p>&mdash; dal (@dal_geek) <a href="https://twitter.com/dal_geek/status/1484833869207846916?ref_src=twsrc%5Etfw">January 22, 2022</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<p><br /></p>

<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">They pore over the tomes, and finally decide: they&#39;ll use A Midsummer Night&#39;s Dream. Just a little bit of setting, a little twist of character, one thing leads to another...</p>&mdash; dal (@dal_geek) <a href="https://twitter.com/dal_geek/status/1484834390400442373?ref_src=twsrc%5Etfw">January 22, 2022</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<p><br /></p>

<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">... and suddenly the climax of the book is yet another fey performance which puts the villagefolk, and then the reader, to sleep, and when they awaken, all the threads of plot have disappeared and been replaced by a neatly woven image. An empty saucer with dregs of milk resides.</p>&mdash; dal (@dal_geek) <a href="https://twitter.com/dal_geek/status/1484834852545634306?ref_src=twsrc%5Etfw">January 22, 2022</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<p><br /></p>

<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">I really don&#39;t know what it is with British authors and over-seasoning their novels with Shakespearean references, like a wealthy merchant pouring an ounce of nutmeg into their wine because it&#39;s posh - perhaps it&#39;s required for entry into the writers&#39; guild? - but it has to stop.</p>&mdash; dal (@dal_geek) <a href="https://twitter.com/dal_geek/status/1484835676898361344?ref_src=twsrc%5Etfw">January 22, 2022</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<p><br /></p>

<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">This level of cultural influence feels like Brisbane insisting that it&#39;s a world city because it hosted the World Expo in 1988. Yes, well done, but keep up with the times. Do something new. Don&#39;t stand on the shoulders of giants until they&#39;re a weathered pair of legs in a desert.</p>&mdash; dal (@dal_geek) <a href="https://twitter.com/dal_geek/status/1484836821188378627?ref_src=twsrc%5Etfw">January 22, 2022</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<p><br /></p>

<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">This rant was brought to you by the creeping horror of reaching Rotherweird&#39;s final act and realising that it&#39;s been quietly assembling the Midsummer Night Exodia for the last two hundred pages.</p>&mdash; dal (@dal_geek) <a href="https://twitter.com/dal_geek/status/1484837412723650569?ref_src=twsrc%5Etfw">January 22, 2022</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<p><br /></p>

<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">(I did like Rotherweird, and I do like Brisbane, and I even excuse Pratchett given that he referenced the sum total of human history in his time. But I wish Shakspere didn&#39;t crop up so often. What magic was there is gone now that we have high-speed rail and paperless offices.)</p>&mdash; dal (@dal_geek) <a href="https://twitter.com/dal_geek/status/1484838440294547466?ref_src=twsrc%5Etfw">January 22, 2022</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<p>I’m pretty happy with this. It was all written off the cuff, tweet by tweet, and incorporates enough references of my own to add some spice. <em>A Midsummer Night’s Dream</em>, pixie folklore, spice trade, Brisbane’s expo, Newton into Ozymandias, Yu-Gi-Oh. It’s scattered but, I dunno, I like that breadth.</p>

<p>However… it’s also, in the cold light of day, a bit ruder than I’m proud of. This is hard to quantify - it’s fun that way, but the internet has enough flame wars, you know?</p>]]></content><author><name>David Lord</name></author><summary type="html"><![CDATA[I had some strong opinions on the internet: give Shakespeare a rest, and leave him out of original content occasionally.]]></summary></entry><entry><title type="html">Prettypipe</title><link href="https://blog.lord.geek.nz/2021/10/13/rust-prettypipe/" rel="alternate" type="text/html" title="Prettypipe" /><published>2021-10-13T00:00:00+00:00</published><updated>2021-10-13T00:00:00+00:00</updated><id>https://blog.lord.geek.nz/2021/10/13/rust-prettypipe</id><content type="html" xml:base="https://blog.lord.geek.nz/2021/10/13/rust-prettypipe/"><![CDATA[<p>After dealing with a program that unexpectedly printed to stderr when everything was fine, I had an idea for a utility to make the next time easier. It should wrap another program and print its stdout in green, and stderr in red.</p>

<p>I’ve also been wanting to play with Rust, because of its attitude to performance and distribution. You can only create so many virtualenvs on so many hosts before Python starts to wear thin.</p>

<p>Figured this would be a good way to see whether the grass really is greener. After some futzing around with lifetimes… maybe not. The most interesting bug was from taking a <code class="language-plaintext highlighter-rouge">std::process:ChildStdout</code> by value, calling <code class="language-plaintext highlighter-rouge">.as_raw_fd</code> by reference in the same expression, leaving the <code class="language-plaintext highlighter-rouge">ChildStdout</code> without a reference and therefore dropping it, and it closed the file descriptor on drop. So it was just closed as soon as I wanted to use it.</p>

<p>The project could really use some tests and probably some modularity, but the perfect is the enemy of the good and all that. Maybe I’ll come back to it.</p>

<p>Thanks heaps to the friendly folks in the <a href="https://uqcs.org/">UQ Computing Society’s</a> #rust channel in Slack for unblocking me twice.</p>

<p><strong>Anyway, here’s <a href="https://github.com/d-lord/prettypipe/">prettypipe</a>.</strong></p>

<p><img src="/assets/images/prettypipe-full.png" alt="Screenshot of a curl command, with stdout in green and stderr in red" /></p>]]></content><author><name>David Lord</name></author><category term="rust" /><category term="programming" /><summary type="html"><![CDATA[My very first Rust program helps you distinguish between stdout and stderr.]]></summary></entry><entry><title type="html">Catstrap</title><link href="https://blog.lord.geek.nz/2021/09/28/catstrap/" rel="alternate" type="text/html" title="Catstrap" /><published>2021-09-28T00:00:00+00:00</published><updated>2021-09-28T00:00:00+00:00</updated><id>https://blog.lord.geek.nz/2021/09/28/catstrap</id><content type="html" xml:base="https://blog.lord.geek.nz/2021/09/28/catstrap/"><![CDATA[<p>Another project recently tripped and fell into my portfolio at work. It’s a little internal webapp which uses Bootstrap 3.</p>

<p>I wanted to play with Bootstrap out of hours, so I can make front-end changes if need be, and inspiration struck. Figured it’d be worth learning v4 to a) see where things are going and b) be prepared to migrate ours if needed in future.</p>

<p><strong>Anyway, here’s <a href="https://lord.geek.nz/catstrap">catstrap</a>.</strong></p>]]></content><author><name>David Lord</name></author><category term="webdev" /><summary type="html"><![CDATA[Better shitposting through legitimate front-end libraries.]]></summary></entry><entry><title type="html">Animated timer shenanigan in After Effects</title><link href="https://blog.lord.geek.nz/2021/04/12/important-dog-video/" rel="alternate" type="text/html" title="Animated timer shenanigan in After Effects" /><published>2021-04-12T00:00:00+00:00</published><updated>2021-04-12T00:00:00+00:00</updated><id>https://blog.lord.geek.nz/2021/04/12/important-dog-video</id><content type="html" xml:base="https://blog.lord.geek.nz/2021/04/12/important-dog-video/"><![CDATA[<p>I learned something about scripting in After Effects by building my own timer.</p>

<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/MwiiX5KJHtc" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>

<hr />

<p>Behind the scenes looks something like:</p>

<p><img src="/assets/images/ae-timer-workspace.png" alt="Screenshot of the Adobe After Effects workspace used for this project." /></p>

<p>Adapted from <a href="https://www.youtube.com/watch?v=tzMbwnv79MA">tutvid’s tutorial on countdown timers</a>.</p>]]></content><author><name>David Lord</name></author><category term="after-effects" /><summary type="html"><![CDATA[Back to making important videos of other people's dogs 😎]]></summary></entry><entry><title type="html">How to use hybrid properties with regular expressions in SQLAlchemy</title><link href="https://blog.lord.geek.nz/2021/03/09/sqlalchemy-hybrid-properties-regex/" rel="alternate" type="text/html" title="How to use hybrid properties with regular expressions in SQLAlchemy" /><published>2021-03-09T00:00:00+00:00</published><updated>2021-03-09T00:00:00+00:00</updated><id>https://blog.lord.geek.nz/2021/03/09/sqlalchemy-hybrid-properties-regex</id><content type="html" xml:base="https://blog.lord.geek.nz/2021/03/09/sqlalchemy-hybrid-properties-regex/"><![CDATA[<p>SQLAlchemy has these things called <a href="https://docs.sqlalchemy.org/en/13/orm/extensions/hybrid.html">hybrid properties</a>, which let you create class attributes that can be accessed from Python but also from SQL. They can be tricky to understand.</p>

<p>If you want to use them with a regular expression, life can get a lot harder; there are a couple of pitfalls you need to avoid.</p>

<p>This is a quick info post to demonstrate that combination. It uses SQLAlchemy 1.3 and MySQL 5.7.</p>

<h1 id="heres-our-problem">Here’s our problem</h1>

<p>The queries we want to run will provide boolean attributes called <code class="language-plaintext highlighter-rouge">is_alert</code> and <code class="language-plaintext highlighter-rouge">is_update</code>, which look like this:</p>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">SELECT</span> <span class="n">title</span><span class="p">,</span>
<span class="n">title</span> <span class="n">REGEXP</span> <span class="s1">'- (?:UPDATED? )?ALERT'</span> <span class="k">AS</span> <span class="n">is_alert</span>
<span class="k">FROM</span> <span class="n">nodes</span><span class="p">;</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">SELECT</span> <span class="n">title</span><span class="p">,</span>
<span class="n">title</span> <span class="n">REGEXP</span> <span class="s1">'[[.full-stop.]][[:digit:]]{4}[[.full-stop.]][[:digit:]]+$'</span> <span class="k">AS</span> <span class="n">is_update</span>
<span class="k">FROM</span> <span class="n">nodes</span><span class="p">;</span></code></pre></figure>

<p>It can sometimes be difficult for beginners (like me!) to translate SQL into SQLAlchemy, so the rest of the post will explain how.</p>

<h1 id="just-give-me-the-example">Just give me the example!</h1>

<p>Here’s how to create two hybrid attributes, <code class="language-plaintext highlighter-rouge">is_alert</code> and <code class="language-plaintext highlighter-rouge">is_update</code>, which use regular expressions, with a MySQL back-end.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">sqlalchemy</span> <span class="kn">import</span> <span class="n">Column</span><span class="p">,</span> <span class="n">Text</span>
<span class="kn">from</span> <span class="nn">sqlalchemy.ext.declarative</span> <span class="kn">import</span> <span class="n">declarative_base</span>
<span class="n">Base</span> <span class="o">=</span> <span class="n">declarative_base</span><span class="p">()</span>

<span class="k">class</span> <span class="nc">Bulletin</span><span class="p">(</span><span class="n">Base</span><span class="p">):</span>
    <span class="n">__tablename__</span> <span class="o">=</span> <span class="s">'nodes'</span>
    <span class="n">title</span> <span class="o">=</span> <span class="n">Column</span><span class="p">(</span><span class="n">Text</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s">'title'</span><span class="p">)</span>

    <span class="s">""" The pattern for is_alert is the same in both Python and MySQL, so could be a class constant """</span>

    <span class="o">@</span><span class="n">hybrid_property</span>
    <span class="k">def</span> <span class="nf">is_alert</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
        <span class="k">return</span> <span class="nb">bool</span><span class="p">(</span><span class="n">re</span><span class="p">.</span><span class="n">search</span><span class="p">(</span><span class="sa">r</span><span class="s">'- (?:UPDATED? )?ALERT'</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">title</span><span class="p">))</span>

    <span class="o">@</span><span class="n">is_alert</span><span class="p">.</span><span class="n">expression</span>
    <span class="k">def</span> <span class="nf">is_alert</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
        <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">title</span><span class="p">.</span><span class="n">op</span><span class="p">(</span><span class="s">'regexp'</span><span class="p">)(</span><span class="sa">r</span><span class="s">'- (?:UPDATED? )?ALERT'</span><span class="p">)</span>

    <span class="s">""" The patterns for is_update are different between Python and MySQL as they use different regex dialects """</span>

    <span class="o">@</span><span class="n">hybrid_property</span>
    <span class="k">def</span> <span class="nf">is_update</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
        <span class="k">return</span> <span class="nb">bool</span><span class="p">(</span><span class="n">re</span><span class="p">.</span><span class="n">search</span><span class="p">(</span><span class="sa">r</span><span class="s">'\.\d{4}\.\d+$'</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">node_reference</span><span class="p">))</span>

    <span class="o">@</span><span class="n">is_update</span><span class="p">.</span><span class="n">expression</span>
    <span class="k">def</span> <span class="nf">is_update</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
        <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">node_reference</span><span class="p">.</span><span class="n">op</span><span class="p">(</span><span class="s">'regexp'</span><span class="p">)(</span><span class="s">'[[.full-stop.]][[:digit:]]{4}[[.full-stop.]][[:digit:]]+$'</span><span class="p">)</span>
        <span class="c1"># for more information on this regex dialect, see https://dev.mysql.com/doc/refman/5.7/en/regexp.html
</span></code></pre></div></div>

<h1 id="what-are-the-tricky-parts">What are the tricky parts?</h1>

<h2 id="the-regexp-or-rlike-operator">The ‘REGEXP’ or ‘RLIKE’ operator</h2>

<p><a href="https://dev.mysql.com/doc/refman/5.7/en/regexp.html">MySQL uses ‘REGEXP’ or ‘RLIKE’</a>, as in <code class="language-plaintext highlighter-rouge">title REGEXP 'some pattern'</code>. If you’re using another back-end, you might need a different operator.</p>

<p>Remember, the queries we want to run will look like this:</p>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">SELECT</span> <span class="n">title</span><span class="p">,</span>
<span class="n">title</span> <span class="n">REGEXP</span> <span class="s1">'some pattern'</span> <span class="k">AS</span> <span class="n">attribute_name</span>
<span class="k">FROM</span> <span class="n">nodes</span><span class="p">;</span></code></pre></figure>

<h2 id="op">‘op’</h2>

<p>SQLAlchemy 1.3 doesn’t have a built-in operator for regular expressions, so we have to use <code class="language-plaintext highlighter-rouge">.op</code> to access it. You don’t need to import anything - it’s available straight from the Column. It takes a string, which is the name of the SQL operator you want.</p>

<p>So to get access to that from Python, we use <code class="language-plaintext highlighter-rouge">title.op('regexp')('some pattern')</code>. We could use <code class="language-plaintext highlighter-rouge">title.op('rlike')('some pattern')</code>, since MySQL considers it a different name for the same thing.</p>

<h3 id="op-is-not-func">‘op’ is not ‘func’</h3>

<p>There’s nothing wrong with using SQL functions in general, but I’m highlighting this to make you aware:</p>

<p><strong>SQL functions and SQL operators are different things</strong>. Write down the SQL you want to run and make sure you know which you want.</p>

<p>It might sound obvious, but I wish someone had highlighted that while I was trying to solve this - mixing them up will do you no favours. 😄</p>

<p>We are <strong>not</strong> doing this right now, because it’s a different approach which (in MySQL) requires the <code class="language-plaintext highlighter-rouge">EXECUTE</code> permission:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">sqlalchemy</span> <span class="kn">import</span> <span class="n">func</span>

<span class="k">class</span> <span class="nc">Bulletin</span><span class="p">:</span>
    <span class="p">...</span>

    <span class="o">@</span><span class="n">is_alert</span><span class="p">.</span><span class="n">expression</span>
    <span class="k">def</span> <span class="nf">is_alert</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
        <span class="n">regexp_func</span> <span class="o">=</span> <span class="n">func</span><span class="p">(</span><span class="s">'regexp'</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">regexp_func</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">title</span><span class="p">,</span> <span class="s">'some pattern'</span><span class="p">)</span>

</code></pre></div></div>

<p>This code will give you a SQL <em>function</em>, not a SQL <em>operator</em>. The difference is this:</p>

<table>
  <thead>
    <tr>
      <th>name</th>
      <th>are we using it here?</th>
      <th>example</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>operator</td>
      <td>yes</td>
      <td><code class="language-plaintext highlighter-rouge">title REGEXP pattern</code></td>
    </tr>
    <tr>
      <td>function</td>
      <td>no</td>
      <td><code class="language-plaintext highlighter-rouge">regexp_match(title, pattern)</code></td>
    </tr>
  </tbody>
</table>

<p>The <code class="language-plaintext highlighter-rouge">regexp_match</code> function provided by MySQL will do the job, but it’s a function, and this example is using operators. So we’re not using that function.</p>

<h2 id="hybrid_property-and-expression">@hybrid_property and expression</h2>

<p>A hybrid property, in short, is a property on the class which you can use in both of these ways:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># As a Python property (example A):
</span><span class="n">my_bulletin</span> <span class="o">=</span> <span class="n">db_session</span><span class="p">.</span><span class="n">query</span><span class="p">(</span><span class="n">Bulletin</span><span class="p">).</span><span class="n">first</span><span class="p">()</span>
<span class="k">if</span> <span class="n">my_bulletin</span><span class="p">.</span><span class="n">is_update</span><span class="p">:</span>
    <span class="k">print</span><span class="p">(</span><span class="s">"Yep, it's an update"</span><span class="p">)</span>

<span class="c1"># As an attribute in SQL (example B):
</span><span class="n">updates</span> <span class="o">=</span> <span class="n">db_session</span><span class="p">.</span><span class="n">query</span><span class="p">(</span><span class="n">Bulletin</span><span class="p">).</span><span class="nb">filter</span><span class="p">(</span><span class="n">Bulletin</span><span class="p">.</span><span class="n">is_update</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span>
<span class="kn">from</span> <span class="nn">sqlalchemy</span> <span class="kn">import</span> <span class="ow">not</span>
<span class="n">not_updates</span> <span class="o">=</span> <span class="n">db_session</span><span class="p">.</span><span class="n">query</span><span class="p">(</span><span class="n">Bulletin</span><span class="p">).</span><span class="nb">filter</span><span class="p">(</span><span class="n">not_</span><span class="p">(</span><span class="n">Bulletin</span><span class="p">.</span><span class="n">is_update</span><span class="p">))</span>
</code></pre></div></div>

<p>The first examples in <a href="https://docs.sqlalchemy.org/en/13/orm/extensions/hybrid.html">SQLAlchemy’s documentation</a> use some convenient examples where you can write some Python code which will also be translated implicitly to SQL. That’s cool because it’s good to know that can work. However, it’s often not the case with regexes, and here’s where I stumbled. For this task you need to specify two versions - one native Python (example A) and one in SQLAlchemy’s own Python-that-means-SQL (example B).</p>

<p>To know <em>how</em> these are declared separately, see the top example again. But I hope you now understand <em>why</em>.</p>

<p>There are also hybrid methods, which we aren’t going into here.</p>

<h1 id="wrapping-up">Wrapping up</h1>

<p>You hopefully understand, or at the very least have working example code for, querying on regular expressions in SQLAlchemy.</p>

<p>Cheers!</p>]]></content><author><name>David Lord</name></author><category term="python" /><category term="sqlalchemy" /><category term="programming" /><summary type="html"><![CDATA[SQLAlchemy has these things called hybrid properties, which let you create class attributes that can be accessed from Python but also from SQL. They can be tricky to combine with other logic like regular expressions.]]></summary></entry><entry><title type="html">I’ve finally clicked with roguelikes</title><link href="https://blog.lord.geek.nz/2021/02/19/roguelikes/" rel="alternate" type="text/html" title="I’ve finally clicked with roguelikes" /><published>2021-02-19T00:00:00+00:00</published><updated>2021-02-19T00:00:00+00:00</updated><id>https://blog.lord.geek.nz/2021/02/19/roguelikes</id><content type="html" xml:base="https://blog.lord.geek.nz/2021/02/19/roguelikes/"><![CDATA[<p>I feel like I’ve suddenly clicked with the roguelike indie genre, a few years late, and now I’m working my way through it in reverse chronological order.</p>

<p><a href="https://www.supergiantgames.com/games/hades/">Hades</a> is superb, with art and writing to match the gameplay, and I’ve played through it enough times to reach ~5 heat with most of the weapons. When we were in managed isolation in early November, it was one of my coping strategies for being stuck in a room with my girlfriend, both working full-time and on video calls, for two weeks. <sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup></p>

<p><a href="https://www.megacrit.com/">Slay the Spire</a> is also a great game. I tried it a few years ago on iOS, but didn’t really grok it<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup> until seeing <a href="https://www.youtube.com/watch?v=0AkW9yk0D-M">Firebat’s thunder strike combo run</a> last week. It’s the first deck-building game I’ve enjoyed. The card game Dominion is many people’s first introduction to the concept, and it’s all very well, but playing it with other people means you can’t just slow down and really think about your turn.</p>

<p>Although… I did get into <a href="https://subsetgames.com/ftl.html">Faster Than Light</a> when it was released, and again when they released a free full expansion (!). It also has the core gameplay loop of playing through a map, facing discrete encounters, and accruing loot, abilities and scars. It can be unrelenting at times, where a run of bad luck can throw away an hour’s play, and I ended up playing it on its easier mode for more enjoyment. It also had enough original ideas that Door Monster <a href="https://www.youtube.com/watch?v=RVHw5Hcat9s">did a great sketch about it</a>.</p>

<p>Actually… I lied, <a href="https://playhearthstone.com/">Hearthstone</a>’s dungeon run mode was probably my first introduction to a fun deck-builder. It takes a game you already know well, with familiar mechanics and art, and then sneaks in cards and abilities, and suddenly you’re vastly overpowered but facing a boss whose mechanics you didn’t anticipate, and trying to figure out how to stay alive one more turn and hang on to the deck you just spent an hour building.</p>

<p>One thing I’d like to see explored in the genre is valuing the player’s time. If you die in a run, you lose a state that you’ve probably spent half an hour assembling. That does introduce a tension which helps make the battle feel meaningful, but… for a working stiff, with finite leisure time, it would be nice to have a “rewind” button.</p>

<p>Dark Souls was really satisfying to learn and get right, and just as unrelenting when you got it wrong, but I never even managed to finish the first game because it also uses this tradeoff of leisure time for tension. It’s very effective, but so costly.</p>

<p>Leisure time should respect the player’s time.</p>

<p>Oh, and one more thing, which seems to be an original thought; Slay the Spire’s boss music put me so strongly in mind of <a href="https://www.youtube.com/watch?v=hjS689rvbOw">One-Winged Angel</a> that after finishing the last battle, I couldn’t remember the actual music, but I did now have One-Winged Angel stuck in my head. It’s a legendary track, and if you’ve never heard it (Ben) then it’s worth a listen. But when I went looking for discussion on it, I found none. Shame - you always wonder if someone can explain precisely what makes them similar, in fancy music words.</p>

<p><img src="/assets/images/spire_first_victory.png" alt="Screenshot of the Slay the Spire stats screen showing the successful build." class="center-image" /></p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>I adore her, but being in one room with one person for a fortnight (plus walking breaks in the prison yard) will strain the sweetest of temperaments, possibly through a sieve. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>“Grok” is another word for “understand”, favoured by nerds particularly for its use in the famous StackOverflow post about <a href="https://stackoverflow.com/a/1220118/2209946">grokking vim</a>. Wikipedia reckons <a href="https://en.wikipedia.org/wiki/Grok">it showed up in scifi in 1961</a>. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>David Lord</name></author><category term="games" /><category term="lifestyle" /><summary type="html"><![CDATA[Still not becoming a video game writer, though.]]></summary></entry></feed>