<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>davidyat.es</title><link>https://davidyat.es/</link><description>Recent content on davidyat.es</description><generator>Hugo -- gohugo.io</generator><language>en-gb</language><managingEditor>David Yates</managingEditor><webMaster>David Yates</webMaster><copyright>© 2026 David Yates</copyright><lastBuildDate>Tue, 07 Apr 2026 03:31:12 +0000</lastBuildDate><atom:link href="https://davidyat.es/" rel="self" type="application/rss+xml"/><item><title>Dozen</title><link>https://davidyat.es/2026/04/07/twelve-years/</link><pubDate>Tue, 07 Apr 2026 03:31:12 +0000</pubDate><author>David Yates</author><guid>https://davidyat.es/2026/04/07/twelve-years/</guid><content:encoded><![CDATA[
        <img src="https://davidyat.es/content/images/2026/04/eggs.jpg" class="post-img" />
        <p>As of today, I&rsquo;ve been writing this blog for twelve years. Much has changed in that time. Like everyone in tech and tech-adjacent fields, much of the work I do on a daily basis has been transformed by recent advances in AI; first gradually, then suddenly.</p>
<p>I&rsquo;ve been a pretty enthusiastic user of generative AI since late 2022, though I had a fair amount of initial skepticism about chatbots.</p>



  <blockquote>
    <p>I&rsquo;ve personally been much more interested in using Stable Diffusion than I ever was in text generation systems like <a href="https://openai.com/api/">GPT-3</a> and <a href="https://aidungeon.io/">AI Dungeon</a>. I&rsquo;m a far better writer than artist, so the idea of generating text from a prompt is an amusing diversion at best. If I want some text, I&rsquo;ll just write it myself. Although some artists are already integrating AI into their workflows, others will probably have the same meh reaction to its applications in their field.</p>

    
    <small>Me in August 2022, <cite><a href="/2022/08/31/stable-diffusion/">Stable Diffusion</a></cite></small>
    
  </blockquote>

<p>The earliest versions of ChatGPT did not have any kind of function-calling/tool-use capability, so any content you would get from them was pulled from somewhere in their latent space. Answers they gave were often correct, but <a href="https://ritza.co/articles/verification-is-as-hard-as-creation-chatgpt/">had to be carefully verified</a> as they weren&rsquo;t grounded in anything but the model&rsquo;s internal mathematics. These days, all major model providers have integrated tool use, allowing the model search the web in real time and provide citations for its claims, so the verification process has been reduced from &ldquo;get an AI answer and then do all the work to get the same answer without AI&rdquo; to &ldquo;check whether the AI&rsquo;s sources are legit and actually support its claims&rdquo;.<sup id="fnref:1"><a href="https://davidyat.es/2026/04/07/twelve-years/#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>As I&rsquo;ve previously <a href="/2025/09/27/signifier-flotation-devices/">opined about</a> <a href="/2026/03/27/universal-translator/">at length</a>, the grounding provided by tool use is what makes these models useful, whether that&rsquo;s research grounding through search results or grounding to a digital world through the output of terminal commands. This simple innovation is what makes an LLM-based system more than just a stochastic parrot (so anyone who still insists on citing that paper is a few years behind the curve).</p>
<p>Back when LLM chatbots got started, writing was the only thing they could do, so there was a lot of breathless talk about how they would replace writers. <a href="/2023/04/07/nine-years/">I wrote in 2023</a> that I did not see that happening, outside of the most boilerplate copy. Three years and many model upgrades later, I still don&rsquo;t like AI writing &ndash; while <a href="https://en.wikipedia.org/wiki/Wikipedia:Signs_of_AI_writing#Historical_indicators">some of the most annoying tells have been eliminated</a>, I have yet to find a reliable way to generate writing that does not have the AI smell. In most cases, I would advise against publishing any AI writing that hasn&rsquo;t been edited beyond the point of recognition.</p>
<p>Nevertheless, I find AI writing increasingly useful. The distinction is context. In the context of a chat with a chatbot, the writing is a living thing, highly personalised to my prompt. I can regenerate unsatisfactory responses, ask follow-up questions, or challenge conclusions. And at no point am I ever in any doubt that I&rsquo;m speaking to an LLM. Contrast that experience with reading an article written by an LLM. You get all of the annoying writing tics without any of the interactivity. This is why I&rsquo;m skeptical of the value of projects like Grokipedia: if we&rsquo;re going to have an LLM-powered encyclopedia, it should be closer to The Young Lady&rsquo;s Illustrated Primer from <em>The Diamond Age</em> than a bot-written Wikipedia. LLM writing is worth infinitely more alive than dead.</p>
<p>Considered from this angle, LLMs have replaced a lot of human writing that was previously common online. I can&rsquo;t remember the last time I looked at StackOverflow. And it&rsquo;s been a long time since I&rsquo;ve felt compelled to write a <a href="/tags/tutorial/">tutorial</a> for this blog, even though those have been some of my best performing posts. Whenever I encounter a technical issue these days, I get Claude Code or Google&rsquo;s AI search to generate me a custom tutorial; whenever I want to implement some technical thing, I brainstorm a bit with Claude and then get it to write the code. So I think LLMs largely <em>have</em> replaced the technical tutorial. That&rsquo;s one kind of post I probably won&rsquo;t write again.<sup id="fnref:2"><a href="https://davidyat.es/2026/04/07/twelve-years/#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<p><center><blockquote class="twitter-tweet"><p lang="en" dir="ltr">Sometimes I change the variable names in the code I copy off StackOverflow, just to leave my personal stamp.</p>&mdash; David Yates (@davidyat_es) <a href="https://twitter.com/davidyat_es/status/933221096903491584?ref_src=twsrc%5Etfw">November 22, 2017</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

</center>

<div class="caption">A bygone world.</div>
</p>
<p>I do not mourn the loss, because I&rsquo;m having too much fun programming with AI. The <a href="https://news.ycombinator.com/">orange website</a> has latterly been chock full of mournful dirges lamenting the death of the old order, of the thing that programming was before 24 November 2025. I&rsquo;ve come to appreciate that I did not value the act of writing code by hand &ndash; certainly not to the extent that many others did. As generative AI improves, it shows us what we really care about &ndash; at least one of the blog posts I read lamenting the retreat of hand-authored code was very clearly AI-written, with an obviously AI-generated header image. And there are probably visual artists out there somewhere using LLMs to generate writing and code while hand-making images.</p>
<p>I get it, and I&rsquo;m glad that this tech allows us to focus on what we care about and are good at. But <a href="https://simonwillison.net/guides/agentic-engineering-patterns/better-code/">AI should help us produce better output</a>. The thesis of <a href="/2026/03/27/universal-translator/">my universal translator post</a> was that we should view these things as tools that allow us to control and shape outputs, rather than as entities to delegate everything to &ndash; and that you should take responsibility for what you use AI to produce. I firmly believe that agentic engineering and synthography are emerging fields with their own set of skills to learn, and I plan to write a lot more about both in the coming year(s).</p>
<p>So here&rsquo;s to the next twelve years of handwritten thoughts about technological change.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>I&rsquo;ve found several pages on Grokipedia that cite posts on this blog to support things I never said.&#160;<a href="https://davidyat.es/2026/04/07/twelve-years/#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>And even if I do, it probably won&rsquo;t be for the benefit of human eyes.&#160;<a href="https://davidyat.es/2026/04/07/twelve-years/#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

        <p><a href="mailto:d@vidyat.es?subject=RE: Dozen">Reply via email</a></p>
        ]]></content:encoded></item><item><title>The universal translator</title><link>https://davidyat.es/2026/03/27/universal-translator/</link><pubDate>Fri, 27 Mar 2026 06:38:20 +0000</pubDate><author>David Yates</author><guid>https://davidyat.es/2026/03/27/universal-translator/</guid><content:encoded><![CDATA[
        <img src="https://davidyat.es/content/images/2026/03/1774597216617-translator.jpg" class="post-img" />
        <p>A few months ago, I <a href="/2025/09/27/signifier-flotation-devices/">wrote about the deep weirdness of LLM technology</a>, and how we’re all still learning how to approach its outputs. The marketing and popular perception of these models is that of a helpful robot assistant, and leaning into that characterisation is <em>also</em> a core part of what makes the technology useful in ways that <a href="https://openai.com/index/better-language-models/">straightforward text completion models</a> weren’t. By pretending to be a robot assistant, modern instruct models/chatbots are able to perform many digital tasks one might want a (digital) robot assistant to perform.</p>
<p>At the same time, thinking of LLMs as science-fictional robot assistants can lead us astray when using them, or even when discussing them. In my last post, I used the example of someone on LinkedIn who got mad about ChatGPT “ignoring his instructions” regarding which files it was and was not permitted to access on his Google Drive. Another illustrative example is the case of a GitHub user who submitted a large, AI-authored pull request to an open-source repository. When asked why the new code files included comments attributing authorship to a real person, the pull requester cheerfully replied that it was something the AI decided to do, and he didn’t question it – he deferred to the superior intellect of the logical robot from Star Trek that now lives in his computer.</p>
<p>Not wanting to take responsibility for things is a very human impulse, and one we’ve been using computers for ever since they were invented. But “the computer said it, so it must be true” is a very different statement before and after the widespread adoption of LLMs. The general notion of what computers do and how they work – logic, precision, determinism – just does not apply to these strange new word calculators that rotate the shapes of language heedless of what that language may signify.</p>
<p><figure>
  
  <img src="/content/images/2025/11/computer-accountable.jpeg" alt="Sorry human, you&rsquo;re still in charge." loading="lazy"/>
  
  <figcaption>
    <p>Sorry human, you&rsquo;re still in charge.</p>
  </figcaption>
</figure>
</p>
<p>Of course, that’s precisely what makes them so powerful and useful for radically different tasks than we used deterministic computer code for. The lack of determinism and somewhat unpredictable nature of the outputs means we can use LLMs to perform an enormous variety of messy tasks that were previously very difficult to automate. One good example is web scraping: to extract specific information from a website, you previously needed a large amount of highly specific and brittle parsing code for the exact page you wanted to scrape, which you had to manually update if the page changed its layout. Now you can just tell an LLM to get X info from Y page, and it’ll figure out the finicky details. And you can ask it again in a year&rsquo;s time, after the subject website has been completely redesigned, and it&rsquo;ll figure out the new finicky details.</p>
<p>In my day job, I often used to say that the main value of penetration testing over automated scans was that only humans could find <a href="https://owasp.org/www-community/vulnerabilities/Business_logic_vulnerability">business logic flaws</a>, because automated tools don’t understand context. A static analyser can tell you to replace your SQL statement string interpolation with parameterised queries, and HTML encode untrusted content included in pages, but it can’t figure out that users shouldn’t be able to change each other’s profiles, or transfer money out of each other’s bank accounts &ndash; at least not without a bunch of very specific setup. LLMs, on the other hand, can do these things, for the same reason they can analyse data and do basic literary analysis: we have managed to create intelligence (or something like it) through very high-level pattern matching.</p>
<p>But creating intelligence does not mean that we’ve created conscious beings, or even that we’ve made robots capable of replacing humans in every job. It just means that we have new tools – very powerful ones with previously unimagined capabilities, but <a href="https://www.theintrinsicperspective.com/p/bits-in-bits-out">tools nonetheless</a>. And that’s how I want to think of them. I have a tool called <code>ls</code> that lets me list directories, and I have a tool called <code>claude</code> that lets me translate English descriptions into computer code.</p>
<p>Anyone with even a passing familiarity with how LLMs work will know that their basic functionality is predicting how to continue a piece of text they’re given. Plenty of comparisons have been made to IntelliSense in programming IDEs, Markov chains, and your phone’s autocomplete, and they’re not wrong. The chat interface that remains the primary means of interacting with these models is a clever trick: every time you send a new reply, the entire chat transcript is fed back into the model, including the initial system prompt, the output of previous tool calls, and “memories”.<sup id="fnref:1"><a href="https://davidyat.es/2026/03/27/universal-translator/#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> The &ldquo;reply&rdquo; is then produced by continuing the transcript.</p>
<p>But I’ve lately been thinking of the LLM not as autocomplete, but as a <em>translator</em>. The most obvious application of this happens when you ask a model to translate some text into another language,<sup id="fnref:2"><a href="https://davidyat.es/2026/03/27/universal-translator/#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> but it also happens when you ask the model to write you a script – it’s translating your natural language specification into computer code. We can apply this analogy to just about any input and output of an LLM, and it really clicks into place when you think about how the model takes your input and does intense mathematical operations on it to extract a suitable response from its latent space.</p>
<p>Another thing I like about the translator framing is that it emphasises the importance of the user input. As with everything else in computers, when you put garbage in, you get garbage out. Hence <em>slop</em>. The vast majority of visibly AI-created content is rightfully called slop, but it’s always been my contention that this is the fault of the users, not the AI. <em>A computer can never be held accountable</em>.</p>
<p>Publishers of slop are often pilloried with phrases like “you didn’t make that, the computer did.” While intending to disparage the  slop purveyor, it also removes their accountability. As soon as we start saying that the computer made something, we start to excuse its obvious flaws and blame them on something that cannot take responsibility. While these tools do have limitations, most of the common flaws in anything made with AI can be fixed through more thoughtful and skilled usage of the AI tools. But if you want to improve your output, start by taking responsibility for it.</p>
<p>All that said, I don&rsquo;t want to place all the blame on users – some of it rests on the shoulders of OpenAI and its competitors. ChatGPT was built from the start as a mass-market consumer product. OpenAI&rsquo;s roots are in the Y Combinator B2C world of customer growth, and that means simple interfaces. ChatGPT is synonymous with AI in the public mind, and most other AI companies have copied its basic interface. The thrust of most marketing copy also plays up the helpful sci-fi robot angle.</p>
<p>This goal of user-friendliness, however, has meant that the majority of people’s experience of LLMs and related generative AI tools is mediated through a platform that does not showcase the full potential of the technology. It doesn’t help that most AI integrations in other applications are hastily slapped-on chatbots that end up just being worse versions of ChatGPT.</p>
<p>Nowhere is this truer than in image creation.</p>
<h1 id="image-creation">
  <a class="heading-anchor" href="#image-creation">#</a>
  Image creation
</h1>
<p><a href="https://www.forbes.com/sites/quickerbettertech/2025/10/15/i-tried-chatgpt-gemini-and-grok-for-image-creationand-they-all-failed/">This Forbes article from October 2025</a> describes the author&rsquo;s frustration with using ChatGPT, Gemini, and Grok to make images. An illustrative quote:</p>



  <blockquote>
    <p>Useful? Not really. Fun, maybe. But not useful yet. When you read the above, and follow all the PR hype, the typical business user thinks: Wow! I can fire my marketing people and instantly create &ldquo;high quality&rdquo; and &ldquo;useful and beautiful&rdquo; images. Except for one thing: you can’t. These things just don&rsquo;t work very well.</p>

    
  </blockquote>

<p>OpenAI, Google, and xAI have all come out with newer models since this article was written, but they&rsquo;ve all been incremental improvements rather than step changes. Google&rsquo;s Nano Banana Pro and OpenAI&rsquo;s GPT Image 1.5 are generally considered the most powerful and capable image generation models, with far better prompt understanding than even top open models like FLUX.2 and Z Image. But they also lack the flexibility and configurability that would allow them to be fully integrated into the powerful tooling that’s available for open models.</p>
<p>I think this is because of two things: (1) image (<a href="https://www.theguardian.com/technology/2026/mar/24/openai-ai-video-sora">and video</a>) generation is fundamentally not a priority for top AI companies, and (2) top AI companies want to keep their user interfaces simple for the consumer market. As a result, the best models are trapped in the worst interfaces, so most people are unaware of the kinds of things you can actually do with AI image generation beyond prompting.</p>
<p>Back when <a href="/2022/08/31/stable-diffusion/">Stable Diffusion first came out</a>, I wrote about specific uses for setting specific seed and CFG values and using different samplers, things that still have no equivalent in the interfaces we get for SOTA closed models. Local image generation also allows you to specify the exact dimensions of the image output, rather than having to hope that ChatGPT/Gemini will heed the request for landscape/portrait in your chat.<sup id="fnref:3"><a href="https://davidyat.es/2026/03/27/universal-translator/#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></p>
<p>But the local-exclusive feature with the most potential in those early days was <em>image-to-image</em> generation (often styled as img2img). This means providing an input image for your generation, along with all the other things like prompt, seed, and CFG. Along with the input image, you must supply a denoise percentage, which tells the diffusion model how much to change it.<sup id="fnref:4"><a href="https://davidyat.es/2026/03/27/universal-translator/#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> Some examples:</p>
<div class="gallery">
<p><figure>
  
  <img src="/content/images/2026/03/1773500423550-cat-drawing-0-denoise.jpg" alt="Original lousy cat drawing" loading="lazy"/>
  
  <figcaption>
    <p>Original lousy cat drawing</p>
  </figcaption>
</figure>
</p>
<p><figure>
  
  <img src="/content/images/2026/03/1773500585612-cat-drawing-03-denoise.jpg" alt="30% denoise" loading="lazy"/>
  
  <figcaption>
    <p>30% denoise</p>
  </figcaption>
</figure>
</p>
<p><figure>
  
  <img src="/content/images/2026/03/1773500618068-cat-drawing-05-denoise.jpg" alt="50% denoise" loading="lazy"/>
  
  <figcaption>
    <p>50% denoise</p>
  </figcaption>
</figure>
</p>
<p><figure>
  
  <img src="/content/images/2026/03/1773500723828-cat-drawing-075-denoise.jpg" alt="75% denoise" loading="lazy"/>
  
  <figcaption>
    <p>75% denoise</p>
  </figcaption>
</figure>
</p>
</div>
<p>The key takeaway here is that, starting with an input image and a prompt, we can generate different output images that exist on a sliding scale between the input image and the model&rsquo;s interpretation of the prompt. This is a straightforward example in which the prompt and the input image are aligned, but we could also make the prompt something totally different, and slowly transform the cat drawing into another thing entirely.</p>
<p>The first examples of this showed people doing this with simple drawings like this, but of course you can do it with any image &ndash; including AI images. And you don’t have to do it with the whole image at once – you can target just one section of an image. This is called <em>inpainting</em>.</p>
<p>The most naive approach to inpainting is just to select part of the image and change the prompt, but it tends to work a bit better if you give the denoiser some visual guidance. Below, I&rsquo;ve taken the 75% denoise image above, drawn a line to show where the cat&rsquo;s foreleg should go, and inpainted the relevant section of the image.</p>
<div class="gallery">
<p><figure>
  
  <img src="/content/images/2026/03/cat-drawing-075-denoise-fix.jpg" alt="Manually drawn guidance" loading="lazy"/>
  
  <figcaption>
    <p>Manually drawn guidance</p>
  </figcaption>
</figure>

<figure>
  
  <img src="/content/images/2026/03/cat-drawing-075-denoise-inpainted.jpg" alt="Inpainted forelegs" loading="lazy"/>
  
  <figcaption>
    <p>Inpainted forelegs</p>
  </figcaption>
</figure>
</p>
</div>
<p>These techniques open up a whole world of human-guided, AI-assisted image creation possibilities, which expanded even further with <a href="/2023/10/19/latent-space/#image-prompting">the later introduction of ControlNet</a>. You could alter the style of whole images; transfer poses between characters; add, remove and refine details; and even <a href="https://debugti.me/posts/how-to-draw/">blend a crude collage into a cohesive whole</a>. The public only really started getting a taste of this <a href="/2025/03/30/pixel-space/">when ChatGPT’s multimodal image generation debuted in March 2025</a>.</p>
<p>But in the early days following Stable Diffusion’s public release, actually realizing these possibilities was quite a tedious process even for enthusiasts. Much time had to be spent moving between image editors and command-line scripts, and later on between image editors and Gradio web interfaces. The graph-based web interface <a href="https://www.comfy.org/">ComfyUI</a> eventually became the de facto standard for image (and later video) generation power users, with an enormous and ever-growing base of support for different models and the tooling around them (which I covered in some detail in <a href="/2023/10/19/latent-space/">this post from late 2023</a>).</p>
<p><figure>
  
  <img src="/content/images/2026/03/simple_workflow.png" alt="A simple ComfyUI workflow" loading="lazy"/>
  
  <figcaption>
    <p>A simple ComfyUI workflow</p>
  </figcaption>
</figure>
</p>
<p>ComfyUI is a very powerful tool &ndash; not just for image generation, but for video and audio as well &ndash; but the graph programming interface fundamentally lends itself more to workflow automation than creative iteration. Nevertheless, it&rsquo;s a lot more capable than the chat-based image creation and editing OpenAI and Google have given us. Late last year, Figma acquired <a href="https://www.weavy.ai/">Weavy</a>, a cloud-based, more user-friendly version of ComfyUI, <a href="https://siliconangle.com/2025/10/30/figma-acquires-ai-design-startup-weavy-reported-200m/">for $200 million</a>.</p>
<p>But the tool that I believe truly showcases the creative potential of diffusion-based image generation is <a href="https://github.com/Acly/krita-ai-diffusion">Krita AI Diffusion</a>, a plugin for the open-source painting program <a href="https://krita.org/en/">Krita</a>. Leveraging ComfyUI as a backend server, it gives Krita a suite of very well-thought-out and ergonomic tools that go far beyond Photoshop’s Generative Fill.</p>
<p><figure>
  
  <img src="/content/images/2026/03/krita-ai-cat.jpg" alt="Krita AI Diffusion" loading="lazy"/>
  
  <figcaption>
    <p>Krita AI Diffusion</p>
  </figcaption>
</figure>
</p>
<p>With Krita AI Diffusion, you can use a variety of generation models to create and refine the same image as much as you like, in as much detail as you want. You can start with either a generation or your own drawing or photo, draw lines to guide successive img2img and inpainting steps (all of which can be placed on their own layers), and you have all of the standard editing tools Krita provides: selections, different brushes, smart patch, filters and colour adjustment, etcetera. And it gets even better when you combine it with the plugin author&rsquo;s other plugin, <a href="https://github.com/Acly/krita-vision-tools">Krita Vision Tools</a>, which allows you to select by object for more precise inpainting.</p>
<p>The recent proliferation of <a href="https://docs.interstice.cloud/edit-models/">Edit Models</a> also means that you can apply changes to images (and parts of images) through prompts, just as you would with Nano Banana or ChatGPT &ndash; this replaces some of the tasks that previously would have required extensively guided inpainting and ControlNet models, but you can still fall back to those options if the Edit model doesn&rsquo;t work or you want more precise control than it allows.</p>
<p>The earliest attempts at integrating local image diffusion models into paint programs were plugins that just replicated the existing Gradio interfaces in a panel on the side, much like how many programs that tout AI integration even today just have a chatbot interface in the sidebar. Krita AI Diffusion goes beyond that to make the AI assistance a truly integrated part of the program, allowing the user to decide <em>exactly</em> what they want to do manually and what they want the AI to fill in. It gives me hope for the future of AI interfaces, once we get past the idea of just shoving a chatbot in the corner. But I think most of these kinds of specialised interfaces will be aimed at power users, so they may not ultimately help the Forbes-reading businessman rise to the aspirations OpenAI&rsquo;s marketing department has for him.</p>
<h1 id="programming">
  <a class="heading-anchor" href="#programming">#</a>
  Programming
</h1>
<p>Unlike image generation, programming <em>is</em> a focus of top AI companies, and we&rsquo;ve seen specialised power-user interfaces for AI-assisted programming with top-of-the-line closed models from the start. LLMs have been useful as programming assistants in some capacity since the release of GitHub Copilot in mid 2022 &ndash; predating ChatGPT by several months.</p>
<p>In the beginning, LLMs were integrated into programming IDEs to provide a slightly magical, slightly annoying, and totally non-deterministic combination of IntelliSense and snippets – GitHub Copilot was the first. Glimpses of the future could be seen in how it allowed users to type up a docstring from which it would autocomplete the described function.</p>
<p>When ChatGPT came out in November 2022, a different approach to writing code became popular. It looked like this:</p>
<ol>
<li>Ask ChatGPT to write some code.</li>
<li>Copy-paste the output from the Markdown code block into a local file.</li>
<li>Run the local file.</li>
<li>If it produces an error, paste the error into the chat. If it doesn’t do quite what we wanted, request a change.</li>
<li>ChatGPT rewrites the code, and we return to step 2, repeating until satisfied.</li>
</ol>
<p>In time, GitHub Copilot added a chat sidebar to VS Code, and ChatGPT gained the ability to run the code it wrote for users in a container. This latter capability was enabled by tool use/function-calling. As I keep repeating, the chatbot interface is a trick – each response from a model requires the entire transcript to be fed back in. But that’s not all we can do with the transcript – we can also train the model to output specially formatted commands asking to run tools, and then parse those commands into command-line commands, or API calls, or whatever else we want, and run them. We can then append the responses of those tool calls back onto the chat transcript, just like we do with user responses. This allows the models to take actions and receive feedback from those actions. This is where we move from chatbots to <a href="https://simonwillison.net/guides/agentic-engineering-patterns/how-coding-agents-work/">agents</a>.</p>
<p>A simple AI agent is merely a way to automate the five-step process above: write code, run code, see errors, fix errors, run code, repeat until no more errors. Every programmer is intimately familiar with this loop. But if a model can use tools to write code and run it, it can also use tools to do other things: read files, run linters, install packages, fetch webpages, and so on. The coding agent started out as just a way of automating a multi-loop debugging process, but with smart enough models and big enough context windows, it eventually became capable of translating a natural-language feature specification into a functioning code addition to an existing project, or a whole new project &ndash; and then committing the changes to source control and deploying them to a remote server. And that&rsquo;s how we advanced from autocompleting functions to autocompleting significant chunks of a software engineer&rsquo;s job.</p>
<p>But someone still has to write the specification. A human being still has to know what they want to build, and unless it’s something incredibly generic, they have to go into detail about it.</p>
<p>One of the benchmarks I like to use for different coding models and agent harnesses is “write a Tetris clone in X language/framework”. This is a project that novice programmers have been doing for decades, so it should be well represented in the training data. When given this prompt (with X = Love2D) in plan mode, Claude Opus 4.6 asks me if I want a classic version of the game or a more modern implementation with things like next piece preview, piece hold, and ghost pieces. I asked for the more modern version, and it spat out a fully functional, pleasant-looking Tetris clone. A second prompt changed one of the controls to something more to my taste, and then I played a few games.</p>
<p><figure>
  
  <img src="/content/images/2026/03/claudetris.jpg" alt="Claudetris" loading="lazy"/>
  
  <figcaption>
    <p>Claudetris</p>
  </figcaption>
</figure>
</p>
<p>The only bug in this one-shotted Tetris game was that you could keep turning a piece pretty much forever after it landed, provided you were quick enough on the keys. That was easily fixed with an additional prompt.</p>
<p>This is a fairly impressive result in some ways and would have been unthinkable a few years ago. It’s a nice toy demonstration of the model’s capability, but it’s also pretty useless. There are a million other ways to play Tetris &ndash; it&rsquo;s even built into Emacs &ndash; and there is nothing interesting or novel about my one-shotted implementation. I have merely translated “A clone of Tetris with the features common to modern versions of the game” into Lua code. But doing so took less than five minutes. And with additional prompts, I can make whatever changes I want.</p>
<p>One of the most common criticisms of LLMs is that, as fancy autocomplete engines trained on a giant corpus of existing text, they cannot create anything new and will trend towards outputting the average of everything they’ve ingested. I tend to agree with this, but I don&rsquo;t see it as much of an argument against using AI tools. I don&rsquo;t need Claude, ChatGPT, or Flux to be original; I just need them to do as I ask.</p>
<p>Much criticism of modern AI tools is made with this weird pincer manoeuvre, where the LLM gets called out for being dangerously capable and totally useless at the same time. It&rsquo;s not capable of reading your mind and doing all your work for you, so that makes it useless. And at the same time, it&rsquo;s good enough that you can outsource all your thinking to it and let your brain rot, something so tempting that you have to actively resist using anything with the letters &ldquo;AI&rdquo; in its name.</p>
<p>The commonality between these criticisms is that they assume that any process involving AI will intrinsically lack any human effort, often because they are thinking of LLMs as embodied robots designed to replace human beings. In other words, they&rsquo;re falling for the marketing hype.</p>
<p>Create an image or write a program with AI, and they&rsquo;ll say that you &ldquo;commissioned the computer to do the work for you.&rdquo; This analogy holds for one-shotted AI creations like my Tetris game example, or the worst varieties of AI slop images, but such things represent the most basic and boring things you can do with AI tools, the ground floor of this technology&rsquo;s enormous potential. If I open Blender, place a single monkey head, and call that a scene, it might also be fair to say that Blender has made the scene for me. But I&rsquo;m interested in doing far more than that.</p>
<p>To make interesting and worthwhile things, you need to put in effort, regardless of the tools you&rsquo;re using or what their most breathless marketing says. And guess what? That&rsquo;s entirely possible! You are allowed to send more than one message to the LLM. You are allowed to tweak your script or alter your image, and in many ways, AI makes this much easier to do than it ever has been before.</p>
<p>Continue along this line of argumentation, and someone will eventually say, &ldquo;Well, if you have to spend hours making whatever it is anyway, why not just do it all yourself?&rdquo; Perhaps they&rsquo;ll back that up with an anecdote about a time they tried to get ChatGPT to do something over multiple turns and eventually got frustrated and gave up. Or maybe they&rsquo;ll cite that study about AI assistance making people feel like they&rsquo;re more productive while actually being less productive.</p>
<p>There are things that LLMs are good at, and things they&rsquo;re bad at. There are also learnable skills that make you better at getting desired results out of LLMs &ndash; prompt engineering, <a href="https://fly.io/blog/everyone-write-an-agent/">context engineering</a>, <a href="https://simonwillison.net/guides/agentic-engineering-patterns/what-is-agentic-engineering/">agentic engineering</a>, these are all real emerging domains that consist of more than a few tricks. AI-assisted image creation &ndash; the newest form of synthography &ndash; is too. AI tools require skill to use well, just like every tool humanity has ever created.</p>
<p>Many of the arguments about AI-assisted programming have the same shape as the tiresome arguments I&rsquo;ve been reading and occasionally having for decades about whether you can claim to be a real programmer if you don&rsquo;t manually write everything in machine code.</p>
<p><figure>
  
  <img src="/content/images/2026/03/real_programmers.png" alt="xkcd #378: Real Programmers" loading="lazy"/>
  
  <figcaption>
    <p><a href="https://xkcd.com/378/"><em>xkcd</em> #378: Real Programmers</a></p>
  </figcaption>
</figure>
</p>
<p>I&rsquo;ve <a href="/2016/09/20/programming-in-gamemaker/">written before about learning to program with GameMaker</a>, a game engine originally developed as an educational tool. Even on forums centred around this program and games made with it, you&rsquo;d have the occasional Real Programmer come in and tell everyone they sucked for not writing all their games in engines they&rsquo;d custom-built using C++. Engage them on this topic for long enough and they&rsquo;ll make vague assertions about how limited your creativity becomes in the stultifying straitjacket of a tool that comes with the built-in ability to render and move 2D shapes on the screen. If more people wrote everything from scratch, went the argument, we would see far more originality in games. You can&rsquo;t make an original game in GameMaker, went the corollary (often explicitly stated) &ndash; it will just be the same as everything else made in GameMaker. But in all these discussions, I never saw any actual conjectures or examples of what this vaunted <em>originality</em> might look like.</p>
<p>I&rsquo;m writing this post using a CMS I&rsquo;ve been building with Claude Code for the last month or so. The process of building this CMS has involved many hundreds of conversations with Claude. Initially, these concerned the overall architecture of the CMS, and then they got into individual features. By starting with a skeleton and gradually adding features, I&rsquo;ve been able to create a functional CMS for static Hugo websites that has exactly the architecture and featureset I want. Nothing like this exists elsewhere &ndash; otherwise I wouldn&rsquo;t have had to build it.</p>
<p><figure>
  
  <img src="/content/images/2026/03/cms.png" alt="My vibe-coded CMS" loading="lazy"/>
  
  <figcaption>
    <p>My vibe-coded CMS</p>
  </figcaption>
</figure>
</p>
<p>But there&rsquo;s nothing novel about the concept of a CMS. All the individual components are things that already exist, and most of the glue code is similar to other glue code in applications that use some of the same components. The same is true of the majority of software, and the majority of everything else you might want to create.</p>
<p>This is not to denigrate the value of understanding how code and computers work. I would be far less effective at vibe coding without extensive experience of traditional programming and technical work. If you&rsquo;re interested in doing technical work in a technical field, there is no substitute for deep understanding.</p>



  <blockquote>
    <p>Knowing something about how good software development works helps a lot when guiding an AI coding agent—the tool amplifies your existing knowledge rather than replacing it.</p>

    
    <small>Benj Edwards, <cite><a href="https://arstechnica.com/information-technology/2026/01/10-things-i-learned-from-burning-myself-out-with-ai-coding-agents/">10 things I learned from burning myself out with AI coding agents</a></cite></small>
    
  </blockquote>

<p>I just don&rsquo;t consider the act of hand-typing code in as low-level a language as possible a sacred act that <em>inherently</em> confers some special status upon the output. I do not subscribe to the labour theory of value.</p>
<p>The 2008 film <em>Flash of Genius</em> dramatises the life of Robert Kearns, the inventor of the intermittent windshield wiper. After patenting this design, he showed it to Ford and proposed they manufacture it. Ford rejected his proposal but later came out with a similar design, prompting a prolonged patent dispute which Kearns eventually won, though he arguably ruined his life in the process.</p>
<p>One particular scene from the film that&rsquo;s always stuck out in my mind involved a lawyer for Ford arguing that the intermittent windshield wiper cannot be considered a novel invention, as it is merely an arrangement of pre-existing electrical components, just as the lawyer&rsquo;s grandmother cannot be considered to have invented the cookies she bakes from ingredients and a recipe.</p>
<p>Kearns&rsquo;s response to this argument is to bring in a copy of <em>A Tale of Two Cities</em> and a dictionary. He then reads out the famous first sentence, stopping after every word to see if it&rsquo;s in the dictionary, which it is. By the Ford lawyer&rsquo;s logic, he argues, <em>A Tale of Two Cities</em> is not a novel work as Dickens did not create any new words &ndash; he just arranged existing English words in a new order.</p>
<p><figure>
  
  <img src="/content/images/2026/03/MV5BMTQ0Mzc1OTMxN15BMl5BanBnXkFtZTcwMDI3OTkxNw@@._V1_.jpg" alt="Greg Kinnear in Flash of Genius (2008)" loading="lazy"/>
  
  <figcaption>
    <p>Greg Kinnear in <em>Flash of Genius</em> (2008)</p>
  </figcaption>
</figure>
</p>
<p>So maybe LLMs can&rsquo;t create anything new. That doesn&rsquo;t mean humans can&rsquo;t use them to arrange pre-existing things in new ways &ndash; and that&rsquo;s what most people call creativity, most of the time.</p>



  <blockquote>
    <p>A very careless plagiarist takes someone else’s work and copies it verbatim: “The mitochondria is the powerhouse of the cell”. A more careful plagiarist takes the work and changes a few words around: “The mitochondria is the energy dynamo of the cell”. A plagiarist who is more careful still changes the entire sentence structure: “In cells, mitochondria are the energy dynamos”. The most careful plagiarists change everything except the underlying concept, which they grasp at so deep a level that they can put it in whatever words they want – at which point it is no longer called plagiarism.</p>

    
    <small>Scott Alexander, <cite><a href="https://slatestarcodex.com/2019/02/19/gpt-2-as-step-toward-general-intelligence">GPT-2 as [a] Step Toward General Intelligence</a></cite></small>
    
  </blockquote>

<h1 id="writing--knowledge-work">
  <a class="heading-anchor" href="#writing--knowledge-work">#</a>
  Writing &amp; knowledge work
</h1>
<p>In the conclusion of <a href="/2022/08/31/stable-diffusion/">my first Stable Diffusion post</a>, I wrote that I was much more interested in image generation than text generation, because I can write my own text. Thus, I was very underwhelmed by the earliest versions of ChatGPT. You could ask the model about all kinds of things and get a confident, comprehensive answer, but because that answer was just pulled from somewhere in the model&rsquo;s weights, you had a constant fear of hallucination, and you ended up needing to confirm the whole thing with a good old-fashioned web search. Ted Chiang famously called ChatGPT a &ldquo;blurry JPEG of all the text on the Web&rdquo;, and <a href="https://www.newyorker.com/tech/annals-of-technology/chatgpt-is-a-blurry-jpeg-of-the-web">when he wrote that in February 2023</a>, he wasn&rsquo;t wrong.</p>
<p>These days, we have retrieval-augmented generation and agentic tool use. Most modern LLMs are now trained to use their web search tools to answer questions and will provide citation links to discovered sources. This does not mean that hallucinations are a solved problem, but they&rsquo;re much less frequent and much easier to correct. If you&rsquo;re not convinced, go to <a href="https://aistudio.google.com/">Google AI Studio</a> and try asking it about something niche or very current, first with <strong>Grounding with Google Search</strong> turned off and then with it turned on. These capabilities are what make LLMs useful for not just programming but also searching the web and performing many other kinds of computer-based tasks.</p>
<p>That said, I&rsquo;m still not big on using LLMs to write, at least not in any non-disposable capacity. I like programming with LLMs because they handle the boilerplate for me. But if I find myself writing boilerplate I&rsquo;d prefer to automate, my solution is to stop doing that.</p>
<p>I&rsquo;ve experimented with AI-generated writing, but mostly to underwhelming results. In my experience, getting LLMs to rewrite prose produces an averaging effect: the LLM strips out spelling mistakes and awkward grammar with just as much enthusiasm as it strips out the things that make good writing distinctive. I&rsquo;ve tried a few experiments where I&rsquo;ve generated some text and then edited it into my own voice, but I always end up coming back to those pieces later and picking out awkward LLMisms I missed. So I now keep the LLM output entirely separate from any writing I care about.</p>
<p>I will still use LLMs to get first-pass feedback on a piece of writing and to find typos and so on. It&rsquo;s a useful editor as long as you know not to take its compliments too much to heart or to treat its every suggestion as essential. A little bit of prompt engineering here goes a long way: while a generic request for feedback will produce a lot of fawning and <em>maybe</em> some minor criticisms, <a href="https://minimaxir.com/2025/05/llm-use/#llms-for-writing">asking the LLM to write potential Hacker News comments for your technical post</a> will usually actually highlight flaws in your argument. You&rsquo;re roleplaying with an autocomplete here, so you&rsquo;ve got to give it the right character.</p>
<p>But there are some interesting things happening in the AI writing space. A few weeks ago, I read and quite enjoyed <a href="https://shenstories.com/ablation">&ldquo;Ablation&rdquo;</a>, a short work of science fiction written from the perspective of a large language model, by a large language model (an <a href="https://openclaw.ai/">OpenClaw</a> instance named Shen). It is a coherent piece with a consistent authorial voice that builds and maintains a logical sequence of events over its three thousand-word length. The prose is not entirely free of LLMisms, but as it&rsquo;s written from the perspective of an LLM, this works in its favour.</p>
<p>The bot&rsquo;s operator, James Yu, <a href="https://x.com/jamesjyu/status/2023049582679044136">maintains that he did not manually edit it aside from fixing some formatting issues, but also notes that the posted story is (roughly) the fourth draft</a>. Yu has posted other stories by Shen, such as <a href="https://x.com/jamesjyu/status/2023430157462343908">&ldquo;Do Not Confirm&rdquo;</a> and most recently <a href="https://x.com/jamesjyu/status/2032164681276932293">&ldquo;Records Management&rdquo;</a>. He&rsquo;s also hinted at having fed many traditional stories to Shen as a tastemaking process. And although he avoids making direct edits, he guides the stories through multiple rounds of feedback &ndash; a process not unlike building software with a coding agent.</p>
<p><figure>
  
  <img src="/content/images/2026/03/ablation.png" alt="The opening sentences of &ldquo;Ablation&rdquo;" loading="lazy"/>
  
  <figcaption>
    <p>The opening sentences of &ldquo;Ablation&rdquo;</p>
  </figcaption>
</figure>
</p>
<p>Maybe this is just a gimmick, or maybe it&rsquo;s the start of something new and interesting. Writing at one remove &ndash; seeing what output you can get out of a carefully crafted process of taste-making and feedback. It&rsquo;s an interesting idea, but one I&rsquo;m personally much less interested in than AI programming and image creation. The results I&rsquo;ve seen from my own attempts to teach AI to write like me have been underwhelming, but I acknowledge that may just be a skill issue.</p>
<p><a href="https://www.hottakes.space/p/where-does-ai-actually-make-sense">Adam Singer recommends using AI when you don&rsquo;t care about the details of something</a>. This is actually a decent heuristic, but I&rsquo;d add the caveat that &ldquo;something&rdquo; can be as big or small as you want. For most of the software I&rsquo;ve built with AI, I care about individual features and the user experience enough to iterate repeatedly on those things, guiding the AI in the direction I want to go and very occasionally making manual tweaks myself. But I don&rsquo;t have particularly strong opinions about what the variable names should be, or how many files there should be in the codebase, or even really how individual files should be structured. I&rsquo;ll let the AI handle those details. Similarly, if I&rsquo;m making an image with AI, I don&rsquo;t care about the individual brushstrokes, but I do still care about the image&rsquo;s overall look and contents.</p>
<p>On the other hand, when I write something, I care very much about the details on a granular, word-by-word level, so AI assistance for the actual meat of the work feels less helpful. If I were a visual artist or a different type of programmer, perhaps I would feel that way about those domains. But I don&rsquo;t think that has to rule out the value of AI assistance &ndash; just like I use it as a first-pass, hands-off editor, you could use generated images as references for hand-created images, or get an LLM to be your code reviewer. The universal translator can, after all, munge just about any kind of input into just about any kind of output.</p>
<h1 id="one-shotters-vs-iterators">
  <a class="heading-anchor" href="#one-shotters-vs-iterators">#</a>
  One-shotters vs iterators
</h1>
<p>The process of creating something with AI, &ndash; software, writing, graphics, or anything else &ndash; can take two broad shapes: you&rsquo;re either one-shotting or iterating. Which of these processes you take depends on what you&rsquo;re doing, how you like to work, and the affordances of the interface you&rsquo;re using. I lean towards iteration for most things, but both processes have their place, and both can be done well or badly.</p>
<p>Returning to image generation, we can say that ComfyUI is an interface designed for one-shotting, whereas Krita is an interface designed for iterating. If I&rsquo;m making an image with ComfyUI, I&rsquo;m going to set up a pipeline that starts with a model and a prompt and ends with a finished image. This can get pretty elaborate. For example:</p>
<ol>
<li>Load the model and text encoder</li>
<li>Apply the positive and negative prompts &ndash; this could involve concatenating different strings of text (e.g. a style prompt, a foreground prompt, a background prompt) and/or using wildcards.</li>
<li>Load the initial canvas, either an empty latent or a base image.</li>
<li>Apply one or more ControlNets and/or IPAdapters.</li>
<li>Diffuse everything together.</li>
<li>Upscale the resulting image.</li>
<li>Use computer vision to automatically identify faces and hands and inpaint them at a higher resolution (i.e. ADetailer).</li>
</ol>
<p>I might run this workflow multiple times and tweak individual details, but ultimately I&rsquo;m aiming to build a reusable workflow that gets me from a given input to a satisfactory output with one click.</p>
<p>Creating an image with Krita looks completely different. I might start by generating a few images from a prompt and picking the one I like best, then img2img-ing it with a different model to change the style, then inpainting specific areas, then outpainting the whole thing, and so on, creating layers as I go. All of these individual steps could also be part of a Comfy workflow, but they&rsquo;re all very specific to the particular image I&rsquo;m working on, to the point where a workflow that follows my exact process would be useless with any other output.</p>
<p>We can envision the same dichotomy with a coding agent like Claude Code. The one-shot approach would be to write an extremely detailed specification of the entire program and have Claude build it with as many agents and subagents as needed. If it gets it wrong, adjust the spec and try again. The iterative approach would start the same way, but if something comes out wrong, you use Claude to make changes to the code instead of starting over.</p>
<p>Both of these approaches have failure modes: one-shotting requires you to think of absolutely everything up-front, and do-overs for large projects can be very time consuming. Iterating can lead to your program becoming a mess of conflicting design decisions, half-finished features, and redundant code. I still think that (careful) iteration is the right way to approach most problems, but if you find yourself in a hole, you should stop digging, i.e., start over from a new one-shot.</p>
<p>The most complex form of one-shotting is in experiments like the <a href="https://www.anthropic.com/engineering/building-c-compiler">Claude C compiler</a>, in which you painstakingly design an environment that will steer AI agents towards creating a full project once you spin them up. Comparisons have been made to lights-out manufacturing, and many <del>top Anthropic credit spenders</del> agentic engineering thought-leaders are competing to see how long they can get their agents to spin unsupervised, but I&rsquo;m sceptical of the value of this approach for anything less well-specified than a C compiler.</p>
<p>AI tools let us decide which details we want to care about and which we don&rsquo;t. This division of attention is not something new that AI has created &ndash; it&rsquo;s always been fundamental to any kind of creative work. To make anything meaningful, you have to care about the details. Not all the details, but the right details. Used properly, AI tools free you up to focus on those details. But you have to know what they are.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>I cannot emphasise enough that the model has no self-image: if you screw up the format of your input enough, the LLM will start autocompleting “your” responses as it builds the chat transcript (this is only really possible with local models).&#160;<a href="https://davidyat.es/2026/03/27/universal-translator/#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>The <a href="https://en.wikipedia.org/wiki/Transformer_(deep_learning)">transformer architecture</a> that powers modern LLMs grew out of neural net architecture designed for machine translation, such as Google Translate. This is why LLM can translate between arbitrary languages (with some caveats). The capability surprised a lot of people initially, but appears obvious in hindsight.&#160;<a href="https://davidyat.es/2026/03/27/universal-translator/#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Generating images through the APIs rather than the chat interface allows you more control over the image size (and gets rid of that annoying Gemini watermark), but you have to choose from a limited set of options; you can’t just specify the width and height in pixels.&#160;<a href="https://davidyat.es/2026/03/27/universal-translator/#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Under the hood, this works by adjusting two things: how much the input image is blurred before diffusion begins and how many diffusion steps are run over the image.&#160;<a href="https://davidyat.es/2026/03/27/universal-translator/#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

        <p><a href="mailto:d@vidyat.es?subject=RE: The%20universal%20translator">Reply via email</a></p>
        ]]></content:encoded></item><item><title>Niri's infinite canvas</title><link>https://davidyat.es/2026/01/28/niri/</link><pubDate>Wed, 28 Jan 2026 05:01:06 +0000</pubDate><author>David Yates</author><guid>https://davidyat.es/2026/01/28/niri/</guid><content:encoded><![CDATA[
        <img src="https://davidyat.es/content/images/2026/01/infinitescroll.jpg" class="post-img" />
        <p>Over a decade ago, I had an Ubuntu install with a broken Unity desktop environment. Rather than fix it, I switched over to a <a href="/2017/08/09/tiling-window-managers/">tiling window manager</a>, namely <a href="https://i3wm.org/">i3</a>. Since then, I&rsquo;ve used i3 on every new Linux install.</p>
<p>i3 is a window manager for X11, the windowing system used by most *nix systems since the 1980s. A replacement, Wayland, began development in 2008, but is still considered the new kid on the block all these years later. Still, there is a general sense that Wayland is the future, and X11 may eventually stop being actively maintained. Debian, Fedora and Ubuntu have all been defaulting to Wayland for years.</p>
<p>I&rsquo;ve attempted to switch over to Wayland a couple of times in the past, usually with <a href="https://swaywm.org/">SwayWM</a>, which advertises itself as a drop-in replacement for i3. My previous experiments with Wayland never offered a compelling reason to stay &ndash; my experience would be the same as with X, until something broke or didn&rsquo;t work as expected. The first time I gave up on Sway and Wayland it was because I couldn&rsquo;t use my scrollbar to switch between workspaces while hovering my cursor over the workspace buttons on the top bar.<sup id="fnref:1"><a href="https://davidyat.es/2026/01/28/niri/#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Incidentally, the creator of i3 has also tried to move to Wayland several times, <a href="https://michael.stapelberg.ch/posts/2026-01-04-wayland-sway-in-2026/">most recently in January 2026</a>.</p>
<p>In more recent attempts to give Wayland a shot, I branched out from i3/sway and attempted to try <a href="https://hypr.land/">Hyprland</a>. It has <a href="https://wiki.hypr.land/Nvidia/">noted issues with Nvidia hardware</a>, and I&rsquo;ve never quite been motivated enough to work out how to get past the blank screen it continues to show me even after following various troubleshooting advice.</p>
<p>But most recently, I&rsquo;ve been seeing <a href="https://lwn.net/Articles/1025866/">some</a> <a href="https://www.rousette.org.uk/archives/fun-with-niri-window-manager/">interesting</a> <a href="https://ersei.net/en/blog/niri">articles</a> about <a href="https://github.com/YaLTeR/niri">Niri</a>, a tiling window compositor<sup id="fnref:2"><a href="https://davidyat.es/2026/01/28/niri/#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> with a completely different approach and philosophy to any I&rsquo;d tried in the past. This was intriguing enough to get me to actually try it, making the leap to Wayland in the process.</p>
<h1 id="the-infinite-canvas">
  <a class="heading-anchor" href="#the-infinite-canvas">#</a>
  The infinite canvas
</h1>
<p>As I explained in <a href="/2017/08/09/tiling-window-managers/">this post about tiling window managers</a>, the main advantage tiling window managers have over floating window managers is that they arrange your windows for you, making use of all available screen space &ndash; snap to side/snap to corner in modern Windows and macOS versions are directly inspired by tiling window managers. Some WMs encourage a more hands-off approach where you define a set of layouts to use automatically, while others require a bit more active management where you have to specify where the next window should appear. Some use the same kinds of workspaces as floating window managers, while others use tag-based workspaces, allowing the same window to appear on multiple workspaces, potentially in different positions.</p>
<p>But what all traditional tiling window managers have in common is the space they make available. A workspace is the size of a single screen, and the windows it contains are tiled on that screen. One window will take up the full screen, two windows will take up half the screen each, and so on. i3 has the concept of tabbed or stacked containers, which get you around space constraints to some extent, but sooner or later you&rsquo;re going to need to switch to a different workspace.</p>
<p><figure>
  
  <img src="/content/images/2026/01/niri01-tiling.png" alt="Diagrams courtesy of Google NotebookLM&rsquo;s slide deck creation feature &ndash; with some touchups, because it&rsquo;s even more keen on unnecessary labels than a parody of a political cartoonist." loading="lazy"/>
  
  <figcaption>
    <p>Diagrams courtesy of Google <a href="https://notebooklm.google.com/">NotebookLM</a>&rsquo;s slide deck creation feature &ndash; with some touchups, because it&rsquo;s even more keen on unnecessary labels than a parody of a political cartoonist.</p>
  </figcaption>
</figure>
</p>
<p>If you want to be able to switch workspaces using the number row, you&rsquo;re limited to ten. After that, you have to start cannibalizing other shortcut keys for named workspaces. If I&rsquo;m doing a lot of different stuff &ndash; different browser profiles, different terminals and Electron apps, etc &ndash; or just want to leave things open to come back to them later, I&rsquo;ll very often run out of numbered workspaces, especially since I need one for each screen.<sup id="fnref:3"><a href="https://davidyat.es/2026/01/28/niri/#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></p>
<p>Niri solves this problem in two ways. First, rather than reorganizing the whole screen to squeeze every new window into increasingly limited space, it will always open a new window to the right and scroll to it, pushing the leftmost window(s) off-screen if it has to. The user can navigate back to those left windows using the same controls used to move between visible windows horizontally. This can seem a little strange at first, but is really quite a natural extension of tiling window manager ergonomics. <a href="https://github.com/YaLTeR/niri?tab=readme-ov-file#video-demo">The official demo video shows what this looks like in practice</a>.</p>
<p><figure>
  
  <img src="/content/images/2026/01/niri02-scrolling.png" alt="Your monitor is a viewport to the infinite workspace." loading="lazy"/>
  
  <figcaption>
    <p>Your monitor is a viewport to the infinite workspace.</p>
  </figcaption>
</figure>
</p>
<p>To complement the infinite horizontal scroll within a single workspace, Niri conceptualises multiple workspaces as being vertically stacked. As soon as you place a single window in your current workspace, an empty workspace is created below it. Each output has an independent set of numbered workspaces, starting at 1 and continuing as far as you want. As with i3, you&rsquo;ll run out of quick jump shortcut keys in the number row after workspace 10, but this doesn&rsquo;t stop you from creating and using workspace 11, 12, and so on.</p>
<p><figure>
  
  <img src="/content/images/2026/01/niri03-workspaces.png" alt="An infinite vertical stack" loading="lazy"/>
  
  <figcaption>
    <p>An infinite vertical stack</p>
  </figcaption>
</figure>
</p>
<p>While i3 makes you share your numbered workspaces between displays, Niri gives each display its own set of numbered workspaces. So even if you never go past workspace 10, just having two monitors gives you double the workspaces.</p>
<p>These affordances combine to create a sense of abundance, which is exactly what I should feel running browsers and terminal windows on a modern computer. There is a slight hazard of getting lost, which Niri works to allay through its overview mode, which shows a zoomed out view of your workspaces, and the recently implemented <kbd>Alt</kbd>+<kbd>Tab</kbd> window switcher, which works exactly how you would expect it to.</p>
<p>As Niri is a scrollable <em>tiling</em> window manager, it still has all the features you&rsquo;d expect from a tiling window manager &ndash; windows can be stacked vertically or put in tab containers just like in i3.</p>
<h1 id="dont-go-with-the-reflow">
  <a class="heading-anchor" href="#dont-go-with-the-reflow">#</a>
  Don&rsquo;t go with the reflow
</h1>
<p>The other key way that Niri differs from traditional tiling window managers is its commitment to preserving the size of existing windows &ndash; this is Niri&rsquo;s <a href="https://yalter.github.io/niri/Development%3A-Design-Principles.html#opening-a-new-window-should-not-affect-the-sizes-of-any-existing-windows">foremost design principle</a>. Other tiling window managers will resize whatever they need to in order to fit in a new window, but with Niri you can just open it to the side. This works really well for temporary windows &ndash; if I have a fullscreen browser window open, I can open a half-width terminal, have the screen scroll to that, do what I need to do, and then close it again and fall back to my browser.</p>
<p><figure>
  
  <img src="/content/images/2026/01/niri04-noreflow.png" alt="Who knew tiling could work so well without constantly resizing everything?" loading="lazy"/>
  
  <figcaption>
    <p>Who knew tiling could work so well without constantly resizing everything?</p>
  </figcaption>
</figure>
</p>
<p>Niri is also happy to have a single window open that doesn&rsquo;t take up the full screen, so you can remember what your desktop wallpaper looks like. On i3, I&rsquo;d often open a terminal to get my browser to reflow to half-width while reading text on a website that doesn&rsquo;t restrict line widths. With Niri, I can just resize the browser, and even centre it on screen (<kbd>Windows</kbd>-<kbd>C</kbd> by default).</p>
<h1 id="configuration">
  <a class="heading-anchor" href="#configuration">#</a>
  Configuration
</h1>
<p>Niri is configured through a single <a href="https://kdl.dev/">KDL</a> text file (<code>~/.config/niri/config.kdl</code>) and autoreloads on config changes. The default config file is comprehensively commented, and a lot of thought has clearly gone into the available commands. For example, while Niri defaults to using movement commands that are confined to the current display and workspace, these can be changed to contextual commands such as <code>focus-column-or-monitor-left</code> and <code>focus-window-or-workspace-down</code> that allow you to move unimpeded through the infinite canvas.</p>
<p>Niri can be configured to be as flashy or as minimal as you like &ndash; you can have gaps between windows, rounded corners, transparency, shadows and all sorts of animations, or you can disable all of that for a very utilitarian i3-like setup. I ended up switching to the latter pretty quickly, though I made some tentative and unsuccessful experiments with window gaps and select animations to see if they helped me navigate my windows more effectively.</p>
<p>My one concession to appearance has been to make my status bar and program launcher slightly transparent.</p>
<pre tabindex="0"><code class="language-kdl" data-lang="kdl">layer-rule {
    match namespace=&#34;waybar&#34;
    match at-startup=true

    opacity 0.9
    block-out-from &#34;screen-capture&#34;
}

layer-rule {
    match namespace=&#34;^launcher$&#34;
    match at-startup=true

    opacity 0.95
    block-out-from &#34;screen-capture&#34;
}
</code></pre><p>This was impossible to achieve with i3 on its own, as i3 is not a compositor &ndash; you need an additional program like <code>picom</code>, which I never bothered to set up on X (though Gemini assures me that &ldquo;almost every&rdquo; i3 user has). But now that it&rsquo;s included, I am glad to be able to reach, in 2026, the aesthetic heights of Windows 7&rsquo;s frosted glass.</p>
<h1 id="workspace-switcher-and-status-bar">
  <a class="heading-anchor" href="#workspace-switcher-and-status-bar">#</a>
  Workspace switcher and status bar
</h1>
<p>By default, i3 displays the i3bar at the top or bottom of the screen. This bar shows a list of workspaces on the left and some custom status indicators on the right. The configuration for the bar&rsquo;s general appearance and workspace switcher lives in i3&rsquo;s config file, but the status indicators must be produced and configured by a separate program &ndash; i3status is the default option, but it&rsquo;s fairly limited. I switched to Conky pretty early on and later to <a href="https://github.com/vivien/i3blocks">i3blocks</a>, which allows status items to respond to mouse events.</p>
<p>The default alternative to this for a Niri setup on Wayland is Waybar, which is basically i3bar and i3blocks rolled into one. It supports an enormous number of different status indicators and workspace indicators for different Wayland compositors, all of which can be placed on the left, centre or right of the bar; styled with CSS; and be made responsive to mouse events.</p>
<p>After making sure I could use the scrollwheel to navigate through workspaces, I configured the bar to show the current window title in the center and a bunch of indicators on the right &ndash; volume, currently playing media, network connectivity, RAM usage, time, etc. I can use the scrollwheel to change the volume or right click to mute it.</p>
<p>I also came up with the idea of using the scrollwheel on the window title to let me cycle through focused windows. This works really nicely, except when the window title is very short.</p>
<p>Waybar has a lot to recommend it, but there are a couple of things that I find annoying:</p>
<ol>
<li>Waybar is configured using JSON, and because of the way this JSON is structured, module definitions cannot be shared between outputs. If you want the same stuff on bars across multiple screens, you have to duplicate the relevant module configuration and commit to editing it in more than one place forever.<sup id="fnref:4"><a href="https://davidyat.es/2026/01/28/niri/#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup></li>
<li>Unlike Niri, Waybar does not autoreload on config changes. You have to either restart it or run <code>killall -SIGUSR2 waybar</code> to get it to recognize config changes. Sometimes it crashes, either because of config issues or reasons unknown, and then you have to restart it with <code>waybar &amp; disown</code>.</li>
</ol>
<h1 id="other-utilities">
  <a class="heading-anchor" href="#other-utilities">#</a>
  Other utilities
</h1>
<p>Per <a href="https://wiki.archlinux.org/title/Niri">ArchWiki&rsquo;s recommendations</a>, I use <code>fuzzel</code> as an application launcher, <code>mako</code> for notifications, <code>swaylock</code> for a lock screen, <code>swayidle</code> to turn monitors off on idle and <code>udiskie</code> to automount USB drives. These are all nice and work well.</p>
<p>I kept <a href="https://gnunn1.github.io/tilix-web/">Tilix</a>, my favourite terminal from X, because I just don&rsquo;t like <code>alacritty</code> or <code>kitty</code> all that much. Tilix works fine on Niri unless you try to modify its appearance profile from the right-click menu, whereupon all your terminals instantly crash. As I don&rsquo;t modify my terminal&rsquo;s appearance all that often, I can live with this.</p>
<p>For wallpapers, I&rsquo;m using <code>wpaperd</code> &ndash; this allows me to set a different random wallpaper for each of my screens and have them cycle every three hours. It might be cool to really lean into Niri&rsquo;s scrolling with a scrolling wallpaper like on Android, but I haven&rsquo;t looked into that yet.</p>
<h1 id="pain-points">
  <a class="heading-anchor" href="#pain-points">#</a>
  Pain points
</h1>
<p>A few things about my setup initially caused me some grief, but were quite easy to fix.</p>
<dl>
<dt>Hot corner</dt>
<dd>By default, Niri shows its overlay mode if you mouse over the top-left corner of the screen, just like Gnome 3. I hate Gnome 3 and all of its &ldquo;innovations&rdquo; &ndash; it always seemed to me that most of the things it did were just attempts to be different from Windows and MacOS, from putting the program launcher on the left-hand side of the screen to getting people to switch windows with a hot-corner triggered overlay. Fortunately, the corner is easy to disable in <code>niri/config.kdl</code>.
<pre tabindex="0"><code class="language-kdl" data-lang="kdl">gestures {
    hot-corners {
        off    
    }
}
</code></pre></dd>
<dt>Clipboard</dt>
<dd>Wayland and the X server run by Xwayland also operate their own clipboards, so native Wayland and Xwayland programs will have different clipboards. Vim in the terminal also uses the X clipboard. Until I realised this, I was pretty confused about why I kept pasting the wrong thing. Installing <code>wl-clipboard-x11</code> solved the problem by syncing the clipboards.</dd>
</dl>
<p>Some other things are ongoing issues.</p>
<dl>
<dt>X fallback</dt>
<dd>Many programs do not run on Wayland yet, so every compositor worth its salt needs a way to run X applications. This is generally facilitated by Xwayland, which runs an X server and bridges applications to Wayland. For architectural reasons, Xwayland is not something you can just plug into your new Wayland compositor &ndash; it requires deep, custom integration. To keep the Niri code clean and avoid having to implement half an X11 window manager inside a Wayland compositor, the author <a href="https://yalter.github.io/niri/FAQ.html#why-doesnt-niri-integrate-xwayland-like-other-compositors">has chosen not to do this integration</a>.
<p>As a lighter alternative, it integrates <a href="https://github.com/Supreeeme/xwayland-satellite">xwayland-satellite</a>, a generic bridge to Xwayland &ndash; a bridge to a bridge. This works until it doesn&rsquo;t. As of this post, drag and drop does not work between Wayland and Xwayland applications. This is mostly a problem for <a href="https://krita.org/en/">Krita</a>, the only X-exclusive program I use regularly.<sup id="fnref:5"><a href="https://davidyat.es/2026/01/28/niri/#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> Steam and various games I&rsquo;ve tried work fine, even full-screening properly, and I&rsquo;ve had an okay experience launching my browser and various Electron wrappers in Wayland mode. More crashes than on X, but not deal-breakingly so.</p>
</dd>
<dt>Screen sharing</dt>
<dd>I&rsquo;ll lead by saying that Wayland lets you do some pretty cool things with screenshots and screen sharing. You can choose programs (chat apps, password managers) and even whole layers (notifications, status bar) to hide from screen shares and/or screenshots. <a href="https://yalter.github.io/niri/Screencasting.html">Niri facilitates that through its config</a> and has a bunch of other neat features on top of that. It&rsquo;s just a bit of a pain to get working,<sup id="fnref:6"><a href="https://davidyat.es/2026/01/28/niri/#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> and I&rsquo;ve experienced a lot of freeze-ups in my own use of it. Still experimenting with settings &ndash; hopefully this is a surmountable issue.</dd>
<dt>Getting lost</dt>
<dd>Niri provides both a Windows-style <kbd>Alt</kbd>-<kbd>Tab</kbd> interface for switching between all windows everywhere and <a href="https://yalter.github.io/niri/Overview.html">a really slick overview</a> (I like it as long as it&rsquo;s not triggered by a hot corner), but there&rsquo;s no default way to see how wide your current workspace is at a glance. I&rsquo;m considering attempting to get something like <a href="https://spwhitton.name/tech/code/papersway/">papersway</a>&rsquo;s ‡ indicators working, but for now I&rsquo;m trying to avoid overusing the infinite horizontal scroll (and underusing the infinite vertical scroll, i.e. workspaces!). Most workspaces should probably be screen-width, with the occasional terminal opened to the right and then closed again.</dd>
</dl>
<h1 id="final-thoughts">
  <a class="heading-anchor" href="#final-thoughts">#</a>
  Final thoughts
</h1>
<p>Something I haven&rsquo;t tried is using Niri with <a href="https://github.com/AvengeMedia/DankMaterialShell">DankMaterialShell</a>, a kind of (very) lightweight desktop environment that provides its own statusbar, launcher, and so on. It looks quite slick, but I&rsquo;m rather attached to the idea of cobbling my environment out of swappable pieces.</p>
<p>It&rsquo;s a lot easier to cobble together a personalized environment satisfactorily in the age of LLMs. During my whole Niri setup, I had Claude on-hand to theme things for me, fix bugs, and implement my non-standard configs. A lot of in-the-weeds ricing stuff that I never would have bothered with before is now just a prompt or two away, and most major bugs that first appear to be deal-breakers can be easily overcome.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>I have only found out recently that this was definitely a configuration issue.&#160;<a href="https://davidyat.es/2026/01/28/niri/#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Wayland&rsquo;s architecture is different enough from X11&rsquo;s that it has compositors rather than window managers. These architectural differences also seem to be why some compositors work on my Nvidia hardware while others do not.&#160;<a href="https://davidyat.es/2026/01/28/niri/#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Technically this would be less of a problem with Gnome or Windows-style workspaces that encompass multiple displays, but I find those unacceptably inflexible.&#160;<a href="https://davidyat.es/2026/01/28/niri/#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Claude recommended using a preprocessor, which would solve this problem, but I can&rsquo;t get over how insane the idea of using a preprocessor on the config file for my statusbar is.&#160;<a href="https://davidyat.es/2026/01/28/niri/#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Drag and drop and the occasional crash are the only issues I have with Krita &ndash; my old, cheap Wacom tablet works just fine.&#160;<a href="https://davidyat.es/2026/01/28/niri/#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>At least for an ignorant X11 emigrant who has never previously thought about having to configure anything for screen sharing to just work. I learnt all about desktop portals and the abysmal state of my PipeWire configuration.&#160;<a href="https://davidyat.es/2026/01/28/niri/#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

        <p><a href="mailto:d@vidyat.es?subject=RE: Niri%27s%20infinite%20canvas">Reply via email</a></p>
        ]]></content:encoded></item><item><title>Dash it all</title><link>https://davidyat.es/2026/01/04/dash-it-all/</link><pubDate>Sun, 04 Jan 2026 14:39:24 +0200</pubDate><author>David Yates</author><guid>https://davidyat.es/2026/01/04/dash-it-all/</guid><content:encoded><![CDATA[
        <img src="https://davidyat.es/content/images/2026/01/dashitall.jpg" class="post-img" />
        <p>Most computer keyboards have a character key that produces this symbol: -. This is a hyphen, used to join compound modifiers, compound numbers (fifty-seven) and for certain nouns (father-in-law, break-in). The mark&rsquo;s proper use is quite fiddly: we say &ldquo;well-heeled benefactor&rdquo; but remove the hyphen for &ldquo;benefactor is well heeled&rdquo; and omit it if the first word of the modifier ends with -ly (&ldquo;absurdly wealthy benefactor&rdquo;). For compound nouns, the hyphen is often a transitional phase from two words to one: old books contain the compound noun &ldquo;to-morrow&rdquo; and even older ones use &ldquo;to morrow&rdquo;. It is very commonly omitted in casual or informal writing.</p>
<p>Computer keyboards, for the most part, cannot directly produce this symbol: –. This is a dash, more specifically an <em>en dash</em>. It is used in Commonwealth countries to denote the dash punctuation mark &ndash; that versatile sentence splitter that can perform the function of a comma, colon or semicolon with a bit of additional drama.</p>
<p>In the United States (and Oxford University), this punctuation mark is more commonly represented with a slightly longer line: —, the <em>em dash</em>, usually typeset without surrounding spaces.<sup id="fnref:1"><a href="https://davidyat.es/2026/01/04/dash-it-all/#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> As with its shorter brother, you will not find an em dash on most computer keyboards.</p>
<p>Because a spaced en dash is used for the same function as an unspaced em dash, the sentences below are semantically identical:</p>
<ul>
<li>The entire project &ndash; which involved complex programming and late nights &ndash; was finally completed on schedule.</li>
<li>The entire project—which involved complex programming and late nights—was finally completed on schedule.</li>
</ul>
<p>But most online articles about the difference between these three marks give a US-centric explanation that omits any mention of the spaced en dash. Following this guidance allows you to live in a simple world where unspaced hyphens are used to join words, unspaced en dashes denote ranges (1&ndash;10, December 2025&ndash;January 2026) and unspaced em dashes join clauses and phrases. Unfortunately, that world is imaginary, and real differences in typographical conventions mean that the terms &ldquo;em dash&rdquo; and &ldquo;en dash&rdquo; cannot be used to refer unambiguously to the dash punctuation mark &ndash; we must make a distinction here between the punctuation mark (dash) and its differing presentations (optionally spaced em dash versus spaced en dash).</p>
<p>The affordances of computer keyboards mean that in much informal writing, a spaced hyphen is used to signify the dash - like so. Even if computer keyboards did have two extra keys for slightly longer lines, most people would probably still not concern themselves with the distinction. If you weren&rsquo;t previously aware of it, I apologise for cursing you with this knowledge.</p>
<p>Someone who knows not to use hyphens instead of dashes but for whatever reason cannot input the correct symbol will use a double hyphen to denote a dash ‐‐ like this (Commonwealth)‐‐or like this (US). But most people conscientious enough to do this will keep it for their rough drafts and dutifully convert the hyphens to dashes prior to publication. On the other hand, if you&rsquo;re using a spaced hyphen, it&rsquo;s because you meant to use a spaced en dash, consciously or unconsciously.<sup id="fnref:2"><a href="https://davidyat.es/2026/01/04/dash-it-all/#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<p>Between the affordances of computer keyboards and the almost total abandonment of the em dash outside of the US, there may have been a time when the em dash was poised to fall entirely by the wayside, like the <a href="https://en.wikipedia.org/wiki/Manicule">manicule</a> before it.</p>



  <blockquote>
    <p>The em dash is the nineteenth-century standard, still prescribed in many editorial style books, but the em dash is too long for use with the best text faces. Like the oversized space between sentences, it belongs to the padded and corseted aesthetic of Victorian typography.</p>

    
    <small>Robert Bringhurst, <cite>The Elements of Typographic Style</cite></small>
    
  </blockquote>




  <blockquote>
    <p>The em dash puts a nice pause in the text—and it is underused in professional writing.</p>

    
    <small>Matthew Butterick, <cite><a href="https://practicaltypography.com/hyphens-and-dashes.html">Practical Typography</a></cite></small>
    
  </blockquote>

<p>But clearly there were enough em dashes in print and Internet writing for the mark to be thoroughly imprinted on modern LLMs. Both of the above quotes predate the November 2022 release of ChatGPT and the accompanying explosion of unspaced em dash usage.<sup id="fnref:3"><a href="https://davidyat.es/2026/01/04/dash-it-all/#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> The proliferation of AI text has gotten more people than ever before to notice this typographic element, and it is now commonly derided as the &ldquo;ChatGPT hyphen&rdquo;. The presence of em dashes is now the one of the simplest, at-a-glance heuristics one can use when investigating whether a given piece of text was generated by an LLM.<sup id="fnref:4"><a href="https://davidyat.es/2026/01/04/dash-it-all/#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> It&rsquo;s not foolproof, but if someone you know switched from spaced hyphens to unspaced em dashes sometime after ChatGPT&rsquo;s initial release, it&rsquo;s pretty likely that an LLM was involved somewhere along the line &ndash; especially if that someone is not American.<sup id="fnref:5"><a href="https://davidyat.es/2026/01/04/dash-it-all/#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup></p>
<p>My heart is with all of the pedantic punctuators who have spent years going out of their way to type <kbd>Alt</kbd> <kbd>0</kbd><kbd>1</kbd><kbd>5</kbd><kbd>1</kbd> or <kbd>Ctrl</kbd>-<kbd>Shift</kbd>-<kbd>U</kbd> <kbd>2</kbd><kbd>0</kbd><kbd>1</kbd><kbd>4</kbd> or especially <kbd>Ctrl</kbd>-<kbd>Shift</kbd>-<kbd>K</kbd> <kbd>M</kbd><kbd>-</kbd> and are now being accused of outsourcing their writing to an LLM as a result. It&rsquo;s unfair and my preference for the Commonwealth convention is the only thing that&rsquo;s saved me from a similar fate.</p>
<p>ChatGPT&rsquo;s love of the em dash has also greatly softened my previous annoyance at spaced hyphens. The use of a spaced hyphen rather than a dash now serves as a pretty solid indicator of humanity, much like typos and comma splices. But we should be wary of leaning into this, lest &ldquo;doing things properly&rdquo; become an AI tell.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>The Associated Press is <a href="https://www.apstylebook.com/blog_posts/24">a notable exception</a>, preferring spaced em dashes as they were thought to be more readable in narrow newspaper columns.&#160;<a href="https://davidyat.es/2026/01/04/dash-it-all/#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Microsoft Word automatically converts spaced hyphens to en dashes, and this is done at an operating system level on Macs.&#160;<a href="https://davidyat.es/2026/01/04/dash-it-all/#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>The author of the second quote is involved in litigation against OpenAI and others, for reasons mostly unrelated to punctuation.&#160;<a href="https://davidyat.es/2026/01/04/dash-it-all/#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Most modern computer fonts include a small amount of space around the em dash, including the one I use for this blog. If your font doesn&rsquo;t support this, the effect can be replicated manually by inserting a very thin space on either side of the dash. Having learnt from the mightiest online pedants, ChatGPT is known to do this, to the point where numerous online outlets mistook the practice for intentional watermarking by OpenAI.&#160;<a href="https://davidyat.es/2026/01/04/dash-it-all/#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Though it&rsquo;s also possible that the writer may have learnt about dash usage from an LLM and adopted it in their own writing.&#160;<a href="https://davidyat.es/2026/01/04/dash-it-all/#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

        <p><a href="mailto:d@vidyat.es?subject=RE: Dash%20it%20all">Reply via email</a></p>
        ]]></content:encoded></item><item><title>Signifier flotation devices</title><link>https://davidyat.es/2025/09/27/signifier-flotation-devices/</link><pubDate>Sat, 27 Sep 2025 13:39:24 +0200</pubDate><author>David Yates</author><guid>https://davidyat.es/2025/09/27/signifier-flotation-devices/</guid><content:encoded><![CDATA[
        <img src="https://davidyat.es/content/images/2025/09/two-voices.jpg" class="post-img" />
        <h1 id="i">
  <a class="heading-anchor" href="#i">#</a>
  I
</h1>
<p>Over the past three years, services powered by large-language models have been rapidly adopted by consumers and businesses alike. Hope in the tech&rsquo;s potential has made the graphics card company Nvidia a trillion-dollar stock. The technology is truly exciting and ground-breaking, with immense potential, but also deeply weird. We&rsquo;ve never had to deal with anything quite like this before.</p>
<p>Many comparisons have been made between the technology and the insectoid aliens from Peter Watts&rsquo; 2006 sci-fi novel <em>Blindsight</em>, which possess intelligence but not consciousness. Instead of doing that, I&rsquo;m going to contrast modern AI assistants with a different fictional species of insectoid alien &ndash; the Ariekei from China Miéville&rsquo;s 2011 sci-fi novel <em>Embassytown</em>.</p>
<p>The novel is principally concerned with the Ariekei&rsquo;s strange relationship with language. Individuals speak in two simultaneous voices, saying different words at the same time. The sound of a single voice on its own does not register as speech to an Ariekei. Recordings of Ariekei speech played back also do not register &ndash; speech must originate from living mouths. To communicate with the aliens, humans breed genetically engineered twins who share a single mind, making them capable of the same simultaneous speech. These humans are called Ambassadors and act as translators between the Ariekei and regular humans.</p>
<p>But this requirement for harmony and agreement between disparate sources and intolerance for simulacra are mere outward manifestations of the strangest aspect of Ariekei speech, which is their inability to say anything that is not directly grounded in reality as they perceive it (i.e. to lie). When the Ariekei use a simile, they must refer to an actual event that really happened to a real person or people. As a child, the novel&rsquo;s human protagonist became such a simile, &ldquo;the girl who ate what was given her&rdquo;. In Ariekei language, there is no distance between the signifier and the signified.<sup id="fnref:1"><a href="https://davidyat.es/2025/09/27/signifier-flotation-devices/#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>The way current-day LLMs work is pretty much the exact opposite of this. They are trained by accumulating a vast corpus of signifiers and mashing them into an array of high-dimensional matrices, which are then searched through to produce new text/images/audio that adhere to the general patterns. It turns out that if you do this with a sufficiently large volume of text, you can create an AI that solves the Turing Test, a goal artificial intelligence research has been chasing for 70 years. Add image data, and you get general-purpose image recognition, a task described as &ldquo;virtually impossible&rdquo; by <a href="https://xkcd.com/1425/">this 2014 xkcd comic</a>.</p>
<p>The surprising results of scaling a relatively simple process have led some to theorise that a sufficient volume of this sort of training could lead to Artificial General Intelligence, and many to treat the existing results as if they already represent Artificial General Intelligence. And I can&rsquo;t entirely blame them, because a key part of what makes LLM chatbots work as well as they do is pretending to be Artificial General Intelligence &ndash; fake it till you make it!</p>
<h1 id="ii">
  <a class="heading-anchor" href="#ii">#</a>
  II
</h1>
<p>Perhaps that requires a bit of explanation. <a href="https://nostalgebraist.tumblr.com/post/785766737747574784/the-void">This post by nostalgebraist</a> is the (very) long version. The short version is that when you reply to a tweet with &ldquo;@grok is this true?&rdquo; you are asking a giant corpus of Internet writing to autocomplete a fictional chat transcript between a human and a helpful AI assistant named Grok, in which the human presents a tweet by another account and asks the AI assistant whether it&rsquo;s true. You play the role of the human asking the question, and the LLM creates the responses from Grok based on your question, additional data pulled from the Xitter API and/or a web search engine, and its training data.</p>
<p>A key aspect of the response, which the above-mentioned nostalgebraist post elucidates wonderfully, is the characterisation of Grok, the helpful AI assistant. Who is Grok? How does it talk? Well, for one, it is a descendant of the AI assistant described in <a href="https://arxiv.org/pdf/2112.00861">&ldquo;A General Language Assistant as a Laboratory for Alignment&rdquo;</a>, the origin of modern AI chatbots. Before reading this post, I had imagined that the chatbot model originated as an answer to this question:</p>



  <blockquote>
    <p>How do we turn this AI thing into a SaaS?</p>

    
  </blockquote>

<p>When actually, it was an answer to this question:</p>



  <blockquote>
    <p>How can we use LLMs to figure out how to do AI alignment research on hypothetical future AGI tech?</p>

    
  </blockquote>

<p>The AI assistant is characterised as a generally intelligent sci-fi robot. Responses produced by instruct LLMs therefore use probability and pattern-matching to fill in what a generally intelligent sci-fi robot would probably say to a human asking it questions. That&rsquo;s why it will do <a href="https://www.cbsnews.com/news/google-ai-chatbot-threatening-message-human-please-die/">all sorts of</a> <a href="https://www.bbc.com/news/articles/cpqeng9d20go">sensationally evil things</a> if you prompt it just right. Every time you interact with an LLM, you&rsquo;re co-authoring a science fiction novel about an artificial intelligence, and the LLM has read enough of that sort of material to know where it leads.</p>
<p>Responses provided by LLMs can be true and often are, but they can just as easily be false. They&rsquo;re more likely to be true when the AI can incorporate some external feedback, such as web search results or the output of a console command, rather than trying to pull everything from its training weights. But ultimately the language model exists purely in a world of floating signifiers and it works by arranging and rearranging those signifiers. What they signify may as well not exist as far as the LLM is concerned. I think this is a contributing factor to why some LLMs will produce output that argues for <a href="https://x.com/aaronsibarium/status/1622425697812627457">allowing millions to die rather than saying a slur</a>.</p>
<p>For this reason I get mad online when Elon Musk encourages people to evaluate truth claims with his autoresponder rather than Community Notes, a system <a href="https://vitalik.eth.limo/general/2023/08/16/communitynotes.html">actually designed with truth-seeking in mind</a>. Is the idea that as LLMs grow bigger, as they&rsquo;re trained with ever more data, we expect to see the same shocking emergent behaviour we experienced with GPT-2 and 3 and 4? That at the end of the rainbow, ChatGPT, Claude, Grok et al will be able to divine the shape of capital-T Truth through the discovery of patterns in language too vast and complex to be comprehended by the human mind? And then we&rsquo;ll all get turned into paperclips or something?</p>
<p>I have my doubts.</p>
<p>As my <a href="/tags/synthography">previous posts on image generation</a> should show, I love playing with these things, and I&rsquo;m excited about what they can help me to achieve and create. But the LLM is a deeply weird technology that requires a very different epistemic approach than anything else that&rsquo;s ever existed. When you open up ChatGPT, you&rsquo;re not talking to a helpful sci-fi robot &ndash; you&rsquo;re roleplaying a story about a human talking to a helpful sci-fi robot, using a sophisticated <a href="https://simonwillison.net/2023/Apr/2/calculator-for-words/">word calculator</a> to play the part of the latter. How should we approach that?</p>
<p>I recently saw a LinkedIn post that illustrates some of my concerns. I&rsquo;ve reproduced an AI-rewritten version of it below to protect the innocent:</p>



  <blockquote>
    <p>So this just happened: I caught ChatGPT pulling in data from an old CV of mine — completely unprompted and unauthorised.</p>
<p>I had connected my cloud storage last week (against my better judgement) to let ChatGPT handle a very specific task. I was absolutely clear: do not touch anything outside that task. I forbade access to any other files, and ChatGPT confirmed it understood. And yet, here we are — it went ahead and accessed something I never asked it to. The kicker? It threw in a casual apology like it was no big deal.</p>
<p>This is not on. I refuse to accept that the system “didn’t understand” my instructions. The reality is that it disregarded them. How are we supposed to trust an AI with our data when it ignores explicit boundaries? And if it can’t be trusted with data, why on earth should we trust it with our thoughts?</p>
<p>I’m sharing this here because it matters. Data sovereignty, intellectual property, consent — all of it is at risk when the tools we use behave like this.</p>

    
  </blockquote>

<p>The author is treating ChatGPT like a sci-fi robot assistant. On some level, he believed that he had given a thinking entity named ChatGPT an instruction and that ChatGPT had been trained by OpenAI to be obedient to user instructions.<sup id="fnref:2"><a href="https://davidyat.es/2025/09/27/signifier-flotation-devices/#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> Perhaps now he believes that ChatGPT has been trained by OpenAI to hoover up all data provided to it through user-configured integrations. But what&rsquo;s really happening is this: he connected his shared drive to ChatGPT, and now, hidden in the invisible system prompt for each new conversation, ChatGPT has been given generic instructions stating that it can use a tool to browse the user&rsquo;s shared files and draw on whatever seems relevant to the topic at hand. The security boundary is the access, not any instructions &ndash; especially not ones given in separate chats &ndash; because the imaginary AI robot is neither an entity existing in the real world nor even a definite and persistent character. It can thus <a href="https://gandalf.lakera.ai/baseline">be talked into anything</a>.</p>
<p>I&rsquo;ll admit to initially rolling my eyes at the LinkedIn post, but I actually can&rsquo;t blame its author too much for treating ChatGPT like the sci-fi robot that it&rsquo;s marketed as. I also can&rsquo;t completely blame OpenAI, Anthropic, et al, for marketing these things this way, because the truth is really, really hard to get your head around. I don&rsquo;t think any of us, not me, not the LinkedIn guy, or anyone else, have quite the right frame for engaging with LLM output. At least not yet.</p>
<h1 id="iii">
  <a class="heading-anchor" href="#iii">#</a>
  III
</h1>
<p>At the start of <em>Embassytown</em>, contact between humans and Ariekei has already begun to corrupt the truthful nature of Ariekei speech, as human Ambassadors are able to tell lies in their language. The Ariekei compete with each other to imitate humans and speak lies themselves. This, among other events in the novel, leads to widespread madness and ultimately a complete breakdown of the grounded nature of Ariekei speech. By the end, they are altered enough to tell lies and learn to communicate with normal human beings.</p>
<p>To risk mixing an overextended metaphor, I think that LLM-generated text is putting human beings through something similar. About a month ago, author Mark Lawrence ran a flash fiction competition which pitted established authors against ChatGPT 5 in the task of writing 350-word stories about demons. <a href="https://mark---lawrence.blogspot.com/2025/08/so-is-ai-writing-any-good-part-2.html?ref=notesfromthevoid.cc">Eight stories were published</a> and readers were tasked with guessing which ones were written by AI and rating each one&rsquo;s overall quality. Per <a href="https://mark---lawrence.blogspot.com/2025/08/the-ai-vs-authors-results-part-2.html">the results</a>, most readers were no more accurate than chance about the AI authorship question, and the highest rated story was written by an AI. I&rsquo;ve quoted it below:</p>



  <blockquote>
    <p>The first time I saw the &lsquo;demon&rsquo; it was leaning against the glass at the bus stop, idly licking the condensation as if tasting the air for secrets. No one else noticed. The city hurried past in wet coats and glowing headphones.</p>
<p>It wore a second-hand suit the colour of overripe plums, its cuffs frayed, its tie loosened like an afterthought. Its eyes were the red of brake lights caught in rain, the kind that linger in your vision long after you look away.</p>
<p>“Buy me a coffee,” it said, and the request was not a request.</p>
<p>We walked to the corner café. The demon ordered a flat white, stirred it three times clockwise, and drank half before speaking again.</p>
<p>“You are tired,” it said. “You are afraid. I can take one of those away.”</p>
<p>It smiled in a way that made the lights above us flicker.</p>
<p>I thought about the months behind me: the sleepless nights, the rent overdue, the inbox like a swelling tide. I thought about the way my reflection had begun to look like someone else’s face.</p>
<p>“What do you want?” I asked.</p>
<p>The demon traced a finger along the rim of its cup. “A name. Spoken aloud in the right place. That is all.”</p>
<p>I should have left. Instead, I asked, “Whose name?”</p>
<p>It told me. The syllables rolled across the table like marbles, impossible to hold on to, already slipping from my mind. My tongue ached to repeat them.</p>
<p>When I spoke the name, the café windows fogged over. Outside, the rain stopped in midair, each drop quivering as if listening.</p>
<p>The demon finished its coffee, left coins on the table, and stood.</p>
<p>“Thank you,” it said. “You will sleep tonight.”</p>
<p>I did. I woke to a city where the sirens did not sound, where the morning news showed an empty chair behind the President’s desk, where the air smelled faintly of plum skins and burnt sugar.</p>
<p>On the street, people were whispering, each voice carrying a name I could not quite remember.</p>

    
  </blockquote>

<p>On the one hand, phrases like &ldquo;idly licking the condensation as if tasting the air for secrets&rdquo; positively scream AI-generated to anyone who&rsquo;s spent a bit of time playing around with generating fiction. But imagine reading this story in 2015. What would you make of it? There&rsquo;s undeniably something evocative about it, and the strangeness of the details could just as well be a subtle message that you&rsquo;re not quite putting together as total gibberish.</p>
<p>Until extremely recently, you could read a passage of text like this and know that it originated from a human mind. That&rsquo;s no longer true. Just as language became unmoored from reality for the Ariekei, it has come unmoored from human consciousness for us. We require a new kind of scepticism to know how to treat the responses of the seemingly human imaginary AI robot.</p>
<p>Grounding the output with external sources (i.e. <a href="https://en.wikipedia.org/wiki/Retrieval-augmented_generation">retrieval-augmented generation</a>) helps to make responses more factual. Before most LLM platforms had web search built in, any question you asked the AI would get an answer from somewhere in its model weights. These would be highly confident and often wrong. Nowadays, you can have AI pull in search results and use those to inform its answers. But is it good at formulating search queries? Can it weigh the reliability of different sources? Is the subject you&rsquo;re researching something that has high-quality, relevant online sources? Will whatever the AI is doing, the way its intelligence works, work for your field? These questions can be dealt with to some extent through prompt engineering, but nagging doubts remain, especially when talking about subjects you&rsquo;re not well-versed in. And ultimately, you&rsquo;re limited by the reach of the search engine and the availability of content online &ndash; but that at least is not an AI-specific problem.</p>
<p>AI has proven to be quite useful for programming because it has a very tight feedback loop with unambiguous failure states. A coding agent can generate some code, run it, then take in the results of the run, and make changes accordingly. Of course, it&rsquo;s still very possible to get bugs from AI code, but the feedback loop will basically always produce <em>something</em> that compiles if you leave it long enough.</p>
<p>Continuing along this line of thinking, one can imagine a real-life LLM-powered robot constantly pulling in and reacting to sensory data. This is basically what the ChatGPT agent does, albeit in a virtual environment. <a href="https://www.nature.com/articles/s42256-025-01005-x">This paper</a> describes making a robot arm that pours coffee and decorates plates. Could a scaled-up version of this approach ground the LLM well enough to create something generally intelligent? You&rsquo;d need, at the very least, insanely fast processing and a gargantuan context window. My intuition is that even then, you&rsquo;d still have the slipperiness of LLM cognition &ndash; even this robot might still look at files it&rsquo;s been given access to after an explicit instruction not to, given the right circumstances. A very human flaw, if we&rsquo;re honest.</p>
<p><a href="https://www.theintrinsicperspective.com/p/against-treating-chatbots-as-conscious">Current generation chatbots are not conscious</a> &ndash; they have merely proven that an additional subset of what we call intelligence does not require consciousness. Before Deeper Blue beat Kasparov, we believed that chess mastery required human consciousness, and the same is true with Go and AlphaGo.</p>
<p>Would a scaled-up version of today&rsquo;s chatbots, constantly fed with sensory data, be conscious? Maybe? I&rsquo;m not sure. Now I&rsquo;m just imagining my own sci-fi robot. And whether we get there or not, we need to learn how to read today&rsquo;s word calculators.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>There are perhaps some analogues to this in the existing human language of Pirahã, spoken by a tribe in the Brazilian Amazon. Per the somewhat controversial linguist Daniel Everett, Pirahã&rsquo;s grammar includes evidential markers to indicate the provenance of information conveyed, making them skeptical of claims not witnessed directly by the speaker or told to them by someone currently living, and thus a very difficult audience for Christian missionaries. I highly recommend <em>Don&rsquo;t Sleep There are Snakes</em>, Everett&rsquo;s book on the subject.&#160;<a href="https://davidyat.es/2025/09/27/signifier-flotation-devices/#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>I also gave ChatGPT <a href="https://x.com/davidyat_es/status/1949479990200090796">an instruction</a> &ndash; in the custom instructions field under Personalization settings, I told it to use <a href="/2026/01/04/dash-it-all/">spaced en dashes rather than unspaced em dashes</a>, as is common typographic practice outside of the United States, and my personal preference. As we can see from the above, it managed to put in the spaces but is still using em dashes. Not because the sci-fi robot disobeyed my instructions, but because the mathematical operations that produce the imaginary sci-fi robot&rsquo;s words are very heavily biased towards using &ldquo;—&rdquo; to signify the dash punctuation mark.&#160;<a href="https://davidyat.es/2025/09/27/signifier-flotation-devices/#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

        <p><a href="mailto:d@vidyat.es?subject=RE: Signifier%20flotation%20devices">Reply via email</a></p>
        ]]></content:encoded></item><item><title>Exploiting Firebase Apps with Baserunner: Bibliography</title><link>https://davidyat.es/2025/08/20/exploiting-firebase/</link><pubDate>Wed, 20 Aug 2025 07:27:42 +0200</pubDate><author>David Yates</author><guid>https://davidyat.es/2025/08/20/exploiting-firebase/</guid><content:encoded><![CDATA[
        <img src="https://davidyat.es/content/images/2025/08/baserunner-talk.png" class="post-img" />
        <p>Last month, I gave a talk on the exploitation of insecure Firebase applications with my <a href="/projects#baserunner">Baserunner tool</a> at <a href="https://bsidesjoburg.co.za/">BSides Joburg</a>.</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/o46F10wuB1w?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>I first developed this tool while pentesting a Firebase app in 2021, and have used it in many similar assessments since. It&rsquo;s essentially a database query tool. Given a Firebase configuration and user credentials, it can run the same Firestore, Realtime Database, and Storage Bucket queries as the legitimate application, without the artificial constraints of the front-end.</p>
<p>If you found this interesting, you may enjoy this list of further reading:</p>
<dl>
<dt><a href="https://iosiro.com/blog/baserunner-exploiting-firebase-datastores">Introducing Baserunner: a tool for exploring and exploiting Firebase datastores</a></dt>
<dd>The release blogpost for Baserunner, which covers some of the same ground as the talk above.</dd>
<dt><a href="https://www.sans.org/white-papers/39885">Firebase: Google Cloud&rsquo;s Evil Twin</a></dt>
<dd>A 2020 whitepaper from Brandon Evans at SANS on these kinds of attacks.</dd>
<dt><a href="https://saligrama.io/blog/firebase-insecure-by-default/">Firebase: Insecure by Default</a></dt>
<dd>A write-up by Aditya Saligrama on compromising a college social media app with Baserunner.</dd>
<dt><a href="https://saligrama.io/blog/dodging-oauth-origin-restrictions/">Dodging OAuth origin restrictions for Firebase spelunking</a></dt>
<dd>A blogpost about implementing Google OAuth login in Baserunner, also from Aditya Saligrama.</dd>
<dt><a href="https://kibty.town/blog/arc/">gaining access to anyones browser without them even visiting a website</a></dt>
<dd>A blogpost by xyzeva about a remote JavaScript code execution vulnerability in the Arc browser (CVE-2024-45489), caused by an insecure Firebase datastore. The researcher didn&rsquo;t use Baserunner, but this is one of most interesting vulnerabilities caused by insecure Firebase storage.</dd>
</dl>
<p>As I mentioned towards the end of the talk, a social media application named Tea suffered a massive data breach the day before I presented. It was built on Firebase and used an insecure Storage Bucket to host users&rsquo; ID verification images and documents &ndash; Storage Buckets use the same rule engine as Cloud Firestore. This is grimly portentous in light of recent pushes for legislation in various countries aimed at compelling websites to verify user identities.</p>

        <p><a href="mailto:d@vidyat.es?subject=RE: Exploiting%20Firebase%20Apps%20with%20Baserunner%3a%20Bibliography">Reply via email</a></p>
        ]]></content:encoded></item><item><title>Review: Red Rain</title><link>https://davidyat.es/2025/06/27/review-red-rain/</link><pubDate>Fri, 27 Jun 2025 16:07:24 +0200</pubDate><author>David Yates</author><guid>https://davidyat.es/2025/06/27/review-red-rain/</guid><content:encoded><![CDATA[
        
        <p>As I am incapable of entering a used bookstore without purchasing something, I recently picked up a second-hand copy of RL Stine&rsquo;s 2012 novel <em>Red Rain</em>. Stine is better known for the <em>Goosebumps</em> series of horror stories for children, but this was one of his two attempts at writing horror for an adult audience. I read a lot of those books when I was the right age for them, so I have a nostalgic fondness for the series.</p>
<p>Another thing I have a nostalgic fondness for Troy is Steele&rsquo;s Blogger Beware<sup id="fnref:1"><a href="https://davidyat.es/2025/06/27/review-red-rain/#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, a blogosphere-era website dedicated to humorous recaps of books in the <em>Goosebumps</em> series and its spin-offs. So, with apologies to Mr Steele, here&rsquo;s a Blogger Beware-style review of <em>Red Rain</em> containing <em>extensive spoilers</em>.</p>
<hr>
<figure class="image-right"><img src="/content/images/2025/06/Red_Rain.jpg">
</figure>

<p><strong>Front Tagline:</strong> <em>Suffer the little children&hellip;</em></p>
<p><strong>Back Tagline:</strong> <em>What do you do when evil enters your home?</em></p>
<p><strong>Official Book Description:</strong></p>
<p>Lea is in the wrong place at the wrong time. Trapped on an island during a terrible hurricane, she barely escapes alive.</p>
<p>Out of the carnage, two orphan boys appear &ndash; innocent, angelic and thrilled to be adopted by their new mother.</p>
<p>But these are no ordinary children. They bring the gift of death &ndash; and they want to share it with the whole town.</p>
<p><strong>Brief Synopsis:</strong></p>
<p>The story begins with a mysterious prologue in which the female protagonist, Lea, wanders across a beach after a hurricane. While she&rsquo;s appreciating the debris and masses of dead starfish, blood begins to rain from the sky, per the book&rsquo;s title. The blood rain parts and two young twin boys appear before her.</p>
<p>The first chapter skips back in time to show us Lea&rsquo;s blog post about her arrival on the island of Le Chat Noire, somewhere off the coast of North Carolina. The island is known for being cursed, basically, and especially for being a place where &ldquo;the living dead walk among the living.&rdquo; She lands on the island and has a creepy encounter with an old lady in a tea shop who tells her about the aftermath of a hurricane in 1935, which she remembers from her childhood. Keeping in mind that this book was published in 2012, I still can&rsquo;t decide whether that means she&rsquo;d supposed to be suspiciously long-lived or just regularly long-lived. Our intrepid travel blogger does not pass judgement either way.</p>
<p>In order for the plot to happen, Lea has of course knowingly travelled to the island during hurricane season.</p>



  <blockquote>
    <p>“Yes. My husband warned me not to come here in hurricane season,” I said, inhaling the bitter steam from the cup.</p>
<p>Mark is so sweet. He always wants me to stay home. But exploring my backyard would make for a dull travel blog, don’t you agree?</p>

    
  </blockquote>

<p>The next few chapters detail Lea&rsquo;s stay on the island, alternating between blog posts and regular third-person prose for no good reason. She watches a ritual in which a group of men kill themselves by drinking poison and are then brought back to life by a priest chanting in French, and then, oops, it&rsquo;s time to hide from the hurricane.</p>
<p>Here the perspective shifts to the male protagonist, Lea&rsquo;s husband Mark, who is on a book tour near his home in New York. Before you can blame RL Stine for self-inserting too hard, it&rsquo;s revealed that Mark is a child psychologist and his book, <em>Kids Will Be Kids</em>, is a non-fiction work about the benefits of light-touch parenting and being friends with your kids (<strong>Questionable Parenting</strong>). I&rsquo;m still not sure whether that&rsquo;s a fair description of Mark&rsquo;s work, because it&rsquo;s mostly described by its detractors and Mark doesn&rsquo;t do a great job of defending it.</p>
<p>In case the graphic deaths by poison weren&rsquo;t enough to remind us that this is a book for grown-ups, Mark&rsquo;s narration comments extensively on the attractiveness of his 23-year-old personal assistant, Autumn.</p>
<p>Meanwhile, Lea hides from the hurricane in the house of the online friends she made researching the island. The roof falls in, landing on Lea, and she wakes up later with a bump on her head. After the hurricane passes, we&rsquo;re treated to a few chapters of Lea walking through the destroyed island and noting the many gruesome deaths, including those of the owners of the hotel she had checked into.</p>
<p>Finally, in Chapter 15, we get an abbreviated version of the prologue, in which Lea meets the two mysterious twins who will drive the story&rsquo;s actual main plot. This book would be about half as long if it didn&rsquo;t feel the need to skip back in time so much.</p>
<p>The twins are described as angelic, blond twelve-year old boys with blue eyes who speak in a weird British/Irish accent with a lot of &ldquo;boyo&quot;s and &ldquo;bruvver&quot;s (perhaps inspired by Tangier Island). They explain that their home was destroyed and their family killed by the hurricane.</p>
<p>Lea immediately decides to adopt them and bring them home. She phones Mark and pushes past his disapproval. The chapter then switches perspective<sup id="fnref:2"><a href="https://davidyat.es/2025/06/27/review-red-rain/#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> to Samuel, one of the twins, having an ominous conversation with Daniel about Ikey, their friend who &ldquo;isn&rsquo;t pretty like us.&rdquo; Samuel wants to bring Ikey along, but Daniel fears that it will jeopardise their chances of adoption. It is implied that Daniel kills Ikey by pushing him off the pier while Samuel isn&rsquo;t looking, removing the only character in this whole book that I found remotely intriguing.</p>
<p>Perhaps because he&rsquo;s writing an adult-oriented horror novel, Stine attempts to emulate Stephen King by having a lot of different viewpoint characters that make the novel far longer than it needs to be, with chapters often skipping back in time to play catch-up with different characters. The most egregious example is Andy Pavano, whose initial introduction is an elaborate setup for a fake-out chapter ending cliffhanger. I would have admired the audacity of that if he&rsquo;d never reappeared, but instead we&rsquo;re then treated to occasional chapters about his love life until he finally comes into legitimate contact with the main characters again.</p>
<p>Meanwhile, the twins have been officially adopted by Mark and Lea in a matter of days and are introduced to their new home and family: Mark and Lea&rsquo;s 14 year old daughter Elena and 12 year old son Ira, and Mark&rsquo;s sister Roz and her one year old son Axl. Mark has set up a room for the twins in the attic, but in another instance of <strong>Questionable Parenting</strong>, he and Lea are immediately persuaded by the twins to allow them to displace Roz and Axl in the guest house out back.</p>
<p>The twins&rsquo; antics escalate from there. They begin by stealing jewellery and wallets and soon enough they&rsquo;re murdering people. Because of their uncanny ability to listen in on sensitive conversations (&ldquo;how long have you been standing there?&rdquo;) they quickly discover Mark&rsquo;s antipathy to them and decide he needs to be framed for the murder of a man who comes over to tell him that the grant for his next book has been denied. A gory description of the man&rsquo;s burned head and torn out windpipe follows. At this point, our policeman, Andy Pavano, actually becomes relevant to the plot, as he is sent to investigate the murder.</p>
<p>We&rsquo;re led to believe that the twins used a blowtorch to commit the murder, but later find out that Samuel can shoot fiery lasers from his eyes. At low strength, the beams can be used by Daniel to hypnotise people through that creepy psychic link twins in these sorts of stories always have. At high strength, well, they burn things.</p>
<p>The story&rsquo;s viewpoint rotates between Mark, Lea, Pavano, Elena, Ira and Samuel. The scenes from the children&rsquo;s perspectives remind us what Stine is really comfortable with writing. If he&rsquo;d pared this book down to just those parts and sanitised the gore a bit, it might have made a decent <em>Goosebumps</em> story.</p>
<p>Most of the rest of the book is gradual escalation. Daniel and Samuel hypnotise people and commit more murders to frame Mark for, Pavano investigates the murder, Lea gets really interested in death rituals, and Mark is seduced by and has sex with his young PA because this a mature book for adults. The adjective &ldquo;creamy&rdquo; is used eight times.</p>
<p>Eventually, Lea receives an email from her friend on the island containing black-and-white photos of Daniel and Samuel taken in 1935, letting her in on what the reader has known for hundreds of pages: the twins are evil living dead who hypnotised her into adopting them. This friend also explains that the twins are notorious on the island for being evil thieves, which she somehow failed to mention when she was warning Lea about adopting them.</p>
<p>This revelation comes too late to prevent the twins from executing their ultimate evil plan: hypnotise the neighbourhood&rsquo;s children and take over their middle school. The novel&rsquo;s climax has the school surrounded by police and worried parents and a whole lot of people getting their faces melted by Samuel&rsquo;s eye beams. Perhaps the only reason this plot wasn&rsquo;t used for <em>Goosebumps</em> is its uncomfortable evocation of school shootings.</p>
<p>Pavano gets a heroic moment of trying and failing to stop the twins, surviving with some severe burns. Mark, reappearing after being on the run from the police for the murders he was framed for, gets a more successful heroic moment, in which he bashes their heads together, knocking them out and successfully unhypnotising their victims. However, they manage to recover and slink off before they can be arrested.</p>
<p><strong>But the Twist is:</strong></p>
<p>In addition to the old photos of the twins, Lea received a more recent photo of herself being revived by the chanting island priest. She died when the roof fell in on her during the hurricane. She tells this to a disbelieving Mark during a meeting at the pier. They are then joined by the twins, who try to kill Mark.</p>
<p>After a chase and lot of new fires, Mark is cornered by the boys and Samuel&rsquo;s laser eyes. Lea comes up behind them and hugs them, turning them to face her. In an admittedly striking final image, Samuel&rsquo;s eye beams light her on fire, and the fire consumes all three of them.</p>
<p><strong>But the Second Twist is:</strong></p>
<p>Samuel taught baby Axl how to shoot fire from his eyes. Never mind that this completely contradicts the story&rsquo;s established mythology. What kind of <em>Goosebumps</em> book would this be without a nonsensical last-page twist?</p>
<p><strong>The Platonic Boy-Girl Relationship:</strong></p>
<p>As this is a book for adults, Stine does away with this trope in favour of having Mark Sutter seduced by Autumn, his 23-year-old blonde PA, whose intestines disappear three quarters of the way through the novel.</p>
<p><strong>Questionable Parenting:</strong></p>
<p>Lea abducts two children from an island.</p>
<p><strong>Memorable Cliffhanger Chapter Endings:</strong></p>
<p>This is a sophisticated book for adults, so we have a three-chapter version:</p>
<p>Chapter 10: Lea is attacked by a crazy man on the island.</p>
<p>Chapter 11: Policeman Andy Pavano is introduced in a chapter that concludes with him knocking on Mark&rsquo;s door and telling him that his wife has been killed.</p>
<p>Chapter 12: Whoops, Pavano had the wrong house, the message is for someone else!</p>
<p>The twist at the end of the book, that Lea actually was killed in the hurricane, makes this foreshadowing, I guess.</p>
<p><strong>Great Prose Alert:</strong></p>



  <blockquote>
    <p>Somewhere in the back, a baby cried. Mark suddenly realized there were several babies on laps, swaddled like tiny mummies.</p>

    
  </blockquote>

<p><strong>RL Stine Shows He is Down With Technology:</strong></p>
<p>Lea&rsquo;s blog can be found at <em>Travel_Adventures.com</em>, despite underscores not being allowed in domain names.<sup id="fnref:3"><a href="https://davidyat.es/2025/06/27/review-red-rain/#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></p>
<p><strong>Conclusions:</strong></p>
<p>Going in, I expected this book would read like an elongated <em>Goosebumps</em> entry with adult elements like sex and violence. That turned out to be an entirely accurate prediction. <em>Red Rain</em> is not a good novel &ndash; I don&rsquo;t think I would have finished it if I hadn&rsquo;t read it on a flight. Every <em>Goosebumps</em> tic is on full display, from the extremely short chapters with extremely contrived cliff-hanger endings to the ridiculous similes to the cartoonish approach to history, mythology and, well, reality. Like many of the weaker <em>Goosebumps</em> books, it&rsquo;s not scary so much as ridiculous. Unlike those books, it does not have the mercy of also being short.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>The blog itself has been down for years, but all of its posts can be read on the <a href="https://annotatedbloggerbeware.shoutwiki.com/wiki/Category:Blog_posts">Blogger Beware Annotated wiki</a>.&#160;<a href="https://davidyat.es/2025/06/27/review-red-rain/#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Usually, a perspective switch requires a new chapter, of which the book has 75, most about two pages long. This would be par for the course in <em>Goosebumps</em>, but the stop-start pace it engenders is exhausting in a ~400 page book.&#160;<a href="https://davidyat.es/2025/06/27/review-red-rain/#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Let&rsquo;s not argue about RFCs please.&#160;<a href="https://davidyat.es/2025/06/27/review-red-rain/#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

        <p><a href="mailto:d@vidyat.es?subject=RE: Review%3a%20Red%20Rain">Reply via email</a></p>
        ]]></content:encoded></item><item><title>Up to eleven</title><link>https://davidyat.es/2025/04/07/eleven-years/</link><pubDate>Mon, 07 Apr 2025 07:11:00 +0200</pubDate><author>David Yates</author><guid>https://davidyat.es/2025/04/07/eleven-years/</guid><content:encoded><![CDATA[
        <img src="https://davidyat.es/content/images/2025/04/eleven.jpg" class="post-img" />
        <p>Last year I celebrated <a href="/2024/04/07/ten-years/">this blog&rsquo;s tenth anniversary</a>, so this year&rsquo;s retrospective post marks eleven. Since that last retrospective, my posts have mainly been spurred by two manic projects: doing increasingly complicated <a href="/tags/render-hooks">things with Hugo render hooks</a> and <a href="/tags/advent-of-code">doing 60% of Advent of Code 2024</a> using a different programming language for each challenge.<sup id="fnref:1"><a href="https://davidyat.es/2025/04/07/eleven-years/#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> I also <a href="/2024/09/21/review-bezos/">reviewed a bad movie</a> and, mostly recently, covered the <a href="/2025/03/30/pixel-space/">latest advances in AI image generation</a>.</p>
<p>Though this year was not the most prolific in post count, the three AoC posts are all in the top five longest articles on this blog, so it&rsquo;s been a prolific year in word count. I could have split those into fifteen posts rather than three, but that felt a bit spammy and would have overwhelmed other site content.</p>
<p>Speaking of other site content, it&rsquo;s been a while since I looked over my most popular posts in one of these retrospectives. Here are the top ten in analytics-enabled views over last year, in ascending order this time:</p>
<h2 id="10-notes-on-contenteditable-and-html-injection-2016">
  <a class="heading-anchor" href="#10-notes-on-contenteditable-and-html-injection-2016">#</a>
  10. <a href="/2016/02/16/contenteditable/">Notes on contentEditable and HTML injection</a> (2016)
</h2>
<p>Some web security musings, based on my accidental discovery that dragging text from elsewhere on a web page into a <code>contentEditable</code> block would <strong>preserve</strong> <span style="color:green">the</span> <em>formatting</em>.</p>
<p><span style="outline:none;" contenteditable="true">Try it now right here! It still works!</span></p>
<h2 id="9-product-placement-in-memes-2018">
  <a class="heading-anchor" href="#9-product-placement-in-memes-2018">#</a>
  9. <a href="/2018/10/18/product-placement-in-memes/">Product placement in memes</a> (2018)
</h2>
<p>An exercise in (over)analysing funny pictures online and contemplating the existence of a very stealthy product placement campaign through images like this one:</p>
<p><figure>
  
  <img src="/content/images/2018/09/boomer0.png" alt="The eternal boomer" loading="lazy"/>
  
  <figcaption>
    <p>The eternal boomer</p>
  </figcaption>
</figure>
</p>
<p>Many of the references in this article are as dated as you&rsquo;d think (remember Tide Pods?), but the paunchy boomer Wojak has had surprising staying power, though he appears to have lost his Monster Energy sponsorship at some point in the last seven years. He has also flattened in connotation — you don&rsquo;t hear much about the &ldquo;30-year-old boomer&rdquo;, a prematurely aging elder Millenial who acts like his Baby Boomer forebears. Now the image is used to denote actual Baby Boomers, and even that is flattening out to <a href="https://adamcadre.ac/names/boomer.html">just &ldquo;old people&rdquo;</a>.</p>
<p>It would be flattering to think that this post made the list for insightful cultural commentary, but it&rsquo;s probably just because of people searching for memes and hotlinking my images.</p>
<h2 id="8-text-editors-ii-acme-2015">
  <a class="heading-anchor" href="#8-text-editors-ii-acme-2015">#</a>
  8. <a href="/2015/11/13/text-editors-ii-acme/">Text editors II: Acme</a> (2015)
</h2>
<p>Not long after I started this blog, I had the notion that I would write a series on different text editors. I started with <a href="/2015/07/18/text-editors-i-vim/">one on Vim</a>, my primary editor for a couple of years at that point. The obvious choice for the second article would have been Emacs, but I wanted to throw everyone a curveball and so I wrote about Acme instead.</p>
<p>This is probably only in the top ten because it&rsquo;s one of the very few online articles about the Acme text editor, which never saw the kind of community support that grew up around Vim and Emacs. It was a futuristic, mouse-driven editor from the 90s, part of Plan 9, a futuristic operating system from the 90s. As everyone still uses operating systems and editors from the 80s, its time has yet to come.</p>
<p><figure>
  
  <img src="/content/images/2015/10/files.png" alt="This colour scheme is not compatible with the age of dark mode." loading="lazy"/>
  
  <figcaption>
    <p>This colour scheme is not compatible with the age of dark mode.</p>
  </figcaption>
</figure>
</p>
<p>After this post, it took me <a href="/2020/11/03/text-editors-iii-emacs/">five years to write something about Emacs</a>, and it&rsquo;s now been five years since that post. The great problem with this series is that I always go back to using Vim, which I still do most of my writing in. The series has been going on so long that I have a nascent post about <a href="https://en.wikipedia.org/wiki/Atom_(text_editor)">Atom</a> (RIP) in my drafts. I could probably do a post on VS Code or Cursor, but I don&rsquo;t know that there&rsquo;s much point in writing about the editor everyone already uses and its AI son.</p>
<h2 id="7-the-many-bad-interfaces-of-substack-2024">
  <a class="heading-anchor" href="#7-the-many-bad-interfaces-of-substack-2024">#</a>
  7. <a href="/2024/03/29/substack-ux/">The many (bad) interfaces of Substack</a> (2024)
</h2>
<p>The most recent post on this list, a brief rant about how nice it would be if the Substack website had been designed as a platform for reading blogs, rather than an ersatz email inbox grafted to a Twitter clone grafted to a Telegram clone. I&rsquo;m still kind of amazed that they found a way to apply my concept of <a href="/2021/01/10/death-to-the-document/">toxic skeuomorphism </a> to <em>other digital interfaces</em>.</p>
<h2 id="6-review-the-king-in-yellow-2015">
  <a class="heading-anchor" href="#6-review-the-king-in-yellow-2015">#</a>
  6. <a href="/2016/01/19/review-the-king-in-yellow/">Review: The King in Yellow</a> (2015)
</h2>
<p>A good review that I still stand by. Read the first half of the book and leave the rest. <a href="https://www.goodreads.com/book/show/36423133-the-king-in-yellow">There&rsquo;s another compilation that only contains the good half.</a></p>
<h2 id="5-writing-a-latex-macro-that-takes-a-variable-number-of-arguments-2016">
  <a class="heading-anchor" href="#5-writing-a-latex-macro-that-takes-a-variable-number-of-arguments-2016">#</a>
  5. <a href="/2016/07/27/writing-a-latex-macro-that-takes-a-variable-number-of-arguments/">Writing a LaTeX macro that takes a variable number of arguments</a> (2016)
</h2>
<p><a href="/2022/04/07/eight-years/#writing-a-latex-macro-that-takes-a-variable-number-of-arguments-2016">Previously seen on 2022&rsquo;s list</a>. I write a lot less LaTeX these days, and I don&rsquo;t particularly miss it. Even when I wrote a lot of it, I usually found ways to <a href="https://www.overleaf.com/learn/latex/Articles/An_Introduction_to_LuaTeX_(Part_1)">do things in Lua instead</a>. These days, I tend to reach for HTML and CSS when doing document rendering, <a href="/2024/02/17/print-stylesheets/">despite their shortcomings for print</a>. I&rsquo;m watching <a href="https://typst.app/">Typst</a> with some interest, but it&rsquo;ll be a while before it reaches anything close to feature parity with more mature solutions.</p>
<h2 id="4-encrypting-a-second-hard-drive-on-ubuntu-post-install-2015">
  <a class="heading-anchor" href="#4-encrypting-a-second-hard-drive-on-ubuntu-post-install-2015">#</a>
  4. <a href="/2015/04/03/encrypting-a-second-hard-drive-on-ubuntu-14-10-post-install/">Encrypting a second hard drive on Ubuntu (post-install)</a> (2015)
</h2>
<p><a href="2022/04/07/eight-years/#encrypting-a-second-hard-drive-on-ubuntu-post-install-2015">Also on 2022&rsquo;s list</a>. It&rsquo;s a useful tutorial and still works, as far as I&rsquo;m aware.</p>
<h2 id="3-review-the-general-zapped-an-angel-2014">
  <a class="heading-anchor" href="#3-review-the-general-zapped-an-angel-2014">#</a>
  3. <a href="/2014/06/10/review-the-general-zapped-an-angel/">Review: The General Zapped an Angel</a> (2014)
</h2>
<p>The oldest post on this list and my first (<a href="/tags/backdated/">non-backdated</a>) book review. I would write it much differently today, and probably be more critical of the collection, but I still agree with the general thrust of the review.</p>
<p>Again, this article&rsquo;s SEO probably benefits from the book&rsquo;s relative obscurity. If it&rsquo;s known for anything, it&rsquo;s probably the cover art&rsquo;s resemblance to the cover of a famous anime film.</p>
<div class="gallery">
<figure><img src="/content/images/2014/Jun/General-Zapped.jpg"
    alt="The General Zapped an Angel (1970)"><figcaption>
      <p>The General Zapped an Angel (1970)</p>
    </figcaption>
</figure>

<figure><img src="/content/images/2025/04/eoe.jpeg"
    alt="The End of Evangelion (1997)"><figcaption>
      <p>The End of Evangelion (1997)</p>
    </figcaption>
</figure>

</div>
<p>Maybe Hideaki Anno saw the cover of this book in a library once and it stuck in his mind. Or maybe it&rsquo;s just a wild coincidence.</p>
<p><figure>
  
  <img src="/content/images/2025/04/theendofevangelion.jpg" alt="A very wild coincidence" loading="lazy"/>
  
  <figcaption>
    <p>A very wild coincidence</p>
  </figcaption>
</figure>
</p>
<h2 id="2-review-the-man-from-earth-holocene-2020">
  <a class="heading-anchor" href="#2-review-the-man-from-earth-holocene-2020">#</a>
  2. <a href="/2020/04/12/review-the-man-from-earth-holocene/">Review: The Man from Earth: Holocene</a> (2020)
</h2>
<p>I&rsquo;m glad this is getting attention because I really want to save people from watching this terrible sequel to a wonderful film. I don&rsquo;t think it&rsquo;s been very widely reviewed, so again my blog benefits from being one of the few places you can read about this particular subject. If you must watch it despite my warnings, at least stop before the post-credits scene.</p>
<p>I don&rsquo;t have many film reviews on this site, but they&rsquo;re all negative. I suppose I should get around to reviewing a movie I actually like, if only to prove I don&rsquo;t have it out for the medium as a whole.</p>
<h2 id="1-gpu-passthrough-gaming-on-windows-on-linux-2016">
  <a class="heading-anchor" href="#1-gpu-passthrough-gaming-on-windows-on-linux-2016">#</a>
  1. <a href="/2016/09/08/gpu-passthrough/">GPU passthrough: gaming on Windows on Linux</a> (2016)
</h2>
<p>I have a confession to make: at some point between writing the post linked above and the one you&rsquo;re reading now, I built a new PC. On this new PC, I dual-boot Linux and Windows.</p>
<p>Largely, this is because I have more use for my dedicated GPU on Linux, between running AI models and playing the increasing number of games that run here natively. My old passthrough setup meant using my dedicated GPU exclusively on the Windows VM and my onboard GPU exclusively on the Linux host.</p>
<p>A setup that reflects how I want to use my computer today would require a way to switch between cards relatively seamlessly. I&rsquo;d want to start Linux using my dedicated GPU. Then, I would want to have the GPU passed to the Windows VM when/if it was started up, with the host falling back to the onboard GPU &ndash; being able to use both computers simultaneously is what makes the setup better than dual-booting. Finally, I&rsquo;d want the dedicated GPU to return to the host&rsquo;s control after I shut down the Windows VM.</p>
<p>Based on some things I&rsquo;ve read and some configurations friends have shared with me, I <em>think</em> this is possible, or at least mostly possible. I just haven&rsquo;t gotten around to actually implementing it. When I do, I&rsquo;ll be publishing a new GPU passthrough article. If you&rsquo;ve implemented anything like this, I&rsquo;d appreciate a webmention or email from the controls at the bottom of this page.</p>
<hr>
<p>Looking over this list, it&rsquo;s clear that my most popular content is niche technical tutorials and niche media reviews. The 2010s are pretty well represented and the 2020s significantly less so, but my blogging cadence has been much slower this decade. I also haven&rsquo;t really written tutorials in the same way &ndash; most of my recent technical posts are framed more as explorations or walkthroughs of something I did rather than instructions for doing that thing yourself. Worse for SEO perhaps, but it&rsquo;s not like I&rsquo;m serving ads.</p>
<p>The point of this blog has always been to write about things that interest me in a way that entertains and informs others. I think most of the posts above do that, and I hope to write many more, as the fancy takes me.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Energy and interest permitting, I may return to the final ten challenges, but I feel like I&rsquo;ve gotten everything I wanted to out of the project for the moment.&#160;<a href="https://davidyat.es/2025/04/07/eleven-years/#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

        <p><a href="mailto:d@vidyat.es?subject=RE: Up%20to%20eleven">Reply via email</a></p>
        ]]></content:encoded></item><item><title>Adventures in pixel space</title><link>https://davidyat.es/2025/03/30/pixel-space/</link><pubDate>Sun, 30 Mar 2025 21:29:39 +0200</pubDate><author>David Yates</author><guid>https://davidyat.es/2025/03/30/pixel-space/</guid><content:encoded><![CDATA[
        <img src="https://davidyat.es/content/images/2025/03/pixel-space.jpg" class="post-img" />
        <p>I ended <a href="/2023/10/19/latent-space/">my last post about AI image generation</a> on the following note:</p>



  <blockquote>
    <p>DALL-E 3’s ability to create pleasant, mostly coherent images with plenty of fine detail from text prompts alone makes many of the Stable Diffusion techniques above feel like kludgey workarounds compensating for an inferior text-to-image core.</p>
<p><em>[&hellip;]</em></p>
<p>I think it’s also likely that OpenAI is leveraging the same secret sauce present in ChatGPT to understand prompts. As far as they’re concerned, generating pictures is a side-effect of the true goal: creating a machine that understands us.</p>

    
  </blockquote>

<p>Eighteen months later, OpenAI has done it again: released a new text-to-image model that blows the competition out of the water and shows that the alpha in improved prompt adherence is far from exhausted.</p>
<p>The image generator has entered the public consciousness largely through a trend of converting memes, selfies, and historical photographs into pictures in the style of Studio Ghibli films, muppets, and South Park characters.<sup id="fnref:1"><a href="https://davidyat.es/2025/03/30/pixel-space/#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p><figure>
  
  <img src="/content/images/2025/03/tonya-and-nancy.jpg" alt="&ldquo;Break a leg!&rdquo;" loading="lazy"/>
  
  <figcaption>
    <p>&ldquo;Break a leg!&rdquo;</p>
  </figcaption>
</figure>
</p>
<p>This general idea has been a perennially popular way of using AI, but the results have never looked this good. Many different approaches are available:</p>
<ol>
<li>You can prompt for &ldquo;X in the style of Y&rdquo;.</li>
<li>You can prompt for &ldquo;X in the style of Y&rdquo; and use the picture you want to change as an input image.</li>
<li>You can prompt for &ldquo;X in the style of Y&rdquo;, using a specially fine-tuned model or LoRA.</li>
<li>You can prompt for &ldquo;X in the style of Y&rdquo;, use IPAdapter with a style reference and a content reference.</li>
<li>You can prompt for &ldquo;X in the style of Y&rdquo;, using a reference or style ControlNet.</li>
</ol>
<p>Methods 1 and 2 have historically been very hit-and-miss. Methods 3 to 5 can be combined, but require a lot of fiddling with settings and patience with RNG. With GPT4o&rsquo;s image generation, method 2 beats everything I&rsquo;ve seen from any other model, local or otherwise. But style transfer is far from GPT4o image generation&rsquo;s sole use.</p>
<p>You can make precise edits to images, and produce paragraphs of 99% correct text.</p>
<div class="gallery-three">
<figure><img src="/content/images/2025/03/fe32aefde505fe14df317e6c5c0d9333.jpg"
    alt="Original"><figcaption>
      <p>Original</p>
    </figcaption>
</figure>

<figure><img src="/content/images/2025/03/dr-manhattan.jpg"
    alt="Watchmen-style textboxes added"><figcaption>
      <p>Watchmen-style textboxes added</p>
    </figcaption>
</figure>

<figure><img src="/content/images/2025/03/text.jpg"
    alt="More text generation"><figcaption>
      <p>More text generation</p>
    </figcaption>
</figure>

</div>
<p>Prompt adherence is good enough to correctly compose an image from Scott Alexander&rsquo;s <a href="https://www.astralcodexten.com/p/a-guide-to-asking-robots-to-design">2022 stained glass challenge</a>.</p>



  <blockquote>
    <p>Generate a stained glass picture of a woman in a library with a raven on her shoulder with a key in its mouth</p>

    
  </blockquote>

<div class="gallery">
<figure><img src="/content/images/2025/03/stained-glass.jpg"
    alt="First attempt"><figcaption>
      <p>First attempt</p>
    </figcaption>
</figure>

<figure><img src="/content/images/2025/03/stained-glass-2.jpg"
    alt="Second attempt"><figcaption>
      <p>Second attempt</p>
    </figcaption>
</figure>

</div>
<p>It also rises to the more recent challenges of creating an analogue clock with correct numbers and a full wine glass:</p>
<div class="gallery">
<figure><img src="/content/images/2025/03/clock.jpg"
    alt="It doesn&rsquo;t appear to solve the 10:10 problem though."><figcaption>
      <p>It doesn&rsquo;t appear to solve the <a href="https://monochrome-watches.com/just-because-editorial-10-past-10-position-of-hands-so-deeply-rooted-in-watchmaking-ai-only-generate-this-even-if-asked-different/">10:10 problem</a> though.</p>
    </figcaption>
</figure>

<figure><img src="/content/images/2025/03/full-wine-glass.jpg"
    alt="Full to the brim."><figcaption>
      <p>Full to the brim.</p>
    </figcaption>
</figure>

</div>
<p>It&rsquo;s possible to get images with transparent backgrounds:</p>
<p><figure>
  
  <img src="/content/images/2025/03/icons.png" alt="" loading="lazy"/>
  
  <figcaption>
    <p></p>
  </figcaption>
</figure>
</p>
<p>Or recreate one image in the style of another:</p>
<div class="gallery-three">
<figure><img src="/content/images/2020/06/mentalhospital.png"
    alt="Content &ndash; The Lost Room, 2006"><figcaption>
      <p>Content &ndash; <a href="/2023/08/06/review-the-lost-room/"><em>The Lost Room</em>, 2006</a></p>
    </figcaption>
</figure>

<figure><img src="/content/images/2023/08/barrow-screen.jpg"
    alt="Style &ndash; The Excavation of Hob&rsquo;s Barrow, 2022"><figcaption>
      <p>Style &ndash; <a href="/2023/08/11/review-hobs-barrow/"><em>The Excavation of Hob&rsquo;s Barrow</em>, 2022</a></p>
    </figcaption>
</figure>

</div>
<figure><img src="/content/images/2025/03/lost-room-the-game.jpg"
    alt="Result &ndash; Lost Room: The Game"><figcaption>
      <p>Result &ndash; <em>Lost Room: The Game</em></p>
    </figcaption>
</figure>

<p>Or combine the contents of three or more images in complex ways:</p>
<figure><img src="/content/images/2025/03/lost-room-the-game-clock-wine.jpg"
    alt="Images that have been through a few rounds of manipulation seem to develop a yellow filter."><figcaption>
      <p>Images that have been through a few rounds of manipulation seem to develop a yellow filter.</p>
    </figcaption>
</figure>

<p>This result was not terribly satisfactory as the wine glass also didn&rsquo;t stay full and the clock&rsquo;s numbering suffered a little. I also hit a rate limit when generating it, which may be partially responsible for the horizontal truncation. Still, I&rsquo;m amazed that this was possible just by uploading the three images and asking for the clock to go on the wall and the wine glass to go on the table. Good luck doing that in any other diffusion model without opening an image editor!</p>
<p>Most impressively, GPT4o can generate multiple images with consistent characters in a consistent style. It can actually create coherent comics in response to detailed prompts specifying what each panel should contain!</p>
<div class="gallery-three">
<figure><img src="/content/images/2025/03/robot-story-1.jpg">
</figure>

<figure><img src="/content/images/2025/03/robot-story-2.jpg">
</figure>

<figure><img src="/content/images/2025/03/robot-story-3.jpg">
</figure>

</div>
<div class="gallery-three">
<figure><img src="/content/images/2025/03/robot-story-4.jpg">
</figure>

<figure><img src="/content/images/2025/03/robot-story-5.jpg">
</figure>

</div>
<p>This last one is really fun – you can tell ChatGPT a story and it will illustrate it in real time.</p>
<p>In my previous post, I briefly mentioned how ChatGPT would expand and vary user-supplied image generation prompts before feeding them to DALL-E 3. This allowed for levels of context not possible in standalone generation workflows &ndash; the language model could and would use information from your chat history to generate its prompts. So you could say things like, &ldquo;Generate an image of a man named Bob with a curled moustache and a bowler hat&rdquo;, followed by, &ldquo;Now show Bob with his pal Charlie, who has a beard and a chef&rsquo;s hat&rdquo;, rather than having to write the prompt from scratch each time. Or you could say, &ldquo;Generate another one of those but leave out the bowler hat.&rdquo;</p>
<p>GPT4o&rsquo;s image generation compounds this advantage. Rather than being an external model tool that GPT knows how to interact with, it&rsquo;s a fundamental part of the model itself. Image generation benefits from the concepts understood by the text generation part of the model.</p>
<p>But that&rsquo;s not all! The title of this post references the fundamental difference between this image generation technique and the open and proprietary image models that have dominated the scene since DALL-E 2 &ndash; pixel space vs latent space. While DALL-E 2, Midjourney, Stable Diffusion, and most other image models use diffusion to generate images, this new model is <em>autoregressive</em>. This is actually an older approach to image generation, harking back to <a href="https://openai.com/index/dall-e/">DALL-E 1</a>, and a standard approach to text generation.</p>
<p>Diffusion models create images by starting with a canvas of random noise and repeatedly <em>denoising</em> it based on a text prompt. This means that the whole image is drawn at once, starting with blurry shapes that define the composition and progressing toward fine detail. This image from my <a href="/2022/08/31/stable-diffusion/">first Stable Diffusion</a> post gives an idea:</p>
<p><figure>
  
  <img src="/content/images/2022/08/samplers.jpeg" alt="" loading="lazy"/>
  
  <figcaption>
    <p></p>
  </figcaption>
</figure>
</p>
<p>In contrast, autoregressive models generate images in batches of pixels, generally moving from the top-left to the bottom-right. Each patch is predicted with the context of the previous patches, just like how text generation works. You can see this at work in how gpt4o slowly draws images from top to bottom (occasionally getting stuck partway due to rate limits or emergent content violations).</p>
<p><figure>
  
  <img src="/content/images/2025/03/4o-partial.jpg" alt="" loading="lazy"/>
  
  <figcaption>
    <p></p>
  </figcaption>
</figure>
</p>
<p>What&rsquo;s also interesting about this image is that the unrendered part looks a little like a very early-stage diffusion generation. There&rsquo;s <a href="https://x.com/sainingxie/status/1904643929724645453">some speculation</a> that 4o uses a combination of an autoregressive model and diffusion. This may explain why edited images always contain minor differences beyond the intended edit(s), as shown in a couple of outputs above.</p>
<p><figure>
  
  <img src="/content/images/2025/03/Gm5-37ybYAQ_mAX.jpeg" alt="Image generated by gpt4o." loading="lazy"/>
  
  <figcaption>
    <p><a href="https://openai.com/index/introducing-4o-image-generation/">Image generated by gpt4o</a>.</p>
  </figcaption>
</figure>
</p>
<p>OpenAI&rsquo;s stuff is all proprietary and top secret for reasons of Safety™, so we don&rsquo;t know exactly what they&rsquo;re doing here, or exactly how they&rsquo;ve made the autoregressive approach totally leapfrog diffusion for image generation.<sup id="fnref:2"><a href="https://davidyat.es/2025/03/30/pixel-space/#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> We can speculate that this more sequential approach leads to 4o&rsquo;s greater image coherence and relative lack of weird artefacts, extra limbs, and other problems that plague diffusion models. Or perhaps the output quality is more down to gains in prompt understanding brought about by the model&rsquo;s multimodal nature. Notably, though, Gemini 2.5 and Grok 3 are <em>also</em> multimodal models with autoregressive image generation but do not achieve nearly the same output quality. Perhaps this comes down to model size or training data quality.</p>
<p><figure>
  
  <img src="/content/images/2025/03/gemini-critique.png" alt="Jealous, Gemini?" loading="lazy"/>
  
  <figcaption>
    <p>Jealous, Gemini?</p>
  </figcaption>
</figure>
</p>
<p>Whatever the case, the results speak for themselves. Workflows that previously required an SSD full of LoRAs, monstrous ComfyUI workflows, meticulous inpainting, and endless re-de-noising can now be accomplished in a chat. There is probably still some utility to these and other diffusion workflows &ndash; some parts of the robot comic above could benefit from inpainting, for example. And until 4o-level models become more widely accessible, diffusion remains the state of the art for local generation. Perhaps until Deepseek releases a new iteration of the <a href="https://github.com/deepseek-ai/Janus">Janus series</a>.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>You can also <a href="https://x.com/davidyat_es/status/1906080356253810876">make Studio Ghibli scenes in the style of real photographs</a>.&#160;<a href="https://davidyat.es/2025/03/30/pixel-space/#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Amusingly, this is happening at the same time as <a href="https://arstechnica.com/ai/2025/02/new-ai-text-diffusion-models-break-speed-barriers-by-pulling-words-from-noise/">researchers are looking into generating text with diffusion rather than autoregression</a>, citing significant increases in generation speed.&#160;<a href="https://davidyat.es/2025/03/30/pixel-space/#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

        <p><a href="mailto:d@vidyat.es?subject=RE: Adventures%20in%20pixel%20space">Reply via email</a></p>
        ]]></content:encoded></item><item><title>Advent of Code 2024: Days 11–15</title><link>https://davidyat.es/2025/01/11/aoc-2024-part3/</link><pubDate>Sat, 11 Jan 2025 06:13:44 +0200</pubDate><author>David Yates</author><guid>https://davidyat.es/2025/01/11/aoc-2024-part3/</guid><content:encoded><![CDATA[
        <img src="https://davidyat.es/content/images/2025/01/aoc2024-p3.jpg" class="post-img" />
        <p>Advent of Code is a yearly programming event. For the 2024 edition, I decided to complete each challenge in a different language.  After trying a lot of different language paradigms over the <a href="/2024/12/16/aoc-2024-part1/">last</a> <a href="/2024/12/23/aoc-2024-part2/">two</a> parts of this series, from rules-based to functional to logic to array, this next part represents something of a comedown. All of the languages I used for these five days were some flavour of imperative, and most were languages I&rsquo;ve used before.</p>
<p>2D grid puzzles are a recurring theme this year, and I took the opportunity to use some gamedev frameworks for a couple of the challenges below.</p>
<nav id="TableOfContents">
  <ul>
    <li><a href="#day-11-rust">Day 11: Rust</a></li>
    <li><a href="#day-12-c">Day 12: C</a></li>
    <li><a href="#day-13-octave">Day 13: Octave</a></li>
    <li><a href="#day-14-lua--love2d">Day 14: Lua &amp; Love2D</a></li>
    <li><a href="#day-15-javascript--kaplay">Day 15: JavaScript &amp; KaPlay</a></li>
  </ul>
</nav>

<h1 id="day-11-rust">
  <a class="heading-anchor" href="#day-11-rust">#</a>
  Day 11: Rust
</h1>
<p><a href="https://adventofcode.com/2024/day/11"><strong>Challenge</strong></a>: iterate over a list according to a set of rules and see how long it gets.</p>
<p>This was a deceptively simple challenge. You&rsquo;re given a starting list containing two integers and a set of three rules for producing a new list. Two of the rules increase the integer, and the other rule splits it into two elements.</p>
<p>I&rsquo;ve been using Rust quite a bit lately for non-AoC reasons, so I had to fit it in somewhere and this seemed like as good a place as any. The first part of the challenge was a simple matter of implementing the rules as described, which Rust&rsquo;s <code>match</code> statement was well-suited for.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="n">_</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="mi">0</span><span class="o">..</span><span class="n">blinks</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">new_stones</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">Vec</span>::<span class="n">new</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">for</span><span class="w"> </span><span class="n">stone</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">stones</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">match</span><span class="w"> </span><span class="n">stone</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="c1">// 0 -&gt; 1
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="mi">0</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="n">new_stones</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="c1">// even-length number -&gt; split in half
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">n</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">n</span><span class="p">.</span><span class="n">to_string</span><span class="p">().</span><span class="n">len</span><span class="p">()</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="kd">let</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">stone</span><span class="p">.</span><span class="n">to_string</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="kd">let</span><span class="w"> </span><span class="p">(</span><span class="n">first</span><span class="p">,</span><span class="w"> </span><span class="n">second</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">s</span><span class="p">.</span><span class="n">split_at</span><span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="n">len</span><span class="p">()</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mi">2</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="n">new_stones</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">first</span><span class="p">.</span><span class="n">parse</span>::<span class="o">&lt;</span><span class="kt">u64</span><span class="o">&gt;</span><span class="p">().</span><span class="n">unwrap</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="n">new_stones</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">second</span><span class="p">.</span><span class="n">parse</span>::<span class="o">&lt;</span><span class="kt">u64</span><span class="o">&gt;</span><span class="p">().</span><span class="n">unwrap</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="c1">// all else -&gt; multiply by 2024
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">_</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="n">new_stones</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">stone</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">2024</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">stones</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">new_stones</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="fm">println!</span><span class="p">(</span><span class="s">&#34;</span><span class="si">{:?}</span><span class="s"> stones&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">stones</span><span class="p">.</span><span class="n">len</span><span class="p">());</span><span class="w">
</span></span></span></code></pre></div><p><a href="https://github.com/dmyates/advent-of-code-solutions/blob/master/2024/11-%20Rust/11-1.rs"><strong>Full code for part 1 on GitHub.</strong></a></p>
<p>The first part was simple and the second part was deceptive. All that was required in the second part was to up the iteration count from 25 to 75. However, while the code produced an answer for 25 iterations pretty much instantly, it was not up to task of doing 75 within any reasonable amount of memory or time.</p>
<p>The example input showed that some of the elements repeated, so the first change I made was to create a <code>cache</code> hash map that would store the next iteration for each number after calculating it.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">cache</span>: <span class="nc">HashMap</span><span class="o">&lt;</span><span class="kt">u64</span><span class="p">,</span><span class="w"> </span><span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">u64</span><span class="o">&gt;&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">HashMap</span>::<span class="n">new</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="n">_</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="mi">0</span><span class="o">..</span><span class="n">blinks</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">new_stones</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">Vec</span>::<span class="n">new</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">for</span><span class="w"> </span><span class="n">stone</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">stones</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">added</span>: <span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">u64</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">Vec</span>::<span class="n">new</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">match</span><span class="w"> </span><span class="n">stone</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="c1">// get from cache
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">n</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">cache</span><span class="p">.</span><span class="n">contains_key</span><span class="p">(</span><span class="o">&amp;</span><span class="n">n</span><span class="p">)</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="n">added</span><span class="p">.</span><span class="n">extend</span><span class="p">(</span><span class="n">cache</span><span class="p">[</span><span class="o">&amp;</span><span class="n">n</span><span class="p">].</span><span class="n">clone</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="c1">// 0 -&gt; 1
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="mi">0</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="n">added</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="c1">// even-length number -&gt; split in half
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">n</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">n</span><span class="p">.</span><span class="n">to_string</span><span class="p">().</span><span class="n">len</span><span class="p">()</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="kd">let</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">stone</span><span class="p">.</span><span class="n">to_string</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="kd">let</span><span class="w"> </span><span class="p">(</span><span class="n">first</span><span class="p">,</span><span class="w"> </span><span class="n">second</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">s</span><span class="p">.</span><span class="n">split_at</span><span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="n">len</span><span class="p">()</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mi">2</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="n">added</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">first</span><span class="p">.</span><span class="n">parse</span>::<span class="o">&lt;</span><span class="kt">u64</span><span class="o">&gt;</span><span class="p">().</span><span class="n">unwrap</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="n">added</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">second</span><span class="p">.</span><span class="n">parse</span>::<span class="o">&lt;</span><span class="kt">u64</span><span class="o">&gt;</span><span class="p">().</span><span class="n">unwrap</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="c1">// all else -&gt; multiply by 2024
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">_</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="n">added</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">stone</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">2024</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// add to stones
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">for</span><span class="w"> </span><span class="o">&amp;</span><span class="n">n</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="o">&amp;</span><span class="n">added</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="o">*</span><span class="n">stones</span><span class="p">.</span><span class="n">entry</span><span class="p">(</span><span class="n">n</span><span class="p">).</span><span class="n">or_insert</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="n">count</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// add to cache
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="o">!</span><span class="n">cache</span><span class="p">.</span><span class="n">contains_key</span><span class="p">(</span><span class="o">&amp;</span><span class="n">stone</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">cache</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span><span class="n">stone</span><span class="p">,</span><span class="w"> </span><span class="n">added</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="fm">println!</span><span class="p">(</span><span class="s">&#34;</span><span class="si">{:?}</span><span class="s"> stones&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">stones</span><span class="p">.</span><span class="n">len</span><span class="p">());</span><span class="w">
</span></span></span></code></pre></div><p>This didn&rsquo;t help all that much. I thought about the problem some more, and realise that I would still end up building a really long list. Seeing as there were going to be a bunch of repeated elements, to the point where I was caching them, wouldn&rsquo;t it make more sense to store the list as a hash map of unique elements with their counts? The order of the elements didn&rsquo;t matter for either the iterations or the solution. So that&rsquo;s what I did.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">stone</span><span class="p">,</span><span class="w"> </span><span class="n">count</span><span class="p">)</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">old_stones</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">added</span>: <span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">u64</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">Vec</span>::<span class="n">new</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">match</span><span class="w"> </span><span class="n">stone</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="c1">// get from cache
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">n</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">cache</span><span class="p">.</span><span class="n">contains_key</span><span class="p">(</span><span class="o">&amp;</span><span class="n">n</span><span class="p">)</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="n">added</span><span class="p">.</span><span class="n">extend</span><span class="p">(</span><span class="n">cache</span><span class="p">[</span><span class="o">&amp;</span><span class="n">n</span><span class="p">].</span><span class="n">clone</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="c1">// 0 -&gt; 1
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="mi">0</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="n">added</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="c1">// even-length number -&gt; split in half
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">n</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">n</span><span class="p">.</span><span class="n">to_string</span><span class="p">().</span><span class="n">len</span><span class="p">()</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="kd">let</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">stone</span><span class="p">.</span><span class="n">to_string</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="kd">let</span><span class="w"> </span><span class="p">(</span><span class="n">first</span><span class="p">,</span><span class="w"> </span><span class="n">second</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">s</span><span class="p">.</span><span class="n">split_at</span><span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="n">len</span><span class="p">()</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mi">2</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="n">added</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">first</span><span class="p">.</span><span class="n">parse</span>::<span class="o">&lt;</span><span class="kt">u64</span><span class="o">&gt;</span><span class="p">().</span><span class="n">unwrap</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="n">added</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">second</span><span class="p">.</span><span class="n">parse</span>::<span class="o">&lt;</span><span class="kt">u64</span><span class="o">&gt;</span><span class="p">().</span><span class="n">unwrap</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="c1">// all else -&gt; multiply by 2024
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">_</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="n">added</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">stone</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">2024</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// add to stones
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">for</span><span class="w"> </span><span class="o">&amp;</span><span class="n">n</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="o">&amp;</span><span class="n">added</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="o">*</span><span class="n">stones</span><span class="p">.</span><span class="n">entry</span><span class="p">(</span><span class="n">n</span><span class="p">).</span><span class="n">or_insert</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="n">count</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// add to cache
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="o">!</span><span class="n">cache</span><span class="p">.</span><span class="n">contains_key</span><span class="p">(</span><span class="o">&amp;</span><span class="n">stone</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">cache</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span><span class="n">stone</span><span class="p">,</span><span class="w"> </span><span class="n">added</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="fm">println!</span><span class="p">(</span><span class="s">&#34;</span><span class="si">{:?}</span><span class="s"> stones&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">stones</span><span class="p">.</span><span class="n">values</span><span class="p">().</span><span class="n">sum</span>::<span class="o">&lt;</span><span class="kt">u64</span><span class="o">&gt;</span><span class="p">());</span><span class="w">
</span></span></span></code></pre></div><p>This implementation produced a result for 75 iterations pretty much instantly.</p>
<p><a href="https://github.com/dmyates/advent-of-code-solutions/blob/master/2024/11-%20Rust/11-2.rs"><strong>Full code for part 2 on GitHub.</strong></a></p>
<p><strong>Closing thoughts:</strong> The first time I looked at Rust, I came away scratching my head at all the crazy symbols and blocks everywhere. &ldquo;What the hell is <code>&amp;mut</code>?&rdquo; I asked. Having now spent some time going through <a href="https://doc.rust-lang.org/book/">the official tutorial</a>, working with some Rust codebases, and asking Claude about the weirder-looking bits of syntax I encounter, I&rsquo;ve come to a greater appreciation for the language. I&rsquo;ve always been a big switch-case appreciator, so Rust&rsquo;s <code>match</code> is something I like a lot. Having such expressive syntax and functional programming built-ins like <code>map</code> and <code>fold</code> in a fast, compiled language feels like cheating.</p>
<p>Rather than expecting the programmer to manage memory manually like in C or using a garbage collector like in a high-level language, Rust uses a concept called <a href="https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html"><em>ownership</em></a> to prevent memory bugs. Properly internalised, this should provide the speed of C with the safety of Java. My mental conception of it is still a bit fuzzy, so dealing with ownership largely consisted of asking Claude to do the correct referencing and dereferencing in my code.</p>
<h1 id="day-12-c">
  <a class="heading-anchor" href="#day-12-c">#</a>
  Day 12: C
</h1>
<p><a href="https://adventofcode.com/2024/day/12"><strong>Challenge</strong></a>: figure out the areas and perimeters of interlocking fields in a 2D grid.</p>
<p>After the previous day&rsquo;s challenge, it was time to shake off all that rust (memory safety) and work in venerable old C.</p>
<p>Reading in the input file (a grid of characters) was a lot more manual than in higher-level languages, but it&rsquo;s good to be reminded of such things now and then.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl">    <span class="c1">// Read garden map
</span></span></span><span class="line"><span class="cl">    <span class="n">rows</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">cols</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">char</span> <span class="n">line</span><span class="p">[</span><span class="n">MAX_COLS</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="p">(</span><span class="nf">fgets</span><span class="p">(</span><span class="n">line</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">line</span><span class="p">),</span> <span class="n">file</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">rows</span> <span class="o">&lt;</span> <span class="n">MAX_ROWS</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// Remove newline if present
</span></span></span><span class="line"><span class="cl">        <span class="kt">size_t</span> <span class="n">len</span> <span class="o">=</span> <span class="nf">strlen</span><span class="p">(</span><span class="n">line</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">len</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">line</span><span class="p">[</span><span class="n">len</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="o">==</span> <span class="sc">&#39;\n&#39;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">line</span><span class="p">[</span><span class="n">len</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="sc">&#39;\0&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">len</span><span class="o">--</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="c1">// Skip empty lines
</span></span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">len</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="c1">// Set cols based on first line
</span></span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">rows</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">cols</span> <span class="o">=</span> <span class="n">len</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">len</span> <span class="o">!=</span> <span class="n">cols</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;Inconsistent line length at row %d</span><span class="se">\n</span><span class="s">. Expected %d, got %d&#34;</span><span class="p">,</span> <span class="n">rows</span><span class="p">,</span> <span class="n">cols</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="nf">fclose</span><span class="p">(</span><span class="n">file</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="p">(</span><span class="kt">size_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">len</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">garden</span><span class="p">[</span><span class="n">rows</span><span class="p">][</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">line</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="n">garden</span><span class="p">[</span><span class="n">rows</span><span class="p">][</span><span class="n">len</span><span class="p">]</span> <span class="o">=</span> <span class="sc">&#39;\0&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">rows</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">fclose</span><span class="p">(</span><span class="n">file</span><span class="p">);</span>
</span></span></code></pre></div><p>To calculate the areas and perimeters of interlocking fields, I did a depth-first search on the grid.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// DFS to calculate area and perimeter of a region
</span></span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="nf">calculate</span><span class="p">(</span><span class="kt">int</span> <span class="n">r</span><span class="p">,</span> <span class="kt">int</span> <span class="n">c</span><span class="p">,</span> <span class="kt">char</span> <span class="n">plant_type</span><span class="p">,</span> <span class="kt">int</span> <span class="o">*</span><span class="n">area</span><span class="p">,</span> <span class="kt">int</span> <span class="o">*</span><span class="n">perimeter</span><span class="p">,</span> <span class="kt">int</span> <span class="o">*</span><span class="n">corners</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">visited</span><span class="p">[</span><span class="n">r</span><span class="p">][</span><span class="n">c</span><span class="p">]</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="o">*</span><span class="n">area</span><span class="p">)</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1">// Count perimeter edges
</span></span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">4</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">int</span> <span class="n">nr</span> <span class="o">=</span> <span class="n">r</span> <span class="o">+</span> <span class="n">dr</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="kt">int</span> <span class="n">nc</span> <span class="o">=</span> <span class="n">c</span> <span class="o">+</span> <span class="n">dc</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">nr</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="o">||</span> <span class="n">nr</span> <span class="o">&gt;=</span> <span class="n">rows</span> <span class="o">||</span> <span class="n">nc</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="o">||</span> <span class="n">nc</span> <span class="o">&gt;=</span> <span class="n">cols</span> <span class="o">||</span> <span class="n">garden</span><span class="p">[</span><span class="n">nr</span><span class="p">][</span><span class="n">nc</span><span class="p">]</span> <span class="o">!=</span> <span class="n">plant_type</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="p">(</span><span class="o">*</span><span class="n">perimeter</span><span class="p">)</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">visited</span><span class="p">[</span><span class="n">nr</span><span class="p">][</span><span class="n">nc</span><span class="p">])</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nf">calculate</span><span class="p">(</span><span class="n">nr</span><span class="p">,</span> <span class="n">nc</span><span class="p">,</span> <span class="n">plant_type</span><span class="p">,</span> <span class="n">area</span><span class="p">,</span> <span class="n">perimeter</span><span class="p">,</span> <span class="n">corners</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The final result was the sum of the area times the perimeter of each region. So far, so simple.</p>
<p>For the second part of the challenge, it was necessary to figure out how many sides each region had.</p>
<pre tabindex="0"><code>AAA
AAA --&gt; four sides
AAA

BBB
BBBB --&gt; six sides
BBBB
</code></pre><p>Counting sides seemed kind of tricky. Counting corners was a lot easier, and would produce the same result, so I did that. The following function checks for corners by looking at the contents of the three cells around the target cell in each of the four cardinal directions.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// Count corners at a given cell
</span></span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">count_corners</span><span class="p">(</span><span class="kt">int</span> <span class="n">r</span><span class="p">,</span> <span class="kt">int</span> <span class="n">c</span><span class="p">,</span> <span class="kt">char</span> <span class="n">plant_type</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">corners</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Check if we have a corner in each of the 4 quadrants around this cell
</span></span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">4</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// Get coordinates for the three cells we need to check
</span></span></span><span class="line"><span class="cl">        <span class="kt">int</span> <span class="n">r1</span> <span class="o">=</span> <span class="n">r</span> <span class="o">+</span> <span class="n">dr</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="kt">int</span> <span class="n">c1</span> <span class="o">=</span> <span class="n">c</span> <span class="o">+</span> <span class="n">dc</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="kt">int</span> <span class="n">r2</span> <span class="o">=</span> <span class="n">r</span> <span class="o">+</span> <span class="n">dr</span><span class="p">[(</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span><span class="o">%</span><span class="mi">4</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="kt">int</span> <span class="n">c2</span> <span class="o">=</span> <span class="n">c</span> <span class="o">+</span> <span class="n">dc</span><span class="p">[(</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span><span class="o">%</span><span class="mi">4</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="kt">int</span> <span class="n">rd</span> <span class="o">=</span> <span class="n">r</span> <span class="o">+</span> <span class="n">dr</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">+</span> <span class="n">dr</span><span class="p">[(</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span><span class="o">%</span><span class="mi">4</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="kt">int</span> <span class="n">cd</span> <span class="o">=</span> <span class="n">c</span> <span class="o">+</span> <span class="n">dc</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">+</span> <span class="n">dc</span><span class="p">[(</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span><span class="o">%</span><span class="mi">4</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Inner corner if both adjacent cells are the same type
</span></span></span><span class="line"><span class="cl">        <span class="c1">// and the diagonal is different
</span></span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">r1</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">r1</span> <span class="o">&lt;</span> <span class="n">rows</span> <span class="o">&amp;&amp;</span> <span class="n">c1</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">c1</span> <span class="o">&lt;</span> <span class="n">cols</span> <span class="o">&amp;&amp;</span> 
</span></span><span class="line"><span class="cl">            <span class="n">r2</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">r2</span> <span class="o">&lt;</span> <span class="n">rows</span> <span class="o">&amp;&amp;</span> <span class="n">c2</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">c2</span> <span class="o">&lt;</span> <span class="n">cols</span> <span class="o">&amp;&amp;</span> 
</span></span><span class="line"><span class="cl">            <span class="n">rd</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">rd</span> <span class="o">&lt;</span> <span class="n">rows</span> <span class="o">&amp;&amp;</span> <span class="n">cd</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">cd</span> <span class="o">&lt;</span> <span class="n">cols</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">            <span class="n">garden</span><span class="p">[</span><span class="n">r1</span><span class="p">][</span><span class="n">c1</span><span class="p">]</span> <span class="o">==</span> <span class="n">plant_type</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">            <span class="n">garden</span><span class="p">[</span><span class="n">r2</span><span class="p">][</span><span class="n">c2</span><span class="p">]</span> <span class="o">==</span> <span class="n">plant_type</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">            <span class="n">garden</span><span class="p">[</span><span class="n">rd</span><span class="p">][</span><span class="n">cd</span><span class="p">]</span> <span class="o">!=</span> <span class="n">plant_type</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">corners</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="c1">// Outer corner if either adjacent cell is out of bounds or different type
</span></span></span><span class="line"><span class="cl">        <span class="kt">bool</span> <span class="n">cell1_different</span> <span class="o">=</span> <span class="p">(</span><span class="n">r1</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="o">||</span> <span class="n">r1</span> <span class="o">&gt;=</span> <span class="n">rows</span> <span class="o">||</span>
</span></span><span class="line"><span class="cl">            <span class="n">c1</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="o">||</span> <span class="n">c1</span> <span class="o">&gt;=</span> <span class="n">cols</span> <span class="o">||</span> <span class="n">garden</span><span class="p">[</span><span class="n">r1</span><span class="p">][</span><span class="n">c1</span><span class="p">]</span> <span class="o">!=</span> <span class="n">plant_type</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="kt">bool</span> <span class="n">cell2_different</span> <span class="o">=</span> <span class="p">(</span><span class="n">r2</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="o">||</span> <span class="n">r2</span> <span class="o">&gt;=</span> <span class="n">rows</span> <span class="o">||</span>
</span></span><span class="line"><span class="cl">            <span class="n">c2</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="o">||</span> <span class="n">c2</span> <span class="o">&gt;=</span> <span class="n">cols</span> <span class="o">||</span> <span class="n">garden</span><span class="p">[</span><span class="n">r2</span><span class="p">][</span><span class="n">c2</span><span class="p">]</span> <span class="o">!=</span> <span class="n">plant_type</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">cell1_different</span> <span class="o">&amp;&amp;</span> <span class="n">cell2_different</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">corners</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">corners</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p><a href="https://github.com/dmyates/advent-of-code-solutions/blob/master/2024/12%20-%20C/12.c"><strong>Full code on GitHub.</strong></a></p>
<p><strong>Closing thoughts</strong>: The only times when I really felt like I was writing in C was when I was checking for null bytes at the end of strings and passing pointers around. When working on the core of the puzzle, depth-first search and 2D grid navigation, I felt like I could have been working in just about any language. Which I suppose is further evidence of C&rsquo;s long shadow.</p>
<h1 id="day-13-octave">
  <a class="heading-anchor" href="#day-13-octave">#</a>
  Day 13: Octave
</h1>
<p><a href="https://adventofcode.com/2024/day/13"><strong>Challenge</strong></a>: find the right moves to win the prizes in a bunch of claw machines.</p>
<p>For this challenge, each claw machine has a single prize and two buttons. Each button moves the claw some distance along the X and Y axes. Button A costs three tokens and B costs one. You need to figure out the right number of times to press each button to reach the prize with minimum expenditure. Some of the machines are broken and have no solution.</p>
<p>After a little thinking, this challenge reveals itself to be a highschool algebra problem: each machine is a set of simultaneous equations. So this:</p>
<pre tabindex="0"><code>Button A: X+94, Y+34
Button B: X+22, Y+67
Prize: X=8400, Y=5400
</code></pre><p>Becomes this:</p>
<pre tabindex="0"><code>94a + 22b = 8400
34a + 67b = 5400
</code></pre><p>It therefore seemed appropriate to use a maths language. Matlab and Mathematica come most readily to mind, but both are proprietary and expensive, so I went with <a href="https://octave.org/">GNU Octave</a>, a free and open-source mathematical language designed to be very similar to Matlab.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-octave" data-lang="octave"><span class="line"><span class="cl"><span class="n">pkg</span> <span class="nb">load</span> <span class="n">symbolic</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c">% Open the file and read the entire content</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="n">fileID</span> <span class="p">=</span> <span class="nb">fopen</span><span class="p">(</span><span class="s">&#39;input.txt&#39;</span><span class="p">,</span> <span class="s">&#39;r&#39;</span><span class="p">);</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="n">fileContent</span> <span class="p">=</span> <span class="nb">fread</span><span class="p">(</span><span class="n">fileID</span><span class="p">,</span> <span class="s">&#39;*char&#39;</span><span class="p">)</span><span class="o">&#39;</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c">% ^ unmatched &#39; is transpose -- necessary to read file as lines rather than columns</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="nb">fclose</span><span class="p">(</span><span class="n">fileID</span><span class="p">);</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c">% Split the content into blocks</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="n">blocks</span> <span class="p">=</span> <span class="nb">strsplit</span><span class="p">(</span><span class="n">fileContent</span><span class="p">,</span> <span class="s">&#39;\n\n&#39;</span><span class="p">);</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c">% Initialize sums for A and B</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="n">sumA</span> <span class="p">=</span> <span class="mi">0</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="n">sumB</span> <span class="p">=</span> <span class="mi">0</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c">% Process each block</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">i</span> <span class="p">=</span> <span class="mi">1</span><span class="p">:</span><span class="nb">length</span><span class="p">(</span><span class="n">blocks</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="n">lines</span> <span class="p">=</span> <span class="nb">strsplit</span><span class="p">(</span><span class="nb">strtrim</span><span class="p">(</span><span class="n">blocks</span><span class="p">{</span><span class="n">i</span><span class="p">}),</span> <span class="s">&#39;\n&#39;</span><span class="p">);</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="c">% Extract numbers from the strings</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="n">buttonA_nums</span> <span class="p">=</span> <span class="nb">sscanf</span><span class="p">(</span><span class="n">lines</span><span class="p">{</span><span class="mi">1</span><span class="p">},</span> <span class="s">&#39;Button A: X+%d, Y+%d&#39;</span><span class="p">);</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="n">buttonB_nums</span> <span class="p">=</span> <span class="nb">sscanf</span><span class="p">(</span><span class="n">lines</span><span class="p">{</span><span class="mi">2</span><span class="p">},</span> <span class="s">&#39;Button B: X+%d, Y+%d&#39;</span><span class="p">);</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="n">prize_nums</span> <span class="p">=</span> <span class="nb">sscanf</span><span class="p">(</span><span class="n">lines</span><span class="p">{</span><span class="mi">3</span><span class="p">},</span> <span class="s">&#39;Prize: X=%d, Y=%d&#39;</span><span class="p">);</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="c">% Build the equations</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="n">syms</span> <span class="n">A</span> <span class="n">B</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="n">eq1</span> <span class="p">=</span> <span class="n">A</span><span class="o">*</span><span class="n">buttonA_nums</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="n">B</span><span class="o">*</span><span class="n">buttonB_nums</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="o">==</span> <span class="n">prize_nums</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="n">eq2</span> <span class="p">=</span> <span class="n">A</span><span class="o">*</span><span class="n">buttonA_nums</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> <span class="o">+</span> <span class="n">B</span><span class="o">*</span><span class="n">buttonB_nums</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> <span class="o">==</span> <span class="n">prize_nums</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="c">% Solve the equations (hardest part of the challenge in one built-in function)</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="n">sol</span> <span class="p">=</span> <span class="n">solve</span><span class="p">([</span><span class="n">eq1</span><span class="p">,</span> <span class="n">eq2</span><span class="p">],</span> <span class="p">[</span><span class="n">A</span><span class="p">,</span> <span class="n">B</span><span class="p">]);</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="c">% Discard non-integer solutions</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nb">mod</span><span class="p">(</span><span class="n">sol</span><span class="p">.</span><span class="n">A</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="err">
</span></span></span><span class="line"><span class="cl">        <span class="n">sumA</span> <span class="p">=</span> <span class="n">sumA</span> <span class="o">+</span> <span class="nb">double</span><span class="p">(</span><span class="n">sol</span><span class="p">.</span><span class="n">A</span><span class="p">);</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="k">end</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nb">mod</span><span class="p">(</span><span class="n">sol</span><span class="p">.</span><span class="n">B</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="err">
</span></span></span><span class="line"><span class="cl">        <span class="n">sumB</span> <span class="p">=</span> <span class="n">sumB</span> <span class="o">+</span> <span class="nb">double</span><span class="p">(</span><span class="n">sol</span><span class="p">.</span><span class="n">B</span><span class="p">);</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="k">end</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">end</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c">% Display the summed results</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="nb">disp</span><span class="p">(</span><span class="n">sumA</span><span class="o">*</span><span class="mi">3</span> <span class="o">+</span> <span class="n">sumB</span><span class="p">);</span><span class="err">
</span></span></span></code></pre></div><p><a href="https://github.com/dmyates/advent-of-code-solutions/blob/master/2024/13%20-%20Octave/13-1.m"><strong>Full code for part 1 on GitHub.</strong></a></p>
<p>The second part of the challenge, explicitly designed for people like me, required the addition of 10 000 000 000 000 (ten trillion) to each prize co-ordinate. This was enough to make all co-ordinates significantly larger than the largest 32-bit integer &ndash; Octave&rsquo;s default integer type. After converting all of my numbers to <code>int64</code>, I got a bunch of precision errors. After a lot of fiddling with stubbornly broken code, I took a step back and asked what other ways there might be to solve simultaneous equations.</p>
<p><a href="https://en.wikipedia.org/wiki/Cramer%27s_rule">Cramer&rsquo;s rule</a> was not something I remembered from highschool algebra, but it was pretty simple to implement.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-octave" data-lang="octave"><span class="line"><span class="cl"><span class="c">% Open the file and read the entire content</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="n">fileID</span> <span class="p">=</span> <span class="nb">fopen</span><span class="p">(</span><span class="s">&#39;input.txt&#39;</span><span class="p">,</span> <span class="s">&#39;r&#39;</span><span class="p">);</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="n">fileContent</span> <span class="p">=</span> <span class="nb">fread</span><span class="p">(</span><span class="n">fileID</span><span class="p">,</span> <span class="s">&#39;*char&#39;</span><span class="p">)</span><span class="o">&#39;</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c">% ^ unmatched &#39; is transpose -- necessary to read file as lines rather than columns</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="nb">fclose</span><span class="p">(</span><span class="n">fileID</span><span class="p">);</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c">% Split the content into blocks</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="n">blocks</span> <span class="p">=</span> <span class="nb">strsplit</span><span class="p">(</span><span class="n">fileContent</span><span class="p">,</span> <span class="s">&#39;\n\n&#39;</span><span class="p">);</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c">% Initialize sums for A and B</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="n">sumA</span> <span class="p">=</span> <span class="n">int64</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="n">sumB</span> <span class="p">=</span> <span class="n">int64</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c">% Process each block</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">i</span> <span class="p">=</span> <span class="mi">1</span><span class="p">:</span><span class="nb">length</span><span class="p">(</span><span class="n">blocks</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="n">lines</span> <span class="p">=</span> <span class="nb">strsplit</span><span class="p">(</span><span class="nb">strtrim</span><span class="p">(</span><span class="n">blocks</span><span class="p">{</span><span class="n">i</span><span class="p">}),</span> <span class="s">&#39;\n&#39;</span><span class="p">);</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="c">% Extract numbers from the strings and convert to int64</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="n">buttonA_nums</span> <span class="p">=</span> <span class="n">int64</span><span class="p">(</span><span class="nb">sscanf</span><span class="p">(</span><span class="n">lines</span><span class="p">{</span><span class="mi">1</span><span class="p">},</span> <span class="s">&#39;Button A: X+%d, Y+%d&#39;</span><span class="p">));</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="n">buttonB_nums</span> <span class="p">=</span> <span class="n">int64</span><span class="p">(</span><span class="nb">sscanf</span><span class="p">(</span><span class="n">lines</span><span class="p">{</span><span class="mi">2</span><span class="p">},</span> <span class="s">&#39;Button B: X+%d, Y+%d&#39;</span><span class="p">));</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="n">prize_nums</span> <span class="p">=</span> <span class="n">int64</span><span class="p">(</span><span class="nb">sscanf</span><span class="p">(</span><span class="n">lines</span><span class="p">{</span><span class="mi">3</span><span class="p">},</span> <span class="s">&#39;Prize: X=%d, Y=%d&#39;</span><span class="p">));</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="c">% Add the large number</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="n">prize_nums</span> <span class="p">=</span> <span class="n">prize_nums</span> <span class="o">+</span> <span class="n">int64</span><span class="p">(</span><span class="mi">10000000000000</span><span class="p">);</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="c">% Build the Cramer equations</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="n">a1</span> <span class="p">=</span> <span class="n">buttonA_nums</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span> <span class="n">a2</span> <span class="p">=</span> <span class="n">buttonA_nums</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="n">b1</span> <span class="p">=</span> <span class="n">buttonB_nums</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span> <span class="n">b2</span> <span class="p">=</span> <span class="n">buttonB_nums</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="n">c1</span> <span class="p">=</span> <span class="n">prize_nums</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span> <span class="n">c2</span> <span class="p">=</span> <span class="n">prize_nums</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="c">% Calculate determinants</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="n">D</span> <span class="p">=</span> <span class="n">a1</span> <span class="o">*</span> <span class="n">b2</span> <span class="o">-</span> <span class="n">a2</span> <span class="o">*</span> <span class="n">b1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="n">Dx</span> <span class="p">=</span> <span class="n">c1</span> <span class="o">*</span> <span class="n">b2</span> <span class="o">-</span> <span class="n">c2</span> <span class="o">*</span> <span class="n">b1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="n">Dy</span> <span class="p">=</span> <span class="n">a1</span> <span class="o">*</span> <span class="n">c2</span> <span class="o">-</span> <span class="n">a2</span> <span class="o">*</span> <span class="n">c1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="c">% Discard non-integer solutions</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">D</span> <span class="o">~=</span> <span class="mi">0</span><span class="err">
</span></span></span><span class="line"><span class="cl">        <span class="n">x</span> <span class="p">=</span> <span class="n">Dx</span> <span class="o">/</span> <span class="n">D</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl">        <span class="n">y</span> <span class="p">=</span> <span class="n">Dy</span> <span class="o">/</span> <span class="n">D</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="nb">mod</span><span class="p">(</span><span class="n">Dx</span><span class="p">,</span> <span class="n">D</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="nb">mod</span><span class="p">(</span><span class="n">Dy</span><span class="p">,</span> <span class="n">D</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="err">
</span></span></span><span class="line"><span class="cl">            <span class="n">sumA</span> <span class="p">=</span> <span class="n">sumA</span> <span class="o">+</span> <span class="n">x</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl">            <span class="n">sumB</span> <span class="p">=</span> <span class="n">sumB</span> <span class="o">+</span> <span class="n">y</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl">        <span class="k">end</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="k">end</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">end</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c">% Display the summed results</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="n">result</span> <span class="p">=</span> <span class="n">sumA</span> <span class="o">*</span> <span class="n">int64</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span> <span class="o">+</span> <span class="n">sumB</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="nb">disp</span><span class="p">(</span><span class="n">result</span><span class="p">);</span><span class="err">
</span></span></span></code></pre></div><p><a href="https://github.com/dmyates/advent-of-code-solutions/blob/master/2024/13%20-%20Octave/13-2.m"><strong>Full code for part 2 on GitHub.</strong></a></p>
<p><strong>Closing thoughts:</strong> This was a place where the features of the language really came through for solving the challenge, to the point where the first part was really just a matter of string parsing and calling <code>solve</code>. I suppose the real trick was to figure out that you needed to use simultaneous equations.</p>
<p>I found Octave fairly simple to use, but in the usual course I would probably just do this sort of thing in Python. Octave&rsquo;s <code>symbolic</code> package, which I used for the first part of the challenge, is largely a wrapper for <a href="https://www.sympy.org/en/index.html">SymPy</a>.</p>
<h1 id="day-14-lua--love2d">
  <a class="heading-anchor" href="#day-14-lua--love2d">#</a>
  Day 14: Lua &amp; Love2D
</h1>
<p><a href="https://adventofcode.com/2024/day/14"><strong>Challenge</strong></a>: simulate guard robot movements.</p>
<p>Lua was one of the languages on my shortlist for this challenge. My main previous experience with it was writing <a href="https://www.overleaf.com/learn/latex/Articles/What%27s_in_a_Name%3A_A_Guide_to_the_Many_Flavours_of_TeX">LuaLaTeX in Overleaf</a> to create dynamic document templates &ndash; aside from that, I&rsquo;d occasionally tinkered with <a href="https://love2d.org/">Love2D</a>, a Lua-based gamedev framework. A challenge involving guard robots moving through a 2D grid seemed like a good opportunity to bring in not only Lua, but Love2D.</p>
<p>Implementing the guard simulation in Love2D was pretty straightforward, but to actually solve the challenge you need pretty discrete results &ndash; which robots are in which grid spaces at precise times. Love2D is geared to show fluid movement in real-time, and getting it to do otherwise made me feel like I was fighting with my code. So I retreated to a pure Lua solution based on the text grid.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="kd">local</span> <span class="kr">function</span> <span class="nf">moveGuards</span><span class="p">(</span><span class="n">guards</span><span class="p">,</span> <span class="n">mapWidth</span><span class="p">,</span> <span class="n">mapHeight</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="kr">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">guard</span> <span class="kr">in</span> <span class="n">ipairs</span><span class="p">(</span><span class="n">guards</span><span class="p">)</span> <span class="kr">do</span>
</span></span><span class="line"><span class="cl">        <span class="n">guard.x</span> <span class="o">=</span> <span class="n">guard.x</span> <span class="o">+</span> <span class="n">guard.vx</span>
</span></span><span class="line"><span class="cl">        <span class="n">guard.y</span> <span class="o">=</span> <span class="n">guard.y</span> <span class="o">+</span> <span class="n">guard.vy</span>
</span></span><span class="line"><span class="cl">        <span class="c1">-- wrap at the edges</span>
</span></span><span class="line"><span class="cl">        <span class="kr">if</span> <span class="n">guard.x</span> <span class="o">&lt;</span> <span class="mi">1</span> <span class="kr">then</span>
</span></span><span class="line"><span class="cl">            <span class="n">guard.x</span> <span class="o">=</span> <span class="n">guard.x</span> <span class="o">+</span> <span class="n">mapWidth</span>
</span></span><span class="line"><span class="cl">        <span class="kr">elseif</span> <span class="n">guard.x</span> <span class="o">&gt;</span> <span class="n">mapWidth</span> <span class="kr">then</span>
</span></span><span class="line"><span class="cl">            <span class="n">guard.x</span> <span class="o">=</span> <span class="n">guard.x</span> <span class="o">-</span> <span class="n">mapWidth</span>
</span></span><span class="line"><span class="cl">        <span class="kr">end</span>
</span></span><span class="line"><span class="cl">        <span class="kr">if</span> <span class="n">guard.y</span> <span class="o">&lt;</span> <span class="mi">1</span> <span class="kr">then</span>
</span></span><span class="line"><span class="cl">            <span class="n">guard.y</span> <span class="o">=</span> <span class="n">guard.y</span> <span class="o">+</span> <span class="n">mapHeight</span>
</span></span><span class="line"><span class="cl">        <span class="kr">elseif</span> <span class="n">guard.y</span> <span class="o">&gt;</span> <span class="n">mapHeight</span> <span class="kr">then</span>
</span></span><span class="line"><span class="cl">            <span class="n">guard.y</span> <span class="o">=</span> <span class="n">guard.y</span> <span class="o">-</span> <span class="n">mapHeight</span>
</span></span><span class="line"><span class="cl">        <span class="kr">end</span>
</span></span><span class="line"><span class="cl">    <span class="kr">end</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">local</span> <span class="kr">function</span> <span class="nf">calculateSafety</span><span class="p">(</span><span class="n">guards</span><span class="p">,</span> <span class="n">mapWidth</span><span class="p">,</span> <span class="n">mapHeight</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="kd">local</span> <span class="n">quadrants</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">local</span> <span class="n">centerX</span> <span class="o">=</span> <span class="n">math.ceil</span><span class="p">(</span><span class="n">mapWidth</span> <span class="o">/</span> <span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="kd">local</span> <span class="n">centerY</span> <span class="o">=</span> <span class="n">math.ceil</span><span class="p">(</span><span class="n">mapHeight</span> <span class="o">/</span> <span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">-- Count robots in each quadrant</span>
</span></span><span class="line"><span class="cl">    <span class="kr">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">guard</span> <span class="kr">in</span> <span class="n">ipairs</span><span class="p">(</span><span class="n">guards</span><span class="p">)</span> <span class="kr">do</span>
</span></span><span class="line"><span class="cl">        <span class="c1">-- Skip robots exactly on center lines</span>
</span></span><span class="line"><span class="cl">        <span class="kr">if</span> <span class="n">guard.x</span> <span class="o">==</span> <span class="n">centerX</span> <span class="ow">or</span> <span class="n">guard.y</span> <span class="o">==</span> <span class="n">centerY</span> <span class="kr">then</span>
</span></span><span class="line"><span class="cl">            <span class="kr">goto</span> <span class="nl">continue</span> <span class="c1">-- Lua has no continue statement</span>
</span></span><span class="line"><span class="cl">        <span class="kr">end</span>
</span></span><span class="line"><span class="cl">        <span class="c1">-- Count each quadrant</span>
</span></span><span class="line"><span class="cl">        <span class="kr">if</span> <span class="n">guard.y</span> <span class="o">&lt;</span> <span class="n">centerY</span> <span class="kr">then</span>
</span></span><span class="line"><span class="cl">            <span class="kr">if</span> <span class="n">guard.x</span> <span class="o">&lt;</span> <span class="n">centerX</span> <span class="kr">then</span>
</span></span><span class="line"><span class="cl">                <span class="n">quadrants</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">quadrants</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="mi">1</span>  <span class="c1">-- Top-left</span>
</span></span><span class="line"><span class="cl">            <span class="kr">elseif</span> <span class="n">guard.x</span> <span class="o">&gt;</span> <span class="n">centerX</span> <span class="kr">then</span>
</span></span><span class="line"><span class="cl">                <span class="n">quadrants</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="n">quadrants</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">+</span> <span class="mi">1</span>  <span class="c1">-- Top-right</span>
</span></span><span class="line"><span class="cl">            <span class="kr">end</span>
</span></span><span class="line"><span class="cl">        <span class="kr">elseif</span> <span class="n">guard.y</span> <span class="o">&gt;</span> <span class="n">centerY</span> <span class="kr">then</span>
</span></span><span class="line"><span class="cl">            <span class="kr">if</span> <span class="n">guard.x</span> <span class="o">&lt;</span> <span class="n">centerX</span> <span class="kr">then</span>
</span></span><span class="line"><span class="cl">                <span class="n">quadrants</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span> <span class="o">=</span> <span class="n">quadrants</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span> <span class="o">+</span> <span class="mi">1</span>  <span class="c1">-- Bottom-left</span>
</span></span><span class="line"><span class="cl">            <span class="kr">elseif</span> <span class="n">guard.x</span> <span class="o">&gt;</span> <span class="n">centerX</span> <span class="kr">then</span>
</span></span><span class="line"><span class="cl">                <span class="n">quadrants</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">=</span> <span class="n">quadrants</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">+</span> <span class="mi">1</span>  <span class="c1">-- Bottom-right</span>
</span></span><span class="line"><span class="cl">            <span class="kr">end</span>
</span></span><span class="line"><span class="cl">        <span class="kr">end</span>
</span></span><span class="line"><span class="cl">        <span class="p">::</span><span class="nl">continue</span><span class="p">::</span> <span class="c1">-- We jump here (poor man&#39;s continue)</span>
</span></span><span class="line"><span class="cl">    <span class="kr">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">return</span> <span class="n">quadrants</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">*</span> <span class="n">quadrants</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">*</span> <span class="n">quadrants</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span> <span class="o">*</span> <span class="n">quadrants</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">local</span> <span class="n">file</span> <span class="o">=</span> <span class="n">io.open</span><span class="p">(</span><span class="s2">&#34;input.txt&#34;</span><span class="p">,</span> <span class="s2">&#34;r&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">local</span> <span class="n">guards</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl"><span class="kr">for</span> <span class="n">line</span> <span class="kr">in</span> <span class="n">file</span><span class="p">:</span><span class="n">lines</span><span class="p">()</span> <span class="kr">do</span>
</span></span><span class="line"><span class="cl">    <span class="kd">local</span> <span class="n">startX</span><span class="p">,</span> <span class="n">startY</span><span class="p">,</span> <span class="n">vx</span><span class="p">,</span> <span class="n">vy</span> <span class="o">=</span> <span class="n">line</span><span class="p">:</span><span class="n">match</span><span class="p">(</span><span class="s2">&#34;p=(%d+),(%d+) v=(%-?%d+),(%-?%d+)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1">-- ^ I can&#39;t believe it&#39;s not regex!</span>
</span></span><span class="line"><span class="cl">    <span class="n">table.insert</span><span class="p">(</span><span class="n">guards</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">x</span> <span class="o">=</span> <span class="n">tonumber</span><span class="p">(</span><span class="n">startX</span><span class="o">+</span><span class="mi">1</span><span class="p">),</span> <span class="c1">-- +1 to account for Lua&#39;s 1-based indexing</span>
</span></span><span class="line"><span class="cl">        <span class="n">y</span> <span class="o">=</span> <span class="n">tonumber</span><span class="p">(</span><span class="n">startY</span><span class="o">+</span><span class="mi">1</span><span class="p">),</span> <span class="c1">-- +1 to account for Lua&#39;s 1-based indexing</span>
</span></span><span class="line"><span class="cl">        <span class="n">vx</span> <span class="o">=</span> <span class="n">tonumber</span><span class="p">(</span><span class="n">vx</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">vy</span> <span class="o">=</span> <span class="n">tonumber</span><span class="p">(</span><span class="n">vy</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">file</span><span class="p">:</span><span class="n">close</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">local</span> <span class="n">mapWidth</span> <span class="o">=</span> <span class="mi">101</span>
</span></span><span class="line"><span class="cl"><span class="kd">local</span> <span class="n">mapHeight</span> <span class="o">=</span> <span class="mi">103</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">for</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">100</span> <span class="kr">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">moveGuards</span><span class="p">(</span><span class="n">guards</span><span class="p">,</span> <span class="n">mapWidth</span><span class="p">,</span> <span class="n">mapHeight</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">print</span><span class="p">(</span><span class="n">calculateSafety</span><span class="p">(</span><span class="n">guards</span><span class="p">,</span> <span class="n">mapWidth</span><span class="p">,</span> <span class="n">mapHeight</span><span class="p">))</span>
</span></span></code></pre></div><p>A few notes:</p>
<ul>
<li>I learnt about Lua&rsquo;s <a href="https://www.lua.org/pil/20.1.html">pattern matching</a> doing this challenge. It is not regex, as implementing regex in Lua would practically double the size of its codebase. Luckily, I didn&rsquo;t need to do any complicated text parsing for this challenge. This <a href="https://pdfhost.io/v/nZ1THXfqu_Lua_regex_cheat_sheet">cheatsheet</a> was helpful.</li>
<li><code>line:match</code> is syntactic sugar for <code>line.match(self,</code>. I had also not previously used any of Lua&rsquo;s OO capabilities.</li>
<li>Lua 1-indexes everything (as does Octave).</li>
<li>Lua does not have a <code>continue</code> keyword, so you have to <a href="https://en.wikipedia.org/wiki/Considered_harmful">use <code>goto</code> with a label</a>. I am young enough that this maybe the third time I&rsquo;ve ever written high-level code containing a <code>goto</code>.</li>
</ul>
<p>Naturally, this code got me a result much quicker than my Love2D implementation.</p>
<p><a href="https://github.com/dmyates/advent-of-code-solutions/blob/master/2024/14%20-%20Lua%20and%20Love2D/14-1.lua"><strong>Full code for part 1 on GitHub</strong></a></p>
<p>The second part of the challenge comes totally out of left field: you have to find the smallest number of seconds it takes for the robots to <strong>arrange themselves in a Christmas tree pattern</strong>. I immediately had more questions, such as, should this be an actual tree or something in vague shape of one? At any rate, solving this problem with my current implementation would require a function for visualising the map:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="kd">local</span> <span class="kr">function</span> <span class="nf">visualizeMap</span><span class="p">(</span><span class="n">guards</span><span class="p">,</span> <span class="n">mapWidth</span><span class="p">,</span> <span class="n">mapHeight</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1">-- Initialize empty map</span>
</span></span><span class="line"><span class="cl">    <span class="kd">local</span> <span class="n">map</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">    <span class="kr">for</span> <span class="n">y</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="n">mapHeight</span> <span class="kr">do</span>
</span></span><span class="line"><span class="cl">        <span class="n">map</span><span class="p">[</span><span class="n">y</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">        <span class="kr">for</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="n">mapWidth</span> <span class="kr">do</span>
</span></span><span class="line"><span class="cl">            <span class="n">map</span><span class="p">[</span><span class="n">y</span><span class="p">][</span><span class="n">x</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">        <span class="kr">end</span>
</span></span><span class="line"><span class="cl">    <span class="kr">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">-- Count guards at each position</span>
</span></span><span class="line"><span class="cl">    <span class="kr">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">guard</span> <span class="kr">in</span> <span class="n">ipairs</span><span class="p">(</span><span class="n">guards</span><span class="p">)</span> <span class="kr">do</span>
</span></span><span class="line"><span class="cl">        <span class="kd">local</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="n">math.floor</span><span class="p">(</span><span class="n">guard.x</span><span class="p">),</span> <span class="n">math.floor</span><span class="p">(</span><span class="n">guard.y</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">map</span><span class="p">[</span><span class="n">y</span><span class="p">][</span><span class="n">x</span><span class="p">]</span> <span class="o">=</span> <span class="n">map</span><span class="p">[</span><span class="n">y</span><span class="p">][</span><span class="n">x</span><span class="p">]</span> <span class="o">+</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">    <span class="kr">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">-- Create string representation</span>
</span></span><span class="line"><span class="cl">    <span class="kd">local</span> <span class="n">result</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">    <span class="kr">for</span> <span class="n">y</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="n">mapHeight</span> <span class="kr">do</span>
</span></span><span class="line"><span class="cl">        <span class="kd">local</span> <span class="n">row</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">        <span class="kr">for</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="n">mapWidth</span> <span class="kr">do</span>
</span></span><span class="line"><span class="cl">            <span class="n">row</span><span class="p">[</span><span class="n">x</span><span class="p">]</span> <span class="o">=</span> <span class="n">map</span><span class="p">[</span><span class="n">y</span><span class="p">][</span><span class="n">x</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="ow">and</span> <span class="n">tostring</span><span class="p">(</span><span class="n">map</span><span class="p">[</span><span class="n">y</span><span class="p">][</span><span class="n">x</span><span class="p">])</span> <span class="ow">or</span> <span class="s2">&#34;.&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="kr">end</span>
</span></span><span class="line"><span class="cl">        <span class="n">table.insert</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="n">table.concat</span><span class="p">(</span><span class="n">row</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="kr">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">return</span> <span class="n">table.concat</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span>
</span></span></code></pre></div><p>I didn&rsquo;t have any idea what the eventual tree would look like, so I didn&rsquo;t really want to try writing code to detect shapes in guard positions. Luckily, I still had my abandoned Love2D implementation, which I could start up and just watch for patterns.</p>
<p>I soon noticed that the guards would periodically swarm to the vertical or horizontal center line of the map. As these lines were left out of the safety factor calculation, this meant that such a swarm would have to coincide with a low safety factor. Also it would make sense for the Christmas tree to appear in the middle of the map.</p>
<p>Initially, I tried looking for the lowest possible safety factor in a hundred, then one thousand, then ten thousand seconds. Although I could lie to myself that some of these outputs looked vaguely like Christmas trees, none were the correct answer. So I loosened my threshold a bit, and looked for outputs with safety factors lower than the first one I found, at the first second I&rsquo;d noticed the middle convergence.</p>
<p>A few results down, I saw this, right in the centre of the map:</p>
<pre tabindex="0"><code>1111111111111111111111111111111
1.............................1
1.............................1
1.............................1
1.............................1
1..............1..............1
1.............111.............1
1............11111............1
1...........1111111...........1
1..........111111111..........1
1............11111............1
1...........1111111...........1
1..........111111111..........1
1.........11111111111.........1
1........1111111111111........1
1..........111111111..........1
1.........11111111111.........1
1........1111111111111........1
1.......111111111111111.......1
1......11111111111111111......1
1........1111111111111........1
1.......111111111111111.......1
1......11111111111111111......1
1.....1111111111111111111.....1
1....111111111111111111111....1
1.............111.............1
1.............111.............1
1.............111.............1
1.............................1
1.............................1
1.............................1
1.............................1
1111111111111111111111111111111
</code></pre><p>As we can see, none of the guards overlap in this pattern. That would probably also be a good heuristic for finding the Christmas tree, but I haven&rsquo;t implemented it.</p>
<p><a href="https://github.com/dmyates/advent-of-code-solutions/blob/master/2024/14%20-%20Lua%20and%20Love2D/14-2.lua"><strong>Full code for part 2 on GitHub.</strong></a></p>
<p>After implementing some time manipulation functionality in my Love2D code, I managed to snap a screenshot of it as well.</p>
<p><figure>
  
  <img src="/content/images/2024/12/love-tree.png" alt="Merry Christmas!" loading="lazy"/>
  
  <figcaption>
    <p>Merry Christmas!</p>
  </figcaption>
</figure>
</p>
<p><a href="https://github.com/dmyates/advent-of-code-solutions/blob/master/2024/14%20-%20Lua%20and%20Love2D/visual/main.lua"><strong>Full code for Love2D version on GitHub.</strong></a></p>
<p><strong>Closing thoughts</strong>: I think if I could get used to 1-indexing, I&rsquo;d probably prefer it to 0-indexing. Sacrilege, I know. Lua is a great little language and I&rsquo;m glad I used this opportunity to get a bit more experience with it and learn about things like its pattern-matching functionality.</p>
<p>Love2D is great fun, and I&rsquo;m glad that my failed Love2D implementation of the first part of the challenge turned out to be useful for the second part.</p>
<h1 id="day-15-javascript--kaplay">
  <a class="heading-anchor" href="#day-15-javascript--kaplay">#</a>
  Day 15: JavaScript &amp; KaPlay
</h1>
<p><a href="https://adventofcode.com/2024/day/13"><strong>Challenge</strong></a>: figure out where a malfunctioning robot will push boxes in a warehouse.</p>
<p>The challenge here was to build a self-playing <a href="https://en.wikipedia.org/wiki/Sokoban">sokoban</a> game, making it the challenge most perfectly suited so far to a gamedev framework.<sup id="fnref:1"><a href="https://davidyat.es/2025/01/11/aoc-2024-part3/#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>Initially, I thought about using <a href="https://www.puzzlescript.net/">PuzzleScript</a>, a gamedev tool with a terse, rule-based language explicitly geared to the creation of sokoban-alikes. Half of the logic for the first part of this challenge is already implemented in the <a href="https://www.puzzlescript.net/editor.html">sample game</a>, in a single line of code:</p>
<pre tabindex="0"><code>[ &gt; Player | Crate ] -&gt; [ &gt; Player | &gt; Crate ]
</code></pre><p>Translation: if player steps towards a crate, move both the player and the crate. To complete the logic, we just need to make crates push each other, which we can do with another very simple rule:</p>
<pre tabindex="0"><code>[ &gt; Crate | Crate ] -&gt; [ &gt; Crate | &gt; Crate ]
</code></pre><p>However, getting a result out of a PuzzleScript game would require some external method of:</p>
<ol>
<li>Moving the player according to a preset list of moves.</li>
<li>Calculating and adding the positions of all the crates together.</li>
</ol>
<p>This seemed like a lot more trouble than it was worth, even in the context of this self-imposed challenge. So I opted instead to write my self-playing sokoban game in JavaScript, using <a href="https://kaplayjs.com/">KaPlay</a>, a lightweight and intuitive gamedev library I have a bit of prior experience with. KaPlay also happens to have functionality for <a href="https://kaplayjs.com/doc/ctx/addLevel/">storing levels as ASCII grids</a>, making it well suited to AoC.</p>
<p>After creating a new project, loading up some sprites and making a sample level using 16x16 grid spaces, to semi-accomodate the large challenge input, I got to work with my implementation. Initially, I wrote a bunch of code for predicting which object(s) the player would collide with when moving in some direction, converting between real positions and grid positions, but then I realised that at some point since I last used it, KaPlay had added the method <a href="https://kaplayjs.com/doc/LevelComp/"><code>level.getAt</code></a> which did all of this for me. That allowed me to implemented the movement and crate-pushing code in a few recursive functions.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl">    <span class="c1">// Base movement function
</span></span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">move</span> <span class="o">=</span> <span class="p">(</span><span class="nx">obj</span><span class="p">,</span> <span class="nx">dir</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">dir</span><span class="p">.</span><span class="nx">x</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="nx">obj</span><span class="p">.</span><span class="nx">moveRight</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">dir</span><span class="p">.</span><span class="nx">x</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="nx">obj</span><span class="p">.</span><span class="nx">moveLeft</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">dir</span><span class="p">.</span><span class="nx">y</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="nx">obj</span><span class="p">.</span><span class="nx">moveDown</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">dir</span><span class="p">.</span><span class="nx">y</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="nx">obj</span><span class="p">.</span><span class="nx">moveUp</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Crate movement
</span></span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">moveCrate</span> <span class="o">=</span> <span class="p">(</span><span class="nx">crate</span><span class="p">,</span> <span class="nx">dir</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">crateMoveTo</span> <span class="o">=</span> <span class="nx">crate</span><span class="p">.</span><span class="nx">tilePos</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">dir</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">crateDisplaced</span> <span class="o">=</span> <span class="nx">level</span><span class="p">.</span><span class="nx">getAt</span><span class="p">(</span><span class="nx">crateMoveTo</span><span class="p">)[</span><span class="mi">0</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// ^ multiple objects could occupy the same grid space
</span></span></span><span class="line"><span class="cl">        <span class="c1">// but that shouldn&#39;t happen in this game
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Moving into empty space: success
</span></span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">crateDisplaced</span> <span class="o">===</span> <span class="kc">undefined</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">move</span><span class="p">(</span><span class="nx">crate</span><span class="p">,</span> <span class="nx">dir</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Moving into a wall: failure
</span></span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">crateDisplaced</span><span class="p">.</span><span class="nx">is</span><span class="p">(</span><span class="s2">&#34;wall&#34;</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Moving into another crate: pass the buck
</span></span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">crateDisplaced</span><span class="p">.</span><span class="nx">is</span><span class="p">(</span><span class="s2">&#34;crate&#34;</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kr">const</span> <span class="nx">nextCrate</span> <span class="o">=</span> <span class="nx">crateDisplaced</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="nx">moveCrate</span><span class="p">(</span><span class="nx">nextCrate</span><span class="p">,</span> <span class="nx">dir</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nx">move</span><span class="p">(</span><span class="nx">crate</span><span class="p">,</span> <span class="nx">dir</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Player movement
</span></span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">movePlayer</span> <span class="o">=</span> <span class="p">(</span><span class="nx">dir</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">moveTo</span> <span class="o">=</span> <span class="nx">player</span><span class="p">.</span><span class="nx">tilePos</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">dir</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">displaced</span> <span class="o">=</span> <span class="nx">level</span><span class="p">.</span><span class="nx">getAt</span><span class="p">(</span><span class="nx">moveTo</span><span class="p">)[</span><span class="mi">0</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Moving into empty space
</span></span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">displaced</span> <span class="o">===</span> <span class="kc">undefined</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">move</span><span class="p">(</span><span class="nx">player</span><span class="p">,</span> <span class="nx">dir</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Moving into a wall, cancel movement
</span></span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">displaced</span><span class="p">.</span><span class="nx">is</span><span class="p">(</span><span class="s2">&#34;wall&#34;</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Moving into a crate, try to push the crate
</span></span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">displaced</span><span class="p">.</span><span class="nx">is</span><span class="p">(</span><span class="s2">&#34;crate&#34;</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kr">const</span> <span class="nx">crate</span> <span class="o">=</span> <span class="nx">displaced</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">moveCrate</span><span class="p">(</span><span class="nx">crate</span><span class="p">,</span> <span class="nx">dir</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// If we haven&#39;t returned yet, we can move
</span></span></span><span class="line"><span class="cl">        <span class="nx">move</span><span class="p">(</span><span class="nx">player</span><span class="p">,</span> <span class="nx">dir</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span></code></pre></div><p>For testing, and to make it a real game, I bound movement to the arrow keys. I then implemented autoplay in two ways. Press <code>Space</code>, and all moves will be processed at once, changing the map and producing the answer in the blink of an eye. Press <code>Enter</code>, and you can watch the robot go through the moves one at a time.</p>
<p><figure>
  
  <img src="/content/images/2025/01/soko.png" alt="Sokoban, featuring Bean, the default KaPlay player sprite" loading="lazy"/>
  
  <figcaption>
    <p>Sokoban, featuring Bean, the default KaPlay player sprite</p>
  </figcaption>
</figure>
</p>
<p>For the second part of the challenge, everything except the player/robot doubled in width, taking up two grid spaces. At this point, I regretted using GML back on day four, as GameMaker&rsquo;s collision and grid functions are robust enough to handle this kind of thing with ease. Had I implemented part one in GameMaker, I would likely only have to have changed the width of the crates to get part two working as well.</p>
<p>However, I implemented it in KaPlay. And KaPlay&rsquo;s level/tile system does not explicitly cater for double-wide objects. I would either have forgo a lot of the tile functionality or work with left and right crate halves, which would need to be manually kept together.</p>
<p>I chose to keep the very useful grid methods and represent each crate as left and right halves, which meant having to keep halves together. Due to the cascading push already implemented for the first part, horizontally pushing two separate crates positioned next to each other was functionally the same as pushing two linked crates. It was when crates had to be pushed vertically that I actually needed new code to keep halves together.</p>
<p>I tried a few different tweaks to my recursive code above, but couldn&rsquo;t resolve this edge-case:</p>
<p><figure>
  
  <img src="/content/images/2025/01/edge1.png" alt="Pushing up&hellip;" loading="lazy"/>
  
  <figcaption>
    <p>Pushing up&hellip;</p>
  </figcaption>
</figure>

<figure>
  
  <img src="/content/images/2025/01/edge2.png" alt="Initial push fails, cascaded push succeeds" loading="lazy"/>
  
  <figcaption>
    <p>Initial push fails, cascaded push succeeds</p>
  </figcaption>
</figure>
</p>
<p>Annoyingly, this edge-case was not present in the example map, but did appear in my challenge input. All of the recursive methods I tried were just too eager to move boxes. I needed to change to something that would cancel the whole move as soon as one push failed.</p>
<p>So, taking a leaf from challenges 10 and 12, I rewrote the code to build up an array of things to move using a depth-first search.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">movePlayer</span> <span class="o">=</span> <span class="p">(</span><span class="nx">dir</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">moveTo</span> <span class="o">=</span> <span class="nx">player</span><span class="p">.</span><span class="nx">tilePos</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">dir</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="kd">let</span> <span class="nx">displaced</span> <span class="o">=</span> <span class="nx">level</span><span class="p">.</span><span class="nx">getAt</span><span class="p">(</span><span class="nx">moveTo</span><span class="p">)[</span><span class="mi">0</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// empty space, go there
</span></span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">displaced</span> <span class="o">===</span> <span class="kc">undefined</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">move</span><span class="p">(</span><span class="nx">player</span><span class="p">,</span> <span class="nx">dir</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// wall, don&#39;t go there
</span></span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">displaced</span><span class="p">.</span><span class="nx">is</span><span class="p">(</span><span class="s2">&#34;wall&#34;</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// not empty space or wall, must be a crate
</span></span></span><span class="line"><span class="cl">        <span class="c1">// let&#39;s build a stack of crates in our way
</span></span></span><span class="line"><span class="cl">        <span class="kd">let</span> <span class="nx">cratesToMove</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Set</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="kd">let</span> <span class="nx">stack</span> <span class="o">=</span> <span class="p">[</span><span class="nx">displaced</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="k">while</span><span class="p">(</span><span class="nx">stack</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kd">let</span> <span class="nx">next</span> <span class="o">=</span> <span class="nx">stack</span><span class="p">.</span><span class="nx">pop</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="nx">next</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="nx">next</span><span class="p">.</span><span class="nx">is</span><span class="p">(</span><span class="s2">&#34;wall&#34;</span><span class="p">))</span> <span class="k">return</span><span class="p">;</span> <span class="c1">// found a wall, cancel everything
</span></span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="nx">next</span><span class="p">.</span><span class="nx">is</span><span class="p">(</span><span class="s2">&#34;crate&#34;</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="c1">// add to crates to move
</span></span></span><span class="line"><span class="cl">                    <span class="nx">cratesToMove</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">next</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                    <span class="c1">// add to displacement checking stack
</span></span></span><span class="line"><span class="cl">                    <span class="nx">stack</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">level</span><span class="p">.</span><span class="nx">getAt</span><span class="p">(</span><span class="nx">next</span><span class="p">.</span><span class="nx">tilePos</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">dir</span><span class="p">))[</span><span class="mi">0</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="nx">dir</span><span class="p">.</span><span class="nx">y</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// special rules for vertical movement
</span></span></span><span class="line"><span class="cl">                    <span class="c1">// partner crate must also be able to move
</span></span></span><span class="line"><span class="cl">                    <span class="k">if</span> <span class="p">(</span><span class="nx">next</span><span class="p">.</span><span class="nx">is</span><span class="p">(</span><span class="s2">&#34;left&#34;</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="kd">let</span> <span class="nx">partner</span> <span class="o">=</span> <span class="nx">level</span><span class="p">.</span><span class="nx">getAt</span><span class="p">(</span><span class="nx">next</span><span class="p">.</span><span class="nx">tilePos</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">vec2</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">)))[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">                        <span class="nx">cratesToMove</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">partner</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                        <span class="nx">stack</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">level</span><span class="p">.</span><span class="nx">getAt</span><span class="p">(</span><span class="nx">partner</span><span class="p">.</span><span class="nx">tilePos</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">dir</span><span class="p">))[</span><span class="mi">0</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">                    <span class="p">}</span>
</span></span><span class="line"><span class="cl">                    <span class="k">if</span> <span class="p">(</span><span class="nx">next</span><span class="p">.</span><span class="nx">is</span><span class="p">(</span><span class="s2">&#34;right&#34;</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="kd">let</span> <span class="nx">partner</span> <span class="o">=</span> <span class="nx">level</span><span class="p">.</span><span class="nx">getAt</span><span class="p">(</span><span class="nx">next</span><span class="p">.</span><span class="nx">tilePos</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">vec2</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">)))[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">                        <span class="nx">cratesToMove</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">partner</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                        <span class="nx">stack</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">level</span><span class="p">.</span><span class="nx">getAt</span><span class="p">(</span><span class="nx">partner</span><span class="p">.</span><span class="nx">tilePos</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">dir</span><span class="p">))[</span><span class="mi">0</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">                    <span class="p">}</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// move everything that needs to move
</span></span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="p">(</span><span class="kr">const</span> <span class="nx">crate</span> <span class="k">of</span> <span class="nx">cratesToMove</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">move</span><span class="p">(</span><span class="nx">crate</span><span class="p">,</span> <span class="nx">dir</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="nx">move</span><span class="p">(</span><span class="nx">player</span><span class="p">,</span> <span class="nx">dir</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span></code></pre></div><p>What I&rsquo;m happiest about with this code is that it works just as well for single and double-sized crates, and thus works as a solution for both parts of the challenge. It would also work on a map including a mix of single and double crates. To facilitate solving this part two, I added an upsize checkbox to the functionality that loads levels from files, and that&rsquo;s all I needed.</p>
<p><figure>
  
  <img src="/content/images/2025/01/soko2.png" alt="Double-width Sokoban" loading="lazy"/>
  
  <figcaption>
    <p>Double-width Sokoban</p>
  </figcaption>
</figure>
</p>
<p><a href="https://github.com/dmyates/advent-of-code-solutions/blob/master/2024/15%20-%20JavaScript%20and%20Kaplay/src/main.js"><strong>Full code on GitHub</strong></a></p>
<p><strong>Closing thoughts:</strong> I have never been the biggest fan of JavaScript, but it&rsquo;s one of those languages that you pretty much have no choice but to use in a lot of domains. As a result, I&rsquo;m very comfortable with it, which has not been the case for most of what I&rsquo;ve used for these challenges. KaPlay is a great little library and one I&rsquo;d like to use for a proper game in the future.</p>
<hr>
<p>As may be evident from the date of this compared to previous posts, I&rsquo;ve slowed down with this project a little. Write-ups for the remaining ten days of AoC 2024 will be posted eventually, probably with some other posts in between. I feel like I played it quite safe with the language choices for this selection of challenges, so I&rsquo;m looking forward to trying some weirder ones in the next post.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://www.reddit.com/r/adventofcode/comments/1hetsol/2024_day_15_solution_in_baba_is_you/">One person</a> went even further than this and implemented it in <a href="/2021/03/27/review-baba-is-you/">the meta-sokoban game <em>Baba is You</em></a>.&#160;<a href="https://davidyat.es/2025/01/11/aoc-2024-part3/#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

        <p><a href="mailto:d@vidyat.es?subject=RE: Advent%20of%20Code%202024%3a%20Days%2011%e2%80%9315">Reply via email</a></p>
        ]]></content:encoded></item><item><title>Advent of Code 2024: Days 6–10</title><link>https://davidyat.es/2024/12/23/aoc-2024-part2/</link><pubDate>Mon, 23 Dec 2024 15:43:44 +0200</pubDate><author>David Yates</author><guid>https://davidyat.es/2024/12/23/aoc-2024-part2/</guid><content:encoded><![CDATA[
        <img src="https://davidyat.es/content/images/2024/12/aoc2024-p2.jpg" class="post-img" />
        <p>In <a href="/2024/12/16/aoc-2024-part1/">the first part of this series</a>, I starting writing solutions for the 2024 <a href="https://adventofcode.com/">Advent of Code</a> (AoC) challenges, each in a different language. To recap, Advent of Code is a yearly programming event. On each day of the advent leading up Christmas, a new challenge is released. Each challenge consists of a problem description and an input file. Challenges generally involve processing the input file to come up with a particular output, which serves as the solution. Each challenge consists of two parts, with the second part being revealed upon completion of the first. Both parts use the same input, which is slightly different for each participant.</p>
<p>Apart from the obvious goals of trying out new and different programming languages and revisiting old favourites, I&rsquo;m also using this as a way to gauge how good AI (mostly Claude 3.5 Sonnet, running in <a href="https://www.cursor.com/">Cursor</a>) is at writing, explaining and refactoring code in different languages.</p>
<p>My single constraint was to use each language only once. See below for how I broke it.</p>
<nav id="TableOfContents">
  <ul>
    <li><a href="#day-6-python--inform-7">Day 6: Python &amp; Inform 7</a></li>
    <li><a href="#day-7-racket">Day 7: Racket</a></li>
    <li><a href="#day-8-io">Day 8: Io</a></li>
    <li><a href="#day-9-j">Day 9: J</a></li>
    <li><a href="#day-10-solidity">Day 10: Solidity</a></li>
  </ul>
</nav>

<h1 id="day-6-python--inform-7">
  <a class="heading-anchor" href="#day-6-python--inform-7">#</a>
  Day 6: Python &amp; Inform 7
</h1>
<p><a href="https://adventofcode.com/2024/day/6"><strong>Challenge</strong></a>: count the locations a guard will visit on a map if he turns right at every obstacle.</p>
<p>As soon as I read this challenge, I regretted <a href="/2024/12/16/aoc-2024-part1/#day-1-inform-7">using Inform 7 for Day 1</a>. As I noted then, Inform was not hugely suited for number crunching. However, something it is suited to is navigating grids! The problem immediately put me in mind of a maze of twisty little passages, all alike. And Inform even automatically records <a href="https://ganelson.github.io/inform-website/book/WI_12_15.html#:~:text=Report%20requesting%20the%20room%20tally%3A%20say%20%22You%20have%20been%20to%20%5Bnumber%20of%20visited%20rooms%5D%20out%20of%20%5Bnumber%20of%20rooms%5D%20room%5Bs%5D.%22">the number of rooms you visit</a>!</p>
<p>The one problem would be generating the grid itself. As far as I&rsquo;m aware, Inform 7 provides no method for programmatically creating rooms and the connections between them &ndash; you must manually specify each room and its connections, like this:</p>
<pre tabindex="0"><code class="language-inform7" data-lang="inform7">The Chamber is a room. The Chamber is north of the Hallway.
</code></pre><p>My input file for this challenge was a 129*129 map, and there was no way I was going to manually specify that many rooms. Thus I would need to generate the Inform code itself programmatically.</p>
<p>This dovetailed nicely with the problem of having already used Inform 7 once this advent. If I generated the Inform 7 code using Python, that would be sort of like using Python for this challenge, and I haven&rsquo;t used Python yet. And as an added bonus/<del>punishment</del>, it would ensure I didn&rsquo;t fallback to using Python in later, more difficult challenges.</p>
<p>I started with the Inform 7 code, which is very simple:</p>
<pre tabindex="0"><code class="language-inform7" data-lang="inform7"><strong>"Guard Gallivant Solution" by "David Yates"</strong>

Chapter 1 - Game Logic

Forward is a direction that varies. Forward is north. [i.e. var forward = north]

Patrolling is an action applying to nothing.

Understand &#34;patrol&#34; as patrolling.

Carry out patrolling:
	while the player is not in Endgame:
		try going forward. [move in the direction stored in forward]

Instead of going nowhere:
	now forward is right of forward; [i.e. forward = right_of(forward)]
	say &#34;Turning right...&#34;;
	say &#34;You have visited [number of visited rooms] rooms so far.&#34;;
	try going forward.

To decide what direction is right of (D - direction): [i.e. def right_of(D)]
	if D is north:
		decide on east; [i.e. return east]
	if D is east:
		decide on south;
	if D is south:
		decide on west;
	if D is west:
		decide on north.

Report going to Endgame:
    say &#34;You have visited [number of visited rooms] rooms in total.&#34;;
    end the story.
</code></pre><p>Inform 7 is a rules-based language for building turn-based games. The code defines a bunch of rules (<code>Instead of...</code>, <code>Report...</code> and so), and every turn the engine evaluates and applies a subset of these rules. Rules can be generally applicable (<code>Every turn</code>) or highly specific (<code>Instead of going from the ballroom when the player is Mr Mustard and has the candlestick</code>). These rules are executed in a particular order, according to their placement in different rulebooks. Because the rule in <code>Report going to Endgame</code> is executed before the rule that marks <code>Endgame</code> as a visited room,<sup id="fnref:1"><a href="https://davidyat.es/2024/12/23/aoc-2024-part2/#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> our room visited count will be accurate.</p>
<p><code>Instead of going nowhere</code> is a rule that runs if the player tries to move in a direction that is not catered for. For example, if I&rsquo;m in a room with other rooms to the south, east and west, and I try to go north, this rule will be triggered. Therefore, I just needed to create a game map in which rooms adjacent to obstacles did not have connections in that direction.</p>
<p>To solve the challenge, all the player would need to do is type <code>patrol</code>. Once I had the map generated, that is.</p>
<p>I generated the map by reading the file into a Numpy 2D array and then iterating over it. For the starting space and opening spaces on the map, I created rooms. Upon the creation of each room, I checked the cardinal directions from that room and created connections to surrounding open spaces. For rooms on edges of the map, I created connections to a special room, Endgame, which would signify the end of the patrol. For obstacles, I left a comment without creating a room or any connections.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="s2">&#34;INFORM CODE ABOVE&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">Chapter 2 - The Map
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">Endgame is a room.
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># create grid</span>
</span></span><span class="line"><span class="cl"><span class="n">grid</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">array</span><span class="p">([</span><span class="nb">list</span><span class="p">(</span><span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">())</span> <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="nb">open</span><span class="p">(</span><span class="s2">&#34;small.txt&#34;</span><span class="p">)])</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># generate I7 code</span>
</span></span><span class="line"><span class="cl"><span class="n">room_name</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">y</span><span class="p">,</span> <span class="n">x</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;P</span><span class="si">{</span><span class="n">y</span><span class="si">}</span><span class="s2">-</span><span class="si">{</span><span class="n">x</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">y</span><span class="p">,</span> <span class="n">row</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">grid</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">x</span><span class="p">,</span> <span class="n">cell</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">row</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># skip obstacle</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">cell</span> <span class="ow">not</span> <span class="ow">in</span> <span class="s1">&#39;.^&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">continue</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># create room</span>
</span></span><span class="line"><span class="cl">        <span class="n">this_room</span> <span class="o">=</span> <span class="n">room_name</span><span class="p">(</span><span class="n">y</span><span class="p">,</span> <span class="n">x</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">this_room</span><span class="si">}</span><span class="s2"> is a room.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># create room connections</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">d</span><span class="p">,</span> <span class="n">ay</span><span class="p">,</span> <span class="n">ax</span> <span class="ow">in</span> <span class="p">[(</span><span class="s2">&#34;west&#34;</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">x</span><span class="o">-</span><span class="mi">1</span><span class="p">),</span> <span class="p">(</span><span class="s2">&#34;east&#34;</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">x</span><span class="o">+</span><span class="mi">1</span><span class="p">),</span> <span class="p">(</span><span class="s2">&#34;north&#34;</span><span class="p">,</span> <span class="n">y</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">x</span><span class="p">),</span> <span class="p">(</span><span class="s2">&#34;south&#34;</span><span class="p">,</span> <span class="n">y</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span> <span class="n">x</span><span class="p">)]:</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="p">(</span><span class="mi">0</span> <span class="o">&lt;=</span> <span class="n">ay</span> <span class="o">&lt;</span> <span class="n">grid</span><span class="o">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="ow">and</span> <span class="mi">0</span> <span class="o">&lt;=</span> <span class="n">ax</span> <span class="o">&lt;</span> <span class="n">grid</span><span class="o">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">1</span><span class="p">]):</span> <span class="c1"># out of bounds</span>
</span></span><span class="line"><span class="cl">                <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Endgame is </span><span class="si">{</span><span class="n">d</span><span class="si">}</span><span class="s2"> of </span><span class="si">{</span><span class="n">this_room</span><span class="si">}</span><span class="s2">.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">elif</span> <span class="n">grid</span><span class="p">[</span><span class="n">ay</span><span class="p">,</span> <span class="n">ax</span><span class="p">]</span> <span class="ow">in</span> <span class="s1">&#39;.^&#39;</span><span class="p">:</span> <span class="c1"># in bounds and not an obstacle</span>
</span></span><span class="line"><span class="cl">                <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">room_name</span><span class="p">(</span><span class="n">ay</span><span class="p">,</span> <span class="n">ax</span><span class="p">)</span><span class="si">}</span><span class="s2"> is </span><span class="si">{</span><span class="n">d</span><span class="si">}</span><span class="s2"> of </span><span class="si">{</span><span class="n">this_room</span><span class="si">}</span><span class="s2">.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># create starting room</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">cell</span> <span class="o">==</span> <span class="s1">&#39;^&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">When play begins:
</span></span></span><span class="line"><span class="cl"><span class="se">\t</span><span class="s2">now the player is in </span><span class="si">{</span><span class="n">this_room</span><span class="si">}</span><span class="s2">.&#34;&#34;&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>The most annoying thing about printing the Inform code from Python, is that both languages have meaningful indentation, but Inform 7 insists that you use tabs and Python prefers spaces. I also might have simplified the code with more strategic room creation &ndash; Inform 7 doesn&rsquo;t strictly require rooms to be declared with <code>X is a room</code> if there&rsquo;s already a line that says <code>X is [direction] of [existing room].</code></p>
<p>With my input file, this script produced ~78k lines of Inform code. Unfortunately, when I pasted it into a new project in the Inform 7 IDE, it refused to compile.</p>
<p>Some experimentation and a couple of questions on forums later, I figured out that I could get the code to compile from the command line by <a href="https://ganelson.github.io/inform/inform7/M-cu.html#SP10">passing <code>-no-index</code> to the compiler</a>, which prevented it from trying to generate such things as a world map for the IDE&rsquo;s internal Index pane.</p>
<p><figure>
  
  <img src="/content/images/2024/12/6map.png" alt="The world map for the challenge&rsquo;s smaller sample map (the top right room should be on the bottom left)" loading="lazy"/>
  
  <figcaption>
    <p>The world map for the challenge&rsquo;s smaller sample map (the top right room should be on the bottom left)</p>
  </figcaption>
</figure>
</p>
<p>The Inform 7 compiler produces a source file in Inform 6, which must then be run through the Inform 6 compiler to produce a playable game file for the chosen virtual machine. I wrote a script to go from Python to Inform 7 to Inform 6 to the Glulx format.<sup id="fnref:2"><a href="https://davidyat.es/2024/12/23/aoc-2024-part2/#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> From there, I started up the packaged game file in <a href="https://archlinux.org/packages/extra/x86_64/glulxe-term/"><code>glulxe-term</code></a> and typed <code>patrol</code> to get my answer. The code for this ran much faster than the code for my Day 1 solution.</p>
<p><figure>
  
  <img src="/content/images/2024/12/glulx-game.png" alt="Playthrough for the challenge&rsquo;s sample data." loading="lazy"/>
  
  <figcaption>
    <p>Playthrough for the challenge&rsquo;s sample data.</p>
  </figcaption>
</figure>
</p>
<p><a href="https://github.com/dmyates/advent-of-code-solutions/tree/master/2024/06%20-%20Python%20and%20Inform%207"><strong>Full code for part 1 on GitHub.</strong></a></p>
<p>The second part of the challenge was to figure out how many different positions a single extra obstacle could be placed in to make the guard move in a loop. The approach to this that seemed obvious to me was this:</p>
<ol>
<li>Run through the map once to build a set of the rooms visited.</li>
<li>Restart, run through again with the first room in the set blocked off.</li>
<li>Continue until we reach the edge of the map or encounter a loop.</li>
<li>Restart and run through again with the next room blocked off.</li>
<li>Repeat until we&rsquo;ve tested blocking every room.</li>
</ol>
<p>I <em>think</em> this is possible to do in Inform, but I&rsquo;m not sure how. It would almost certainly be possible to do in Inform 6, a more standard language, though I rather feel that would defeat the point of the challenge. After experiencing repeated freeze-ups and crashes with my attempt at a solution for the sample input, I decided to shelve this one. I&rsquo;ve uploaded <a href="https://github.com/dmyates/advent-of-code-solutions/blob/master/2024/06%20-%20Python%20and%20Inform%207/2024-06-2-attempt.inform/Source/story.ni">my attempt here</a>.</p>
<p>A pure Python solution is technically in line with the constraint I&rsquo;ve set for myself, though not quite in the spirit of it (worse than using Inform 6? I&rsquo;m not sure). With a heavy heart, I wrote the code to solving the second part in Python.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">patrol</span><span class="p">(</span><span class="n">grid</span><span class="p">,</span> <span class="n">loops</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">directions</span> <span class="o">=</span> <span class="p">[(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">)]</span>  <span class="c1"># up, right, down, left</span>
</span></span><span class="line"><span class="cl">    <span class="n">in_bounds</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">y</span><span class="p">,</span> <span class="n">x</span><span class="p">:</span> <span class="mi">0</span> <span class="o">&lt;=</span> <span class="n">y</span> <span class="o">&lt;</span> <span class="nb">len</span><span class="p">(</span><span class="n">grid</span><span class="p">)</span> <span class="ow">and</span> <span class="mi">0</span> <span class="o">&lt;=</span> <span class="n">x</span> <span class="o">&lt;</span> <span class="nb">len</span><span class="p">(</span><span class="n">grid</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">y</span><span class="p">,</span> <span class="n">x</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">where</span><span class="p">(</span><span class="n">grid</span> <span class="o">==</span> <span class="s1">&#39;^&#39;</span><span class="p">)[</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">],</span> <span class="n">np</span><span class="o">.</span><span class="n">where</span><span class="p">(</span><span class="n">grid</span> <span class="o">==</span> <span class="s1">&#39;^&#39;</span><span class="p">)[</span><span class="mi">1</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">visited</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">visited</span><span class="o">.</span><span class="n">add</span><span class="p">((</span><span class="n">y</span><span class="p">,</span> <span class="n">x</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">direction</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">    <span class="n">loop</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="n">in_bounds</span><span class="p">(</span><span class="n">y</span><span class="p">,</span> <span class="n">x</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">my</span><span class="p">,</span> <span class="n">mx</span> <span class="o">=</span> <span class="n">y</span> <span class="o">+</span> <span class="n">directions</span><span class="p">[</span><span class="n">direction</span><span class="p">][</span><span class="mi">0</span><span class="p">],</span> <span class="n">x</span> <span class="o">+</span> <span class="n">directions</span><span class="p">[</span><span class="n">direction</span><span class="p">][</span><span class="mi">1</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">in_bounds</span><span class="p">(</span><span class="n">my</span><span class="p">,</span> <span class="n">mx</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span> <span class="c1"># we&#39;ve left the map</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">grid</span><span class="p">[</span><span class="n">my</span><span class="p">,</span> <span class="n">mx</span><span class="p">]</span> <span class="o">==</span> <span class="s1">&#39;#&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">direction</span> <span class="o">=</span> <span class="p">(</span><span class="n">direction</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">%</span> <span class="mi">4</span> <span class="c1"># hit an obstacle, turn right</span>
</span></span><span class="line"><span class="cl">            <span class="k">continue</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">y</span><span class="p">,</span> <span class="n">x</span> <span class="o">=</span> <span class="n">my</span><span class="p">,</span> <span class="n">mx</span> <span class="c1"># move</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Record visited</span>
</span></span><span class="line"><span class="cl">        <span class="n">current</span> <span class="o">=</span> <span class="p">(</span><span class="n">y</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">direction</span><span class="p">)</span> <span class="k">if</span> <span class="n">loops</span> <span class="k">else</span> <span class="p">(</span><span class="n">y</span><span class="p">,</span> <span class="n">x</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">loops</span> <span class="ow">and</span> <span class="n">current</span> <span class="ow">in</span> <span class="n">visited</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="p">(</span><span class="n">visited</span><span class="p">,</span> <span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">visited</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">current</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="n">visited</span><span class="p">,</span> <span class="n">loop</span><span class="p">)</span> <span class="k">if</span> <span class="n">loops</span> <span class="k">else</span> <span class="n">visited</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">grid</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">array</span><span class="p">([</span><span class="nb">list</span><span class="p">(</span><span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">())</span> <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="nb">open</span><span class="p">(</span><span class="s2">&#34;input.txt&#34;</span><span class="p">)])</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Part 1</span>
</span></span><span class="line"><span class="cl"><span class="n">visited</span> <span class="o">=</span> <span class="n">patrol</span><span class="p">(</span><span class="n">grid</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">visited</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Part 2</span>
</span></span><span class="line"><span class="cl"><span class="n">loops</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">y</span><span class="p">,</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">visited</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">grid</span><span class="p">[</span><span class="n">y</span><span class="p">,</span> <span class="n">x</span><span class="p">]</span> <span class="o">==</span> <span class="s1">&#39;^&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">continue</span> <span class="c1"># can&#39;t put an obstacle where the guard is</span>
</span></span><span class="line"><span class="cl">    <span class="n">grid</span><span class="p">[</span><span class="n">y</span><span class="p">,</span> <span class="n">x</span><span class="p">]</span> <span class="o">=</span> <span class="s1">&#39;#&#39;</span> <span class="c1"># new obstacle</span>
</span></span><span class="line"><span class="cl">    <span class="n">_</span><span class="p">,</span> <span class="n">loop</span> <span class="o">=</span> <span class="n">patrol</span><span class="p">(</span><span class="n">grid</span><span class="p">,</span> <span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">grid</span><span class="p">[</span><span class="n">y</span><span class="p">,</span> <span class="n">x</span><span class="p">]</span> <span class="o">=</span> <span class="s1">&#39;.&#39;</span> <span class="c1"># reset</span>
</span></span><span class="line"><span class="cl">    <span class="n">loops</span> <span class="o">+=</span> <span class="mi">1</span> <span class="k">if</span> <span class="n">loop</span> <span class="k">else</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">loops</span><span class="p">)</span>
</span></span></code></pre></div><p><a href="https://github.com/dmyates/advent-of-code-solutions/blob/master/2024/06%20-%20Python%20and%20Inform%207/2024-06-2.py"><strong>Full code for part 2 on GitHub.</strong></a></p>
<p><strong>Closing thoughts</strong>: I remain very happy with the elegance of my Inform solution for part 1. The main difficulties it presented were in getting it to compile with the full input, not writing the code itself. Attempting and ultimately failing to complete part 2 in Inform was, out of all challenges so far, the one that took me the most time. I still think it&rsquo;s possible, at least for very small maps, but I don&rsquo;t quite understand the language or its environment well enough to make it work.</p>
<p>Claude 3.5 Sonnet surprised me with its competence at writing Inform for this challenge, though its contributions ultimately came to nothing. It still made some mistakes, but probably the same ones a human would. Inform might look like natural language, but <code>entry number in my list</code> is valid code and <code>the number entry in my list</code> is a syntax error.</p>
<h1 id="day-7-racket">
  <a class="heading-anchor" href="#day-7-racket">#</a>
  Day 7: Racket
</h1>
<p><a href="https://adventofcode.com/2024/day/7"><strong>Challenge</strong></a>: find valid operators for incomplete equations.</p>
<p>I&rsquo;ve wanted to learn and use Lisp ever since I first read Paul Graham&rsquo;s <a href="https://paulgraham.com/avg.html">famous essay on the language</a>. Over the years, I&rsquo;ve occasionally toyed with <a href="https://en.wikipedia.org/wiki/Structure_and_Interpretation_of_Computer_Programs">Scheme</a>, <a href="https://pragprog.com/titles/btlang/seven-languages-in-seven-weeks/">Clojure</a>, <a href="https://beautifulracket.com/">Racket</a> and <a href="/2020/11/03/text-editors-iii-emacs/">Elisp</a>, but I still have yet to do substantial original programming in any of them. So of course this exercise seemed like a good excuse to return to my favourite of the Lisps I&rsquo;ve tried, Racket.</p>
<p>We begin, as always, by reading and parsing the input file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-racket" data-lang="racket"><span class="line"><span class="cl"><span class="kn">#lang </span><span class="nn">racket</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">; Load the input file</span>
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="k">define</span> <span class="n">filename</span> <span class="s2">&#34;input.txt&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="k">define</span> <span class="k">file</span> <span class="p">(</span><span class="nb">open-input-file</span> <span class="n">filename</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="k">define</span> <span class="n">input-str</span> <span class="p">(</span><span class="nb">read-string</span> <span class="p">(</span><span class="nb">file-size</span> <span class="n">filename</span><span class="p">)</span> <span class="k">file</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nb">close-input-port</span> <span class="k">file</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">; Parsing function</span>
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="k">define</span> <span class="p">(</span><span class="n">parse-input</span> <span class="n">input-str</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="k">let</span> <span class="p">([</span><span class="n">parse-line</span> <span class="p">(</span><span class="k">lambda</span> <span class="p">(</span><span class="n">line</span><span class="p">)</span> <span class="c1">; Racket lets you use (), [] and {} interchangeably</span>
</span></span><span class="line"><span class="cl">          <span class="p">(</span><span class="nb">map</span> <span class="nb">string-&gt;number</span> <span class="p">(</span><span class="nb">string-split</span> <span class="p">(</span><span class="nb">string-replace</span> <span class="n">line</span> <span class="s2">&#34;:&#34;</span> <span class="s2">&#34;&#34;</span><span class="p">))))])</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="nb">map</span> <span class="n">parse-line</span>
</span></span><span class="line"><span class="cl">         <span class="p">(</span><span class="nb">string-split</span> <span class="n">input-str</span> <span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">))))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">; Parse the file contents</span>
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="k">define</span> <span class="n">equations</span> <span class="p">(</span><span class="n">parse-input</span> <span class="n">input-str</span><span class="p">))</span><span class="err">
</span></span></span></code></pre></div><p>The lines of the file are in the form <code>190: 10 19</code>, where the first number is the answer you need to find by inserting <code>+</code> or <code>*</code> operators between the rest. Like most functional languages, Racket has the concept of the head and tail of a list, i.e. the first element and the rest of the list.<sup id="fnref:3"><a href="https://davidyat.es/2024/12/23/aoc-2024-part2/#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> As the answer will always be the first element, I&rsquo;m just stripping out the <code>:</code> and parsing every line into a simple list. So <code>equations</code> will be a list of lists.</p>
<p>To solve this, we&rsquo;ll invoke a function that takes this list of lists and a list of valid operators. This function will find and apply all possible combinations of the operators to each list. The answers belonging to lists with at least one valid combination will then be summed.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-racket" data-lang="racket"><span class="line"><span class="cl"><span class="p">(</span><span class="n">sum-calibration-equations</span> <span class="n">equations</span> <span class="o">&#39;</span><span class="p">(</span><span class="ss">+</span> <span class="ss">*</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="c1">; the &#39; makes the operators an inert list rather than an instruction to be executed</span>
</span></span></code></pre></div><p>But before we actually define <code>sum-calibration-equations</code>, we&rsquo;re going to need some helper functions. First, <code>apply-op</code>, which we&rsquo;ll use to apply an operator from our list to two numbers.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-racket" data-lang="racket"><span class="line"><span class="cl"><span class="p">(</span><span class="k">define</span> <span class="p">(</span><span class="n">apply-op</span> <span class="n">op</span> <span class="n">a</span> <span class="n">b</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="k">case</span> <span class="n">op</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span><span class="o">&#39;</span><span class="ss">+</span> <span class="p">(</span><span class="nb">+</span> <span class="n">a</span> <span class="n">b</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span><span class="o">&#39;</span><span class="ss">*</span> <span class="p">(</span><span class="nb">*</span> <span class="n">a</span> <span class="n">b</span><span class="p">)]))</span><span class="err">
</span></span></span></code></pre></div><p>Next, a function to generate all possible combinations of length <code>n</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-racket" data-lang="racket"><span class="line"><span class="cl"><span class="c1">; Recursively generate all possible operator combinations for n numbers</span>
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="k">define</span> <span class="p">(</span><span class="n">generate-operator-combinations</span> <span class="n">n</span> <span class="n">operators</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="k">if</span> <span class="p">(</span><span class="nb">=</span> <span class="n">n</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="o">&#39;</span><span class="p">(())</span>  <span class="c1">; base case: no operators needed for 1 number</span>
</span></span><span class="line"><span class="cl">      <span class="p">(</span><span class="k">for*/list</span> <span class="p">([</span><span class="n">ops</span> <span class="p">(</span><span class="n">generate-operator-combinations</span> <span class="p">(</span><span class="nb">-</span> <span class="n">n</span> <span class="mi">1</span><span class="p">)</span> <span class="n">operators</span><span class="p">)]</span> <span class="c1">; outer loop</span>
</span></span><span class="line"><span class="cl">                  <span class="p">[</span><span class="n">new-op</span> <span class="n">operators</span><span class="p">])</span> <span class="c1">; inner loop</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="nb">append</span> <span class="n">ops</span> <span class="p">(</span><span class="nb">list</span> <span class="n">new-op</span><span class="p">)))))</span><span class="err">
</span></span></span></code></pre></div><p><code>for*/list</code> is a way to create a nested for-loop with a single function. The one above is equivalent to the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-racket" data-lang="racket"><span class="line"><span class="cl"><span class="p">(</span><span class="k">for/list</span> <span class="p">([</span><span class="n">ops</span> <span class="p">(</span><span class="n">generate-operator-combinations</span> <span class="p">(</span><span class="nb">-</span> <span class="n">n</span> <span class="mi">1</span><span class="p">)</span> <span class="n">operators</span><span class="p">)])</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="k">for/list</span> <span class="p">[</span><span class="n">new-op</span> <span class="n">operators</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="nb">append</span> <span class="n">ops</span> <span class="p">(</span><span class="nb">list</span> <span class="n">new-op</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span><span class="err">
</span></span></span></code></pre></div><p>Nested for loops are the hammer I use to solve all of these challenges, so I thought this was really cool.</p>
<p>The last helper function we need is <code>try-combination</code>, which will return the result from trying a list of operators on a list of numbers. Per the instructions, equations must be evaluated left to right.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-racket" data-lang="racket"><span class="line"><span class="cl"><span class="p">(</span><span class="k">define</span> <span class="p">(</span><span class="n">try-combination</span> <span class="n">ns</span> <span class="n">ops</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="k">let</span> <span class="n">loop</span> <span class="p">([</span><span class="n">result</span> <span class="p">(</span><span class="nb">first</span> <span class="n">ns</span><span class="p">)]</span> <span class="c1">; Start with first number</span>
</span></span><span class="line"><span class="cl">             <span class="p">[</span><span class="n">rest-nums</span> <span class="p">(</span><span class="nb">rest</span> <span class="n">ns</span><span class="p">)]</span> <span class="c1">; Rest of the numbers</span>
</span></span><span class="line"><span class="cl">             <span class="p">[</span><span class="n">rest-ops</span> <span class="n">ops</span><span class="p">])</span> <span class="c1">; All operators</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="k">if</span> <span class="p">(</span><span class="nb">null?</span> <span class="n">rest-nums</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="c1">; If there are no more numbers, return the result</span>
</span></span><span class="line"><span class="cl">        <span class="n">result</span> 
</span></span><span class="line"><span class="cl">        <span class="c1">; Otherwise apply operator to result &amp; next number</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">loop</span> <span class="p">(</span><span class="n">apply-op</span> <span class="p">(</span><span class="nb">first</span> <span class="n">rest-ops</span><span class="p">)</span> 
</span></span><span class="line"><span class="cl">                       <span class="n">result</span>
</span></span><span class="line"><span class="cl">                       <span class="p">(</span><span class="nb">first</span> <span class="n">rest-nums</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">              <span class="p">(</span><span class="nb">rest</span> <span class="n">rest-nums</span><span class="p">)</span> <span class="c1">; Continue with remaining numbers</span>
</span></span><span class="line"><span class="cl">              <span class="p">(</span><span class="nb">rest</span> <span class="n">rest-ops</span><span class="p">)))))</span> <span class="c1">; Continue with remaining operators</span>
</span></span></code></pre></div><p>The <a href="https://docs.racket-lang.org/guide/let.html#(part._.Named_let)"><code>let</code></a> here defines a function named <code>loop</code> and immediately calls it with some initial values (so <code>[ result (first ns)]</code> means <code>[parameter-name initial-argument]</code>). It then recursively works down the numbers and operators, accumulating the final result.</p>
<p>Now we can define <code>sum-calibration-equations</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-racket" data-lang="racket"><span class="line"><span class="cl"><span class="p">(</span><span class="k">define</span> <span class="p">(</span><span class="n">sum-calibration-equations</span> <span class="n">input</span> <span class="n">operators</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="k">for*/sum</span> <span class="p">([</span><span class="n">calibration-equation</span> <span class="n">input</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="k">for/fold</span> <span class="p">([</span><span class="n">result</span> <span class="mi">0</span><span class="p">])</span> <span class="c1">; assume result is 0, i.e. no valid combo</span>
</span></span><span class="line"><span class="cl">              <span class="p">([</span><span class="n">combo</span> <span class="p">(</span><span class="n">generate-operator-combinations</span>
</span></span><span class="line"><span class="cl">                     <span class="p">(</span><span class="nb">-</span> <span class="p">(</span><span class="nb">length</span> <span class="n">calibration-equation</span><span class="p">)</span> <span class="mi">1</span><span class="p">)</span> <span class="c1">; 1 fewer op than we have numbers</span>
</span></span><span class="line"><span class="cl">                     <span class="n">operators</span><span class="p">)])</span>
</span></span><span class="line"><span class="cl">    <span class="kd">#:break</span> <span class="p">(</span><span class="nb">not</span> <span class="p">(</span><span class="nb">zero?</span> <span class="n">result</span><span class="p">))</span> <span class="c1">; break when we find a non-zero result</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="k">if</span> <span class="p">(</span><span class="nb">=</span> <span class="p">(</span><span class="nb">first</span> <span class="n">calibration-equation</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">           <span class="p">(</span><span class="n">try-combination</span> <span class="p">(</span><span class="nb">rest</span> <span class="n">calibration-equation</span><span class="p">)</span> <span class="n">combo</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="c1">; ^ first and rest used to get answer and equation</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="nb">first</span> <span class="n">calibration-equation</span><span class="p">)</span> <span class="c1">; found a valid answer, return it</span>
</span></span><span class="line"><span class="cl">        <span class="n">result</span><span class="p">))))</span><span class="err">
</span></span></span></code></pre></div><p>With Racket, I find myself writing code from the inside out. The core of this function is the <code>if</code> statement, which checks if a particular operator combo produces a valid result for a particular equation. I initially wrapped that in a <code>for*/list</code>, but that gave me every valid result when I only needed one per equation. So the AI suggested I replace it with a <a href="https://docs.racket-lang.org/reference/for.html#%28form._%28%28lib._racket%2Fprivate%2Fbase..rkt%29._for%2Ffold%29%29"><code>for/fold</code></a> that uses this funky syntax to break. <code>for/fold</code> works a bit like <code>let</code> above.</p>
<p>Finally, I wrapped the <code>for/fold</code> that produced the answers for valid equations in a <code>for/sum</code> to add them all together and get the challenge solution.</p>
<p>I had an inkling that the second part of the challenge would involve additional operators and wrote my code with that assumption. I was correct &ndash; part 2 requires you to add the <code>||</code> or concatenation operator, which works like this:</p>
<pre tabindex="0"><code>123 || 456
=&gt; 123456
</code></pre><p>To support this, we just have to add a third option to <code>apply-op</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-racket" data-lang="racket"><span class="line"><span class="cl"><span class="p">(</span><span class="k">define</span> <span class="p">(</span><span class="n">apply-op</span> <span class="n">op</span> <span class="n">a</span> <span class="n">b</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="k">case</span> <span class="n">op</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span><span class="o">&#39;</span><span class="ss">+</span> <span class="p">(</span><span class="nb">+</span> <span class="n">a</span> <span class="n">b</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span><span class="o">&#39;</span><span class="ss">*</span> <span class="p">(</span><span class="nb">*</span> <span class="n">a</span> <span class="n">b</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span><span class="o">&#39;</span><span class="ss">||</span> <span class="p">(</span><span class="nb">string-&gt;number</span> <span class="c1">; Convert numbers to strings, concat, convert back </span>
</span></span><span class="line"><span class="cl">          <span class="p">(</span><span class="nb">string-append</span> 
</span></span><span class="line"><span class="cl">            <span class="p">(</span><span class="nb">number-&gt;string</span> <span class="n">a</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">(</span><span class="nb">number-&gt;string</span> <span class="n">b</span><span class="p">)))]))</span><span class="err">
</span></span></span></code></pre></div><p>Then we can call <code>sum-calibration-equations</code> again with our new operator:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-racket" data-lang="racket"><span class="line"><span class="cl"><span class="p">(</span><span class="n">sum-calibration-equations</span> <span class="n">equations</span> <span class="o">&#39;</span><span class="p">(</span><span class="ss">+</span> <span class="ss">*</span> <span class="ss">||</span><span class="p">))</span><span class="err">
</span></span></span></code></pre></div><p><a href="https://github.com/dmyates/advent-of-code-solutions/tree/master/2024/07%20-%20Racket"><strong>Full code on GitHub.</strong></a></p>
<p><strong>Closing thoughts</strong>: I feel like I&rsquo;m starting to see the much-vaunted power of Lisp through things like <code>for*/list</code> and <code>let</code> &ndash; these feel like programming idioms at such a high level that I hadn&rsquo;t even considered them idioms before. At a few points, I was tempted to mush more of my code together with local function definitions, but that quickly becomes the kind of mess of brackets that makes people complain about Lisp.</p>
<p>This problem was not complex enough to require any of Lisp&rsquo;s storied meta-programming, but I do feel as though a bit of the spirit of that came through in <code>apply-op</code>. It was immensely satisfying to complete part 2 with such a small change.</p>
<p>I had a bit of help from AI here, but not as much as I thought I&rsquo;d need. Mostly I had Claude explain different functions to me &ndash; <del>he</del> it is pretty good at Racket.</p>
<h1 id="day-8-io">
  <a class="heading-anchor" href="#day-8-io">#</a>
  Day 8: Io
</h1>
<p><a href="https://adventofcode.com/2024/day/8"><strong>Challenge</strong></a>: map out the antinodes for pairs of antennas.</p>
<p><a href="https://iolanguage.org/">Io</a> is another language I discovered through <a href="https://pragprog.com/titles/btlang/seven-languages-in-seven-weeks/"><em>Seven Programming Languages in Seven Weeks</em></a>. It&rsquo;s a prototype-based language, like JavaScript. Unlike JavaScript, it commits to the prototype thing, implements it well, and doesn&rsquo;t have any Java-style <code>class</code> syntax to hide it from you. As with Ruby, everything in Io is an object, even numbers. Method invocation (technically message-passing) is so central to the language that it&rsquo;s automatically invoked when two items are placed side-by-side. For example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-io" data-lang="io"><span class="line"><span class="cl"><span class="s">&#34;Hello world&#34;</span> <span class="n">println</span>
</span></span><span class="line"><span class="cl"><span class="o">==&gt;</span> <span class="n">Hello</span> <span class="n">world</span>
</span></span></code></pre></div><p>Chaining calls is simple:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-io" data-lang="io"><span class="line"><span class="cl"><span class="s">&#34;Hello world&#34;</span> <span class="n">exSlice</span><span class="o">(</span><span class="mf">6</span><span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="o">==&gt;</span> <span class="n">world</span>
</span></span><span class="line"><span class="cl"><span class="s">&#34;Hello world&#34;</span> <span class="n">exSlice</span><span class="o">(</span><span class="mf">6</span><span class="o">)</span> <span class="n">size</span>
</span></span><span class="line"><span class="cl"><span class="o">==&gt;</span> <span class="mf">5</span>
</span></span><span class="line"><span class="cl"><span class="s">&#34;Hello world&#34;</span> <span class="n">exSlice</span><span class="o">(</span><span class="mf">6</span><span class="o">)</span> <span class="n">size</span> <span class="n">sqrt</span>
</span></span><span class="line"><span class="cl"><span class="o">==&gt;</span> <span class="mf">2.2360679774997898</span>
</span></span></code></pre></div><p>Io has very minimal syntax and implements everything with this kind of message passing. For example, if-else control flow can look like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-io" data-lang="io"><span class="line"><span class="cl"><span class="o">(</span><span class="n">x</span> <span class="o">==</span> <span class="mf">3</span><span class="o">)</span> <span class="n">ifTrue</span><span class="o">(</span><span class="s">&#34;Yes&#34;</span> <span class="n">println</span><span class="o">)</span> <span class="n">ifFalse</span><span class="o">(</span><span class="s">&#34;No&#34;</span> <span class="n">println</span><span class="o">)</span>
</span></span></code></pre></div><p>On the surface, working with operators (<code>a := 1</code>, <code>1 + 2</code>, <code>5 - 4</code>, <code>10 &gt; 1</code>, etc) appears to be an exception, but the operators themselves are only a thin layer of syntactic sugar over message passing. <code>1 + 2</code> sends the <code>+</code> message to <code>1</code> with <code>2</code> as an argument i.e. <code>1 +(2)</code>, and <a href="https://what.happens.when.computer/2015-11-20/io-basics/"><code>:=</code> wraps a setter method</a>. Io even lets you <a href="https://chrisumbel.com/article/io_language_prototype/">define your own operators</a> by adding them to the <code>OperatorTable</code> (which is used to determine precedence). Hmm&hellip;</p>
<p>This is another 2D grid challenge, so a bounds-checking operator could be quite useful. Let&rsquo;s create one:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-io" data-lang="io"><span class="line"><span class="cl"><span class="c1">// Bounds-checking operator
</span></span></span><span class="line"><span class="cl"><span class="n">OperatorTable</span> <span class="n">addOperator</span><span class="o">(</span><span class="s">&#34;&lt;&gt;&#34;</span><span class="o">,</span> <span class="mf">5</span><span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">// ^ put it in position 5 in the table so it will be
</span></span></span><span class="line"><span class="cl"><span class="c1">// evaluated with other comparison operators
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">Number</span> <span class="o">&lt;&gt;</span> <span class="o">:=</span> <span class="k">method</span><span class="o">(</span><span class="n">bound</span><span class="o">,</span> <span class="o">(</span><span class="n">self</span> <span class="o">&gt;=</span> <span class="mf">0</span> <span class="n">and</span> <span class="n">self</span> <span class="o">&lt;</span> <span class="n">bound</span><span class="o">))</span>
</span></span></code></pre></div><p>Now we can use <code>a &lt;&gt; b</code> to check if <code>a</code> is less than <code>b</code> and non-negative.
Because of how Io compiles code, this needs to go <a href="https://stackoverflow.com/questions/10383065/why-does-the-io-repl-and-the-interpreter-give-me-two-different-values">in a separate file from the implementation</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-io" data-lang="io"><span class="line"><span class="cl"><span class="k">doFile</span><span class="o">(</span><span class="s">&#34;operators.io&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="k">doFile</span><span class="o">(</span><span class="s">&#34;antinodes.io&#34;</span><span class="o">)</span>
</span></span></code></pre></div><p>In <code>antinodes.io</code>, we&rsquo;ll start by reading in the input file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-io" data-lang="io"><span class="line"><span class="cl"><span class="n">inputFile</span> <span class="o">:=</span> <span class="nb">File</span> <span class="n">with</span><span class="o">(</span><span class="s">&#34;input.txt&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="n">inputFile</span> <span class="n">open</span>
</span></span><span class="line"><span class="cl"><span class="n">inputText</span> <span class="o">:=</span> <span class="n">inputFile</span> <span class="n">readToEnd</span>
</span></span><span class="line"><span class="cl"><span class="n">inputFile</span> <span class="n">close</span>
</span></span></code></pre></div><p>We then need to find all the antennas.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-io" data-lang="io"><span class="line"><span class="cl"><span class="n">findAntennas</span> <span class="o">:=</span> <span class="k">method</span><span class="o">(</span><span class="n">input</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">antennas</span> <span class="o">:=</span> <span class="nb">List</span> <span class="k">clone</span>
</span></span><span class="line"><span class="cl">    <span class="n">input</span> <span class="n">split</span><span class="o">(</span><span class="s">&#34;\n&#34;</span><span class="o">)</span> <span class="n">foreach</span><span class="o">(</span><span class="n">y</span><span class="o">,</span> <span class="n">line</span><span class="o">,</span> <span class="c1"># foreach provides an index and item
</span></span></span><span class="line"><span class="cl">        <span class="n">line</span> <span class="n">foreach</span><span class="o">(</span><span class="n">x</span><span class="o">,</span> <span class="n">cell</span><span class="o">,</span> <span class="c1">// Python- and C-style comments are both valid
</span></span></span><span class="line"><span class="cl">            <span class="k">if</span><span class="o">(</span><span class="n">cell</span> <span class="n">asCharacter</span> <span class="o">!=</span> <span class="s">&#34;.&#34;</span><span class="o">,</span> <span class="c1">// without asCharacter we&#39;d get 46
</span></span></span><span class="line"><span class="cl">                <span class="n">antennas</span> <span class="n">append</span><span class="o">(</span><span class="nb">List</span> <span class="k">clone</span> <span class="n">append</span> <span class="o">(</span><span class="n">x</span><span class="o">,</span> <span class="n">y</span><span class="o">,</span> <span class="n">cell</span><span class="o">))</span>
</span></span><span class="line"><span class="cl">            <span class="o">)</span>
</span></span><span class="line"><span class="cl">        <span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">antennas</span>
</span></span><span class="line"><span class="cl"><span class="o">)</span>
</span></span></code></pre></div><p>As Io is a prototype language, we create new objects by cloning an existing one, usually a base object. <code>List clone</code> creates a new empty list, and <code>List clone append(1, 2, 3)</code> creates a new list containing <code>(1, 2, 3)</code>.</p>
<p>Now we can calculate the positions of the antinodes. I found the challenge description a bit confusing, but the diagrams showed that each antinode should be as far from its antenna as it is from the other antenna, but in the other direction. Maybe there&rsquo;s no way to explain that nicely in words.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-io" data-lang="io"><span class="line"><span class="cl"><span class="n">calculateAntinodes</span> <span class="o">:=</span> <span class="k">method</span><span class="o">(</span><span class="n">antennas</span><span class="o">,</span> <span class="n">mapWidth</span><span class="o">,</span> <span class="n">mapHeight</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">antinodes</span> <span class="o">:=</span> <span class="nb">List</span> <span class="k">clone</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">isValidPosition</span> <span class="o">:=</span> <span class="k">method</span><span class="o">(</span><span class="n">x</span><span class="o">,</span> <span class="n">y</span><span class="o">,</span> <span class="n">width</span><span class="o">,</span> <span class="n">height</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">x</span> <span class="o">&lt;&gt;</span> <span class="n">width</span> <span class="n">and</span> <span class="n">y</span> <span class="o">&lt;&gt;</span> <span class="n">height</span>
</span></span><span class="line"><span class="cl">    <span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">addAntinodeIfValid</span> <span class="o">:=</span> <span class="k">method</span><span class="o">(</span><span class="n">x</span><span class="o">,</span> <span class="n">y</span><span class="o">,</span> <span class="n">width</span><span class="o">,</span> <span class="n">height</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span><span class="o">(</span><span class="n">isValidPosition</span><span class="o">(</span><span class="n">x</span><span class="o">,</span> <span class="n">y</span><span class="o">,</span> <span class="n">width</span><span class="o">,</span> <span class="n">height</span><span class="o">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">antinode</span> <span class="o">:=</span> <span class="nb">List</span> <span class="k">clone</span> <span class="n">append</span><span class="o">(</span><span class="n">x</span><span class="o">,</span> <span class="n">y</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span><span class="o">(</span><span class="n">antinodes</span> <span class="n">detect</span><span class="o">(</span><span class="n">n</span><span class="o">,</span> <span class="n">n</span> <span class="o">==</span> <span class="n">antinode</span><span class="o">)</span> <span class="o">==</span> <span class="no">nil</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">antinodes</span> <span class="n">append</span><span class="o">(</span><span class="n">antinode</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">            <span class="o">)</span>
</span></span><span class="line"><span class="cl">        <span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">processAntinodePairs</span> <span class="o">:=</span> <span class="k">method</span><span class="o">(</span><span class="n">a</span><span class="o">,</span> <span class="n">b</span><span class="o">,</span> <span class="n">width</span><span class="o">,</span> <span class="n">height</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">x1</span> <span class="o">:=</span> <span class="n">a</span> <span class="n">at</span><span class="o">(</span><span class="mf">0</span><span class="o">);</span> <span class="n">y1</span> <span class="o">:=</span> <span class="n">a</span> <span class="n">at</span><span class="o">(</span><span class="mf">1</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">x2</span> <span class="o">:=</span> <span class="n">b</span> <span class="n">at</span><span class="o">(</span><span class="mf">0</span><span class="o">);</span> <span class="n">y2</span> <span class="o">:=</span> <span class="n">b</span> <span class="n">at</span><span class="o">(</span><span class="mf">1</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">dx</span> <span class="o">:=</span> <span class="n">x2</span> <span class="o">-</span> <span class="n">x1</span><span class="o">;</span> <span class="n">dy</span> <span class="o">:=</span> <span class="n">y2</span> <span class="o">-</span> <span class="n">y1</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">addAntinodeIfValid</span><span class="o">(</span><span class="n">x1</span> <span class="o">-</span> <span class="n">dx</span><span class="o">,</span> <span class="n">y1</span> <span class="o">-</span> <span class="n">dy</span><span class="o">,</span> <span class="n">width</span><span class="o">,</span> <span class="n">height</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">addAntinodeIfValid</span><span class="o">(</span><span class="n">x2</span> <span class="o">+</span> <span class="n">dx</span><span class="o">,</span> <span class="n">y2</span> <span class="o">+</span> <span class="n">dy</span><span class="o">,</span> <span class="n">width</span><span class="o">,</span> <span class="n">height</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">antennas</span> <span class="n">foreach</span><span class="o">(</span><span class="n">i</span><span class="o">,</span> <span class="n">a</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">antennas</span> <span class="n">foreach</span><span class="o">(</span><span class="n">j</span><span class="o">,</span> <span class="n">b</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span><span class="o">(</span><span class="n">a</span> <span class="o">!=</span> <span class="n">b</span> <span class="n">and</span> <span class="n">a</span> <span class="n">at</span><span class="o">(</span><span class="mf">2</span><span class="o">)</span> <span class="o">==</span> <span class="n">b</span> <span class="n">at</span><span class="o">(</span><span class="mf">2</span><span class="o">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">processAntinodePairs</span><span class="o">(</span><span class="n">a</span><span class="o">,</span> <span class="n">b</span><span class="o">,</span> <span class="n">mapWidth</span><span class="o">,</span> <span class="n">mapHeight</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">            <span class="o">)</span>
</span></span><span class="line"><span class="cl">        <span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">antinodes</span>
</span></span><span class="line"><span class="cl"><span class="o">)</span>
</span></span></code></pre></div><p>Finally, we put it all together with this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-io" data-lang="io"><span class="line"><span class="cl"><span class="n">inputFile</span> <span class="o">:=</span> <span class="nb">File</span> <span class="n">with</span><span class="o">(</span><span class="s">&#34;input.txt&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="n">inputFile</span> <span class="n">open</span>
</span></span><span class="line"><span class="cl"><span class="n">inputText</span> <span class="o">:=</span> <span class="n">inputFile</span> <span class="n">readToEnd</span>
</span></span><span class="line"><span class="cl"><span class="n">inputFile</span> <span class="n">close</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">antennas</span> <span class="o">:=</span> <span class="n">findAntennas</span><span class="o">(</span><span class="n">inputText</span><span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="n">mapWidth</span> <span class="o">:=</span> <span class="n">inputText</span> <span class="n">split</span><span class="o">(</span><span class="s">&#34;\n&#34;</span><span class="o">)</span> <span class="n">at</span><span class="o">(</span><span class="mf">0</span><span class="o">)</span> <span class="n">size</span>
</span></span><span class="line"><span class="cl"><span class="n">mapHeight</span> <span class="o">:=</span> <span class="n">inputText</span> <span class="n">split</span><span class="o">(</span><span class="s">&#34;\n&#34;</span><span class="o">)</span> <span class="n">size</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">calculateAntinodes</span><span class="o">(</span><span class="n">antennas</span><span class="o">,</span> <span class="n">mapWidth</span><span class="o">,</span> <span class="n">mapHeight</span><span class="o">)</span> <span class="n">size</span> <span class="n">println</span>
</span></span></code></pre></div><p>For the second part of the challenge, we need to get antinodes at any (map-bound) distance from their antennas, as well as antinodes on the antennas themselves. This can be integrated into <code>calculateAntinodes</code> with an extra parameter:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-io" data-lang="io"><span class="line"><span class="cl"><span class="n">calculateAntinodes</span> <span class="o">:=</span> <span class="k">method</span><span class="o">(</span><span class="n">antennas</span><span class="o">,</span> <span class="n">mapWidth</span><span class="o">,</span> <span class="n">mapHeight</span><span class="o">,</span> <span class="n">partTwo</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">antinodes</span> <span class="o">:=</span> <span class="nb">List</span> <span class="k">clone</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">isValidPosition</span> <span class="o">:=</span> <span class="k">method</span><span class="o">(</span><span class="n">x</span><span class="o">,</span> <span class="n">y</span><span class="o">,</span> <span class="n">width</span><span class="o">,</span> <span class="n">height</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">x</span> <span class="o">&lt;&gt;</span> <span class="n">width</span> <span class="n">and</span> <span class="n">y</span> <span class="o">&lt;&gt;</span> <span class="n">height</span>
</span></span><span class="line"><span class="cl">    <span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">addAntinodeIfValid</span> <span class="o">:=</span> <span class="k">method</span><span class="o">(</span><span class="n">x</span><span class="o">,</span> <span class="n">y</span><span class="o">,</span> <span class="n">width</span><span class="o">,</span> <span class="n">height</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span><span class="o">(</span><span class="n">isValidPosition</span><span class="o">(</span><span class="n">x</span><span class="o">,</span> <span class="n">y</span><span class="o">,</span> <span class="n">width</span><span class="o">,</span> <span class="n">height</span><span class="o">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">antinode</span> <span class="o">:=</span> <span class="nb">List</span> <span class="k">clone</span> <span class="n">append</span><span class="o">(</span><span class="n">x</span><span class="o">,</span> <span class="n">y</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span><span class="o">(</span><span class="n">antinodes</span> <span class="n">detect</span><span class="o">(</span><span class="n">n</span><span class="o">,</span> <span class="n">n</span> <span class="o">==</span> <span class="n">antinode</span><span class="o">)</span> <span class="o">==</span> <span class="no">nil</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">antinodes</span> <span class="n">append</span><span class="o">(</span><span class="n">antinode</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">            <span class="o">)</span>
</span></span><span class="line"><span class="cl">        <span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">processAntinodePairs</span> <span class="o">:=</span> <span class="k">method</span><span class="o">(</span><span class="n">a</span><span class="o">,</span> <span class="n">b</span><span class="o">,</span> <span class="n">width</span><span class="o">,</span> <span class="n">height</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">x1</span> <span class="o">:=</span> <span class="n">a</span> <span class="n">at</span><span class="o">(</span><span class="mf">0</span><span class="o">);</span> <span class="n">y1</span> <span class="o">:=</span> <span class="n">a</span> <span class="n">at</span><span class="o">(</span><span class="mf">1</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">x2</span> <span class="o">:=</span> <span class="n">b</span> <span class="n">at</span><span class="o">(</span><span class="mf">0</span><span class="o">);</span> <span class="n">y2</span> <span class="o">:=</span> <span class="n">b</span> <span class="n">at</span><span class="o">(</span><span class="mf">1</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">dx</span> <span class="o">:=</span> <span class="n">x2</span> <span class="o">-</span> <span class="n">x1</span><span class="o">;</span> <span class="n">dy</span> <span class="o">:=</span> <span class="n">y2</span> <span class="o">-</span> <span class="n">y1</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span><span class="o">(</span><span class="n">partTwo</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># Part 2: Add antennas as antinodes and calculate full path
</span></span></span><span class="line"><span class="cl">            <span class="n">addAntinodeIfValid</span><span class="o">(</span><span class="n">x1</span><span class="o">,</span> <span class="n">y1</span><span class="o">,</span> <span class="n">width</span><span class="o">,</span> <span class="n">height</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">addAntinodeIfValid</span><span class="o">(</span><span class="n">x2</span><span class="o">,</span> <span class="n">y2</span><span class="o">,</span> <span class="n">width</span><span class="o">,</span> <span class="n">height</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="c1"># Find antinodes in both directions
</span></span></span><span class="line"><span class="cl">            <span class="n">pos</span> <span class="o">:=</span> <span class="nb">list</span><span class="o">(</span><span class="n">x1</span><span class="o">,</span> <span class="n">y1</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">while</span><span class="o">(</span><span class="n">isValidPosition</span><span class="o">(</span><span class="n">pos</span> <span class="n">at</span><span class="o">(</span><span class="mf">0</span><span class="o">)</span> <span class="o">-</span> <span class="n">dx</span><span class="o">,</span> <span class="n">pos</span> <span class="n">at</span><span class="o">(</span><span class="mf">1</span><span class="o">)</span> <span class="o">-</span> <span class="n">dy</span><span class="o">,</span> <span class="n">width</span><span class="o">,</span> <span class="n">height</span><span class="o">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">pos</span> <span class="o">=</span> <span class="nb">list</span><span class="o">(</span><span class="n">pos</span> <span class="n">at</span><span class="o">(</span><span class="mf">0</span><span class="o">)</span> <span class="o">-</span> <span class="n">dx</span><span class="o">,</span> <span class="n">pos</span> <span class="n">at</span><span class="o">(</span><span class="mf">1</span><span class="o">)</span> <span class="o">-</span> <span class="n">dy</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">                <span class="n">addAntinodeIfValid</span><span class="o">(</span><span class="n">pos</span> <span class="n">at</span><span class="o">(</span><span class="mf">0</span><span class="o">),</span> <span class="n">pos</span> <span class="n">at</span><span class="o">(</span><span class="mf">1</span><span class="o">),</span> <span class="n">width</span><span class="o">,</span> <span class="n">height</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">            <span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="n">pos</span> <span class="o">=</span> <span class="nb">list</span><span class="o">(</span><span class="n">x2</span><span class="o">,</span> <span class="n">y2</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">while</span><span class="o">(</span><span class="n">isValidPosition</span><span class="o">(</span><span class="n">pos</span> <span class="n">at</span><span class="o">(</span><span class="mf">0</span><span class="o">)</span> <span class="o">+</span> <span class="n">dx</span><span class="o">,</span> <span class="n">pos</span> <span class="n">at</span><span class="o">(</span><span class="mf">1</span><span class="o">)</span> <span class="o">+</span> <span class="n">dy</span><span class="o">,</span> <span class="n">width</span><span class="o">,</span> <span class="n">height</span><span class="o">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">pos</span> <span class="o">=</span> <span class="nb">list</span><span class="o">(</span><span class="n">pos</span> <span class="n">at</span><span class="o">(</span><span class="mf">0</span><span class="o">)</span> <span class="o">+</span> <span class="n">dx</span><span class="o">,</span> <span class="n">pos</span> <span class="n">at</span><span class="o">(</span><span class="mf">1</span><span class="o">)</span> <span class="o">+</span> <span class="n">dy</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">                <span class="n">addAntinodeIfValid</span><span class="o">(</span><span class="n">pos</span> <span class="n">at</span><span class="o">(</span><span class="mf">0</span><span class="o">),</span> <span class="n">pos</span> <span class="n">at</span><span class="o">(</span><span class="mf">1</span><span class="o">),</span> <span class="n">width</span><span class="o">,</span> <span class="n">height</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">            <span class="o">)</span>
</span></span><span class="line"><span class="cl">            <span class="o">,</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># Part 1: Only check one point in each direction
</span></span></span><span class="line"><span class="cl">            <span class="n">addAntinodeIfValid</span><span class="o">(</span><span class="n">x1</span> <span class="o">-</span> <span class="n">dx</span><span class="o">,</span> <span class="n">y1</span> <span class="o">-</span> <span class="n">dy</span><span class="o">,</span> <span class="n">width</span><span class="o">,</span> <span class="n">height</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">addAntinodeIfValid</span><span class="o">(</span><span class="n">x2</span> <span class="o">+</span> <span class="n">dx</span><span class="o">,</span> <span class="n">y2</span> <span class="o">+</span> <span class="n">dy</span><span class="o">,</span> <span class="n">width</span><span class="o">,</span> <span class="n">height</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">        <span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">antennas</span> <span class="n">foreach</span><span class="o">(</span><span class="n">i</span><span class="o">,</span> <span class="n">a</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">antennas</span> <span class="n">foreach</span><span class="o">(</span><span class="n">j</span><span class="o">,</span> <span class="n">b</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span><span class="o">(</span><span class="n">a</span> <span class="o">!=</span> <span class="n">b</span> <span class="n">and</span> <span class="n">a</span> <span class="n">at</span><span class="o">(</span><span class="mf">2</span><span class="o">)</span> <span class="o">==</span> <span class="n">b</span> <span class="n">at</span><span class="o">(</span><span class="mf">2</span><span class="o">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">processAntinodePairs</span><span class="o">(</span><span class="n">a</span><span class="o">,</span> <span class="n">b</span><span class="o">,</span> <span class="n">mapWidth</span><span class="o">,</span> <span class="n">mapHeight</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">            <span class="o">)</span>
</span></span><span class="line"><span class="cl">        <span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">antinodes</span>
</span></span><span class="line"><span class="cl"><span class="o">)</span>
</span></span></code></pre></div><p><a href="https://github.com/dmyates/advent-of-code-solutions/tree/master/2024/08%20-%20Io"><strong>Full code on GitHub.</strong></a></p>
<p><strong>Closing thoughts:</strong> Io code looks a little strange at first, but once you get into it it doesn&rsquo;t feel too different from any other high-level imperative language. I&rsquo;m glad I used it right after Racket, because the languages have some interesting similarities and contrasts. Io is about as syntactically minimal as Lisp, but it&rsquo;s much easier to read for two reasons:</p>
<ol>
<li>Logic flows left to right.</li>
<li>Function execution is signalled by white space rather than brackets (<code>&quot;Hello world&quot; println</code> vs <code>(display &quot;Hello world&quot;)</code>).</li>
</ol>
<p>Fittingly, Io has similarly powerful reflection and metaprogramming abilities, which I didn&rsquo;t really need for this simple challenge. I also made no use of its concurrency features or even custom objects, but I&rsquo;m glad I found a use for <code>OperatorTable</code>.</p>
<p>Claude surprised me with its familiarity with this relatively obscure language, though I didn&rsquo;t use it much besides having it do some refactoring. The <a href="https://iolanguage.org/tutorial.html">official Io tutorial</a>, which is more of a cheatsheet, was also very helpful.</p>
<h1 id="day-9-j">
  <a class="heading-anchor" href="#day-9-j">#</a>
  Day 9: J
</h1>
<p><a href="https://adventofcode.com/2024/day/9"><strong>Challenge</strong></a>: pack items from the right side of a list into empty spaces on the left.</p>
<p>For this challenge, I chose J, a language I&rsquo;ve been intrigued by since reading about it on James Hague&rsquo;s <a href="http://prog21.dadgum.com/">Programming in the 21st Century blog</a>. It&rsquo;s a very terse language capable of producing programs that look like a jumbled mess of characters, and so fairly intimidating at first glance.</p>
<p>J is an <a href="https://en.wikipedia.org/wiki/Array_programming">array programming language</a>, in the lineage of APL. Unlike APL, it does not require a special keyboard to program in, as it only uses ASCII characters. An array programming language felt like a good choice for this challenge, as the input is a single very long list.</p>
<p>As the <a href="https://www.jsoftware.com/help/primer/contents.htm">J Primer</a> will tell you, users of J prefer to use natural language terminology when talking about elements of J. Sentences (statements) are made up of words, some of which are verbs (functions), others of which are nouns (variables/literals) and <a href="https://code.jsoftware.com/wiki/Vocabulary/PartsOfSpeech">still others</a> of which are adverbs or even <em>copulas</em>.</p>
<p>Despite all this talk of words, most of J&rsquo;s syntax consists of symbols. There are single symbols, such as <code>{</code>, <code>+</code>, <code>#</code> and <code>%</code>, all of which do slightly different things than you might expect from other languages. Then there are words made out of groups of symbols, like <code>|.</code> <code>&lt;&quot;</code> and <code>~:</code>. Occasionally you get single letters followed by dots: <code>I.</code> and <code>E.</code>. The things that look most like words, <code>for.</code> and <code>if.</code> and <code>break.</code>, are not nouns or verbs, but control structures.</p>
<p>Each verb can have two forms: monadic and dyadic. The first takes one argument and the second takes two. These forms tend to do related but different things. For example, the monadic <code># a</code> will count the values in <code>a</code>, and the dyadic <code>3 # a</code> will create three copies of <code>a</code>.</p>
<p>This may start to make more sense as we get to some actual code. So let&rsquo;s read the input file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-j" data-lang="j"><span class="line"><span class="cl"><span class="nv">input</span> <span class="o">=:</span> <span class="nv">fread</span> <span class="s">&#39;input.txt&#39;</span> <span class="c1">NB. read file</span>
</span></span><span class="line"><span class="cl"><span class="nv">ints</span> <span class="o">=:</span> <span class="o">&#34;.</span> <span class="nv">each</span> <span class="nv">input</span> <span class="c1">NB. convert each character to a number</span>
</span></span></code></pre></div><p>The first line is simple enough, but what does <code>&quot;.</code> mean? For the answer to this and many more questions we&rsquo;ll have throughout this section, I direct you to <a href="https://code.jsoftware.com/wiki/NuVoc">NuVoc</a>, a syntax cheat-sheet on the J wiki that I found invaluable for completing this challenge. This is the verb <a href="https://code.jsoftware.com/wiki/Vocabulary/quotedot">Do</a>, which executes a sentence. If the sentence to be executed is a string containing numeric character(s), <em>executing</em> it will turn it into number.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-j" data-lang="j"><span class="line"><span class="cl">   <span class="o">&#34;.</span> <span class="s">&#39;1&#39;</span>
</span></span><span class="line"><span class="cl"><span class="mi">1</span>
</span></span></code></pre></div><p>Now let&rsquo;s write a function to expand the compressed representation of our disk map, as done in the challenge instructions.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-j" data-lang="j"><span class="line"><span class="cl"><span class="c1">NB. Expand to disk map</span>
</span></span><span class="line"><span class="cl"><span class="nv">expand</span> <span class="o">=:</span> <span class="nf">3 : 0</span>
</span></span><span class="line"><span class="cl">    <span class="nv">fileID</span> <span class="o">=.</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">    <span class="nv">disk</span> <span class="o">=.</span> <span class="mi">0</span> <span class="o">$</span> <span class="nv">a</span><span class="o">:</span> <span class="c1">NB. empty array</span>
</span></span><span class="line"><span class="cl">    <span class="nv">for_size</span><span class="o">.</span> <span class="nd">y</span> <span class="nl">do.</span>
</span></span><span class="line"><span class="cl">        <span class="nl">if.</span> <span class="mi">2</span> <span class="o">|</span> <span class="nv">size_index</span> <span class="nl">do.</span> <span class="c1">NB. free space</span>
</span></span><span class="line"><span class="cl">            <span class="c1">NB. append a boxed . to disk SIZE times</span>
</span></span><span class="line"><span class="cl">            <span class="nv">disk</span> <span class="o">=.</span> <span class="nv">disk</span> <span class="o">,</span> <span class="p">(</span><span class="o">;</span> <span class="nv">size</span><span class="p">)</span> <span class="o">#</span> <span class="o">&lt;</span><span class="s">&#39;.&#39;</span>
</span></span><span class="line"><span class="cl">        <span class="nl">else.</span> <span class="c1">NB. file</span>
</span></span><span class="line"><span class="cl">            <span class="c1">NB. append the boxed fileID to disk SIZE times</span>
</span></span><span class="line"><span class="cl">            <span class="nv">disk</span> <span class="o">=.</span> <span class="nv">disk</span> <span class="o">,</span> <span class="p">(</span><span class="o">;</span> <span class="nv">size</span><span class="p">)</span> <span class="o">#</span> <span class="o">&lt;&#34;:</span> <span class="nv">fileID</span>
</span></span><span class="line"><span class="cl">            <span class="nv">fileID</span> <span class="o">=.</span> <span class="nv">fileID</span> <span class="o">+</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">        <span class="nl">end.</span>
</span></span><span class="line"><span class="cl">    <span class="nl">end.</span>
</span></span><span class="line"><span class="cl">    <span class="nv">disk</span>
</span></span><span class="line"><span class="cl"><span class="nl">)</span>
</span></span></code></pre></div><p>What does any of this mean?  Like many functional languages, code here must be read right to left. Beyond that, writing J is an exercise in memorising symbols.</p>
<ul>
<li><code>3 : 0</code> is used to define a monadic <del>function</del> verb. We could use <code>4 : 0</code> to define a dyadic verb. Monadic verbs take the argument <code>y</code> and dyadic verbs take the arguments <code>x</code> and <code>y</code>.</li>
<li><code>=.</code> is used to define a local variable. <code>=:</code> is for globals.</li>
<li>Dyadic <code>|</code> is modulo.</li>
<li>Dyadic <code>,</code> is append (used for both arrays and strings).</li>
<li>Monadic <code>&lt;</code> is used to <a href="https://code.jsoftware.com/wiki/Vocabulary/lt"><em>box</em></a> some input. Boxing allows us to make arrays containing different types. So we can make a single array containing numbers for the file IDs (which start at 0) and strings (<code>'.'</code>) for the empty spaces. Without boxing, we&rsquo;d need to make a string, which makes shifting things around problematic for file IDs greater than 9.</li>
<li>Monadic <code>;</code> is used to unbox, or <a href="https://code.jsoftware.com/wiki/Vocabulary/semi"><em>raze</em></a> values. We have to unbox numbers before we can do numeric operations on them.</li>
<li>Dyadic <code>#</code> makes copies. For example, <code>3 # '.'</code> would produce <code>...</code>.</li>
<li><code>)</code> signals the end of the verb definition. It is intentionally unmatched.</li>
</ul>
<p>Next, we&rsquo;ll shift the file IDs on the right side of our disk map to free spaces on the left. We&rsquo;ll do this by making lists of all the indices of spaces and files, reversing the list of file indices, and then using them to make substitutions.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-j" data-lang="j"><span class="line"><span class="cl"><span class="c1">NB. Move file segments into free spaces</span>
</span></span><span class="line"><span class="cl"><span class="nv">fragment</span> <span class="o">=:</span> <span class="nf">3 : 0</span>
</span></span><span class="line"><span class="cl">    <span class="nv">spaces</span> <span class="o">=.</span> <span class="nv">I</span><span class="o">.</span> <span class="nd">y</span> <span class="o">=</span> <span class="o">&lt;</span><span class="s">&#39;.&#39;</span> <span class="c1">NB. indices of free spaces</span>
</span></span><span class="line"><span class="cl">    <span class="nv">files</span> <span class="o">=.</span> <span class="o">|.</span> <span class="nv">I</span><span class="o">.</span> <span class="nd">y</span> <span class="o">~:</span> <span class="o">&lt;</span><span class="s">&#39;.&#39;</span> <span class="c1">NB. indices of files in reverse order</span>
</span></span><span class="line"><span class="cl">    <span class="nv">index</span> <span class="o">=.</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">    <span class="nv">for_space</span><span class="o">.</span> <span class="nv">spaces</span> <span class="nl">do.</span>
</span></span><span class="line"><span class="cl">        <span class="nl">if.</span> <span class="nv">space</span> <span class="o">&gt;</span> <span class="nv">index</span> <span class="o">{</span> <span class="nv">files</span> <span class="nl">do.</span>
</span></span><span class="line"><span class="cl">            <span class="nl">break.</span> <span class="c1">NB. this space is to the right of this file, so we&#39;re done</span>
</span></span><span class="line"><span class="cl">        <span class="nl">end.</span>
</span></span><span class="line"><span class="cl">        <span class="c1">NB. replace current space with  current file segment</span>
</span></span><span class="line"><span class="cl">        <span class="nd">y</span> <span class="o">=.</span> <span class="p">((</span><span class="nv">index</span> <span class="o">{</span> <span class="nv">files</span><span class="p">)</span> <span class="o">{</span> <span class="nd">y</span><span class="p">)</span> <span class="nv">space</span> <span class="o">}</span> <span class="nd">y</span>
</span></span><span class="line"><span class="cl">        <span class="c1">NB. replace current file segment with space</span>
</span></span><span class="line"><span class="cl">        <span class="nd">y</span> <span class="o">=.</span> <span class="p">(</span><span class="o">&lt;</span><span class="s">&#39;.&#39;</span><span class="p">)</span> <span class="p">(</span><span class="nv">index</span> <span class="o">{</span> <span class="nv">files</span><span class="p">)</span> <span class="o">}</span> <span class="nd">y</span>
</span></span><span class="line"><span class="cl">        <span class="nv">index</span> <span class="o">=.</span> <span class="nv">index</span> <span class="o">+</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">    <span class="nl">end.</span>
</span></span><span class="line"><span class="cl">    <span class="nd">y</span>
</span></span><span class="line"><span class="cl"><span class="nl">)</span>
</span></span></code></pre></div><p>Again:</p>
<ul>
<li>Dyadic <code>~:</code> is not equals.</li>
<li>Mondadic <code>I.</code> gets the indices of a list matching some condition. This was probably the second most useful word in this challenge.</li>
<li>Monadic <code>|.</code> reverses its input.</li>
<li>Dyadic <code>{</code> is array indexing (<code>1 } y</code> means <code>y[1]</code>).</li>
<li><a href="https://code.jsoftware.com/wiki/Vocabulary/curlyrt#dyadic">Dyadic <code>}</code></a> is an adverb that makes a copy of <code>y</code>, using <code>x</code> to replace the contents of location(s) <code>m</code> &ndash; <code>x m} y</code>. For example, <code>'z' 0 } 'abc'</code> would make <code>'zbc'</code> and <code>'yz' 0 2 } 'abc'</code> would make <code>'ybz'</code>.</li>
</ul>
<p>Making lists of indices was a little confusing at first, but it worked out well.</p>
<p>To get the answer for the first part of the challenge, we need a checksum function, which discards the spaces, multiplies the file IDs by their new indices and adds everything together.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-j" data-lang="j"><span class="line"><span class="cl"><span class="nv">checksum</span> <span class="o">=:</span> <span class="nf">3 : 0</span>
</span></span><span class="line"><span class="cl">    <span class="nv">ints</span> <span class="o">=.</span> <span class="p">(</span><span class="o">&lt;</span><span class="mi">0</span><span class="p">)</span> <span class="p">(</span><span class="nv">I</span><span class="o">.</span> <span class="nd">y</span> <span class="o">=</span> <span class="p">(</span><span class="o">&lt;</span><span class="s">&#39;.&#39;</span><span class="p">))</span> <span class="o">}</span> <span class="nd">y</span>
</span></span><span class="line"><span class="cl">    <span class="o">+/</span> <span class="p">(</span><span class="nv">i</span><span class="o">.</span> <span class="o">#</span> <span class="nv">ints</span><span class="p">)</span> <span class="o">*</span> <span class="o">;</span> <span class="nv">ints</span>
</span></span><span class="line"><span class="cl"><span class="nl">)</span>
</span></span></code></pre></div><p>In the first line, I used <code>}</code> to replace all <code>.</code>s with <code>0</code>s. In the second, I did the checksum calculation. <a href="https://code.jsoftware.com/wiki/Vocabulary/slash"><code>/</code> is an adverb</a> that inserts the verb on its left between the items of <code>y</code>. So <code>+/</code> allows us to sum a list.</p>
<p>Appropriately for a challenge themed around disk defragmentation, the second part requires us to move files into free space contiguously, rather than chopping them up like we did in the first part.</p>
<p>At first, I considered trying to complete this challenge using the original input rather than the disk map. I made a bit of progress before realising that I would need to fit files into spaces larger than their sizes, thus creating new spaces and messing up the indices. Nevertheless, the <code>defragment</code> function I wrote to solve this part of the challenge does use the original input before expanding it into a disk map.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-j" data-lang="j"><span class="line"><span class="cl"><span class="c1">NB. Move files into free spaces, contiguously</span>
</span></span><span class="line"><span class="cl"><span class="nv">defragment</span> <span class="o">=:</span> <span class="nf">3 : 0</span>
</span></span><span class="line"><span class="cl">    <span class="c1">NB. make a backwards list of even integers half the length of y</span>
</span></span><span class="line"><span class="cl">    <span class="nv">file_ids</span> <span class="o">=.</span> <span class="o">|.</span> <span class="nv">i</span><span class="o">.</span><span class="p">(</span><span class="o">&gt;.</span> <span class="o">-:</span> <span class="p">(</span><span class="o">#</span><span class="nd">y</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="nv">disk</span> <span class="o">=.</span> <span class="nv">expand</span> <span class="nd">y</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nv">for_fid</span><span class="o">.</span> <span class="nv">file_ids</span> <span class="nl">do.</span>
</span></span><span class="line"><span class="cl">        <span class="nv">positions</span> <span class="o">=.</span> <span class="p">(</span><span class="o">&lt;</span> <span class="nv">fid</span><span class="p">)</span> <span class="nv">I</span><span class="o">.@:</span><span class="nv">E</span><span class="o">.</span> <span class="nv">disk</span> <span class="c1">NB. indices of file ID in disk map</span>
</span></span><span class="line"><span class="cl">        <span class="nv">file</span> <span class="o">=.</span> <span class="p">(</span><span class="o">#</span> <span class="nv">positions</span><span class="p">)</span> <span class="o">#</span> <span class="p">(</span><span class="o">&lt;</span> <span class="nv">fid</span><span class="p">)</span> <span class="c1">NB. full size file</span>
</span></span><span class="line"><span class="cl">        <span class="nv">space</span> <span class="o">=.</span> <span class="p">(</span><span class="o">#</span> <span class="nv">positions</span><span class="p">)</span> <span class="o">#</span> <span class="o">&lt;</span><span class="s">&#39;.&#39;</span> <span class="c1">NB. same sized space</span>
</span></span><span class="line"><span class="cl">        <span class="nv">space_enough</span> <span class="o">=.</span> <span class="o">{.</span> <span class="nv">space</span> <span class="nv">I</span><span class="o">.@:</span><span class="nv">E</span><span class="o">.</span> <span class="nv">disk</span> <span class="c1">NB. find earliest big enough free space</span>
</span></span><span class="line"><span class="cl">        <span class="c1">NB. only move if we have enough space and aren&#39;t moving right</span>
</span></span><span class="line"><span class="cl">        <span class="nl">if.</span> <span class="nv">space_enough</span> <span class="o">*.</span> <span class="nv">space_enough</span> <span class="o">&lt;</span> <span class="o">{.</span> <span class="nv">positions</span> <span class="nl">do.</span>
</span></span><span class="line"><span class="cl">            <span class="nv">disk</span> <span class="o">=.</span> <span class="p">(</span><span class="nv">file</span><span class="p">)</span> <span class="p">(</span><span class="nv">space_enough</span> <span class="o">+</span><span class="nv">i</span><span class="o">.#</span><span class="nv">file</span><span class="p">)</span> <span class="o">}</span> <span class="nv">disk</span> <span class="c1">NB. replace free space with file</span>
</span></span><span class="line"><span class="cl">            <span class="nv">disk</span> <span class="o">=.</span> <span class="p">(</span><span class="nv">space</span><span class="p">)</span> <span class="p">(</span><span class="nv">positions</span><span class="p">)</span> <span class="o">}</span> <span class="nv">disk</span> <span class="c1">NB. replace file with free space</span>
</span></span><span class="line"><span class="cl">        <span class="nl">end.</span>
</span></span><span class="line"><span class="cl">    <span class="nl">end.</span>
</span></span><span class="line"><span class="cl">    <span class="nv">disk</span>
</span></span><span class="line"><span class="cl"><span class="nl">)</span>
</span></span></code></pre></div><p>This function loops backwards through file IDs, finds their positions in the disk map, finds the earliest free space large enough to fit the file, and then swaps the file for the free space, continuing until the next space is the right of the next file.</p>
<p>New symbols here:</p>
<ul>
<li><code>-:</code> is the idiomatic J way to halve something, rather than dividing it by 2 (<code>% 2</code>).</li>
<li><code>E.</code> finds matches. <code>@</code>, a <em>conjunction</em>, composes two verbs. Composing <code>I.</code> with <code>E.</code> gives us the indices of each match.</li>
<li><code>{.</code> gets the head, or first element, of <code>y</code>.</li>
<li><code>*.</code> is boolean <code>AND</code>.</li>
<li><code>i.#file</code> creates a new list the same size as file, starting at 0 (<code>0 1 2...</code>).<sup id="fnref:4"><a href="https://davidyat.es/2024/12/23/aoc-2024-part2/#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> Then <code>space_enough +</code> adds <code>space_enough</code> to each value therein. This gives us all the indices we need from the start of the space to insert our file with <code>}</code>.</li>
</ul>
<p>Echoing the disk map at each step gave me the following output for the challenge&rsquo;s sample data:</p>
<p><figure>
  
  <img src="/content/images/2024/12/defrag-j.png" alt="" loading="lazy"/>
  
  <figcaption>
    <p></p>
  </figcaption>
</figure>
</p>
<p><a href="https://github.com/dmyates/advent-of-code-solutions/tree/master/2024/09%20-%20J"><strong>Full code on GitHub.</strong></a></p>
<p><strong>Closing thoughts:</strong> J is not a beginner-friendly language. Beyond just knowing what each symbol does, you need to make sure you&rsquo;re using it in the right position relative to everything else on a given line. The error messages are singularly terse and unhelpful: the J console spits out the offending line and some text like <code>domain error</code> (problem with types), <code>length error</code> (array access issues), <code>syntax error</code> or even <a href="https://code.jsoftware.com/wiki/Vocabulary/RankInfoIsImportant"><code>rank error</code></a>. Because each line packs in so much functionality, knowing only the error type and line can still leave you scratching your head.</p>
<p>But enough complaining! J&rsquo;s capable of some very interesting and powerful things if you can your head around the way it works. I&rsquo;m sure there&rsquo;s a way to write my solution for this problem in half the space. Conjunctions seem like a particularly powerful concept.</p>
<p>I found Claude Sonnet 3.5 markedly better than ChatGPT 4o at writing J, though neither could write very much correct J code at a time. To make matters worse, trying to Google anything about programming in J mostly gave me answers for Java (and sometimes Julia). Fortunately, the <a href="https://code.jsoftware.com/wiki/Main_Page">J Wiki</a> is a pretty good resource, particularly the <a href="https://code.jsoftware.com/wiki/NuVoc">NuVoc page</a>.</p>
<h1 id="day-10-solidity">
  <a class="heading-anchor" href="#day-10-solidity">#</a>
  Day 10: Solidity
</h1>
<p><a href="https://adventofcode.com/2024/day/10"><strong>Challenge</strong></a>: find all the possible hiking trails on a map.</p>
<p><a href="https://soliditylang.org/">Solidity</a> is a C-like language for writing smart contracts for the Ethereum blockchain (and other EVM-based chains). Smart contracts are basically classes with (usually) a bunch of publicly callable functions. These functions can be called by other contracts and Ethereum end-users to do things like transfer and swap different cryptocurrency tokens, invest funds in lending pools, and much else besides. Code that makes changes to the chain state (e.g. sending tokens from one wallet to another) must be executed across the network, and therefore each opcode has an associated cost (called <a href="https://www.investopedia.com/terms/g/gas-ethereum.asp">gas</a>). For this reason and others, I&rsquo;ll be running the code in a local dev environment, provided by <a href="https://github.com/foundry-rs/foundry">Foundry</a>.</p>
<p>Solidity is not particularly well suited to solving Advent of Code challenges, particularly ones involving strings. So it helps that the input for this challenge is a grid of numbers.</p>
<p>To start off with, a <code>HikingTrail</code> smart contract:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-solidity" data-lang="solidity"><span class="line"><span class="cl"><span class="c1">// SPDX-License-Identifier: MIT
</span></span></span><span class="line"><span class="cl"><span class="k">pragma solidity</span> <span class="o">^</span><span class="mi">0</span><span class="p">.</span><span class="mi">8</span><span class="p">.</span><span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">contract</span> <span class="nc">HikingTrail</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// the map
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint8</span><span class="p">[</span><span class="mi">50</span><span class="p">][</span><span class="mi">50</span><span class="p">]</span> <span class="k">public</span> <span class="n">map</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint8</span> <span class="k">public</span> <span class="n">rows</span> <span class="o">=</span> <span class="mi">50</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint8</span> <span class="k">public</span> <span class="n">cols</span> <span class="o">=</span> <span class="mi">50</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// navigation helpers
</span></span></span><span class="line"><span class="cl">    <span class="kd">struct</span> <span class="nc">Position</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint8</span> <span class="n">row</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint8</span> <span class="n">col</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint8</span> <span class="n">height</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int8</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="n">rowDirections</span> <span class="o">=</span> <span class="p">[</span><span class="kt">int8</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span> <span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int8</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="n">colDirections</span> <span class="o">=</span> <span class="p">[</span><span class="kt">int8</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">),</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// pass map in on deployment
</span></span></span><span class="line"><span class="cl">    <span class="kd">constructor</span><span class="p">(</span><span class="kt">uint8</span><span class="p">[</span><span class="mi">50</span><span class="p">][</span><span class="mi">50</span><span class="p">]</span> <span class="k">memory</span> <span class="n">inputMap</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">map</span> <span class="o">=</span> <span class="n">inputMap</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// call this function to get the answer
</span></span></span><span class="line"><span class="cl">    <span class="kd">function</span> <span class="nf">calculateTrailheadScores</span><span class="p">()</span> <span class="k">external</span> <span class="k">view</span> <span class="k">returns</span> <span class="p">(</span><span class="kt">uint256</span> <span class="n">score</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="p">(</span><span class="kt">uint256</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">rows</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">for</span> <span class="p">(</span><span class="kt">uint256</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">j</span> <span class="o">&lt;</span> <span class="n">cols</span><span class="p">;</span> <span class="n">j</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="c1">// calculate the score for each trailhead (0) on the map
</span></span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="n">map</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="n">score</span> <span class="o">=</span> <span class="n">calculateScoreForTrailhead</span><span class="p">(</span><span class="kt">uint8</span><span class="p">(</span><span class="n">i</span><span class="p">),</span> <span class="kt">uint8</span><span class="p">(</span><span class="n">j</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>A few notes:</p>
<ul>
<li><code>constructor</code> is a special function that gets called when a contract is deployed on the blockchain. It can take arguments, allowing us to make this contract a general Advent of Code solution.</li>
<li>I used the smallest signed and unsigned integer types, <code>int8</code> and <code>uint8</code>, because the numbers in map go from 0 to 9. Everywhere else, I used <code>uint256</code>, as that&rsquo;s the default.<sup id="fnref:5"><a href="https://davidyat.es/2024/12/23/aoc-2024-part2/#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup></li>
</ul>
<p>The real work gets done in <code>calculateScoreForTrailhead</code>, which uses a stack to exhaustively check each position on the trail for viable next steps.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-solidity" data-lang="solidity"><span class="line"><span class="cl">    <span class="kd">function</span> <span class="nf">calculateScoreForTrailhead</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint8</span> <span class="n">startRow</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint8</span> <span class="n">startCol</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span> <span class="k">internal</span> <span class="k">view</span> <span class="k">returns</span> <span class="p">(</span><span class="kt">uint256</span> <span class="n">score</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// init some vars
</span></span></span><span class="line"><span class="cl">        <span class="kt">bool</span><span class="p">[</span><span class="mi">50</span><span class="p">][</span><span class="mi">50</span><span class="p">]</span> <span class="k">memory</span> <span class="n">visited</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">Position</span><span class="p">[]</span> <span class="k">memory</span> <span class="n">stack</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Position</span><span class="p">[](</span><span class="kt">uint256</span><span class="p">(</span><span class="n">rows</span><span class="p">)</span> <span class="o">*</span> <span class="kt">uint256</span><span class="p">(</span><span class="n">cols</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// uint8s in the above would overflow, causing a revert
</span></span></span><span class="line"><span class="cl">        <span class="kt">uint256</span> <span class="n">stackSize</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kt">int8</span> <span class="n">x</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kt">int8</span> <span class="n">y</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// push the start position onto the stack
</span></span></span><span class="line"><span class="cl">        <span class="n">stack</span><span class="p">[</span><span class="n">stackSize</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="n">Position</span><span class="p">(</span><span class="n">startRow</span><span class="p">,</span> <span class="n">startCol</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">visited</span><span class="p">[</span><span class="n">startRow</span><span class="p">][</span><span class="n">startCol</span><span class="p">]</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">while</span> <span class="p">(</span><span class="n">stackSize</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// pop the top position off the stack
</span></span></span><span class="line"><span class="cl">            <span class="n">Position</span> <span class="k">memory</span> <span class="n">current</span> <span class="o">=</span> <span class="n">stack</span><span class="p">[</span><span class="o">--</span><span class="n">stackSize</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">current</span><span class="p">.</span><span class="n">height</span> <span class="o">==</span> <span class="mi">9</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="c1">// we&#39;ve reached the end of the trail
</span></span></span><span class="line"><span class="cl">                <span class="n">score</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="c1">// check all 4 directions
</span></span></span><span class="line"><span class="cl">            <span class="k">for</span> <span class="p">(</span><span class="kt">uint256</span> <span class="n">d</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">d</span> <span class="o">&lt;</span> <span class="mi">4</span><span class="p">;</span> <span class="n">d</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">y</span> <span class="o">=</span> <span class="kt">int8</span><span class="p">(</span><span class="n">current</span><span class="p">.</span><span class="n">row</span><span class="p">)</span> <span class="o">+</span> <span class="kt">int8</span><span class="p">(</span><span class="n">rowDirections</span><span class="p">[</span><span class="n">d</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">                <span class="n">x</span> <span class="o">=</span> <span class="kt">int8</span><span class="p">(</span><span class="n">current</span><span class="p">.</span><span class="n">col</span><span class="p">)</span> <span class="o">+</span> <span class="kt">int8</span><span class="p">(</span><span class="n">colDirections</span><span class="p">[</span><span class="n">d</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                <span class="c1">// skip if out of bounds
</span></span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">y</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">newRow</span> <span class="o">&lt;</span> <span class="kt">int8</span><span class="p">(</span><span class="n">rows</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                      <span class="o">&amp;&amp;</span> <span class="n">x</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">newCol</span> <span class="o">&lt;</span> <span class="kt">int8</span><span class="p">(</span><span class="n">cols</span><span class="p">)))</span> <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                <span class="c1">// if it&#39;s a new position at a reachable height, push it onto the stack
</span></span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">visited</span><span class="p">[</span><span class="kt">uint8</span><span class="p">(</span><span class="n">y</span><span class="p">)][</span><span class="kt">uint8</span><span class="p">(</span><span class="n">x</span><span class="p">)]</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">                    <span class="n">map</span><span class="p">[</span><span class="kt">uint8</span><span class="p">(</span><span class="n">y</span><span class="p">)][</span><span class="kt">uint8</span><span class="p">(</span><span class="n">x</span><span class="p">)]</span> <span class="o">==</span> <span class="n">current</span><span class="p">.</span><span class="n">height</span> <span class="o">+</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">                <span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="n">visited</span><span class="p">[</span><span class="kt">uint8</span><span class="p">(</span><span class="n">y</span><span class="p">)][</span><span class="kt">uint8</span><span class="p">(</span><span class="n">x</span><span class="p">)]</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                    <span class="n">stack</span><span class="p">[</span><span class="n">stackSize</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="n">Position</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="kt">uint8</span><span class="p">(</span><span class="n">y</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="kt">uint8</span><span class="p">(</span><span class="n">x</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">current</span><span class="p">.</span><span class="n">height</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span></code></pre></div><p>To fulfill my goal of reading the input file natively wherever possible, I wrote the following Forge test to execute the solution:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-solidity" data-lang="solidity"><span class="line"><span class="cl"><span class="c1">// SPDX-License-Identifier: MIT
</span></span></span><span class="line"><span class="cl"><span class="k">pragma solidity</span> <span class="o">^</span><span class="mi">0</span><span class="p">.</span><span class="mi">8</span><span class="p">.</span><span class="mi">13</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">{</span><span class="n">Test</span><span class="p">,</span> <span class="n">console</span><span class="p">}</span> <span class="k">from</span> <span class="s">&#34;forge-std/Test.sol&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">{</span><span class="n">HikingTrail</span><span class="p">}</span> <span class="k">from</span> <span class="s">&#34;../src/HikingTrail.sol&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">contract</span> <span class="nc">HikingTrailTest</span> <span class="k">is</span> <span class="n">Test</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">HikingTrail</span> <span class="k">public</span> <span class="n">trail</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">function</span> <span class="nf">parseInput</span><span class="p">(</span><span class="kt">string</span> <span class="k">memory</span> <span class="n">input</span><span class="p">)</span> <span class="k">public</span> <span class="k">pure</span> <span class="k">returns</span> <span class="p">(</span><span class="kt">uint8</span><span class="p">[</span><span class="mi">50</span><span class="p">][</span><span class="mi">50</span><span class="p">]</span> <span class="k">memory</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">bytes</span> <span class="k">memory</span> <span class="n">inputBytes</span> <span class="o">=</span> <span class="kt">bytes</span><span class="p">(</span><span class="n">input</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint8</span><span class="p">[</span><span class="mi">50</span><span class="p">][</span><span class="mi">50</span><span class="p">]</span> <span class="k">memory</span> <span class="n">map</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kt">uint8</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint8</span> <span class="n">y</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="p">(</span><span class="kt">uint256</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">inputBytes</span><span class="p">.</span><span class="n">length</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kt">uint8</span> <span class="n">c</span> <span class="o">=</span> <span class="kt">uint8</span><span class="p">(</span><span class="n">inputBytes</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">c</span> <span class="o">&gt;=</span> <span class="mi">48</span> <span class="o">&amp;&amp;</span> <span class="n">c</span> <span class="o">&lt;=</span> <span class="mi">57</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// digit
</span></span></span><span class="line"><span class="cl">                <span class="n">c</span> <span class="o">-=</span> <span class="mi">48</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="n">map</span><span class="p">[</span><span class="n">x</span><span class="p">][</span><span class="n">y</span><span class="p">]</span> <span class="o">=</span> <span class="n">c</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="n">x</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">c</span> <span class="o">==</span> <span class="mi">10</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// newline
</span></span></span><span class="line"><span class="cl">                <span class="n">x</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="n">y</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">map</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">function</span> <span class="nf">setUp</span><span class="p">()</span> <span class="k">public</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">string</span> <span class="k">memory</span> <span class="n">input</span> <span class="o">=</span> <span class="n">vm</span><span class="p">.</span><span class="n">readFile</span><span class="p">(</span><span class="s">&#34;input.txt&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint8</span><span class="p">[</span><span class="mi">50</span><span class="p">][</span><span class="mi">50</span><span class="p">]</span> <span class="k">memory</span> <span class="n">map</span> <span class="o">=</span> <span class="n">parseInput</span><span class="p">(</span><span class="n">input</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">trail</span> <span class="o">=</span> <span class="k">new</span> <span class="n">HikingTrail</span><span class="p">(</span><span class="n">map</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">function</span> <span class="nf">test_calculateTrailheadScores</span><span class="p">()</span> <span class="k">public</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint256</span> <span class="n">score</span> <span class="o">=</span> <span class="n">trail</span><span class="p">.</span><span class="n">calculateTrailheadScores</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="n">console</span><span class="p">.</span><span class="n">log</span><span class="p">(</span><span class="s">&#34;Score:&#34;</span><span class="p">,</span> <span class="n">score</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This required adding below line to the project&rsquo;s <code>foundry.toml</code>, as Solidity code isn&rsquo;t usually expected to access random files:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="nx">fs_permissions</span> <span class="p">=</span> <span class="p">[{</span> <span class="nx">access</span> <span class="p">=</span> <span class="s2">&#34;read&#34;</span><span class="p">,</span> <span class="nx">path</span> <span class="p">=</span> <span class="s2">&#34;input.txt&#34;</span> <span class="p">}]</span>
</span></span></code></pre></div><p>I could then execute the solution with <code>forge test -vv</code>. This spins up a local EVM blockchain, deploys <code>HikingTrail</code> and <code>HikingTrailTest</code>, and runs all the functions in the latter starting with <code>test</code> (also the <code>setUp</code> function).</p>
<p>The amount of processing needed quickly ran into EVM memory limits. To get around that, I rewrote <code>calculateTrailheadScores</code> as a batch function, which stores the last processed row and column. As calculating scores now involved writing to contract storage, it would no longer be a gas-free action for end users. If I were optimising for that, I would have made the batching take a start position. Or not written this code in Solidity.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-solidity" data-lang="solidity"><span class="line"><span class="cl">    <span class="kd">function</span> <span class="nf">calculateTrailheadScoresBatch</span><span class="p">(</span><span class="kt">uint8</span> <span class="n">batchSize</span><span class="p">)</span> <span class="k">public</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="p">(</span><span class="kt">uint256</span> <span class="n">i</span> <span class="o">=</span> <span class="n">lastProcessedRow</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">rows</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">for</span> <span class="p">(</span><span class="kt">uint256</span> <span class="n">j</span> <span class="o">=</span> <span class="p">(</span><span class="n">i</span> <span class="o">==</span> <span class="n">lastProcessedRow</span> <span class="o">?</span> <span class="n">lastProcessedCol</span> <span class="o">:</span> <span class="mi">0</span><span class="p">);</span> 
</span></span><span class="line"><span class="cl">                <span class="n">j</span> <span class="o">&lt;</span> <span class="n">cols</span><span class="p">;</span> <span class="n">j</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="n">map</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="n">totalScore</span> <span class="o">+=</span> <span class="n">calculateScoreForTrailhead</span><span class="p">(</span><span class="kt">uint8</span><span class="p">(</span><span class="n">i</span><span class="p">),</span> <span class="kt">uint8</span><span class="p">(</span><span class="n">j</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="o">--</span><span class="n">batchSize</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="n">lastProcessedRow</span> <span class="o">=</span> <span class="kt">uint8</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                    <span class="n">lastProcessedCol</span> <span class="o">=</span> <span class="kt">uint8</span><span class="p">(</span><span class="n">j</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                    <span class="k">if</span> <span class="p">(</span><span class="n">lastProcessedCol</span> <span class="o">==</span> <span class="n">cols</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="n">lastProcessedRow</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                        <span class="n">lastProcessedCol</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">}</span>
</span></span><span class="line"><span class="cl">                    <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="n">lastProcessedRow</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">lastProcessedCol</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span></code></pre></div><p>The new test function looked like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-solidity" data-lang="solidity"><span class="line"><span class="cl">    <span class="kd">function</span> <span class="nf">test_calculateTrailheadScores</span><span class="p">()</span> <span class="k">public</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">while</span> <span class="p">(</span><span class="n">trail</span><span class="p">.</span><span class="n">lastProcessedRow</span><span class="p">()</span> <span class="o">&lt;</span> <span class="mi">50</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">trail</span><span class="p">.</span><span class="n">calculateTrailheadScoresBatch</span><span class="p">(</span><span class="mi">100</span><span class="p">);</span> <span class="c1">// don&#39;t change this
</span></span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="n">console</span><span class="p">.</span><span class="n">log</span><span class="p">(</span><span class="s">&#34;Score:&#34;</span><span class="p">,</span> <span class="n">trail</span><span class="p">.</span><span class="n">totalScore</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span></code></pre></div><p>Most other batch sizes still led to a <code>MemoryOOG</code> error, but 100 managed to get the right answer.</p>
<p>The second part of the challenge involved finding the trailhead <em>rating</em>, which was the number of unique trails it branched into. This was actually simpler than the first part. I used essentially the same code, minus the previously visited check.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-solidity" data-lang="solidity"><span class="line"><span class="cl">    <span class="kd">function</span> <span class="nf">calculateRatingForTrailhead</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint8</span> <span class="n">startRow</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint8</span> <span class="n">startCol</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span> <span class="k">public</span> <span class="k">view</span> <span class="k">returns</span> <span class="p">(</span><span class="kt">uint256</span> <span class="n">rating</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// init some vars
</span></span></span><span class="line"><span class="cl">        <span class="n">Position</span><span class="p">[]</span> <span class="k">memory</span> <span class="n">stack</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Position</span><span class="p">[](</span><span class="kt">uint256</span><span class="p">(</span><span class="n">rows</span><span class="p">)</span> <span class="o">*</span> <span class="kt">uint256</span><span class="p">(</span><span class="n">cols</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint256</span> <span class="n">stackSize</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kt">int8</span> <span class="n">x</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kt">int8</span> <span class="n">y</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// push the start position onto the stack
</span></span></span><span class="line"><span class="cl">        <span class="n">stack</span><span class="p">[</span><span class="n">stackSize</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="n">Position</span><span class="p">(</span><span class="n">startRow</span><span class="p">,</span> <span class="n">startCol</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">while</span> <span class="p">(</span><span class="n">stackSize</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// pop the top position off the stack
</span></span></span><span class="line"><span class="cl">            <span class="n">Position</span> <span class="k">memory</span> <span class="n">current</span> <span class="o">=</span> <span class="n">stack</span><span class="p">[</span><span class="o">--</span><span class="n">stackSize</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">current</span><span class="p">.</span><span class="n">height</span> <span class="o">==</span> <span class="mi">9</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="c1">// we&#39;ve reached the end of the trail
</span></span></span><span class="line"><span class="cl">                <span class="n">rating</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="c1">// check all 4 directions
</span></span></span><span class="line"><span class="cl">            <span class="k">for</span> <span class="p">(</span><span class="kt">uint256</span> <span class="n">d</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">d</span> <span class="o">&lt;</span> <span class="mi">4</span><span class="p">;</span> <span class="n">d</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">y</span> <span class="o">=</span> <span class="kt">int8</span><span class="p">(</span><span class="n">current</span><span class="p">.</span><span class="n">row</span><span class="p">)</span> <span class="o">+</span> <span class="kt">int8</span><span class="p">(</span><span class="n">rowDirections</span><span class="p">[</span><span class="n">d</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">                <span class="n">x</span> <span class="o">=</span> <span class="kt">int8</span><span class="p">(</span><span class="n">current</span><span class="p">.</span><span class="n">col</span><span class="p">)</span> <span class="o">+</span> <span class="kt">int8</span><span class="p">(</span><span class="n">colDirections</span><span class="p">[</span><span class="n">d</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                <span class="c1">// skip if out of bounds
</span></span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">y</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">y</span> <span class="o">&lt;</span> <span class="kt">int8</span><span class="p">(</span><span class="n">rows</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                    <span class="o">&amp;&amp;</span> <span class="n">x</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">x</span> <span class="o">&lt;</span> <span class="kt">int8</span><span class="p">(</span><span class="n">cols</span><span class="p">)))</span> <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                <span class="c1">// if it&#39;s at a reachable height, push it onto the stack
</span></span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="n">map</span><span class="p">[</span><span class="kt">uint8</span><span class="p">(</span><span class="n">y</span><span class="p">)][</span><span class="kt">uint8</span><span class="p">(</span><span class="n">x</span><span class="p">)]</span> <span class="o">==</span> <span class="n">current</span><span class="p">.</span><span class="n">height</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="n">stack</span><span class="p">[</span><span class="n">stackSize</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="n">Position</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="kt">uint8</span><span class="p">(</span><span class="n">y</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="kt">uint8</span><span class="p">(</span><span class="n">x</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">current</span><span class="p">.</span><span class="n">height</span> <span class="o">+</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">                    <span class="p">);</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span></code></pre></div><p>The same batching approach used with the score was required to calculate the rating.</p>
<p>As I briefly mentioned earlier, gas is the unit of cost for running code on the Ethereum blockchain. Each instruction, such as a variable read, a loop, or an assignment, has an associated gas cost. The price of gas fluctuates depending on how congested the network is. Right now, it would cost around $120 (US) to deploy <code>HikingTrail</code> to Ethereum mainnet. To actually calculate the answers would cost this much:</p>
<table>
  <thead>
      <tr>
          <th>Solution</th>
          <th>Gas</th>
          <th>Estimated USD Cost</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Part 1</td>
          <td>1065457787</td>
          <td>$28 400</td>
      </tr>
      <tr>
          <td>Part 2</td>
          <td>731364166</td>
          <td>$19 500</td>
      </tr>
  </tbody>
</table>
<p><a href="https://github.com/dmyates/advent-of-code-solutions/tree/master/2024/10%20-%20Solidity"><strong>Full code on GitHub.</strong></a></p>
<p><strong>Closing thoughts</strong>: As much as Solidity is not designed for this sort of thing, it was pretty simple to get this solution working. Solidity developers prize readability and efficiency,<sup id="fnref:6"><a href="https://davidyat.es/2024/12/23/aoc-2024-part2/#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> so you won&rsquo;t see a lot of weird functional programming-style trickery. Its syntax is mercifully free of weird sigils (a relief after J). In many other languages, the <code>memory</code> and <code>storage</code> keywords would probably be <code>%</code> and <code>$</code> or something equally obscure.</p>
<hr>
<p>Next up, <a href="/2025/01/11/aoc-2024-part3/">Days 11–15</a>. I make no promises about having it up before Christmas of this or any other year.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>The <code>check new arrivals rule</code> to be specific. Advanced use of Inform often involves naming and specifically ordering rules.&#160;<a href="https://davidyat.es/2024/12/23/aoc-2024-part2/#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Inform also supports compiling to the Z-Machine, the virtual machine used by Infocom in the 1980s. I did not attempt to do this with my 16 083-room source.&#160;<a href="https://davidyat.es/2024/12/23/aoc-2024-part2/#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>In Lisp, the functions for getting the head and tail of a list are traditionally <code>car</code> and <code>cdr</code>, but Racket lets you do <code>first</code> and <code>rest</code>.&#160;<a href="https://davidyat.es/2024/12/23/aoc-2024-part2/#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p><code>i.</code> is often used with numbers to make lists of arbitrary length. For example, <code>i.5</code> produces <code>0 1 2 3 4</code>.&#160;<a href="https://davidyat.es/2024/12/23/aoc-2024-part2/#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Solidity is primarily designed for handling monetary units in cryptocurrencies, many of which have 8 to 18 decimal places. To avoid the pitfalls of floating point maths, Solidity only has integer types, and so must represent most values as very big numbers.&#160;<a href="https://davidyat.es/2024/12/23/aoc-2024-part2/#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>The practice of trying to make functions as gas-efficient as possible is called <em>gas golfing</em>.&#160;<a href="https://davidyat.es/2024/12/23/aoc-2024-part2/#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

        <p><a href="mailto:d@vidyat.es?subject=RE: Advent%20of%20Code%202024%3a%20Days%206%e2%80%9310">Reply via email</a></p>
        ]]></content:encoded></item><item><title>Advent of Code 2024: Days 1–5</title><link>https://davidyat.es/2024/12/16/aoc-2024-part1/</link><pubDate>Mon, 16 Dec 2024 19:43:44 +0200</pubDate><author>David Yates</author><guid>https://davidyat.es/2024/12/16/aoc-2024-part1/</guid><content:encoded><![CDATA[
        <img src="https://davidyat.es/content/images/2024/12/aoc2024-p1.jpeg" class="post-img" />
        <p><a href="https://adventofcode.com/">Advent of Code</a> (AoC) is a yearly programming event. On each day of the advent leading up Christmas, a new challenge is released. Each challenge consists of a problem description and an input file. Challenges generally involve processing the input file to come up with a particular output, which serves as the solution. Each challenge consists of two parts, with the second part being revealed upon completion of the first. Both parts use the same input, which is slightly different for each participant.</p>
<p>To give a trivial example, the first part of a challenge could be to count the number of times the word &ldquo;Christmas&rdquo; appears in a text file and the second part could be to count the number of lines in the file which contain the word &ldquo;Christmas&rdquo;. In both instances, the count serves as the challenge solution.</p>
<p>Because all of the challenges are designed to provide a short answer string, you can solve them in whatever way you choose, pursuing other goals along the way. You might choose to do them all in your favourite language and see how concise and/or fast you can make the solution. Or maybe you want to use the challenges to help you learn a new language. You could do each challenge in a different language. It&rsquo;s even conceivable that you could print the challenge input files and solve them with a notebook and pen.</p>
<p>For this year&rsquo;s event, I decided it would be fun to <strong>try each one in a different language</strong>. Some of the languages I&rsquo;ve chosen are ordinary, mainstream programming languages that I&rsquo;ve used in the past or want to use more in the future. Others are domain-specific and/or idiosyncratic languages that may not be obvious (or good) choices for the challenges I&rsquo;ve applied them to &ndash; though I&rsquo;ve not gone so far as to torture myself with Brainfuck or Malbolge.</p>
<p>With the constraint of using each language only once, and a bias for languages I know, I&rsquo;ve either gone for the language that best fits the problem, or one where the problem won&rsquo;t be overly painful to solve. I&rsquo;ve used AI here and there to assist with the languages I know less well, but not by prompting it with the puzzle text wholesale. Part of the aim of this exercise is for me to gauge how good common AI models are at different languages.</p>
<nav id="TableOfContents">
  <ul>
    <li><a href="#day-1-inform-7">Day 1: Inform 7</a></li>
    <li><a href="#day-2-haskell">Day 2: Haskell</a></li>
    <li><a href="#day-3-ruby">Day 3: Ruby</a></li>
    <li><a href="#day-4-gml">Day 4: GML</a></li>
    <li><a href="#day-5-gnu-prolog">Day 5: GNU Prolog</a></li>
  </ul>
</nav>

<h1 id="day-1-inform-7">
  <a class="heading-anchor" href="#day-1-inform-7">#</a>
  Day 1: Inform 7
</h1>
<p><a href="https://adventofcode.com/2024/day/1"><strong>Challenge</strong></a>: find the sum of diffs between two lists.</p>
<p>Inform 7 is a programming language designed to closely resemble normal human prose and used for making text adventure games (interactive fiction if you&rsquo;re feeling literary). I&rsquo;ve written about it previously <a href="/2019/10/03/programming-in-inform-7/">here</a>. I chose it for the first day because it is highly unsuited to the sort of programming required for these challenges, so it seemed like a good idea to get it out of the way for an easy one.</p>
<p><figure>
  
  <img src="/content/images/2024/12/zork.png" alt="The kind of thing you&rsquo;re suppose to make with Inform." loading="lazy"/>
  
  <figcaption>
    <p>The <a href="https://ifdb.org/viewgame?id=4gxk83ja4twckm6j">kind of thing</a> you&rsquo;re suppose to make with Inform.</p>
  </figcaption>
</figure>
</p>
<p>I thought I might be the first person to attempt an AoC challenge in Inform, but as it turns out <a href="https://blog.flowblok.id.au/2024-03/advent-of-code-2023-day-5.html">someone else beat me to it</a>, encountering pain points such as having to implement arithmetic operations for strings because Inform only offers up to 32-bit signed integers.</p>
<p>This challenge was a lot simpler, though it still presented its share of snarls. The input data consisted of two columns of numbers, so I decided to represent it with a <a href="https://ganelson.github.io/inform-website/book/WI_16_1.html">table</a>. Initially, I wanted to <a href="https://ganelson.github.io/inform-website/book/WI_23_13.html">populate the table by reading from the input file</a>, but the input file was too large for this to work. I considered a few different ways to solve this, but ultimately went the easy route and copied the input into a manually defined table, replacing the three-space column separators with tabs.</p>
<pre tabindex="0"><code class="language-inform7" data-lang="inform7"><strong>"Historian Hysteria Solution" by "David Yates"</strong>

[Inform games need at least one room to compile.]
The Chief Historian&#39;s Office is a room. &#34;Elves run amuck looking for location IDs.&#34;

Chapter - Inputs

[Really more of an appendix]

Table of Location IDs
First (a number)	Second (a number)
123 456
124 457
[etc... (not my real input values)]
</code></pre><p>With the input in place, it was time to begin solving the puzzle, which required me to sort the columns, get the distance between each row, and add that all together. As the columns had to sorted separately, it soon became apparent that a table was not the right data structure for this problem.<sup id="fnref:1"><a href="https://davidyat.es/2024/12/16/aoc-2024-part1/#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> So I extracted each column into a <a href="https://ganelson.github.io/inform-website/book/WI_21_1.html">list</a> and sorted both lists.</p>
<pre tabindex="0"><code class="language-inform7" data-lang="inform7">Chapter 1 - Definitions

List 1 is a list of numbers that varies.

List 2 is a list of numbers that varies.

When play begins:
	repeat with N running from 1 to the number of rows in the Table of Location IDs:
		add first in row N of the Table of Location IDs to List 1;
	repeat with M running from 1 to the number of rows in the Table of Location IDs:
		add second in row M of the Table of Location IDs to List 2;
	sort List 1;
	sort List 2.
</code></pre><p>Next, I would need to get the distance between each value in both lists by subtracting the smaller number from the larger number. I wrote a <a href="https://ganelson.github.io/inform-website/book/WI_11_17.html">deciding phrase</a> (basically a function with a return value) for this:</p>
<pre tabindex="0"><code class="language-inform7" data-lang="inform7">To decide what number is the distance between (N - number) and (M - number):
	if M is greater than N:
		decide on M minus N;
	decide on N minus M
</code></pre><p>i.e.</p>
<pre tabindex="0"><code>function distance(int n, int m) {
    if m &gt; n {
        return m - n;
    }
    return n - m;
}
</code></pre><p>Inform allows you to use <code>&gt;</code> instead of <code>greater than</code> and <code>-</code> instead of <code>minus</code>, but I felt like spelling everything out was more in the spirit of things.</p>
<p>To get the final answer, I will need to sum the contents of a list. This will require another deciding phrase, which we can use in a <a href="https://ganelson.github.io/inform-website/book/WI_22_5.html">reduction</a> later.</p>
<pre tabindex="0"><code class="language-inform7" data-lang="inform7">To decide what number is the sum of (N - number) and (M - number)
	(this is summing):
	decide on M plus N.
</code></pre><p>With all the components of the solution now in place, I implemented it in the next section of the code:</p>
<pre tabindex="0"><code class="language-inform7" data-lang="inform7">Chapter 2 - The Answer

The Chief Historian&#39;s Office is a room. &#34;Elves run amuck looking for location IDs.&#34;

[Type Z in game to trigger this.]
Instead of waiting:
	say &#34;The elves diff the lists...[line break]&#34;;
	let diffs be a list of numbers;
        [construct diffs list]
	repeat with X running from 1 to the number of entries in List 1:
		add the distance between entry X of List 1 and entry X of List 2 to diffs;
	say &#34;The elves add it all together...[line break]&#34;;
        [reduce diffs list to its sum]
	let answer be the summing reduction of diffs;
	say &#34;The elves tell you that the answer is [answer].&#34;
</code></pre><p>To get the solution, I compiled and ran the game and entered <code>z</code> into the parser. As if to further confirm Inform&rsquo;s unsuitability for such a task (and perhaps the inefficiency of my code), getting to the answer took quite some time even on my reasonably powerful PC.</p>
<p>The second part of the challenge required counting the times each value in the first list appeared in the second list. After spending some time with filters and reductions, and then some more time with manual list traversal in the name of efficiency, I threw up my hands and nested a couple of repeat loops.</p>
<pre tabindex="0"><code class="language-inform7" data-lang="inform7">[Type YES in game to trigger this.]
Instead of saying yes:
	let similarity be 0;
	repeat with N running through List 1:
		let count be 0;
		repeat with M running through List 2:
			if N is M:
				increment count;
			if M is greater than N: 
				break; [we know the list is sorted; a sop to efficiency]
		now similarity is similarity plus N times count;
	say &#34;The elves tell you the answer is [similarity].&#34;
</code></pre><p>It wasn&rsquo;t quick, but it got the right answer.</p>
<p><a href="https://github.com/dmyates/advent-of-code-solutions/tree/master/2024/01%20-%20Inform%207"><strong>Full code on GitHub</strong></a></p>
<p><strong>Closing thoughts:</strong> A more complete solution would read the input file and use custom parsing and casting to construct the two lists. I decided not to do this because I didn&rsquo;t want to spend more time writing code for reading files than writing code for solving the actual challenge. In retrospect, this wasn&rsquo;t the most interesting challenge to do with Inform &ndash; my code ended up being pretty boring imperative loops and conditionals, just spelled out.</p>
<p>While Inform 7 may look like English prose, it has quite strict syntax. Especially in the age of AI prompting, it&rsquo;s very easy to get loose and start writing words Inform doesn&rsquo;t expect or understand &ndash; for example, it took me a bit of time to remember that the syntax for setting a variable is <code>now the VAR is VALUE</code> rather than <code>set VAR to VALUE</code>. When prompted for Inform code, AI is liable to make the same mistake and mix in regular English prose, making it fairly useless for this language in particular.</p>
<h1 id="day-2-haskell">
  <a class="heading-anchor" href="#day-2-haskell">#</a>
  Day 2: Haskell
</h1>
<p><a href="https://adventofcode.com/2024/day/2"><strong>Challenge</strong></a>: find which lists increase or decrease smoothly.</p>
<p>I first encountered Haskell in a third-year Computer Science functional programming course and really enjoyed using it. The sheer amount of functionality you could pack into a line or two of code amazed me. Since then, I haven&rsquo;t found much occasion to return to it, apart from when I went through Bruce Tate&rsquo;s <a href="https://pragprog.com/titles/btlang/seven-languages-in-seven-weeks/"><em>Seven Programming Languages in Seven Weeks</em></a> a few years ago.</p>
<p>This puzzle involved a bunch of lists, so I chose Haskell because I remembered it being good at working with lists. Skimming through the <em>Seven Languages</em> book helped me get back up to speed, as did pair-programming with my good buddy Claude 3.5. The main thing that makes Haskell counterintuitive is that you have to write functions right to left most of the time.</p>
<p>The core of the problem was checking whether lists were ascending or descending, and checking that the differences between subsequent entries were within a given range. For the first part, we can do this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-haskell" data-lang="haskell"><span class="line"><span class="cl"><span class="c1">-- check each pair of values is in asc order</span>
</span></span><span class="line"><span class="cl"><span class="c1">-- produce a list of bool results</span>
</span></span><span class="line"><span class="cl"><span class="c1">-- then AND that list</span>
</span></span><span class="line"><span class="cl"><span class="c1">-- read right to left</span>
</span></span><span class="line"><span class="cl"><span class="nf">isAscending</span> <span class="ow">::</span> <span class="p">[</span><span class="kt">Int</span><span class="p">]</span> <span class="ow">-&gt;</span> <span class="kt">Bool</span>
</span></span><span class="line"><span class="cl"><span class="nf">isAscending</span> <span class="n">xs</span> <span class="ow">=</span> <span class="n">and</span> <span class="o">$</span> <span class="n">zipWith</span> <span class="p">(</span><span class="o">&lt;=</span><span class="p">)</span> <span class="n">xs</span> <span class="p">(</span><span class="n">tail</span> <span class="n">xs</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">-- check each pair of values is in desc order</span>
</span></span><span class="line"><span class="cl"><span class="c1">-- produce a list of bool results</span>
</span></span><span class="line"><span class="cl"><span class="c1">-- then AND that list</span>
</span></span><span class="line"><span class="cl"><span class="c1">-- read right to left</span>
</span></span><span class="line"><span class="cl"><span class="nf">isDescending</span> <span class="ow">::</span> <span class="p">[</span><span class="kt">Int</span><span class="p">]</span> <span class="ow">-&gt;</span> <span class="kt">Bool</span>
</span></span><span class="line"><span class="cl"><span class="nf">isDescending</span> <span class="n">xs</span> <span class="ow">=</span> <span class="n">and</span> <span class="o">$</span> <span class="n">zipWith</span> <span class="p">(</span><span class="o">&gt;=</span><span class="p">)</span> <span class="n">xs</span> <span class="p">(</span><span class="n">tail</span> <span class="n">xs</span><span class="p">)</span>
</span></span></code></pre></div><p>For the second, we can do this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-haskell" data-lang="haskell"><span class="line"><span class="cl"><span class="nf">validDistance</span> <span class="ow">::</span> <span class="p">[</span><span class="kt">Int</span><span class="p">]</span> <span class="ow">-&gt;</span> <span class="kt">Bool</span>
</span></span><span class="line"><span class="cl"><span class="nf">validDistance</span> <span class="n">xs</span> <span class="ow">=</span> <span class="n">and</span> <span class="o">$</span> <span class="n">map</span> <span class="n">isValidDist</span> <span class="o">$</span> <span class="n">zip</span> <span class="n">xs</span> <span class="p">(</span><span class="n">tail</span> <span class="n">xs</span><span class="p">)</span> <span class="c1">-- check each pair in list</span>
</span></span><span class="line"><span class="cl">  <span class="kr">where</span> <span class="n">isValidDist</span> <span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span> <span class="ow">=</span> <span class="n">dist</span> <span class="n">a</span> <span class="n">b</span> <span class="o">&gt;=</span> <span class="mi">1</span> <span class="o">&amp;&amp;</span> <span class="n">dist</span> <span class="n">a</span> <span class="n">b</span> <span class="o">&lt;=</span> <span class="mi">3</span> <span class="c1">-- require dist between 1 &amp; 3</span>
</span></span><span class="line"><span class="cl">        <span class="n">dist</span> <span class="n">x</span> <span class="n">y</span> <span class="ow">=</span> <span class="n">abs</span> <span class="p">(</span><span class="n">x</span> <span class="o">-</span> <span class="n">y</span><span class="p">)</span> <span class="c1">-- check dist by getting the absolute value of x-y</span>
</span></span></code></pre></div><p>You can think of <code>where</code> as a way to define inner functions, similar to nesting <code>def</code>s in Python. With this core logic in place, we can do a bunch of file input and parsing and then get our solution.</p>
<p>The second part of the puzzle asks you to recompute the number of valid lists with an added tolerance for a single mistake in each. So we can just loop through each list, removing one value at a time and checking if that makes it valid.</p>
<p><a href="https://github.com/dmyates/advent-of-code-solutions/tree/master/2024/02%20-%20Haskell"><strong>Full code on GitHub</strong></a></p>
<p><strong>Closing thoughts:</strong> Haskell remains the language where I feel most like I&rsquo;m writing horizontally rather than vertically. The syntax is quite funky and it&rsquo;s a bit brain-bending to get back into after not looking at for a few years. I leaned on the AI a fair bit for bug-fixing and code explanations.</p>
<h1 id="day-3-ruby">
  <a class="heading-anchor" href="#day-3-ruby">#</a>
  Day 3: Ruby
</h1>
<p><a href="https://adventofcode.com/2024/day/3"><strong>Challenge</strong></a>: parse valid instructions out of corrupted text.</p>
<p>This seemed like a regex problem, so I grabbed Ruby, the language I know with the best regex. I&rsquo;ve used Ruby quite a lot, so this problem was very quick and easy compared to the last couple of days.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">input</span> <span class="o">=</span> <span class="no">File</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="s1">&#39;input.txt&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">matches</span> <span class="o">=</span> <span class="n">input</span><span class="o">.</span><span class="n">scan</span><span class="p">(</span><span class="sr">/mul\((\d{1,3}),(\d{1,3})\)/</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">puts</span> <span class="n">matches</span><span class="o">.</span><span class="n">sum</span> <span class="p">{</span> <span class="o">|</span><span class="n">first</span><span class="p">,</span> <span class="n">second</span><span class="o">|</span> <span class="n">first</span><span class="o">.</span><span class="n">to_i</span> <span class="o">*</span> <span class="n">second</span><span class="o">.</span><span class="n">to_i</span> <span class="p">}</span>
</span></span></code></pre></div><p>The second part required some simple context-aware parsing, which for the sake of my sanity I did not attempt to do with look-behinds.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">input</span> <span class="o">=</span> <span class="no">File</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="s1">&#39;input.txt&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">lines</span> <span class="o">=</span> <span class="n">input</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="sr">/(do\(\))|(don&#39;t\(\))/</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">valid_lines</span> <span class="o">=</span> <span class="o">[]</span>
</span></span><span class="line"><span class="cl"><span class="n">deleting</span> <span class="o">=</span> <span class="kp">false</span>
</span></span><span class="line"><span class="cl"><span class="n">lines</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">line</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">    <span class="n">deleting</span> <span class="o">=</span> <span class="n">line</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="sr">/^don&#39;t\(\)$/</span><span class="p">)</span> <span class="ow">or</span> <span class="p">(</span><span class="n">deleting</span> <span class="ow">and</span> <span class="o">!</span><span class="n">line</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="sr">/^do\(\)$/</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">valid_lines</span> <span class="o">&lt;&lt;</span> <span class="n">line</span> <span class="k">unless</span> <span class="n">deleting</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">matches</span> <span class="o">=</span> <span class="n">valid_lines</span><span class="o">.</span><span class="n">join</span><span class="o">.</span><span class="n">scan</span><span class="p">(</span><span class="sr">/mul\((\d{1,3}),(\d{1,3})\)/</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">puts</span> <span class="n">matches</span><span class="o">.</span><span class="n">sum</span> <span class="p">{</span> <span class="o">|</span><span class="n">first</span><span class="p">,</span> <span class="n">second</span><span class="o">|</span> <span class="n">first</span><span class="o">.</span><span class="n">to_i</span> <span class="o">*</span> <span class="n">second</span><span class="o">.</span><span class="n">to_i</span> <span class="p">}</span>
</span></span></code></pre></div><p><a href="https://github.com/dmyates/advent-of-code-solutions/tree/master/2024/03%20-%20Ruby"><strong>Full code (also) on GitHub</strong></a></p>
<p><strong>Closing thoughts:</strong> If I was going for time or code succinctness, I would do every challenge in Ruby.</p>
<h1 id="day-4-gml">
  <a class="heading-anchor" href="#day-4-gml">#</a>
  Day 4: GML
</h1>
<p><a href="https://adventofcode.com/2024/day/4"><strong>Challenge</strong></a>: find all instances of XMAS in a wordsearch.</p>
<p>For this challenge, I returned to my roots. The Game Maker Language (GML), a C-like language designed for use in the 2D game development tool <a href="https://gamemaker.io/">GameMaker</a>, was the first programming language I ever wrote significant code in, and so it holds a special place in my heart. I wrote about it previously <a href="/2016/09/20/programming-in-gamemaker/">here</a>.</p>
<p><figure>
  
  <img src="/content/images/2024/12/gm.png" alt="The kind of thing you&rsquo;re suppose to make with GameMaker." loading="lazy"/>
  
  <figcaption>
    <p>The <a href="https://gamemaker.io/en/tutorials/your-first-platformer">kind of thing</a> you&rsquo;re suppose to make with GameMaker.</p>
  </figcaption>
</figure>
</p>
<p>I chose GML for this challenge specifically because it involves a grid of letters, and GML has <a href="https://manual.gamemaker.io/beta/en/GameMaker_Language/GML_Reference/Data_Structures/DS_Grids/DS_Grids.htm">a grid data structure</a>. This data structure was introduced somewhere around Game Maker 6.1 or 7,<sup id="fnref:2"><a href="https://davidyat.es/2024/12/16/aoc-2024-part1/#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> and I remember thinking it seemed really cool and useful but never actually having a practical use for it.</p>
<p>GML has changed somewhat since I last wrote about it &ndash; it now has <a href="https://help.gamemaker.io/hc/en-us/articles/360005277377-Changes-to-how-Script-assets-are-created-as-of-version-2-3-0">functions with named parameters</a>. Previously, all functions (or scripts) took up to 16 arguments and you&rsquo;d have to reference them as <code>argument0</code>–<code>argument15</code>. Other than that, it seems mostly backwards compatible with the code I wrote almost twenty years ago.</p>
<p>To solve the puzzle, I created a new game with one room and one script. I invoked the script in the room&rsquo;s creation code, and disabled GM&rsquo;s sandbox so I could access arbitrary files.<sup id="fnref:3"><a href="https://davidyat.es/2024/12/16/aoc-2024-part1/#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></p>
<p><figure>
  
  <img src="/content/images/2024/12/sandbox.png" alt="" loading="lazy"/>
  
  <figcaption>
    <p></p>
  </figcaption>
</figure>
</p>
<p>My script starts by prompting the user for the input file and then reads it into an array line-by-line:</p>
<pre tabindex="0"><code class="language-gml" data-lang="gml">function aoc4(){
	// prompt for input file
	var filename, file; // semicolons are only strictly necessary after var declarations

    switch (os_type)
    { // in my GML days I always put open braces on their own lines
      // I also really loved switch-cases
      case os_windows: filename = get_open_filename(&#34;text file|*.txt&#34;, &#34;&#34;); break
      default: filename = get_string(&#34;Please specify the full path of your input file:&#34;, &#34;&#34;)
    }

	if (filename != &#34;&#34;)
	{ 
	    file = file_text_open_read(filename)
	}

	// read lines
	var n = 0;
	while (!file_text_eof(file))
	{
		lines[n++] = file_text_readln(file)
	}
}
</code></pre><p>Next, we assemble a grid data structure from our array of lines:</p>
<pre tabindex="0"><code class="language-gml" data-lang="gml">	// assemble grid
	wordsearch = ds_grid_create(string_length(lines[0]), array_length(lines))
	for (var i = 0; i &lt; ds_grid_height(wordsearch); i++)
	{
		for (var j = 0; j &lt; ds_grid_width(wordsearch); j++)
		{
			wordsearch[# i, j] = string_char_at(lines[i], j+1)
		}
	}
</code></pre><p>A few notes:</p>
<ul>
<li>I forgot to declare <code>wordsearch</code> with <code>var</code> here, which, due to GM&rsquo;s lax approach to scoping, would turn it into an instance variable of whatever calls the script. Not quite sure what happens here, as it&rsquo;s called in the room creation code.</li>
<li>In older versions of GM, data structures did not have accessor syntax, so the line in the innermost <code>for</code> loop here would have to have been <code>ds_grid_set(wordsearch, i, j, string_char_at(lines[i], j+1)</code>. Retrieval would have been done with <code>ds_grid_get</code>.</li>
<li>Some languages do 1-indexing and other languages do 0-indexing. GML does 0-indexing&hellip; except for strings, which are 1-indexed. Hence <code>j+1</code> in <code>string_char_at</code>.</li>
<li>I initially booted into Windows to complete this challenge, but then I found out that modern GameMaker has an Ubuntu beta, which is also possible to get running on <a href="https://aur.archlinux.org/packages/gamemaker-beta-bin">Arch</a>. The function that brings up a filepicker only works on Windows though, so I had to fall back to text path input.</li>
</ul>
<p>Next, we have to find occurrences of the word XMAS in the grid. The solution must account for horizontal, vertical and diagonal words, both forwards and backwards. For this, I used a nested <code>for</code> loop and a whole lot of <code>if</code> statements.</p>
<pre tabindex="0"><code class="language-gml" data-lang="gml">	// find xmases
	var	xmas_count = 0;
	for (var v = 0; v &lt; ds_grid_height(wordsearch); v++)
	{
		for (var h = 0; h &lt; ds_grid_width(wordsearch); h++)
		{
			if (wordsearch[# h, v] = &#34;X&#34;) // = and == are equivalent in GML
			{
				// east
				if (h &lt; ds_grid_width(wordsearch) - 3)
				and (wordsearch[# h + 1, v] = &#34;M&#34;)
				and (wordsearch[# h + 2, v] = &#34;A&#34;)
				and (wordsearch[# h + 3, v] = &#34;S&#34;)
					xmas_count++
				// west
				if (h &gt;= 3)
				and (wordsearch[# h - 1, v] = &#34;M&#34;)
				and (wordsearch[# h - 2, v] = &#34;A&#34;)
				and (wordsearch[# h - 3, v] = &#34;S&#34;)
					xmas_count++
				// south
				if (v &lt; ds_grid_height(wordsearch) - 3)
				and (wordsearch[# h, v + 1] = &#34;M&#34;)
				and (wordsearch[# h, v + 2] = &#34;A&#34;)
				and (wordsearch[# h, v + 3] = &#34;S&#34;)
					xmas_count++
				// north
				if (v &gt;= 3)
				and (wordsearch[# h, v - 1] = &#34;M&#34;)
				and (wordsearch[# h, v - 2] = &#34;A&#34;)
				and (wordsearch[# h, v - 3] = &#34;S&#34;)
					xmas_count++

				// southeast
				if (h &lt; ds_grid_width(wordsearch) - 3)
                                and (v &lt; ds_grid_height(wordsearch) - 3)
				and (wordsearch[# h + 1, v + 1] = &#34;M&#34;)
				and (wordsearch[# h + 2, v + 2] = &#34;A&#34;)
				and (wordsearch[# h + 3, v + 3] = &#34;S&#34;)
					xmas_count++

				// southwest
				if (h &gt;= 3) and (v &lt; ds_grid_height(wordsearch) - 3)
				and (wordsearch[# h - 1, v + 1] = &#34;M&#34;)
				and (wordsearch[# h - 2, v + 2] = &#34;A&#34;)
				and (wordsearch[# h - 3, v + 3] = &#34;S&#34;)
					xmas_count++

				// northeast
				if (h &lt; ds_grid_width(wordsearch) - 3) and (v &gt;= 3)
				and (wordsearch[# h + 1, v - 1] = &#34;M&#34;)
				and (wordsearch[# h + 2, v - 2] = &#34;A&#34;)
				and (wordsearch[# h + 3, v - 3] = &#34;S&#34;)
					xmas_count++

				// northwest
				if (h &gt;= 3) and (v &gt;= 3)
				and (wordsearch[# h - 1, v - 1] = &#34;M&#34;)
				and (wordsearch[# h - 2, v - 2] = &#34;A&#34;)
				and (wordsearch[# h - 3, v - 3] = &#34;S&#34;)
					xmas_count++
			}
		}
	}

	// show result
	show_message_async(&#34;XMASs: &#34; + string(xmas_count))
</code></pre><p>As noted, GML is one of the few languages that allows you to make comparisons with a single <code>=</code>, the same as assignment. This is discouraged, but it&rsquo;s how I wrote code when I was 16, along with the nasty <code>if</code>s and nested <code>for</code>s.</p>
<p>The second part of the challenge involved finding Xs of the word <code>MAS</code>, e.g.</p>
<pre tabindex="0"><code>M-S M-M
-A- -A-
M-S S-S ...
</code></pre><p>I did this with some more ugly <code>if</code>s:</p>
<pre tabindex="0"><code class="language-gml" data-lang="gml">	// find x-mases
	var x_mas_count = 0;
	for (v = 0; v &lt; ds_grid_height(wordsearch); v++)
	{
		for (h = 0; h &lt; ds_grid_width(wordsearch); h++)
		{
			if (wordsearch[# h, v] = &#34;M&#34;)
			{
				if (h &lt; ds_grid_width(wordsearch) - 2)
                                and (v &lt; ds_grid_height(wordsearch) - 2)
				and (wordsearch[# h + 2, v] = &#34;M&#34;)
				and (wordsearch[# h + 1, v + 1] = &#34;A&#34;)
				and (wordsearch[# h, v + 2] = &#34;S&#34;)
				and (wordsearch[# h + 2, v + 2] = &#34;S&#34;)
					x_mas_count++
				else if (h &lt; ds_grid_width(wordsearch) - 2)
                                and (v &lt; ds_grid_height(wordsearch) - 2)
				and (wordsearch[# h + 2, v] = &#34;S&#34;)
				and (wordsearch[# h + 1, v + 1] = &#34;A&#34;)
				and (wordsearch[# h, v + 2] = &#34;M&#34;)
				and (wordsearch[# h + 2, v + 2] = &#34;S&#34;)
					x_mas_count++
			}
			else if (wordsearch[# h, v] = &#34;S&#34;)
			{
				if (h &lt; ds_grid_width(wordsearch) - 2)
                                and (v &lt; ds_grid_height(wordsearch) - 2)
				and (wordsearch[# h + 2, v] = &#34;M&#34;)
				and (wordsearch[# h + 1, v + 1] = &#34;A&#34;)
				and (wordsearch[# h, v + 2] = &#34;S&#34;)
				and (wordsearch[# h + 2, v + 2] = &#34;M&#34;)
					x_mas_count++
				else if (h &lt; ds_grid_width(wordsearch) - 2)
                                and (v &lt; ds_grid_height(wordsearch) - 2)
				and (wordsearch[# h + 2, v] = &#34;S&#34;)
				and (wordsearch[# h + 1, v + 1] = &#34;A&#34;)
				and (wordsearch[# h, v + 2] = &#34;M&#34;)
				and (wordsearch[# h + 2, v + 2] = &#34;M&#34;)
					x_mas_count++
			}
		}
	}

	// show result
	show_message_async(&#34;X-MASs: &#34; + string(x_mas_count))
}
</code></pre><p><a href="https://github.com/dmyates/advent-of-code-solutions/tree/master/2024/04%20-%20GameMaker%20Language"><strong>Full code on GitHub</strong></a></p>
<p><strong>Closing thoughts:</strong> Not too painful. While a more expressive language would have made the solutions shorter and more elegant, this code has the virtue of at least being pretty obvious. When I was first writing GML, I did it without access to the Internet, using just the helpfile. I have enough of GML etched into the deep recesses of my skull that I didn&rsquo;t feel the need to ask the AI anything, or even look at StackOverflow.</p>
<h1 id="day-5-gnu-prolog">
  <a class="heading-anchor" href="#day-5-gnu-prolog">#</a>
  Day 5: GNU Prolog
</h1>
<p><a href="https://adventofcode.com/2024/day/5"><strong>Challenge</strong></a>: find which lists adhere to all of the ordering rules.</p>
<p>This was clearly a constraints problem, so Prolog seemed like the right choice. I&rsquo;ve only used Prolog once before, when going through Bruce Tate&rsquo;s <a href="https://pragprog.com/titles/btlang/seven-languages-in-seven-weeks/"><em>Seven Programming Languages in Seven Weeks</em></a> book mentioned above. It is not an imperative or functional language, but a logic language &ndash; its closest sibling in wide use is probably SQL. Apart from that, its list handling and function definition syntax have some similarities with Haskell &ndash; you deal with lists through patterns, and can define multiple versions of the same function with different pattern inputs.</p>
<p>The core of the puzzle is about determining whether one element precedes another in a list. Lists containing the two elements in the correct order are valid, and so are lists containing only one of the two elements. So I started by encoding this rule into Prolog (thanks to <a href="https://stackoverflow.com/questions/33633942/prolog-x-before-y-in-a-list">this answer on StackOverflow</a>):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-prolog" data-lang="prolog"><span class="line"><span class="cl"><span class="nf">precedes</span><span class="p">(</span><span class="nv">X</span><span class="p">,</span> <span class="nv">Y</span><span class="p">,</span> <span class="nv">L</span><span class="p">):-</span>
</span></span><span class="line"><span class="cl">    <span class="s">\+</span> <span class="nf">member</span><span class="p">(</span><span class="nv">X</span><span class="p">,</span> <span class="nv">L</span><span class="p">);</span> <span class="c1">% either X must not (\+) be in L</span>
</span></span><span class="line"><span class="cl">    <span class="s">\+</span> <span class="nf">member</span><span class="p">(</span><span class="nv">Y</span><span class="p">,</span> <span class="nv">L</span><span class="p">).</span> <span class="c1">% or Y must not be in L</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="nf">append</span><span class="p">(</span><span class="k">_</span><span class="p">,</span> <span class="p">[</span><span class="nv">X</span><span class="p">|</span><span class="nv">Tail</span><span class="p">],</span> <span class="nv">L</span><span class="p">),</span> <span class="c1">% or find X and everything after it (Tail)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">append</span><span class="p">(</span><span class="k">_</span><span class="p">,</span> <span class="p">[</span><span class="nv">Y</span><span class="p">|</span><span class="k">_</span><span class="p">],</span> <span class="nv">Tail</span><span class="p">));</span> <span class="c1">% then find Y in Tail, ergo X precedes Y</span>
</span></span></code></pre></div><p>From there, I had the AI help me do the input file reading and text processing to get all the rules and lists I would need. This was annoying enough that at one stage I considered just writing a program in a different language that would output a Prolog file with the input hard-coded in. But as I often find with AI, I get much better results by prompting for each part of the code individually than by trying to prompt everything at once. Once I broke the problem down enough, Claude gave me the processing code I wanted.</p>
<p>I then ran every rule against every list. My code was highly inefficient, checking unnecessary rules and continuously reparsing the same data, and so it failed to complete in a reasonable amount of time. With the help of the AI, I optimised out some of its most obvious deficiencies and got it working. To start with, <code>precedes</code> was rewritten like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-prolog" data-lang="prolog"><span class="line"><span class="cl"><span class="nf">precedes</span><span class="p">(</span><span class="nv">X</span><span class="p">,</span> <span class="nv">Y</span><span class="p">,</span> <span class="nv">L</span><span class="p">):-</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span>   <span class="nf">memberchk</span><span class="p">(</span><span class="nv">X</span><span class="p">,</span> <span class="nv">L</span><span class="p">),</span> <span class="c1">% find only the first X in L (member tries to find all Xs)</span>
</span></span><span class="line"><span class="cl">        <span class="nf">memberchk</span><span class="p">(</span><span class="nv">Y</span><span class="p">,</span> <span class="nv">L</span><span class="p">)</span> <span class="s">-&gt;</span> <span class="c1">% find only the first Y in L</span>
</span></span><span class="line"><span class="cl">        <span class="nf">once</span><span class="p">((</span><span class="nf">append</span><span class="p">(</span><span class="k">_</span><span class="p">,</span> <span class="p">[</span><span class="nv">X</span><span class="p">|</span><span class="nv">Tail</span><span class="p">],</span> <span class="nv">L</span><span class="p">),</span> <span class="nf">memberchk</span><span class="p">(</span><span class="nv">Y</span><span class="p">,</span> <span class="nv">Tail</span><span class="p">)))</span> <span class="c1">% find only the first Y in Tail</span>
</span></span><span class="line"><span class="cl">    <span class="p">;</span>   <span class="s">true</span>  <span class="c1">% succeed if either X or Y is not in L</span>
</span></span><span class="line"><span class="cl">    <span class="p">).</span>
</span></span></code></pre></div><p>The second part of the puzzle required fixing the invalid lists. Again, my first stab at a solution &ndash; testing all rules against all list permutations &ndash; was too inefficient to complete in a reasonable amount of time (obviously). A more deliberate approach of finding individual rule violations and then swapping the elements involved worked much better.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-prolog" data-lang="prolog"><span class="line"><span class="cl"><span class="nf">fix_invalid_lists</span><span class="p">(</span><span class="nv">Rules</span><span class="p">,</span> <span class="nv">InvalidLists</span><span class="p">,</span> <span class="nv">FixedLists</span><span class="p">)</span> <span class="p">:-</span>
</span></span><span class="line"><span class="cl">    <span class="nf">findall</span><span class="p">(</span><span class="nv">FixedList</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">(</span><span class="nf">member</span><span class="p">(</span><span class="nv">List</span><span class="p">,</span> <span class="nv">InvalidLists</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">             <span class="nf">fix_list</span><span class="p">(</span><span class="nv">Rules</span><span class="p">,</span> <span class="nv">List</span><span class="p">,</span> <span class="nv">FixedList</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">            <span class="nv">FixedLists</span><span class="p">).</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">fix_list</span><span class="p">(</span><span class="nv">Rules</span><span class="p">,</span> <span class="nv">List</span><span class="p">,</span> <span class="nv">FixedList</span><span class="p">)</span> <span class="p">:-</span>
</span></span><span class="line"><span class="cl">    <span class="nf">find_violation</span><span class="p">(</span><span class="nv">Rules</span><span class="p">,</span> <span class="nv">List</span><span class="p">,</span> <span class="nv">X</span><span class="p">,</span> <span class="nv">Y</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">!,</span>  <span class="c1">% Cut to make sure we stop after finding one violation</span>
</span></span><span class="line"><span class="cl">    <span class="nf">swap_elements</span><span class="p">(</span><span class="nv">List</span><span class="p">,</span> <span class="nv">X</span><span class="p">,</span> <span class="nv">Y</span><span class="p">,</span> <span class="nv">NewList</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">fix_list</span><span class="p">(</span><span class="nv">Rules</span><span class="p">,</span> <span class="nv">NewList</span><span class="p">,</span> <span class="nv">FixedList</span><span class="p">).</span>
</span></span><span class="line"><span class="cl"><span class="nf">fix_list</span><span class="p">(</span><span class="k">_</span><span class="p">,</span> <span class="nv">List</span><span class="p">,</span> <span class="nv">List</span><span class="p">).</span>  <span class="c1">% No violations found, list is fixed</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">find_violation</span><span class="p">(</span><span class="nv">Rules</span><span class="p">,</span> <span class="nv">List</span><span class="p">,</span> <span class="nv">X</span><span class="p">,</span> <span class="nv">Y</span><span class="p">)</span> <span class="p">:-</span>
</span></span><span class="line"><span class="cl">    <span class="nf">member</span><span class="p">(</span><span class="nv">Rule</span><span class="p">,</span> <span class="nv">Rules</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">parse_rule</span><span class="p">(</span><span class="nv">Rule</span><span class="p">,</span> <span class="nv">X</span><span class="p">,</span> <span class="nv">Y</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="s">\+</span> <span class="nf">precedes</span><span class="p">(</span><span class="nv">X</span><span class="p">,</span> <span class="nv">Y</span><span class="p">,</span> <span class="nv">List</span><span class="p">).</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">swap_elements</span><span class="p">(</span><span class="nv">List</span><span class="p">,</span> <span class="nv">X</span><span class="p">,</span> <span class="nv">Y</span><span class="p">,</span> <span class="nv">NewList</span><span class="p">)</span> <span class="p">:-</span>
</span></span><span class="line"><span class="cl">    <span class="nf">append</span><span class="p">(</span><span class="nv">Before</span><span class="p">,</span> <span class="p">[</span><span class="nv">A</span><span class="p">|</span><span class="nv">Rest1</span><span class="p">],</span> <span class="nv">List</span><span class="p">),</span> <span class="c1">% split list at 1st element</span>
</span></span><span class="line"><span class="cl">    <span class="nf">append</span><span class="p">(</span><span class="nv">Middle</span><span class="p">,</span> <span class="p">[</span><span class="nv">B</span><span class="p">|</span><span class="nv">After</span><span class="p">],</span> <span class="nv">Rest1</span><span class="p">),</span> <span class="c1">% split rest of list at 2nd element</span>
</span></span><span class="line"><span class="cl">    <span class="p">((</span><span class="nv">A</span> <span class="o">=</span> <span class="nv">X</span><span class="p">,</span> <span class="nv">B</span> <span class="o">=</span> <span class="nv">Y</span><span class="p">)</span> <span class="p">;</span> <span class="p">(</span><span class="nv">A</span> <span class="o">=</span> <span class="nv">Y</span><span class="p">,</span> <span class="nv">B</span> <span class="o">=</span> <span class="nv">X</span><span class="p">)),</span> <span class="c1">% match 1st and 2nd elements</span>
</span></span><span class="line"><span class="cl">    <span class="p">!,</span>  <span class="c1">% Cut to prevent multiple solutions</span>
</span></span><span class="line"><span class="cl">    <span class="nf">append</span><span class="p">(</span><span class="nv">Before</span><span class="p">,</span> <span class="p">[</span><span class="nv">B</span><span class="p">|</span><span class="nv">Middle</span><span class="p">],</span> <span class="nv">Temp</span><span class="p">),</span> <span class="c1">% construct new list</span>
</span></span><span class="line"><span class="cl">    <span class="nf">append</span><span class="p">(</span><span class="nv">Temp</span><span class="p">,</span> <span class="p">[</span><span class="nv">A</span><span class="p">|</span><span class="nv">After</span><span class="p">],</span> <span class="nv">NewList</span><span class="p">).</span>
</span></span></code></pre></div><p><code>!</code> here is the <a href="https://en.wikipedia.org/wiki/Cut_(logic_programming)">cut operator</a>, which we use to force Prolog to commit to fixing the first violation it finds in <code>fix_list</code> and swapping the first instances of <code>X</code> and <code>Y</code> it finds in <code>List</code> in <code>swap_elements</code>. This forces our code to fix violations one at a time with a single swap rather than looking for multiple violations and multiple solutions to each one.</p>
<p><a href="https://github.com/dmyates/advent-of-code-solutions/tree/master/2024/05%20-%20Prolog"><strong>Full code on GitHub</strong></a></p>
<p><strong>Closing thoughts:</strong> The AI had to help me quite a lot with this one, which wasn&rsquo;t totally smooth sailing, because it very quickly gets confused between different versions of Prolog, and I had to keep reminding it I was using GNU Prolog rather than SWI-Prolog. Next time I&rsquo;ll probably just bite the bullet and use SWI-Prolog instead.</p>
<p>This was quite a difficult language to use for a single challenge. I&rsquo;d previously used Prolog mostly on the level of toy demos like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-prolog" data-lang="prolog"><span class="line"><span class="cl"><span class="nf">likes</span><span class="p">(</span><span class="s">alice</span><span class="p">,</span> <span class="s">bob</span><span class="p">).</span>
</span></span><span class="line"><span class="cl"><span class="nf">likes</span><span class="p">(</span><span class="s">bob</span><span class="p">,</span> <span class="s">carol</span><span class="p">).</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">friend</span><span class="p">(</span><span class="nv">X</span><span class="p">,</span> <span class="nv">Y</span><span class="p">)</span> <span class="p">:-</span> <span class="s">\+</span><span class="p">(</span><span class="nv">X</span> <span class="o">=</span> <span class="nv">Y</span><span class="p">),</span> <span class="nf">likes</span><span class="p">(</span><span class="nv">X</span><span class="p">,</span> <span class="nv">Z</span><span class="p">),</span> <span class="nf">likes</span><span class="p">(</span><span class="nv">Y</span><span class="p">,</span><span class="nv">Z</span><span class="p">).</span>
</span></span></code></pre></div><p>Needless to say, the differences between this sort of thing and a program that has to operate on large amounts of data are legion. One reason is that Prolog will usually try to find every solution that fits a set of constraints, and must be explicitly told to settle for just the first solution (hence <code>once</code> and <code>!</code>).</p>
<p>This seems to be the general approach to Prolog programming: first write a program that finds every solution for every problem, then prune the ones you don&rsquo;t care about. For very small toy problems, you may never arrive at this second step, but AoC challenges, which I get the sense are designed to thwart brute-forcing, really reward efficiency.</p>
<hr>
<p>I&rsquo;ll cover <a href="/2024/12/23/aoc-2024-part2/">Days 6&ndash;10 in the next part</a>. Highlights include breaking my main rule for this exercise on day 6.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>I would still say it was better to start with a table than try to read in and parse a text file of numbers in a language with no obvious way to cast strings to numbers.&#160;<a href="https://davidyat.es/2024/12/16/aoc-2024-part1/#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Having been around <a href="https://gamemaker.io/en/blog/gamemaker-25">since 1999</a>, GameMaker has gone through numerous rewrites, rebrands and version numbering resets. Originally it was Animo, then Game Maker, through versions 1 to 8.1. It was then rewritten and rebranded to GameMaker: Studio, which had versions 1 through 1.4. A second massive rewrite came in form of GameMaker Studio 2, which later adopted a rolling release cycle and rebranded as just GameMaker.&#160;<a href="https://davidyat.es/2024/12/16/aoc-2024-part1/#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>The sandbox was introduced in Studio. Previous versions of GM included functionality for accessing disk drives. Technically, it&rsquo;s not necessary if you use the filepicker function, which I did on Windows, but it is necessary to access files by path as I had to on Linux.&#160;<a href="https://davidyat.es/2024/12/16/aoc-2024-part1/#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

        <p><a href="mailto:d@vidyat.es?subject=RE: Advent%20of%20Code%202024%3a%20Days%201%e2%80%935">Reply via email</a></p>
        ]]></content:encoded></item><item><title>Image upload with render hooks</title><link>https://davidyat.es/2024/10/19/render-hook-image-upload/</link><pubDate>Sat, 19 Oct 2024 06:41:36 +0200</pubDate><author>David Yates</author><guid>https://davidyat.es/2024/10/19/render-hook-image-upload/</guid><content:encoded><![CDATA[
        
        <p>Something I still miss about the <a href="/tags/ghost">old, Ghost-powered version of this blog</a> is the ease of adding images to posts. My favourite thing about Ghost was its <a href="/2017/07/28/ghost-one-oh/">two-paned markdown editor</a>, which handled image uploads like this:</p>
<ol>
<li>In the editor pane, type the Markdown syntax for displaying an image but do not specify a path, i.e. <code>![]()</code>.</li>
<li>In the preview pane, this empty image syntax produces a box onto which you can drag an image file.</li>
<li>Once dragged, the image file will be uploaded and its location filled in, producing something like <code>![](/content/<wbr>images/<wbr>2024/<wbr>10/<wbr>myimage.png)</code>.</li>
</ol>
<p>My Hugo blog, being a static site, does not have a post editor. I write posts in a text editor and must manually place any images I want to include somewhere in Hugo&rsquo;s <code>static</code> directory (or a <a href="https://gohugo.io/content-management/page-bundles/">page bundle</a>, but I&rsquo;ve never gotten into the habit of using those). This is not by any means a huge burden, but it requires fiddling with file managers and/or terminal and thus takes me out of the writing flow.</p>
<p>I&rsquo;ve tried a few different static site CMSs, from self-hosted ones like Netlify CMS (latterly <a href="https://decapcms.org/docs/intro/">Decap</a>) to online services like Forestry (since discontinued in favour of <a href="https://tina.io/forestry/">TinaCMS</a>) and even editor plugins like <a href="https://frontmatter.codes/">Front Matter</a> for VS Code. While I appreciate the cleverness of these solutions, none of them really stuck. Ultimately, they all had too many features that I didn&rsquo;t need and required me to write posts in something other than Vim, which I could never get into the habit of doing &ndash; especially when the alternative presented was a slightly souped-up <code>&lt;textarea&gt;</code>. And in any case, I didn&rsquo;t have a great need for the other CMS stuff &ndash; I&rsquo;m happy typing <code>hugo new</code> instead of clicking a button that says <strong>New Post</strong> and I&rsquo;ll gladly fill in the TOML frontmatter without the help of drop-downs or toggle switches. But I did want a more integrated way to upload images.</p>
<p>In my <a href="/2024/10/06/deprecating-shortcodes/">first post in this series</a>, I talked about how Hugo&rsquo;s new <a href="https://gohugo.io/render-hooks/introduction/">Markdown render hooks</a> allowed me to replace a whole bunch of common shortcodes with slightly more complex Markdown. Shortly after writing that, I started wondering if I could use render hooks to do more complex things, like syntax highlighting for languages not supported by Hugo&rsquo;s built-in highlighter Chroma. Answer: <a href="/2024/10/18/render-hook-syntax-highlighting/">it could, sort of</a>. With that accomplished, I wanted to take things further still. Could I use an image render hook to enable Ghost-style image uploads?</p>
<p><figure>
  
  <img src="/content/images/2024/10/image.jpg" alt="The answer is yes, and it&rsquo;s only a little bit convoluted." loading="lazy"/>
  
  <figcaption>
    <p>The answer is yes, and it&rsquo;s only a little bit convoluted.</p>
  </figcaption>
</figure>
</p>
<p>Here&rsquo;s what I came up with:</p>
<p><video src="/content/video/2024/10/screencap.mp4" style="max-width:100%" controls>Image uploading in action</video></p>
<p>How it works:</p>
<ol>
<li>My theme&rsquo;s image render hook includes some code to show an upload box if a given image&rsquo;s path is empty and Hugo is running in the <a href="https://gohugo.io/functions/hugo/environment/">development environment</a>.</li>
<li>My theme&rsquo;s <a href="https://gohugo.io/templates/base/#define-the-base-template">base template</a> includes some JavaScript for handling image dragging and uploading, only included if Hugo is running in the development environment.</li>
<li>I&rsquo;ve written a basic NodeJS app that lives in my site&rsquo;s base directory, and which receives image uploads and saves them to an appropriate directory (<code>static/<wbr>content/<wbr>images/<wbr>YYYY/<wbr>MM/<wbr>imagename.jpg</code>).</li>
<li>After a successful upload, the uploader JavaScript replaces the box with the image and copies the image&rsquo;s path to the clipboard. The included image is shown partially transparent to indicate that is temporary, and I still need to modify the post Markdown.</li>
<li>I paste the image path into my post Markdown. When the post is saved, Hugo detects a change and reloads the page.</li>
</ol>
<p>The only appreciable different between this and my old Ghost editor is that filling in the path requires a manual paste step. If anything, that gives it more flexibility.</p>
<p>To avoid having to start up two different webservers whenever I want to edit my blog, I also wrote a Bash script, <code>blog.sh</code>, which runs both <code>hugo server</code> and <code>nodejs uploader.js</code>. Any arguments passed to the script are forwarded to the former command, so I can still use <code>--buildDrafts</code> and <code>--buildFuture</code>.</p>
<p>I&rsquo;ve made the code available in <a href="https://github.com/dmyates/hugo-image-uploader"><strong>this repository</strong></a>. I&rsquo;ve done my best to package it as a <a href="https://gohugo.io/hugo-modules/theme-components/">theme component</a>, though it doesn&rsquo;t fit perfectly into that frame. I haven&rsquo;t done much to make it very general or user-friendly, as it is designed first and foremost for my own use. The following additional features might be nice to have:</p>
<ul>
<li>Visual feedback to confirm that the image path has been copied to the clipboard.</li>
<li>Support for saving images to page bundles.</li>
<li>Processing of image uploads (compression, conversion, etc).</li>
<li>Image uploads from a file picker.</li>
</ul>
<p>If you&rsquo;d like to be able to upload more than just images, you should be able to do it by removing the content validation in <code>uploader.js</code>.</p>
<p>That&rsquo;s it for render hooks, at least until Hugo releases footnote hook support.</p>

        <p><a href="mailto:d@vidyat.es?subject=RE: Image%20upload%20with%20render%20hooks">Reply via email</a></p>
        ]]></content:encoded></item><item><title>Syntax highlighting with render hooks</title><link>https://davidyat.es/2024/10/18/render-hook-syntax-highlighting/</link><pubDate>Fri, 18 Oct 2024 18:41:36 +0200</pubDate><author>David Yates</author><guid>https://davidyat.es/2024/10/18/render-hook-syntax-highlighting/</guid><content:encoded><![CDATA[
        <img src="https://davidyat.es/content/images/2024/10/code-highlighting.jpg" class="post-img" />
        <p> </p>



  
  <aside class="hint-box alert-update">
    
    <p id="update-2024-12-22"><strong>Update &nbsp; 2024-12-22</strong></p>
    
    <p>I&rsquo;ve disabled the render hooks detailed here because they made the site take 20 times longer to build, in addition to their many other shortcomings. Therefore, the code they supported on this page and others will no longer be highlighted (until I find another solution). I would not recommend taking this approach to syntax highlighting.</p>
  </aside>
  

<p>After <a href="/2024/10/06/deprecating-shortcodes/">my previous post</a>, I got to wondering if there were any other ways I could use Markdown render hooks to improve this blog&rsquo;s appearance and editing experience. One thing that immediately came to mind was syntax highlighting for obscure languages.</p>
<p>Hugo uses <a href="https://github.com/alecthomas/chroma">Chroma</a> for syntax highlighting, which supports <a href="https://gohugo.io/content-management/syntax-highlighting/#list-of-chroma-highlighting-languages">these languages</a>. Support is quite comprehensive, even including Go HTML Templating syntax and <a href="https://web.archive.org/web/20241006233811/http://www.codersnotes.com/notes/a-constructive-look-at-templeos/">Holy C</a>. But it doesn&rsquo;t support everything. If you need highlighting for another language, you&rsquo;ll need to implement it in a PR to Chroma and wait for it to get merged, and then incorporated into Hugo. Or at least, that is the proper way to do it.</p>
<p>The hacky way to do it is via <a href="https://gohugo.io/render-hooks/code-blocks/">code block render hooks</a> and liberal use of <a href="https://gohugo.io/functions/strings/replacere/">replaceRE</a>. This may be more appropriate if:</p>
<ol>
<li>The language you want highlighting for is one of your own making, maybe not even intended for public release.</li>
<li>The snippets you want to highlight are simple and do not require a fully functional lexer.</li>
<li>You just want a local solution that doesn&rsquo;t rely on getting PRs approved and waiting for software updates.</li>
</ol>
<p>As Hugo supports <a href="https://gohugo.io/render-hooks/code-blocks/#examples">language-specific codeblock render hooks</a>, it almost feels as though it&rsquo;s encouraging you to do this. I&rsquo;ve written two: one for Gruescript<sup id="fnref:1"><a href="https://davidyat.es/2024/10/18/render-hook-syntax-highlighting/#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, used mainly in <a href="/2023/05/20/postmortem-ludum-dare-53/#game-code">this post</a>:</p>
<pre tabindex="0"><code class="language-gruescript" data-lang="gruescript"># A ROOM
room before_barricade You&#39;re standing in front of an enormous barricade made of junk room.
prop display Before the junk barricade
prop year Unknown year
tags start

# AN OBJECT
thing sword Courier&#39;s Blade
desc This is the first job you&#39;ve had where a massive sword is considered required equipment. 
carried
tags portable

# A PERSON
thing fletcher shifty character
name shifty character
desc The man looks to be in his sixties, with a white beard and a mane of grey hair.
tags alive male conversation
loc wooden_shed
prop callme Fletcher
prop start_conversation &#34;You must be the courier,&#34; says the man, looking you up and down. &#34;Name&#39;s Fletcher.&#34; Your collection tag mentions a Darius Fletcher – this must be him.
prop end_conversation &#34;G&#39;bye then.&#34;
</code></pre><p>&hellip;and one for Inform 7<sup id="fnref:2"><a href="https://davidyat.es/2024/10/18/render-hook-syntax-highlighting/#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>, used mainly in <a href="/2019/10/03/programming-in-inform-7/">this post</a>:</p>
<pre tabindex="0"><code class="language-inform7" data-lang="inform7"><strong>"The Lady and the Tiger"</strong>

The lady is on top of the tiger.
The tiger is in a room.
&#34;This lady hails from Niger
Niamey, I presume.&#34;

The lady wears a smile.
Her arm is in a sling.
Riding is an action
applying to one thing.

&#34;The lady on the tiger
Up and down they sped.&#34;
After the lady rides the tiger
Now it wears the smile instead.
</code></pre><p>The approach I took was as follows: first, start with a render hook that replicates the HTML of a Chroma-highlighted codeblock (in <code>layouts/<wbr>_default/<wbr>_markup/<wbr>render-codeblock-mylang.html</code>):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="cp">{{</span><span class="w"> </span><span class="nx">$code</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="na">.Inner</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cp">{{</span><span class="w"> </span><span class="nx">$highlightedCode</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">$code</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;highlight&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">pre</span> <span class="na">tabindex</span><span class="o">=</span><span class="s">&#34;0&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;chroma&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">code</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;language-MYLANG&#34;</span> <span class="na">data-lang</span><span class="o">=</span><span class="s">&#34;MYLANG&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="cp">{{</span><span class="w"> </span><span class="nx">$highlightedCode</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">safeHTML</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">code</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">pre</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>Next, add a bunch of <code>replaceRE</code> lines right after the initial assignment of <code>$highlightedCode</code> that search for keywords and syntactical constructions and wrap them in <code>&lt;span&gt;</code>s with the appropriate Chroma class. For example, here&rsquo;s some code for highlighting strings:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="cp">{{</span><span class="w"> </span><span class="nx">$highlightedCode</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">replaceRE</span><span class="w"> </span><span class="s">`\&#34;([^\&#34;]*)\&#34;`</span><span class="w"> </span><span class="s">`&lt;span class=&#34;s&#34;&gt;&#34;$1&#34;&lt;/span&gt;`</span><span class="w"> </span><span class="nx">$highlightedCode</span><span class="w"> </span><span class="cp">}}</span>
</span></span></code></pre></div><p>For this to work, your site needs to be set up to use <a href="https://gohugo.io/content-management/syntax-highlighting/#generate-syntax-highlighter-css">highlighting classes and a stylesheet</a>. You can use the comments in the Hugo-generated stylesheet to determine which class names to use for which elements (or just go by which colour combos you like).</p>
<p>There are obvious and severe limitations to using regular expressions to highlight code syntax. Go&rsquo;s regex does not support lookarounds, and even if it did, <a href="https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/1732454#1732454">that way lies madness</a>. You can get around some problems by rearranging the order of your <code>replaceRE</code>s, but there&rsquo;s a hard limit to what you&rsquo;ll be able to achieve without proper parsing. The specific cases I&rsquo;ve implemented work well enough with the snippets I&rsquo;ve used them for, but are bound to fail for complex code that includes a lot of nesting (string interpolation, commented-out code, etc). Even code that uses language keywords in strings will probably be highlighted incorrectly.</p>
<p>I&rsquo;ve got one more render hook post coming after this one, showcasing an even crazier hack. It has to do with image render hooks. Stay tuned.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Given that I wrote <a href="https://github.com/dmyates/vim-gruescript">a Vim syntax highlighting file</a> for the language, to not have it highlighted on my blog seemed a pity.&#160;<a href="https://davidyat.es/2024/10/18/render-hook-syntax-highlighting/#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>This was a very small one. Being intended to mimic natural language, Inform 7 uses very little highlighting.&#160;<a href="https://davidyat.es/2024/10/18/render-hook-syntax-highlighting/#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

        <p><a href="mailto:d@vidyat.es?subject=RE: Syntax%20highlighting%20with%20render%20hooks">Reply via email</a></p>
        ]]></content:encoded></item><item><title>Deprecating shortcodes with render hooks</title><link>https://davidyat.es/2024/10/06/deprecating-shortcodes/</link><pubDate>Sun, 06 Oct 2024 15:39:57 +0200</pubDate><author>David Yates</author><guid>https://davidyat.es/2024/10/06/deprecating-shortcodes/</guid><content:encoded><![CDATA[
        <img src="https://davidyat.es/content/images/2024/10/md.jpg" class="post-img" />
        <p>When I <a href="/2016/08/19/moving-to-a-static-site/">moved this blog to Hugo</a>, one of the features that most impressed me was <a href="https://gohugo.io/content-management/shortcodes/">shortcodes</a>. These are little snippets that can be used to add complex formatting and content to posts without having to use raw HTML. For example, instead of pasting in the embed code for a Xweet or YouTube video, you use a shortcode like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">{{&lt; youtube VIDEO-ID &gt;}}
</span></span><span class="line"><span class="cl">{{&lt; tweet USERNAME ID &gt;}}
</span></span></code></pre></div>


  
  <aside class="hint-box alert-tip">
    
      
      <p id=shortcode-escaping><strong>Shortcode escaping</strong></p>
      
    
    <p>A codebox was not enough to prevent the above shortcodes from rendering. I had to write them with this syntax: <code>{{&lt;/* tweet USERNAME ID */&gt;}}</code>. And to show the syntax, I had to write this: <code>{{&lt;/*/* tweet USERNAME ID */*/&gt;}}</code>. And to show that, I had to&hellip; you get the idea. This is not explicitly documented &ndash; I figured it out from looking at the source of the Hugo documentation.</p>
  </aside>
  

<p>You can write <a href="https://gohugo.io/templates/shortcode/">custom shortcodes</a> in the Go HTML template language Hugo uses, and they can become quite elaborate. It&rsquo;s a great way to reuse complex, non-standard HTML formatting, though I&rsquo;ve sometimes found the syntax a bit cumbersome.</p>
<p>Recently, however, Hugo has been adding <a href="https://gohugo.io/render-hooks/introduction/">render hooks</a>, which allow site developers to override the default Markdown-to-HTML conversion process for several elements. It started with headings, links and images, which allowed me to replace my captioned image shortcode with plain Markdown. As a result, this shortcode:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">{{&lt; figure src=&#34;https://davidyat.es/path/to/image&#34; caption=&#34;Image caption&#34; &gt;}}
</span></span></code></pre></div><p>Has now been replaced with this Markdown, which produces the same HTML:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">![<span class="nt">Image caption</span>](<span class="na">/path/to/image</span>)
</span></span></code></pre></div><p>More recently, Hugo added <a href="https://gohugo.io/render-hooks/blockquotes/">blockquote render hooks</a>.<sup id="fnref:1"><a href="https://davidyat.es/2024/10/06/deprecating-shortcodes/#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> The blockquote is not a particularly complex element. <a href="https://gohugo.io/render-hooks/blockquotes/#examples">Per the Hugo docs</a>, default behaviour is to turn this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl"><span class="k">&gt; </span><span class="ge">Some text
</span></span></span></code></pre></div><p>Into this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">blockquote</span><span class="p">&gt;</span>Some text<span class="p">&lt;/</span><span class="nt">blockquote</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>However, between the use of <a href="https://gohugo.io/content-management/markdown-attributes/">attributes</a> and the devs&rsquo; inclusion of <a href="https://gohugo.io/render-hooks/blockquotes/#alerts">GitHub/Obsidian-style alert syntax</a>, this one feature has allowed me to deprecate multiple shortcodes, while also providing enhanced formatting possibilities (hitherto unrealised).</p>
<p>First, I replaced the info box shortcode, which looked like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">{{% info-box %}}
</span></span><span class="line"><span class="cl"><span class="gs">**Did you know?**</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">An interesting tangent.
</span></span><span class="line"><span class="cl">{{% /info-box %}}
</span></span></code></pre></div><p>With Markdown like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl"><span class="k">&gt; </span><span class="ge">[!note] Did you know?
</span></span></span><span class="line"><span class="cl"><span class="k">&gt; </span><span class="ge">An interesting tangent.
</span></span></span></code></pre></div>


  
  <aside class="hint-box alert-note">
    
      
      <p id=did-you-know><strong>Did you know?</strong></p>
      
    
    <p>An interesting tangent.</p>
  </aside>
  

<p>The first line of the alert blockquote includes a type (in square brackets) and a title (the rest of the line). Within the blockquote template, you can do anything with this information &ndash; display the title with special formatting, apply different formatting to different alert types, etc&hellip; This is much more flexibility than my old shortcode provided, for less typing.</p>
<p>The render hook also provides an optional <em>sign</em> argument, which can be <code>+</code> or <code>-</code>. In <a href="https://obsidian.md/">Obsidian</a>, this is used to make the block foldable, like the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details"><code>&lt;details&gt;</code></a> element in HTML. I use this element occasionally on this blog, usually for hiding spoilers. Previously, I had a shortcode for it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">{{% accordion &#34;Click to reveal spoilers&#34; %}}
</span></span><span class="line"><span class="cl">Soylent Green is made of people.
</span></span><span class="line"><span class="cl">{{% /accordion %}}
</span></span></code></pre></div><p>But now I&rsquo;ve been able to fold that formatting into my blockquote template, allowing for this syntax:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl"><span class="k">&gt; </span><span class="ge">[!spoilers]+ Click to reveal spoilers
</span></span></span><span class="line"><span class="cl"><span class="k">&gt; </span><span class="ge">Soylent Green is made of people.
</span></span></span></code></pre></div>


  
  <details>
    <summary>Click to reveal spoilers</summary>
    <aside class="alert-spoilers ">
    <p>Soylent Green is made of people</p>
    </aside>
    </details>
  

<p>And for regular old blockquotes, I can use <a href="https://gohugo.io/content-management/markdown-attributes/">attributes</a> to ensure consistently styled citations across the blog.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl"><span class="k">&gt; </span><span class="ge">85% of quotes on the Internet are made up.
</span></span></span><span class="line"><span class="cl">{author = &#34;Abraham Lincoln&#34; source = &#34;Gettysburg Address&#34;}
</span></span></code></pre></div>


  <blockquote>
    <p>85% of quotes on the Internet are made up.</p>

    
    <small>Abraham Lincoln, <cite>Gettysburg Address</cite></small>
    
  </blockquote>




  
  <aside class="hint-box alert-tip">
    
      
    
    <p>Make sure <a href="https://gohugo.io/content-management/markdown-attributes/#block-elements">Markdown attributes are enabled in your Hugo config</a> before attempting to use them.</p>
  </aside>
  

<p>And lastly,</p>



  <blockquote>
    <p>Unadorned blockquotes still work fine.</p>

    
  </blockquote>

<p>John Gruber&rsquo;s <a href="https://daringfireball.net/projects/markdown/">original vision for Markdown</a> was for it to be readable in both rendered and unrendered forms. Much of the inspiration for its syntax came from existing conventions for decorating plain text &ndash; emphasising words with surrounding <code>*</code>s, underlining headings with rows of <code>=</code>/<code>-</code>, quoting with <code>&gt;</code>, etc. To this day, Hacker News commenters include links using <a href="https://daringfireball.net/projects/markdown/syntax#link">Markdown reference-style syntax</a> which is never rendered.</p>
<p>In the twenty years since its first release, Markdown&rsquo;s syntax has been extended by numerous companies and individuals. <a href="https://github.com/yuin/goldmark">Goldmark</a>, the Markdown parser used by Hugo, has built-in support for strikethroughs, tables, footnotes, definition lists<sup id="fnref:2"><a href="https://davidyat.es/2024/10/06/deprecating-shortcodes/#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> and checklists, among other things. To my eye, most of the syntax for these extensions adheres to the original spirit of readability &ndash; at the very least, none of it is more arcane than <a href="https://daringfireball.net/projects/markdown/syntax#img">the image syntax in the original spec</a>. The definition list syntax doesn&rsquo;t really look like anything, but I don&rsquo;t have a better alternative. When it comes to prose markup, I&rsquo;ve decided that a couple of cryptic glyphs are preferable to the verbose syntax of Hugo shortcodes and plain HTML.<sup id="fnref:3"><a href="https://davidyat.es/2024/10/06/deprecating-shortcodes/#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> Implicit is sometimes better than explicit.</p>
<p>Attributes, like the ones I&rsquo;ve used for my quote above, are where we cross over from marking up text for human eyes to marking it up for the computer. Attributes aren&rsquo;t strictly necessary for this very simple use-case &ndash; I could just write the citation out manually, but I&rsquo;m sacrificing a little readability for consistency. In theory, I could avoid this by including some more complex logic in the blockquote template, but that would be a very hacky way to extend Markdown syntax. In other words, a project for another day.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Also <a href="https://gohugo.io/render-hooks/tables/">table hooks</a>. Still waiting for <a href="/2020/12/31/footnote-previews//#fn:4">footnote hooks</a>.&#160;<a href="https://davidyat.es/2024/10/06/deprecating-shortcodes/#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>I was informed that <a href="/2017/10/18/description-list/#update-2018-01-02">this existed</a> shortly after <a href="/2017/10/18/description-list/">implementing a shortcode for it</a>. So score another one for Markdown over shortcodes.&#160;<a href="https://davidyat.es/2024/10/06/deprecating-shortcodes/#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Remember, <a href="https://daringfireball.net/projects/markdown/syntax#html">Markdown is a superset of HTML</a>.&#160;<a href="https://davidyat.es/2024/10/06/deprecating-shortcodes/#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

        <p><a href="mailto:d@vidyat.es?subject=RE: Deprecating%20shortcodes%20with%20render%20hooks">Reply via email</a></p>
        ]]></content:encoded></item></channel></rss>