<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>rostrum.blog</title>
<link>https://www.rostrum.blog/</link>
<atom:link href="https://www.rostrum.blog/index.xml" rel="self" type="application/rss+xml"/>
<description>Realised showerthoughts about R and Python mostly</description>
<generator>quarto-1.8.27</generator>
<lastBuildDate>Sun, 01 Mar 2026 00:00:00 GMT</lastBuildDate>
<item>
  <title>A tiny CLI weather {Rapp}ort</title>
  <dc:creator>Matt Dray</dc:creator>
  <link>https://www.rostrum.blog/posts/2026-03-01-weva/</link>
  <description><![CDATA[ 





<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2026-03-01-weva/resources/weva.png" class="img-fluid quarto-figure quarto-figure-left figure-img" style="width:100.0%" alt="A screenshot of a terminal that's running the comand 'weva tr197aa -h 24 -e', which returns a one-line weather report with temperature and weather emojis for now and a day later, plus the temperature extreems for today. The temperature values are bold and coloured."></p>
</figure>
</div>
<section id="tldr" class="level2">
<h2 class="anchored" data-anchor-id="tldr">tl;dr</h2>
<p>The <a href="https://github.com/matt-dray/weva">{weva} R package</a> contains a titchy command-line interface (CLI) to present an opinionated micro-weather report.</p>
</section>
<section id="building-some-rapport" class="level2">
<h2 class="anchored" data-anchor-id="building-some-rapport">Building some {Rapp}ort</h2>
<p>I’m spending more time in the terminal.</p>
<p>So I’ve been making little CLI concepts:</p>
<ul>
<li><a href="https://www.rostrum.blog/posts/2025-08-25-jot/">jot</a> for recording notes</li>
<li><a href="https://www.rostrum.blog/posts/2025-10-30-pet/">pet</a> for a persistent cyberpet</li>
<li><a href="https://www.rostrum.blog/posts/2026-02-01-tyle/">tyle</a> for a roguelike-like</li>
</ul>
<p>These are all written in Python, which seems a more natural choice for this purpose than R<sup>1</sup>.</p>
<p>But I was pleased to see a recent <a href="https://tidyverse.org/blog/2026/02/rapp-0-3-0/">blog post</a> about a new release of <a href="https://github.com/r-lib/Rapp">{Rapp}</a>, an R package to help create CLIs.</p>
<p>So obviously I needed to try it out.</p>
</section>
<section id="climate" class="level2">
<h2 class="anchored" data-anchor-id="climate">CLImate</h2>
<p>But a CLI for what?</p>
<p>My colleagues and I have enjoyed twee weather CLIs of late, like <a href="https://github.com/Veirt/weathr">weathr</a> and <a href="https://github.com/chubin/wttr.in">wttr.in</a>.</p>
<p>So, because I have no imagination: introducing the concept <a href="https://github.com/matt-dray/weva/">{weva}</a> (‘wevvah’<sup>2</sup>) R package.</p>
<p>It does way less than those other examples and doesn’t have fun ASCII graphics. It’s just a simple toy project to see how {Rapp} works, with no guarantees. Is that enough disclaimers?</p>
<p>Along with {Rapp}, this was made possible by the:</p>
<ul>
<li>free APIs <a href="https://open-meteo.com/">Open-Meteo</a> and <a href="https://postcodes.io/">postcodes.io</a><sup>3</sup>, which do not require a login</li>
<li>the tidyverse packages <a href="https://httr2.r-lib.org/">{httr2}</a> and <a href="https://lubridate.tidyverse.org/">{lubridate}</a></li>
</ul>
<section id="r-console" class="level3">
<h3 class="anchored" data-anchor-id="r-console">R console</h3>
<p>From the R console, you could install<sup>4</sup> {weva} from GitHub like:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1">pak<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">pak</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"matt-dray/weva"</span>)</span></code></pre></div></div>
<p>You then use a {weva} function to install the CLI tool:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1">weva<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">install_cli</span>()</span></code></pre></div></div>
<p>This will print the install location of the CLI, if successful.</p>
</section>
<section id="terminal" class="level3">
<h3 class="anchored" data-anchor-id="terminal">Terminal</h3>
<p>From a terminal—not the R console—you can use <code>weva</code> to look up weather for a given UK postcode<sup>5</sup>.</p>
<p>Let’s compare Lands End (‘top’ of mainland Britain) to John o’ Groats (‘bottom’)<sup>6</sup> as I write this.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb3-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">weva</span> tr197aa</span></code></pre></div></div>
<pre><code>now 10.9°C ☁️ | +1h 11.3°C ☁️ </code></pre>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb5-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">weva</span> kw14yr</span></code></pre></div></div>
<pre><code>now 6.1°C ☁️ | +1h 6.3°C 🌧️🪶 </code></pre>
<p>The one-line display has segments for the current and future temperature and weather. There are emoji modifiers for heavy, light and freezing versions of some weather types.</p>
<p>Surprise, it’s early March everywhere in Britain.</p>
<p>Hopefully your day will be brightened by the styling with <a href="https://en.wikipedia.org/wiki/ANSI_escape_code#Colors">ANSI escape codes</a>, assuming your terminal supports them.</p>
<section id="options" class="level4">
<h4 class="anchored" data-anchor-id="options">Options</h4>
<p>You can add some flags to adjust the settings. Use <code>--hour</code> (or its shortcut <code>-h</code>) to adjust the number of hours for the forward-look. Let’s look one day ahead.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb7-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">weva</span> tr197aa <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-h</span> 24</span></code></pre></div></div>
<pre><code>now 10.9°C ☁️ | +24h 7.7°C 🌧️🪶 </code></pre>
<p>So there’s light drizzle this time tomorrow and it will be cooler (did I mention it’s early March in Britain).</p>
<p>Note that you can only look at forecasts up to the end of the day after tomorrow<sup>7</sup>.</p>
<p>Use the <code>--extremes</code> (<code>-e</code>) flag to also print a segment with today’s low and high temperatures. You can compound these options:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb9-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">weva</span> tr197aa <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-h</span> 24 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-e</span></span></code></pre></div></div>
<pre><code>now 10.9°C ☁️ | +24h 7.7°C 🌧️🪶 | today 9.3°C to 12°C </code></pre>
<p>Well, ‘extremes’ is perhaps a bit extreme for this temperature range.</p>
</section>
<section id="time-is-hard" class="level4">
<h4 class="anchored" data-anchor-id="time-is-hard">Time is hard</h4>
<p>‘Now’ and ‘later’ in this context are actually a bit awkward.</p>
<p>The Open-Meteo API returns the ‘current’ time as a quarter-hour increment, while ‘hourly’ information is the top of the hour.</p>
<p>My low-effort solution was to round ‘now’ to the nearest hour and declare ‘+1 hour’ as the hour after that.</p>
<p>In addition, the hourly data is returned for the whole of the current day (i.e.&nbsp;some of it is from the past). That means we have to match the rounded ‘now’ time to the list of times for today to get its index, then add the user-supplied <code>--hours</code> to find the details for the correct time in the future.</p>
<p>So you can only ask for a forecast for the rest of today or the following two full days.</p>
<p>That’s a bit confusing, so you can use the <code>--datetime</code> (<code>-d</code>) option to expose the datetimes for the data in each segment of the report.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb11-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">weva</span> tr197aa <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-h</span> 24 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-e</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-d</span></span></code></pre></div></div>
<pre><code>2026-03-01 10:00 10.9°C 🌧️🪶 | 2026-03-02 10:00 7.7°C 🌧️🪶 | 2026-03-01 9.3°C to 12°C </code></pre>
<p>As I write, it’s 10:06 on 1 March 2026. That’s rounded to 10:00 for the ‘now’ time. And so 24 hours from now is 10:00 tomorrow: 2 March 2026.</p>
</section>
</section>
</section>
<section id="inside-the-crystal-ball" class="level2">
<h2 class="anchored" data-anchor-id="inside-the-crystal-ball">Inside the crystal ball</h2>
<p>What’s actually happening in the code?</p>
<p>{weva} is just a normal R package with some exported functions to <code>get_latlon()</code>, <code>get_weather()</code> and <code>write_report()</code>.</p>
<p>But there’s one difference: <a href="https://github.com/matt-dray/weva/blob/main/exec/weva.R">a script</a> under <code>exec/</code> that contains R code that {Rapp} will reinterpret (magically?) into a CLI.</p>
<p>At time of writing it looks like this:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb13-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#!/usr/bin/env Rapp</span></span>
<span id="cb13-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#| name: weva</span></span>
<span id="cb13-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#| description: A micro weather report with 'Open-Meteo' and 'postcodes.io' APIs</span></span>
<span id="cb13-4"></span>
<span id="cb13-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#| description: A UK postcode</span></span>
<span id="cb13-6">postcode <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NULL</span></span>
<span id="cb13-7"></span>
<span id="cb13-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#| description: Hours from now for the 'later' segment (up to three days)</span></span>
<span id="cb13-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#| short: h</span></span>
<span id="cb13-10">hours <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>L</span>
<span id="cb13-11"></span>
<span id="cb13-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#| description: Show API and calculated datetimes in 'now' and 'later' segments?</span></span>
<span id="cb13-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#| short: d</span></span>
<span id="cb13-14">datetimes <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span></span>
<span id="cb13-15"></span>
<span id="cb13-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#| description: Show a segment for today's min and max temperatures?</span></span>
<span id="cb13-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#| short: e</span></span>
<span id="cb13-18">extremes <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span></span>
<span id="cb13-19"></span>
<span id="cb13-20"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">get_latlon</span>(postcode) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb13-21">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">get_weather</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb13-22">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">write_report</span>(</span>
<span id="cb13-23">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">hours_to_forecast =</span> hours,</span>
<span id="cb13-24">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">show_datetimes =</span> datetimes,</span>
<span id="cb13-25">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">show_extremes =</span> extremes</span>
<span id="cb13-26">  ) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb13-27">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cat</span>()</span></code></pre></div></div>
<p>Note in this file:</p>
<ul>
<li>the <a href="https://en.wikipedia.org/wiki/Shebang_(Unix)">shebang</a> (<code>#!</code>)<sup>8</sup>, which gives the path to where {Rapp} is installed</li>
<li>the hashpipes (<code>#|</code>) for documenting the CLI and its options</li>
<li>that everything else is normal R code</li>
</ul>
<p>{Rapp} interprets <code>postcode &lt;- NULL</code> as a required positional argument for <code>weva</code>. You can just type the postcode without a flag.</p>
<p>Conversely, the remaining options must be named, though <code>short</code> names can be used to save typing. <code>--hours</code> (<code>-h</code>) is interpreted as an integer with a default of <code>1</code> <code>--datetimes</code> and <code>--extremes</code> (<code>-d</code>, <code>-e</code>) are off (<code>FALSE</code>) by default, but the user can just use the flag without an argument to turn it on.</p>
<p>What’s also nice is that {Rapp} gives us <code>--help</code> (and <code>--help-yaml</code>) for free, based on our variable documentation.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb14-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">weva</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--help</span></span></code></pre></div></div>
<pre><code>Usage: weva [OPTIONS] &lt;POSTCODE&gt;

A micro weather report with 'Open-Meteo' and 'postcodes.io' APIs

Options:
  -h, --hours &lt;HOURS&gt;             Hours from now for the 'later' segment (up to
                                  three days)
                                  [default: 1] [type: integer]
  -d, --datetimes / --no-datetimes  Show API and calculated datetimes in 'now'
                                  and 'later' segments?
                                  [default: false]
                                  Enable with `--datetimes`.
  -e, --extremes / --no-extremes  Show a segment for today's min and max
                                  temperatures?
                                  [default: false]
                                  Enable with `--extremes`.

Arguments:
  &lt;POSTCODE&gt;  A UK postcode</code></pre>
<p>In short, {Rapp} lets you write ‘normal’ looking R code that gets interpreted into a CLI with various types of options.</p>
<p>That’s quite a neat, low-effort way to create a CLI.</p>
</section>
<section id="rapping-up" class="level2">
<h2 class="anchored" data-anchor-id="rapping-up">{Rapp}ing up</h2>
<p>{weva} is [repeated disclaimers]. It would be easy to get carried away and build something even more complex. I’ve achieved the content and learning I wanted, so I stop here.</p>
<p>In fact, I already used this tool yesterday before a trip<sup>9</sup>. I discovered the day was going to be overcast with some showers. Who could have guessed?</p>
<p>But on more variable days, I’m reminded of <a href="https://github.com/francisbarton">Fran</a>’s observation when faced with differing forecasts:</p>
<blockquote class="blockquote">
<p>I had better go outside and see who’s right</p>
</blockquote>
<p>Very wise.</p>

</section>


<div id="quarto-appendix" class="default"><section id="environment" class="level3 appendix"><h2 class="anchored quarto-appendix-heading">Environment</h2><div class="quarto-appendix-contents">

<details>
<summary>
Session info
</summary>
<div class="cell">
<div class="cell-output cell-output-stdout">
<pre><code>Last rendered: 2026-03-07 13:30:38 GMT</code></pre>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>R version 4.5.2 (2025-10-31)
Platform: aarch64-apple-darwin20
Running under: macOS Sequoia 15.6.1

Matrix products: default
BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.1

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: Europe/London
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
 [1] htmlwidgets_1.6.4 compiler_4.5.2    fastmap_1.2.0     cli_3.6.5        
 [5] tools_4.5.2       htmltools_0.5.8.1 yaml_2.3.12       rmarkdown_2.29   
 [9] knitr_1.50        jsonlite_2.0.0    xfun_0.52         digest_0.6.37    
[13] rlang_1.1.7       evaluate_1.0.4   </code></pre>
</div>
</div>
</details>


</div></section><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>I don’t want to simply run scripts from the command line, which is what <code>Rscript</code> is for.↩︎</p></li>
<li id="fn2"><p>With a respectful cockney accent.↩︎</p></li>
<li id="fn3"><p>Packages <a href="https://github.com/tpisel/openmeteo">{openmeteo}</a> and <a href="https://docs.ropensci.org/PostcodesioR/index.html">{PostcodesioR}</a> wrap their respective APIs, but I minimised the dependencies by querying myself with {httr2}.↩︎</p></li>
<li id="fn4"><p>Remember: you shouldn’t trust ‘some dude’ telling you to install random stuff from the internet.↩︎</p></li>
<li id="fn5"><p>For my purposes I don’t care about weather anywhere except in the UK. I’m more likely to know the postcode than latitude-longitude, so I’m happy to limit the tool in this way.↩︎</p></li>
<li id="fn6"><p>Actually these postcodes are for the First &amp; Last House pub and the John o’ Groats brewery.↩︎</p></li>
<li id="fn7"><p>We don’t need to forecast beyond that, because we know the weather’s going to be <a href="https://en.wikipedia.org/wiki/The_Day_After_Tomorrow">really dicey and unpredictable</a>.↩︎</p></li>
<li id="fn8"><p><a href="https://en.wikipedia.org/wiki/She_Bangs">Important disambiguation</a>.↩︎</p></li>
<li id="fn9"><p>Amusingly, given my time-related pains in {weva}, we went to <a href="https://en.wikipedia.org/wiki/Prime_meridian_(Greenwich)">Greenwich</a>.↩︎</p></li>
</ol>
</section><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a></div></div></section></div> ]]></description>
  <category>api</category>
  <category>cli</category>
  <category>r</category>
  <category>weva</category>
  <guid>https://www.rostrum.blog/posts/2026-03-01-weva/</guid>
  <pubDate>Sun, 01 Mar 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Text hex logos with {hext}</title>
  <dc:creator>Matt Dray</dc:creator>
  <link>https://www.rostrum.blog/posts/2026-02-15-hext/</link>
  <description><![CDATA[ 





<pre><code>  ________
 /        \
/   he     \
\     xt   /
 \________/</code></pre>
<section id="tldr" class="level2">
<h2 class="anchored" data-anchor-id="tldr">tl;dr</h2>
<p>The <a href="https://github.com/matt-dray/hext">{hext} R package</a> helps you make text-based hex logos.</p>
</section>
<section id="a-simple-mind" class="level2">
<h2 class="anchored" data-anchor-id="a-simple-mind">A simple mind</h2>
<p>Call me a normie, but I am an R-hex-logo enjoyer.</p>
<p>I took a step further last year to create my own (limited) hex-making package, <a href="https://github.com/matt-dray/gex">{gex}</a>, which I’ve <a href="https://www.rostrum.blog/posts/2025-02-25-gex/">written about</a> before<sup>1</sup>.</p>
<p>The goal of {gex} was to keep things simple: minimal dependencies, only grid graphics<sup>2</sup>.</p>
<p>But I was too short-sighted, too small-brained. I went simple, but <em>not simple enough</em>.</p>
</section>
<section id="do-the-simple-things-well" class="level2">
<h2 class="anchored" data-anchor-id="do-the-simple-things-well">Do the simple things well</h2>
<p>Yihui recently said ‘<a href="https://yihui.org/en/2026/02/bye-stickers/">bye, hex stickers</a>’.</p>
<p>In his post he discussed an entirely text-based hex logo for <a href="https://github.com/yihui/litedown/blob/main/README.md">{litedown}</a>. Charlie Gao’s <a href="https://github.com/shikokuchuo/secretbase/blob/main/README.md">{secretbase}</a><sup>3</sup> has also joined the text revolution.</p>
<p>The spirit of these packages is minimalism. Their logos match this aesthetic.</p>
<p>Simplicity is a probably a good goal in general.</p>
<p>So, naturally, I have overengineered a simple thing to make it simple for you to not think about the complication behind such a simple thing.</p>
</section>
<section id="next-text-hext" class="level2">
<h2 class="anchored" data-anchor-id="next-text-hext">Next: text hext</h2>
<p><a href="https://github.com/matt-dray/hext">{hext}</a> lets you write text-based logos.</p>
<p>You could install like:</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1">pak<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">pak</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"matt-dray/hext"</span>)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># GitHub-only</span></span></code></pre></div></div>
</div>
<p>There is only one function: <code>hext()</code>. Very simple, very demure.</p>
<p>Hex outputs have four lines<sup>4</sup>. You can add text and specify alignment for each of these.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb3-1">hext<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">hext</span>(</span>
<span id="cb3-2">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"keep"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"it"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"simple,"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"silly"</span>,</span>
<span id="cb3-3">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"left"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"centre"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"right"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"left"</span></span>
<span id="cb3-4">)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>  ________
 /keep    \
/    it    \
\   simple,/
 \silly___/</code></pre>
</div>
</div>
<p><code>hext()</code> handles character padding and line-length limits on your behalf. It tries its best with Unicode (like emoji)<sup>5</sup>, but rendering is hard<sup>6</sup>.</p>
<p>That’s it, really.</p>
</section>
<section id="hexagone" class="level2">
<h2 class="anchored" data-anchor-id="hexagone">Hexagone</h2>
<p>Think less, say less.</p>

</section>


<div id="quarto-appendix" class="default"><section id="environment" class="level3 appendix"><h2 class="anchored quarto-appendix-heading">Environment</h2><div class="quarto-appendix-contents">

<details>
<summary>
Session info
</summary>
<div class="cell">
<div class="cell-output cell-output-stdout">
<pre><code>Last rendered: 2026-02-22 21:01:02 GMT</code></pre>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>R version 4.5.1 (2025-06-13)
Platform: aarch64-apple-darwin20
Running under: macOS Sequoia 15.6.1

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.1

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: Europe/London
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
 [1] htmlwidgets_1.6.4 compiler_4.5.1    fastmap_1.2.0     cli_3.6.5        
 [5] tools_4.5.1       htmltools_0.5.8.1 yaml_2.3.10       stringi_1.8.7    
 [9] rmarkdown_2.29    knitr_1.50        jsonlite_2.0.0    xfun_0.52        
[13] digest_0.6.37     rlang_1.1.6       hext_0.1.0        evaluate_1.0.4   </code></pre>
</div>
</div>
</details>


</div></section><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>You know who you are, dear one-user-who-isn’t-me↩︎</p></li>
<li id="fn2"><p>The irony being that the original iteration—called {hexbase}—was even simpler. Dependency-free, in fact.↩︎</p></li>
<li id="fn3"><p>See-also the namesake YouTube channel for sports videos. And documentaries about the history of slipping on banana peels.↩︎</p></li>
<li id="fn4"><p>The community seems divided on the number of characters per row. Like Charlie, I have opted for 8-10-10-8 top-to-bottom, which gives a little extra character space, even if makes the hexagon look a little more plump.↩︎</p></li>
<li id="fn5"><p>This works via <code>nchar(x, type = "width")</code> (or <code>stringi::stri_width()</code> if you have {stringi} installed), rather than the default <code>nchar(x, type = "chars")</code>. The smiley emoji has a width of <code>2</code>, but is <code>1</code> character long, for example. Obviously this impacts the whitespace padding that <code>hext()</code> needs to apply. If this is bothersome, you can use the argument <code>count_type</code> in <code>hext()</code> to switch between these counting modes.↩︎</p></li>
<li id="fn6"><p>‘It may look ugly, especially on Windows’, to quote Yihui’s post. If your logo is borked somewhere then… c’est la vie.↩︎</p></li>
</ol>
</section><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a></div></div></section></div> ]]></description>
  <category>hext</category>
  <category>r</category>
  <guid>https://www.rostrum.blog/posts/2026-02-15-hext/</guid>
  <pubDate>Sun, 15 Feb 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>A roguelike-like with tyle</title>
  <dc:creator>Matt Dray</dc:creator>
  <link>https://www.rostrum.blog/posts/2026-02-01-tyle/</link>
  <description><![CDATA[ 





<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2026-02-01-tyle/resources/tyle-grid.png" class="img-fluid quarto-figure quarto-figure-left figure-img" style="width:100.0%" alt="A 10 by 10 grid of characters printed to a macOS terminal. Tiles are mostly period characters, with some hashmark characters, and an at symbol near the middle. A prompt underneath reads 'Move (WASD)+Enter:', ready for user input."></p>
</figure>
</div>
<section id="tldr" class="level2">
<h2 class="anchored" data-anchor-id="tldr">tl;dr</h2>
<p>I’m writing <a href="https://github.com/matt-dray/tyle">tyle</a> to help in my quest to slay Python.</p>
</section>
<section id="very-early-access" class="level2">
<h2 class="anchored" data-anchor-id="very-early-access">Very early access</h2>
<p>I’ve started ‘tyle’, a concept Python CLI that contains the foundations for a little in-terminal tile- and turn-based game.</p>
<p>The rudimentary tool is available to install <a href="https://github.com/matt-dray/tyle">from GitHub</a> (v0.1.0 at time of writing). I like <a href="https://docs.astral.sh/uv/">uv</a> for this job<sup>1</sup>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">uv</span> tool install git+https://github.com/matt-dray/tyle.git</span></code></pre></div></div>
<p>Then you can start a ‘game’ (heavy emphasis on those quote marks) from your terminal by typing:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">tyle</span></span></code></pre></div></div>
<pre><code>.......#..
..........
.##..#....
....#....#
......#...
.....@....
..#.......
........#.
..........
....#.....
Move (WASD+Enter): </code></pre>
<p>The gameboard is just a tile grid printed to the terminal. You (the <code>@</code> symbol) can travel around the floor tiles (<code>.</code>), but not through obstacles (randomly-placed <code>#</code>) or off the map.</p>
<p>At the prompt you can type <kbd>W</kbd> and <kbd>Enter</kbd> to move up, for example. <kbd>Q</kbd> and <kbd>Enter</kbd> will quit.</p>
<p>Aaand that’s it for now.</p>
<div class="tip">
<p> <b>Note</b></p>
<p>A quick update on changes in v0.2.0, made immediately after this post. I’ve added CLI options so you can do something like <code>tyle -r 10 -c 20 -w 25</code> to set the starting count of <code>--rows</code>, <code>--columns</code> and <code>--walls</code> (see <code>tyle -h</code> for help). I also adjusted the column- and wall-count defaults and added a space between adjacent tiles; and introduced colours (but only if your terminal suports <a href="https://en.wikipedia.org/wiki/ANSI_escape_code">ANSI codes</a>, like zsh in the gif below).</p>
<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2026-02-01-tyle/resources/tyle.gif" class="img-fluid quarto-figure quarto-figure-left figure-img" style="width:50.0%" alt="A 10 by 20 grid of characters printed to a zsh terminal. The majority are floor tiles, which are are grey period symbols. Randomly-placed walls are red hash symbols. The player is a yellow at symbol in the middle. A prompt underneath reads 'Move (WASD)+Enter:', ready for user input. The user inputs values to make the at symbol move around the tiles."></p>
</figure>
</div>
</div>
</section>
<section id="gone-rogue" class="level2">
<h2 class="anchored" data-anchor-id="gone-rogue">Gone rogue</h2>
<p>This game style and ASCII-text ‘graphics’ may be familiar as the blueprint for <a href="https://en.wikipedia.org/wiki/Rogue_(video_game)">the classic dungeoncrawling videogame <em>Rogue</em></a> (1980).</p>
<p>Other key features are turn-based interaction, procedurally-generated environments and player permadeath. Later games aped and adapted these features, giving rise to the <a href="https://en.wikipedia.org/wiki/Roguelike">‘roguelike’ and ‘roguelite’</a> genres.</p>
<p>The scope for tyle is far tighter: I promise nothing. So let’s call this a ‘roguelike-like’ for now.</p>
</section>
<section id="a-sidequest" class="level2">
<h2 class="anchored" data-anchor-id="a-sidequest">A sidequest</h2>
<p>To explain the lore, I’ve been making some twee CLI projects to help me learn Python. First <a href="https://github.com/matt-dray/jot">jot</a> for recording little timestamped notes<sup>2</sup>, then <a href="https://github.com/matt-dray/pet">pet</a> for a mini cyberpet experience.</p>
<p>tyle is also a Python-backed CLI, but is helping me learn more about object-oriented programming and building my own <a href="https://www.w3schools.com/python/python_classes.asp">classes</a>, specifically. It’s nice to organise code into object ‘types’ that contain their own variables (properties) and functions (methods).</p>
<p>As someone with a background in R, I tend to think every nail can be hammered with a function. R supports classes, of course, most recently with <a href="https://rconsortium.github.io/S7/index.html">the {S7} package</a><sup>3</sup>. But the approach feels far more at home in Python with its greater focus on ‘programming’ than ‘doing statistics’<sup>4</sup>.</p>
</section>
<section id="a-class-act" class="level2">
<h2 class="anchored" data-anchor-id="a-class-act">A class act</h2>
<p>First, a high-level overview of what tyle’s three main classes represent:</p>
<ul>
<li><code>Tile</code> represents each square on the grid</li>
<li><code>TileGrid</code> represents the whole grid of tiles</li>
<li><code>Entity</code> represents the player character</li>
</ul>
<p>Each of these have properties and methods that allow them to store data and be queried in certain ways:</p>
<ul>
<li><code>Tile</code> objects store their representative symbol (e.g.&nbsp;<code>#</code>) and if they can be traversed</li>
<li><code>TileGrid</code> objects store and print the tile grid, while also assessing player movement</li>
<li><code>Entity</code> objects store tile coordinates and hit points for the player</li>
</ul>
<p>A tile-grid object is composed of many tile objects. Some tiles are designated as floor tiles (<code>.</code>) and others as walls (<code>#</code>). Think of the tile-grid as the ‘terrain’ layer of the game.</p>
<p>The player is not part of the terrain layer, but exists independently and carries its own coordinate properties. When printing the tilegrid with a drawing method, the player’s symbol (<code>@</code>) is printed preferentially. There’s logic to prevent the player from moving to non-traversable places (walls or off the map).</p>
<section id="grouting" class="level3">
<h3 class="anchored" data-anchor-id="grouting">Grouting</h3>
<p>What does it look like to set up one of these classes?</p>
<p>Here’s an example from <a href="https://github.com/matt-dray/tyle/tree/main/src/tyle">the <code>tiles.py</code> file</a>. The simplest possible example is <code>Tile</code>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Tile:</span>
<span id="cb4-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, symbol: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, traversable: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb4-3">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.symbol <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> symbol</span>
<span id="cb4-4">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.traversable <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> traversable</span></code></pre></div></div>
<p>I’ve shown the type hints<sup>5</sup> but hidden the docstrings for brevity.</p>
<p>To summarise, you:</p>
<ol type="1">
<li>Declare with <code>class</code>.</li>
<li>Use an UpperCamelCase name by convention.</li>
<li><code>def</code>ine a function to <code>__init__</code>ialise the class, with reference to:
<ol type="a">
<li><code>self</code>, i.e.&nbsp;a reference to the class instance itself.</li>
<li>‘normal’ function arguments (<code>symbol</code> is the text character representing the tile and <code>traversable</code> is whether the tile can be moved across).</li>
</ol></li>
<li>Set properties that may just be the arguments, or passed through other functions.</li>
<li><code>def</code>ine functions (methods) within the body of the class (not shown here because the <code>Tile</code> class doesn’t currently have any).</li>
</ol>
<p>And then you can initiate a single instance like:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1">tile <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Tile(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"."</span>, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span></code></pre></div></div>
<p>You can then check parameters and run methods from the class. For example, we can use dot notation on our <code>Tile</code> instance to access the <code>symbol</code> parameter:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1">tile.symbol</span></code></pre></div></div>
<pre><code>'.'</code></pre>
</section>
<section id="fire-up-the-griddle" class="level3">
<h3 class="anchored" data-anchor-id="fire-up-the-griddle">Fire up the griddle</h3>
<p>How does tyle generate a tilegrid from tiles?</p>
<p>Crucially, tile initiates distinct tile objects for every grid position. This means each one can have individual properties, like whether they’re a wall tile. Later we could give them properties for the presence of booby traps, for example.</p>
<p>We store the grid of tiles as a two-dimensional list (a list of lists). The index of:</p>
<ul>
<li>each list gives the tiles their row number</li>
<li>each tile in each list gives them their column number</li>
</ul>
<p>Here’s a simplified part of the <code>create_grid()</code> function, which uses <a href="https://www.w3schools.com/python/python_lists_comprehension.asp">list comprehension</a><sup>6</sup> to generate the grid:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1">grid <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb8-2">    [Tile(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#"</span>, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n_cols)]</span>
<span id="cb8-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n_rows)</span>
<span id="cb8-4">]</span></code></pre></div></div>
<p>Printing <code>grid</code> will reveal the structure. Suppose we had <code>n_rows = n_cols = 2</code> for a tiny 2 × 2 grid, we’d get something like this:</p>
<pre><code>[[&lt;__main__.Tile at 0x107a22120&gt;, &lt;__main__.Tile at 0x107a23170&gt;],
 [&lt;__main__.Tile at 0x107a236e0&gt;, &lt;__main__.Tile at 0x107a23020&gt;]]</code></pre>
<p>You can see the list of lists (<code>[[],[]]</code>) where each inner list contains two tile objects. We can index into it to get a tile at any given ‘coordinates’. So <code>grid[1][0]</code> would return the tile in the second row and first column.</p>
<p>You can see the default printing format for objects, which tells us where it was defined, its class and its unique memory address. Instead, <code>TileGrid</code> has a <code>draw()</code> method to print instead the <code>symbol</code> property of each <code>Tile</code> object (i.e.&nbsp;<code>.</code> or <code>#</code>).</p>
</section>
<section id="upper-class" class="level3">
<h3 class="anchored" data-anchor-id="upper-class">Upper class</h3>
<p>So that was a very simple example of the <code>Tile</code> class in action.</p>
<p>You can imagine how the <code>Entity</code> class has its own parameters like <code>hp</code> (hit points) and <code>TileGrid</code> has a <code>draw()</code> method to print the map to the screen.</p>
<p>Later we could create subclasses from the <code>Entity</code> class. <code>Player</code> and <code>Enemy</code> could inherit the properties and methods from <code>Entity</code>, but get their own unique ones too. Maybe <code>Player</code> could have a <code>name</code>, while <code>Enemy</code> could have a <code>species</code> (think <code>goblin</code>, <code>rat</code> or <code>ingrate-who-puts-their-dirty-feet-up-on-the-train-seat</code>).</p>
<p>Classes give us a much more manageable and readable way of organising and dealing with variables and functions.</p>
</section>
</section>
<section id="hack" class="level2">
<h2 class="anchored" data-anchor-id="hack">Hack</h2>
<blockquote class="blockquote">
<p>But I’ve read about <a href="https://github.com/matt-dray/r.oguelike">the {r.oguelike} R package</a> on <a href="https://www.rostrum.blog/index.html#category=r.oguelike">this blog</a> before. Have you run out of ideas?</p>
</blockquote>
<p>Uh-oh, I’ve been rumbled.</p>
<p>Listen, agitator: an R-function spaghetti was never the ‘right’ choice. Not least because that particular ‘game’ had to be played in the R console, rather than the terminal directly.</p>
<p>Python is probably more suited to this type of activity. In fact, Python can be used as a full-blown game engine, aided by tools like <a href="https://www.pygame.org/docs/">PyGame</a>. In fact, there are several roguelike-specific packages to help you build roguelikes specifically, like <a href="https://github.com/libtcod/libtcod">libtcod</a><sup>7</sup>.</p>
<p>I’m ignoring these because I want to use this little project to help me learn Python skills, not roguelike-making skills, specifically. Classes, yes, but also stuff like type hints, docstrings, writing modules, creating CLIs, etc.</p>
</section>
<section id="do-it-with-tyle" class="level2">
<h2 class="anchored" data-anchor-id="do-it-with-tyle">Do it with tyle</h2>
<p>As ever, I may stop there<sup>8</sup>. But there are some obvious improvements that could also strengthen my Python skills.</p>
<p>For example, a bunch of stuff from {r.oguelike} could be interesting to translate here, like procedural dungeons, enemy battling and pathfinding, and an inventory.</p>
<p>We should also ‘listen’ for keyboard input with something like <a href="https://pynput.readthedocs.io/en/latest/index.html">pynput</a>. A much more natural approach compared to smashing the <kbd>Enter</kbd> key.</p>
<p>Of course, learning new stuff can feel like navigating through a dungeon full of goblins. But to avoid permadeath of coding skills you’ve just got to keep going tile-by-tile, amirite?</p>

</section>


<div id="quarto-appendix" class="default"><section id="environment" class="level3 appendix"><h2 class="anchored quarto-appendix-heading">Environment</h2><div class="quarto-appendix-contents">

<details>
<summary>
Session info
</summary>
<div class="cell">
<div class="cell-output cell-output-stdout">
<pre><code>[project]
name = "2026-02-01-tyle"
version = "0.1.0"
requires-python = "&gt;=3.12"
dependencies = []</code></pre>
</div>
</div>
</details>


</div></section><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>And <code>uv tool update tyle</code> and <code>uv tool uninstall tyle</code> do what you think they do.↩︎</p></li>
<li id="fn2"><p>Actually, I’ve been using jot everyday and it helps me keep track of what I’ve been doing, which is handy for completing weeknotes and timesheets.↩︎</p></li>
<li id="fn3"><p>I wouldn’t dream of taking the cutting edge and <a href="https://www.rostrum.blog/posts/2023-02-26-nook-s7/">being frivolous with it</a>.↩︎</p></li>
<li id="fn4"><p>Wow, a take that’s hotter than the sun.↩︎</p></li>
<li id="fn5"><p><a href="https://astral.sh/">Astral</a> have brought ty out of version beta_BETA_draft_v0.0.0.0.0.900000, huzzah. You can check type hints cheaply <a href="https://docs.astral.sh/uv/guides/tools/">with uv</a> and without installing anything else, like <code>uvx ty check</code>. Or <code>uvx ruff format</code> and <code>uv ruff check</code> for linting and formatting. Real noice.↩︎</p></li>
<li id="fn6"><p>And note the use of the underscore convention as a ‘placeholder’ variable. We don’t need to name the variable because we’re not actually using it.↩︎</p></li>
<li id="fn7"><p>Check out entries from <a href="https://www.roguebasin.com/index.php?title=Seven_Day_Roguelike_Challenge">7DRL</a> (the seven-day roguelike challenge), which includes may games written this way.↩︎</p></li>
<li id="fn8"><p>Take a shot every time you read that phrase on this blog.↩︎</p></li>
</ol>
</section><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a></div></div></section></div> ]]></description>
  <category>cli</category>
  <category>python</category>
  <category>tyle</category>
  <category>uv</category>
  <guid>https://www.rostrum.blog/posts/2026-02-01-tyle/</guid>
  <pubDate>Sun, 01 Feb 2026 00:00:00 GMT</pubDate>
  <media:content url="https://www.rostrum.blog/posts/2026-02-01-tyle/resources/tyle.png" medium="image" type="image/png"/>
</item>
<item>
  <title>Correcting a fib about jot</title>
  <dc:creator>Matt Dray</dc:creator>
  <link>https://www.rostrum.blog/posts/2025-12-28-jot-fib/</link>
  <description><![CDATA[ 





<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2025-12-28-jot-fib/resources/jot-fib.png" class="img-fluid quarto-figure quarto-figure-left figure-img" alt="Screenshot of a macOS terminal window running the jot tool four times, with option 'v' to print the version number; 'w' to print where the config and jot files are; 's' and 'l' to search for the most recent jotting that contains the word 'apple'; and 'f' and 't' flags to return jottings from one date to another."></p>
</figure>
</div>
<section id="tldr" class="level2">
<h2 class="anchored" data-anchor-id="tldr">tl;dr</h2>
<p><a href="https://github.com/matt-dray/jot">jot</a> now has <code>--where</code>, <code>--version</code>, <code>--from</code> and <code>--to</code> options.</p>
</section>
<section id="whoops" class="level2">
<h2 class="anchored" data-anchor-id="whoops">Whoops</h2>
<p>Me, lying, the <a href="https://www.rostrum.blog/posts/2025-08-30-jot-options/">last time I posted about jot</a>:</p>
<blockquote class="blockquote">
<p>It’s basically feature-complete from my perspective.</p>
</blockquote>
<p>Reminder: <a href="https://github.com/matt-dray/jot">jot</a> is my simple little Python CLI for recording timestamped notes to a text file.</p>
<p>In fact, now you can:</p>
<ul>
<li>combine options</li>
<li>search between specific dates</li>
<li>quickly get tool meta-information</li>
</ul>
</section>
<section id="and" class="level2">
<h2 class="anchored" data-anchor-id="and">And?</h2>
<p>Options <code>--search</code> and <code>--list</code> (<code>-s</code> and <code>-l</code>) can now be used in combination.</p>
<p>This was achieved by adding an argument to the <code>search_jottings()</code> function that takes the value given by <code>--list</code>.</p>
<p>So, assuming this little jot file:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>jot.txt</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" data-filename="jot.txt" style="background: #f1f3f5;"><pre class="sourceCode txt code-with-copy"><code class="sourceCode default"><span id="cb1-1">[2025-08-28 11:15] ate an apple</span>
<span id="cb1-2">[2025-08-27 10:58] ate a kiwi</span>
<span id="cb1-3">[2025-08-26 11:09] ate a pineapple</span>
<span id="cb1-4">[2025-08-25 10:40] ate an apple and a pear</span></code></pre></div></div>
</div>
<p>A casual <code>--search</code> will return anything matching your search term.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode sh code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">jot</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-s</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"apple"</span></span></code></pre></div></div>
<pre><code>[2025-08-28 11:15] ate an apple
[2025-08-26 11:09] ate a pineapple
[2025-08-25 10:40] ate an apple and a pear</code></pre>
<p>But that could overwhelm the console if you have a lot of jottings.</p>
<p>So now you can add the <code>--list</code> flag to limit to the most recent <em>n</em> jottings that match the search term.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode sh code-with-copy"><code class="sourceCode bash"><span id="cb4-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">jot</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-s</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"apple"</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-l</span> 1</span></code></pre></div></div>
<pre><code>[2025-08-28 11:15] ate an apple</code></pre>
</section>
<section id="where-what" class="level2">
<h2 class="anchored" data-anchor-id="where-what">Where? What?</h2>
<p>I added helpers <code>--where</code> and <code>--version</code> (<code>-w</code> and <code>-v</code>) as quality-of-life improvements.</p>
<p><code>--where</code> is useful if you forget the file paths handled by jot, which are:</p>
<ul>
<li>the config file, which contains a key-value path to the jot file</li>
<li>the jot file, which is where the text of the jottings is saved</li>
</ul>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode sh code-with-copy"><code class="sourceCode bash"><span id="cb6-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">jot</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-w</span></span></code></pre></div></div>
<pre><code>📍 Default path to config file: /Users/mattdray/.jot-config.json
📍 Path to jot file: /Users/mattdray/Documents/jot.txt</code></pre>
<p>The tool has gone through a bunch of changes, so you can check what version you’re running with <code>--version</code>.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode sh code-with-copy"><code class="sourceCode bash"><span id="cb8-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">jot</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-v</span></span></code></pre></div></div>
<pre><code>0.2.11</code></pre>
<p>The version number is also provided now when you run <code>jot --help</code> (<code>-h</code>) to print the help documentation.</p>
<p>Assuming you’re using uv, run <code>uv tool upgrade jot</code> to get the latest version.</p>
</section>
<section id="when" class="level2">
<h2 class="anchored" data-anchor-id="when">When?</h2>
<p>I mentioned using a regular expression to filter for jottings between dates. That’s awful<sup>1</sup>.</p>
<p>Helpfully, <a href="https://github.com/oiffrig">Olivier Iffrig-Petit</a> contributed a couple of new options to handle this.</p>
<p><code>--from</code> and <code>--to</code> (<code>-f</code> and <code>-t</code>) take values in the form <code>YYYYMMDD</code> to filter from (inclusive) and to (exclusive) given dates. They can be used alone or together, along with <code>--search</code> and <code>--list</code>.</p>
<p>So, to return jottings from 26 August 2025 up to (but not including) 28 August 2025:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode sh code-with-copy"><code class="sourceCode bash"><span id="cb10-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">jot</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-f</span> 20250826 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-t</span> 20250828</span></code></pre></div></div>
<pre><code>[2025-08-27 10:58] ate a kiwi
[2025-08-26 11:09] ate a pineapple</code></pre>
<p>Isolating time periods like this has helped me find information to write weeknotes and complete my timesheets.</p>
<p>Many thanks Olivier.</p>
</section>
<section id="sure" class="level2">
<h2 class="anchored" data-anchor-id="sure">Sure?</h2>
<p>Along with these features, I’ve added basic tests for the Python functions using <a href="https://docs.pytest.org/en/stable/">pytest</a>. A couple of things that were new to me were:</p>
<ul>
<li>the <code>@pytest.mark.parametrize()</code> decorator, which helps set up inputs that can be iterated over in a test</li>
<li>that <code>capsys</code> captures system outputs, as the name implies, which you can compare to expected values</li>
</ul>
<p>And naturally my patience was tested trying to get the pytest GitHub Action to run correctly.</p>
</section>
<section id="next" class="level2">
<h2 class="anchored" data-anchor-id="next">Next?</h2>
<p>Next thing might be to <a href="https://github.com/matt-dray/jot/issues/44">prepare jot for PyPi</a>. There’s probably two breaking changes to be made before then:</p>
<ol type="1">
<li>Save the config file <a href="https://github.com/matt-dray/jot/issues/14">somewhere more suitable</a> than the user’s home directory.</li>
<li><a href="https://github.com/matt-dray/jot/issues/57">Rename the package</a> because there’s <a href="https://ss64.com/mac/jot.html">already a macOS tool named ‘jot’</a>, lol<sup>2</sup>.</li>
</ol>
<p>So, perhaps, finally, maybe, for now:</p>
<blockquote class="blockquote">
<p>It’s basically feature-complete from my perspective.</p>
</blockquote>

</section>


<div id="quarto-appendix" class="default"><section id="environment" class="level3 appendix"><h2 class="anchored quarto-appendix-heading">Environment</h2><div class="quarto-appendix-contents">

<details>
<summary>
Session info
</summary>
<div class="cell">
<div class="cell-output cell-output-stdout">
<pre><code>[project]
name = "2025-12-28-jot-fib"
version = "0.1.0"
requires-python = "&gt;=3.12"
dependencies = []</code></pre>
</div>
</div>
</details>


</div></section><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>‘Regex? Now you have two problems.’↩︎</p></li>
<li id="fn2"><p>Or alias and document it so the user can decide. Either way, ‘jot’ is already taken on PyPi, so the package name will need changing even if the entry point doesn’t.↩︎</p></li>
</ol>
</section><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a></div></div></section></div> ]]></description>
  <category>cli</category>
  <category>jot</category>
  <category>python</category>
  <category>uv</category>
  <guid>https://www.rostrum.blog/posts/2025-12-28-jot-fib/</guid>
  <pubDate>Sun, 28 Dec 2025 00:00:00 GMT</pubDate>
  <media:content url="https://www.rostrum.blog/posts/2025-12-28-jot-fib/resources/jot-fib.png" medium="image" type="image/png" height="51" width="144"/>
</item>
<item>
  <title>How the tables turned</title>
  <dc:creator>Matt Dray</dc:creator>
  <link>https://www.rostrum.blog/posts/2025-11-30-turn-tables/</link>
  <description><![CDATA[ 




<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2025-11-30-turn-tables/resources/let-go.jpg" class="img-fluid figure-img" style="width:50.0%" alt="Meme of Link from The lEgend of Zelda: Windwaker. The camera is zoomed right up under his chin, his face filling the screen. There's an on-screen instruction to press the 'A' button to 'let go'."></p>
</figure>
</div>
<section id="tldr" class="level2">
<h2 class="anchored" data-anchor-id="tldr">tl;dr</h2>
<p>{a11ytables} is dead, long live <a href="https://best-practice-and-impact.github.io/aftables/">{aftables}</a>.</p>
</section>
<section id="regret" class="level2">
<h2 class="anchored" data-anchor-id="regret">Regret?</h2>
<p>A while back I presented <a href="https://www.rostrum.blog/posts/2023-06-13-panic-in-the-toolshed/">‘Panic! In The Toolshed’</a> about how public-sector data scientists should share code to prevent wheel reinvention<sup>1</sup>.</p>
<p>I gave the example of my {a11ytables} R package, which has helped to publish spreadsheets of official statistics that follow <a href="https://analysisfunction.civilservice.gov.uk/policy-store/releasing-statistics-in-spreadsheets/">best practices</a>.</p>
<p>It got harder to find time to maintain the package. <a href="https://www.rostrum.blog/posts/2024-01-27-a11ytables-0.3/">I hemmed-and-hawed</a> about its existence and procrastinated two concept packages to make up for its weaknesses<sup>2</sup>.</p>
<p>But then the weight<sup>3</sup> was lifted!</p>
</section>
<section id="rehome" class="level2">
<h2 class="anchored" data-anchor-id="rehome">Rehome</h2>
<p>The Analysis Function at the Office for National Statistics (ONS) now host and maintain the package. I can’t think of a better home.</p>
<p>Transferring the package GitHub repository was a bit of a dance, passing from the co-analysis org to my personal account and finally the best-practice-and-impact org. I fretted about archiving and forking instead, but a transfer appears not to have caused issues.</p>
<p>As part of this move, the package has been renamed {aftables}. The ‘af’ stands for ‘Analysis Function’ and echoes the related <a href="https://best-practice-and-impact.github.io/afcharts/">{afcharts} package</a> that helps produce best-practice visualisations.</p>
<p>As a parting gift, I used <a href="https://github.com/matt-dray/gex">my {gex} package</a> to make a quick hex logo that echoes the {afcharts} logo:</p>
<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2025-11-30-turn-tables/resources/aftables.png" class="img-fluid figure-img" alt="Hex logo for aftables. Multicoloured squares on the lower half echo the cells of a spreadsheet. The name of the package is above the squares. Reminiscent of the hex logo for afcharts and uses the same colour palette." width="200"></p>
</figure>
</div>
<p>This renaming hasn’t had a major impact as far as I can tell. The team <a href="https://analysisfunction.civilservice.gov.uk/blog/best-practice-for-automated-spreadsheets-in-r-creating-accessible-spreadsheets-with-aftables/">published a launch blog</a>; I updated colleagues via the cross-government Slack; and GitHub auto-rereoutes from <a href="https://github.com/co-analysis/a11ytables">co-analysis/a11ytables</a> to <a href="https://github.com/best-practice-and-impact/aftables">best-practice-and-impact/aftables</a>.</p>
<p>The team has already made updates and <a href="https://CRAN.R-project.org/package=aftables">submitted successfully to CRAN</a> in these past few months, which I definitely didn’t have the energy to tackle.</p>
<p>So now you can just <code>install.packages("aftables")</code> to try it out. Simply lovely.</p>
</section>
<section id="relief" class="level2">
<h2 class="anchored" data-anchor-id="relief">Relief</h2>
<p>So thank you to the Analysis Function team, particularly Olivia and Zach; to Matt and Tim for their early contributions; and to everyone who has used the package and provided feedback.</p>
<p>Panic? What panic?</p>

</section>


<div id="quarto-appendix" class="default"><section id="environment" class="level3 appendix"><h2 class="anchored quarto-appendix-heading">Environment</h2><div class="quarto-appendix-contents">

<details>
<summary>
Session info
</summary>
<div class="cell">
<div class="cell-output cell-output-stdout">
<pre><code>Last rendered: 2025-11-30 17:03:05 GMT</code></pre>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>R version 4.5.1 (2025-06-13)
Platform: aarch64-apple-darwin20
Running under: macOS Sequoia 15.6.1

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.1

locale:
[1] en_GB.UTF-8/en_GB.UTF-8/en_GB.UTF-8/C/en_GB.UTF-8/en_GB.UTF-8

time zone: Europe/London
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
 [1] htmlwidgets_1.6.4 compiler_4.5.1    fastmap_1.2.0     cli_3.6.5        
 [5] tools_4.5.1       htmltools_0.5.8.1 yaml_2.3.10       rmarkdown_2.29   
 [9] knitr_1.50        jsonlite_2.0.0    xfun_0.52         digest_0.6.37    
[13] rlang_1.1.6       evaluate_1.0.4   </code></pre>
</div>
</div>
</details>


</div></section><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>We talk a lot about <em>reproducibility</em> of our work, as we should, but Rhian gave a good talk at the NHS RPySOC 2025 conference about <a href="https://rhian.rbind.io/talks/2024-11-22-reusability/">the power of <em>reusability</em></a>.↩︎</p></li>
<li id="fn2"><p><a href="https://github.com/matt-dray/a11ytables2">{a11ytables2}</a> aimed to use a more pipeable approach and to upgrade the package from {openxlsx} to {openxlsx2} (which the Analysis Function team have already done in {aftables}!), while <a href="https://github.com/matt-dray/yamlsheets">{yamlsheets}</a> attempted to use a text-based YAML ‘blueprint’ file to declare spreadsheet content.↩︎</p></li>
<li id="fn3"><p>The fact I was using phrases like ‘panic’, ‘kill’ and ‘rebuild’ in earlier posts about {a11ytables} tells you something about the entirely self-imposed torment I was inflicting on myself.↩︎</p></li>
</ol>
</section><section class="quarto-appendix-contents"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div id="quarto-reuse" class="quarto-appendix-contents"><div>CC BY-NC-SA 4.0</div></div></section></div> ]]></description>
  <category>a11ytables</category>
  <category>aftables</category>
  <category>r</category>
  <guid>https://www.rostrum.blog/posts/2025-11-30-turn-tables/</guid>
  <pubDate>Sun, 30 Nov 2025 00:00:00 GMT</pubDate>
  <media:content url="https://www.rostrum.blog/posts/2025-11-30-turn-tables/resources/let-go.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>={ o x o}=</title>
  <dc:creator>Matt Dray</dc:creator>
  <link>https://www.rostrum.blog/posts/2025-10-30-pet/</link>
  <description><![CDATA[ 




<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2025-10-30-pet/resources/pet-see.png" class="img-fluid figure-img" style="width:100.0%" alt="Screenshot of a terminal window. There is text with the question 'what would you like to do' and the user has selected the option 'see'. An ASCII-art cat saying 'meow' is printed underneath. Then the same question is asked, with options for 'stats', 'see', 'feed', 'quit' and 'release'. Emoji are sprinkled throughout, like a sushi for the 'see' option."></p>
<figcaption class="figure-caption">Yes, my true calling is ASCII art.</figcaption>
</figure>
</div>
<section id="tldr" class="level2">
<h2 class="anchored" data-anchor-id="tldr">tl;dr</h2>
<p>I’ve made <a href="https://github.com/matt-dray/pet">pet</a>: a (very simple) Python tool to look after a persistent cyberpet on your command line.</p>
</section>
<section id="adopt" class="level2">
<h2 class="anchored" data-anchor-id="adopt">Adopt</h2>
<p>I made this for learning purposes, but if you did want to try it:</p>
<ol start="0" type="1">
<li>Install <a href="https://docs.astral.sh/uv/">uv</a> if you somehow haven’t already.</li>
<li>From your terminal run <code>uv tool install git+https://github.com/matt-dray/pet.git</code>.</li>
<li>Run <code>pet</code>.</li>
<li>Take responsibility.</li>
</ol>
</section>
<section id="pet-the-kitty" class="level2">
<h2 class="anchored" data-anchor-id="pet-the-kitty">Pet the kitty</h2>
<p>Running <code>pet</code> takes control of the terminal and asks you to select an option with <kbd>↓</kbd>, <kbd>↑</kbd> and <kbd>Enter</kbd>:</p>
<ul>
<li>📊 <strong>Stats</strong> to show your pet’s name (supplied by the user when they first run pet), birth (creation datetime), age (days since birth) and health (out of 10)</li>
<li>👀 <strong>See</strong> to print a little picture of your pet (always the same handsome cat)</li>
<li>🍣 <strong>Feed</strong> to simulate your pet eating and gaining one unit of health</li>
<li>❌ <strong>Quit</strong> to exit the program back to the terminal</li>
<li>👋 <strong>Release</strong> to discard your pet (cry) and delete its data</li>
</ul>
<p>Here’s what a playthrough looks like:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2025-10-30-pet/resources/pet-play.png" class="img-fluid figure-img" style="width:100.0%" alt="Screenshot of a terminal window. The command 'pet' has been run. The user is asked 'what would you like to do' and they respond 'stats', which returns the pet name, bith date, age in days and health status. The user chooses eom other options with different effects. Emoji are sprinkled throughout, like a battery for the 'health' status."></p>
<figcaption class="figure-caption">Obligatory <em>Monty Python</em> reference.</figcaption>
</figure>
</div>
<p>Lines starting with a question mark require input from the user. Once selected, the options disappear and the chosen one is displayed in blue. When the user can choose from several options, a blue chevron is moved between the options.</p>
<p>Crucially, if you quit and come back later, your pet will be recalled and its stats will have changed given the amount of time that’s passed. Pay attention! Your pet’s health will diminish over time<sup>1</sup>.</p>
<p>That’s it.</p>
</section>
<section id="the-soft-underbelly" class="level2">
<h2 class="anchored" data-anchor-id="the-soft-underbelly">The soft underbelly</h2>
<p>You can see the ‘game loop’ in <a href="https://github.com/matt-dray/pet/blob/main/src/pet/cli.py">the <code>cli.py</code> module</a>. Basically:</p>
<ol type="1">
<li>If it doesn’t exist, create a json file of pet stats on the user’s computer, having prompted for the pet’s name.</li>
<li>Update the stats given the time elapsed since birth and since your last interaction.</li>
<li>Deplete the pet’s health as a function of elapsed time (1 health unit lost per hour).</li>
<li>Ask the user to select an action from the menu and act accordingly, ad infinitum.</li>
<li>Break the loop on exit, and delete the stats json file if the pet was released.</li>
</ol>
<p>Not too complicated. Behind this, the Python functions in <a href="https://github.com/matt-dray/pet/blob/main/src/pet/utils.py">utils.py</a> are for wrangling and overwriting the stats file, calculating time and health loss, and for initiating the graphics engine (i.e.&nbsp;<code>print()</code>, lol).</p>
</section>
<section id="gaining-xp" class="level2">
<h2 class="anchored" data-anchor-id="gaining-xp">Gaining XP</h2>
<p>As well as getting more general experience in Python packaging, I learnt more about:</p>
<ul>
<li><a href="https://inquirerpy.readthedocs.io/en/latest/">InquirerPy</a>, which does the heavy lifting on user interaction, like <code>inquirer.select()</code> for presenting keyboard-navigable options<sup>2</sup></li>
<li><a href="https://platformdirs.readthedocs.io/en/latest/"><code>user_data_dir()</code> from platformdirs</a>, which can create a platform-independent path to a location on the user’s machine where game data can be stored</li>
</ul>
<p>As in <a href="https://www.rostrum.blog/index.html#category=uv">several recent posts</a>, uv has also been great here for the development loop, including to support use of ruff and ty<sup>3</sup> for code quality, like <code>uvx ruff format</code>.</p>
</section>
<section id="a-good-egg" class="level2">
<h2 class="anchored" data-anchor-id="a-good-egg">A good egg</h2>
<p>This pet tool has been designed for my own learning. There’s loads of missing features, but my goals have been met.</p>
<p>For more of a triple-A<sup>4</sup> virtual pet experience, you can <a href="https://www.rostrum.blog/posts/2022-11-13-tamrgo/">read earlier blogs</a> about <a href="https://github.com/matt-dray/tamRgo">my {tamRgo} package</a>, which does a much better job of this in the R console. It has lots more features, like ‘happy’, ‘hungry’ and ‘dirty’ meters; different pet species; experience points and levelling up; and ‘graphics’ dependent on the pet’s species and level. Ah, and the concept of death.</p>

</section>


<div id="quarto-appendix" class="default"><section id="environment" class="level3 appendix"><h2 class="anchored quarto-appendix-heading">Environment</h2><div class="quarto-appendix-contents">

<details>
<summary>
Session info
</summary>
<div class="cell">
<div class="cell-output cell-output-stdout">
<pre><code>[project]
name = "2025-10-30-pet"
version = "0.1.0"
requires-python = "&gt;=3.12"
dependencies = []</code></pre>
</div>
</div>
</details>


</div></section><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>Do not panic: your pet will not run away or die. I was too lazy to program that.↩︎</p></li>
<li id="fn2"><p>See <a href="https://textual.textualize.io/">Textual</a> as a more advanced tool for building your own complicated terminal user interfaces.↩︎</p></li>
<li id="fn3"><p><a href="https://astral.sh/">Astral</a>: please don’t bait-and-switch me, I need this.↩︎</p></li>
<li id="fn4"><p>Amateur, Adequate, Awkward?↩︎</p></li>
</ol>
</section><section class="quarto-appendix-contents"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div id="quarto-reuse" class="quarto-appendix-contents"><div>CC BY-NC-SA 4.0</div></div></section></div> ]]></description>
  <category>cli</category>
  <category>pet</category>
  <category>python</category>
  <category>uv</category>
  <guid>https://www.rostrum.blog/posts/2025-10-30-pet/</guid>
  <pubDate>Thu, 30 Oct 2025 00:00:00 GMT</pubDate>
  <media:content url="https://www.rostrum.blog/posts/2025-10-30-pet/resources/pet-see.png" medium="image" type="image/png" height="64" width="144"/>
</item>
<item>
  <title>Abandon P*werpoint for the command line</title>
  <dc:creator>Matt Dray</dc:creator>
  <link>https://www.rostrum.blog/posts/2025-09-18-rich-slides/</link>
  <description><![CDATA[ 




<p><img src="https://www.rostrum.blog/posts/2025-09-18-rich-slides/resources/slide-1.svg" class="img-fluid" style="width:100.0%" alt="A dark terminal window with ASCII art of the word 'synergy' and some text under it saying 'promote it'. There's a border line around the edge. These elements are green."></p>
<section id="tldr" class="level2">
<h2 class="anchored" data-anchor-id="tldr">tl;dr</h2>
<p>You can use Python’s <a href="https://rich.readthedocs.io/en/stable/">rich</a> library to make super minimal presentations at the command line.</p>
</section>
<section id="sliding-scale" class="level2">
<h2 class="anchored" data-anchor-id="sliding-scale">Sliding scale</h2>
<p>Recently, conference organisers forced a colleague to convert their Quarto presentation to P*werPoint. Unconscionable.</p>
<p>Proprietary software shouldn’t get in the way of good ideas, maaan.</p>
<p>Actually, if your message is good enough<sup>1</sup> then why waste time <em>rendering</em> anything? Why not just print directly to the terminal, lol?</p>
</section>
<section id="thats-rich" class="level2">
<h2 class="anchored" data-anchor-id="thats-rich">That’s rich</h2>
<p>The <a href="https://rich.readthedocs.io/en/stable/">rich</a> library is for prettifying output and making user interfaces<sup>2</sup>.</p>
<p>There’s nothing to stop you using rich to show faux ‘slides’ at the command line. So, as a thought experiment, I’ve given this a go.</p>
<p>Basic approach:</p>
<ol type="1">
<li>Clear the screen.</li>
<li>Accept keyboard input.</li>
<li>Draw the next slide.</li>
</ol>
<p>I’ve prepared a single Python demo script to do exactly this. Dependencies are declared at the top, so you can run it with <a href="https://docs.astral.sh/uv/">uv</a> like <code>uv run slides.py</code><sup>3</sup>.</p>
<p>Crucially, you’ll have to zoom in to you terminal so the words end up nice and big. I don’t know a simple way to do this automatically (or if it’s even possible).</p>
<p>Then you can use arrow keys to move through the slides and press <kbd>Q</kbd> to quit.</p>
<details>
<summary>
Click to see the full demo script
</summary>
<div class="cell">
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>slides.py</strong></pre>
</div>
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># /// script</span></span>
<span id="cb1-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># requires-python = "&gt;=3.12"</span></span>
<span id="cb1-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># dependencies = [</span></span>
<span id="cb1-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     "pynput",</span></span>
<span id="cb1-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     "rich",</span></span>
<span id="cb1-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ]</span></span>
<span id="cb1-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ///</span></span>
<span id="cb1-8"></span>
<span id="cb1-9"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pynput <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> keyboard</span>
<span id="cb1-10"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> rich.align <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Align</span>
<span id="cb1-11"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> rich.console <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Console</span>
<span id="cb1-12"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> rich.panel <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Panel</span>
<span id="cb1-13"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> os</span>
<span id="cb1-14"></span>
<span id="cb1-15">console <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Console()</span>
<span id="cb1-16"></span>
<span id="cb1-17"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> panelise(</span>
<span id="cb1-18">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>args,</span>
<span id="cb1-19">    sep<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>,</span>
<span id="cb1-20">    panel_title,</span>
<span id="cb1-21">    panel_subtitle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#ShareholderValue"</span>,</span>
<span id="cb1-22">    panel_width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>console.size.width,</span>
<span id="cb1-23">    panel_height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>console.size.height <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>,</span>
<span id="cb1-24">):</span>
<span id="cb1-25">    panel_text <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sep.join(args)</span>
<span id="cb1-26">    panel_body <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Align.center(panel_text, vertical<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"middle"</span>)</span>
<span id="cb1-27">    panel <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Panel(</span>
<span id="cb1-28">        panel_body,</span>
<span id="cb1-29">        title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>panel_title,</span>
<span id="cb1-30">        subtitle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>panel_subtitle,</span>
<span id="cb1-31">        width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>panel_width,</span>
<span id="cb1-32">        height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>panel_height,</span>
<span id="cb1-33">    )</span>
<span id="cb1-34">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> panel</span>
<span id="cb1-35"></span>
<span id="cb1-36">slides <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb1-37">    panelise(</span>
<span id="cb1-38">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">" ___ _   _ _ __   ___ _ __ __ _ _   _ "</span>,</span>
<span id="cb1-39">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/ __| | | | '_ \ / _ \ '__/ _` | | | |"</span>,</span>
<span id="cb1-40">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"\__ \ |_| | | | |  __/ | | (_| | |_| |"</span>,</span>
<span id="cb1-41">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"|___/\__, |_| |_|\___|_|  \__, |\__, |"</span>,</span>
<span id="cb1-42">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"      __/ |                __/ | __/ |"</span>,</span>
<span id="cb1-43">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"     |___/                |___/ |___/ "</span>,</span>
<span id="cb1-44">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"                            promote it"</span>,</span>
<span id="cb1-45">        panel_title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>,</span>
<span id="cb1-46">        panel_subtitle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>,</span>
<span id="cb1-47">    ),</span>
<span id="cb1-48">    panelise(</span>
<span id="cb1-49">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"• Push the envelope"</span>,</span>
<span id="cb1-50">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"• Double down"</span>,</span>
<span id="cb1-51">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"• Think outside the box"</span>,</span>
<span id="cb1-52">        panel_title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Paradigm shift"</span>,</span>
<span id="cb1-53">    ),</span>
<span id="cb1-54">    panelise(</span>
<span id="cb1-55">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"• Move the needle"</span>,</span>
<span id="cb1-56">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"• Circle back"</span>,</span>
<span id="cb1-57">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"• Take this offline"</span>,</span>
<span id="cb1-58">        panel_title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Key deliverables"</span>,</span>
<span id="cb1-59">    ),</span>
<span id="cb1-60">    panelise(</span>
<span id="cb1-61">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You must clap"</span>,</span>
<span id="cb1-62">        panel_title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You're welcome"</span>,</span>
<span id="cb1-63">    ),</span>
<span id="cb1-64">]</span>
<span id="cb1-65"></span>
<span id="cb1-66">index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb1-67">running <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span></span>
<span id="cb1-68"></span>
<span id="cb1-69"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> render_slide():</span>
<span id="cb1-70">    os.system(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"clear"</span>)</span>
<span id="cb1-71"></span>
<span id="cb1-72">    width <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> console.size.width</span>
<span id="cb1-73">    filled <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>((index) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(slides) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> width)</span>
<span id="cb1-74">    bar <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"▬"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> filled <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">" "</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (width <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> filled)</span>
<span id="cb1-75">    console.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(bar, style<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"green"</span>)</span>
<span id="cb1-76"></span>
<span id="cb1-77">    console.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(slides[index], style<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"green"</span>)</span>
<span id="cb1-78"></span>
<span id="cb1-79"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> on_press(key):</span>
<span id="cb1-80">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">global</span> index, running</span>
<span id="cb1-81"></span>
<span id="cb1-82">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">hasattr</span>(key, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"char"</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> key.char <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"q"</span>:</span>
<span id="cb1-83">        running <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span></span>
<span id="cb1-84">        listener.stop()</span>
<span id="cb1-85">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span></span>
<span id="cb1-86"></span>
<span id="cb1-87">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> key <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> keyboard.Key.right:</span>
<span id="cb1-88">        index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(slides)</span>
<span id="cb1-89"></span>
<span id="cb1-90">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> key <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> keyboard.Key.left:</span>
<span id="cb1-91">        index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(slides)</span>
<span id="cb1-92"></span>
<span id="cb1-93">    render_slide()</span>
<span id="cb1-94"></span>
<span id="cb1-95">listener <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> keyboard.Listener(on_press<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>on_press)</span>
<span id="cb1-96"></span>
<span id="cb1-97">render_slide()</span>
<span id="cb1-98">listener.start()</span>
<span id="cb1-99">listener.join() </span></code></pre></div>
</div>
</div>
</details>
<p>The slides in the demo are spat out like this<sup>4</sup>:</p>
<p><img src="https://www.rostrum.blog/posts/2025-09-18-rich-slides/resources/slide-1.svg" class="img-fluid" style="width:49.0%" alt="Title slide: green ASCII art of the word 'synergy' with a green border in a dark terminal window."> <img src="https://www.rostrum.blog/posts/2025-09-18-rich-slides/resources/slide-2.svg" class="img-fluid" style="width:49.0%" alt="Slide 2, titled 'paradigm shift', with a progress bar one-third full, three bullet points including 'push the envelope' and a footer saying '#ShareholderValue'. The elements are all green."> <img src="https://www.rostrum.blog/posts/2025-09-18-rich-slides/resources/slide-3.svg" class="img-fluid" style="width:49.0%" alt="Slide 3, titled 'key deliverables', with a progress bar two-thirds full, three bullet points including 'take this offline' and a footer saying '#ShareholderValue'. The elements are all green."> <img src="https://www.rostrum.blog/posts/2025-09-18-rich-slides/resources/slide-4.svg" class="img-fluid" style="width:49.0%" alt="Slide 4, titled 'you're welcome', with a full progress bar, a line saying 'you must clap' and a footer saying '#ShareholderValue'. The elements are all green."></p>
<p>Some may call this content ‘jargon rich’ but they are losers who have simply never grinded as hard as me, bro.</p>
</section>
<section id="slidecraft" class="level2">
<h2 class="anchored" data-anchor-id="slidecraft">Slidecraft</h2>
<p>I’ll step through the code.</p>
<p>First, we initiate a console object: <code>console = Console()</code>. The <code>print()</code> method against this lets us <code>style</code> the output.</p>
<p>Next, I made a <code>panelise()</code> function, which wraps rich’s <code>Panel()</code>. This simplifies ‘slide’ setup with some defaults.</p>
<p>Incidentally, a ‘<a href="https://rich.readthedocs.io/en/stable/panel.html">panel</a>’ is just an area surrounded by a border, which I’ve ‘hacked’ to use as a bordered slide.</p>
<p>Then we create a dict of slides, passing lines of text and the slide title. In my example I created a title slide with <a href="https://patorjk.com/software/taag/">some cool ASCII art</a> and then a few slides with simple bullets.</p>
<p>Next are two important functions:</p>
<ul>
<li><code>render_slide()</code>, which clears the console, adds a progress bar at the top (oh yeah, did I mention there’s a progress bar?<sup>5</sup>) and then prints the slide content</li>
<li><code>on_press()</code>, which uses the <a href="https://pynput.readthedocs.io/en/latest/index.html">pynput</a> library to accept keypresses (left- and right-arrows to move slides and <kbd>Q</kbd> to quit)</li>
</ul>
<p>Then there are steps to run the presentation itself:</p>
<ol type="1">
<li>Start ‘listening’ to keyboard presses with <code>keyboard.Listener()</code>.</li>
<li>Render the first slide (<code>index = 0</code>) with <code>render_slide()</code>.</li>
<li>Move to the next slide with an arrow press (wrapping back to the start).</li>
<li>Exit the slides when a <kbd>q</kbd> happens (set <code>running = FALSE</code> and stop listening for keypresses).</li>
</ol>
</section>
<section id="the-slideshows-over" class="level2">
<h2 class="anchored" data-anchor-id="the-slideshows-over">The slideshow’s over</h2>
<p>So, this thing I threw together does what I wanted it to do. Improvements are absolutely possible.</p>
<p>Of course, I haven’t made full use of rich’s rich colour and style control. rich also has a module that <a href="https://rich.readthedocs.io/en/stable/markdown.html">interprets Markdown input</a>, which may be preferable to <code>Panel()</code> in some cases.</p>
<p>I know the script works on macOS but I haven’t tested it anywhere else. I suspect there may be better, platform independent approaches.</p>
<p>It’s also true that pressing the arrow keys will advance slides… even if the focus is away from the terminal. Ah well!</p>
<p>Anyway, if you want an actual fully-featured terminal-based tool, you could try something like <a href="https://maaslalani.com/slides/">Maas Lalani’s slides</a>.</p>
<p>Next slide please!</p>

</section>


<div id="quarto-appendix" class="default"><section id="environment" class="level3 appendix"><h2 class="anchored quarto-appendix-heading">Environment</h2><div class="quarto-appendix-contents">

<details>
<summary>
Session info
</summary>
<div class="cell">
<div class="cell-output cell-output-stdout">
<pre><code>[project]
name = "2025-08-30-jot-options"
version = "0.1.0"
requires-python = "&gt;=3.12"
dependencies = []</code></pre>
</div>
</div>
</details>


</div></section><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>I worked at the <a href="https://www.gov.uk/government/organisations/government-digital-service">Government Digital Service</a> (GDS) where there was <a href="https://gds.blog.gov.uk/2016/04/07/giving-clear-presentations/">a culture of good, simple slides</a>.↩︎</p></li>
<li id="fn2"><p>I used its basic features recently to add colour to <a href="https://github.com/matt-dray/jot">my minimal note-taking tool, jot</a>.↩︎</p></li>
<li id="fn3"><p>An approach I wrote about in <a href="https://www.rostrum.blog/posts/2025-08-11-uv-standalone/">a recent post</a>.↩︎</p></li>
<li id="fn4"><p>Add <code>record=TRUE</code> to the <code>Console()</code> call and <code>console.save_svg("slides.svg")</code> at the end to get an image of your slides as you progress through them.↩︎</p></li>
<li id="fn5"><p>Yes, the bar starts on the second slide, after the title slide. This is for aesthetic reasons mostly.↩︎</p></li>
</ol>
</section><section class="quarto-appendix-contents"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div id="quarto-reuse" class="quarto-appendix-contents"><div>CC BY-NC-SA 4.0</div></div></section></div> ]]></description>
  <category>cli</category>
  <category>python</category>
  <category>rich</category>
  <category>uv</category>
  <guid>https://www.rostrum.blog/posts/2025-09-18-rich-slides/</guid>
  <pubDate>Wed, 17 Sep 2025 23:00:00 GMT</pubDate>
  <media:content url="https://www.rostrum.blog/posts/2025-09-18-rich-slides/resources/slide-1.svg" medium="image" type="image/svg+xml"/>
</item>
<item>
  <title>Keeping jot’s options open</title>
  <dc:creator>Matt Dray</dc:creator>
  <link>https://www.rostrum.blog/posts/2025-08-30-jot-options/</link>
  <description><![CDATA[ 





<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2025-08-30-jot-options/resources/jot.png" class="img-fluid quarto-figure quarto-figure-left figure-img" alt="Terribly drawn image of the word 'jot' in cursive with a pencil at the end of the letter 't'.  The dot of the letter 'i' is a red love heart." width="200"></p>
</figure>
</div>
<section id="tldr" class="level2">
<h2 class="anchored" data-anchor-id="tldr">tl;dr</h2>
<p>I’ve added a small set of options to <a href="https://github.com/matt-dray/jot">jot</a>, my minimal opinionated Python command-line tool that helps me remember stuff.</p>
</section>
<section id="forget-me-jot" class="level2">
<h2 class="anchored" data-anchor-id="forget-me-jot">Forget me jot</h2>
<p>You may recall <a href="https://www.rostrum.blog/posts/2025-08-25-jot/">I made a Python CLI tool called jot</a> to help me record work tasks. I go to my terminal and type <code>jot "corrected a typo"</code> and it gets added to a simple text file with a timestamp.</p>
<p>To give it a go, I recommend installing <a href="https://docs.astral.sh/uv/">uv</a> and then install <a href="https://github.com/matt-dray/jot">from GitHub</a> like <code>uv tool install git+https://github.com/matt-dray/jot</code>.</p>
</section>
<section id="opt-in" class="level2">
<h2 class="anchored" data-anchor-id="opt-in">Opt in</h2>
<p>I’ve been using jot for the past couple of weeks and it’s helping me record things I would typically forget about. As a result, I’ve found it easier to unobscure ‘<a href="https://www.youtube.com/watch?v=HiF83i1OLOM">invisible work</a>’ and reflect on what I’ve achieved.</p>
<p>But my list of jottings is getting harder to manage as it gets longer. There’s a couple of things that would make it easier for me: to be able to peek at the past few jottings and to search them for specific terms.</p>
<p>So, I’ve added two options and will demonstrate them against some imaginary data:</p>
<details>
<summary>
<code>breakfast-jot.txt</code>
</summary>
<pre><code>[2025-08-30 09:27] granola and yoghurt
[2025-08-29 07:47] pancakes, blueberries and maple syrup
[2025-08-28 08:00] fried egg on sourdough toast
[2025-08-27 08:06] yoghurt and strawberries
[2025-08-26 08:19] strawberry jam on toast
[2025-08-25 08:52] wholemeal toast and poached eggs
[2025-08-24 09:15] muesli and milk
[2025-08-23 09:23] muesli with berries and milk
[2025-08-22 08:13] hash browns and scrambled eggs
[2025-08-21 07:48] granola and yoghurt
[2025-08-20 07:54] cereal and milk</code></pre>
<p>Believe me, my actual breakfast is more boring than this. Also sometimes I have a hobbitesque second breakfast.</p>
</details>
<p>Everything below is correct as of jot v0.2.3, but I may work to improve some features. Feel free to <a href="https://github.com/matt-dray/jot/issues">make suggestions</a> too.</p>
<section id="list" class="level3">
<h3 class="anchored" data-anchor-id="list">1. List</h3>
<p>The first option is simple: print out the last <em>n</em> jottings. You can use the flag <code>--list</code>, or <code>-l</code> for short, and then provide an integer for the number of entries to show.</p>
<p>So let’s print the most recent three entries:</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode sh code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> jot <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-l</span> 3</span></code></pre></div></div>
</div>
<pre><code>[2025-08-30 09:27] granola and yoghurt
[2025-08-29 07:47] pancakes, blueberries and maple syrup
[2025-08-28 08:00] fried egg on sourdough toast</code></pre>
<p>This is handy for a quick peek at the last few things you’ve been doing.</p>
<p>It reminds me of returning to games like <a href="https://en.wikipedia.org/wiki/Pok%C3%A9mon_FireRed_and_LeafGreen"><em>Pokémon FireRed</em></a> after some time away and being given a quick recap.</p>
<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2025-08-30-jot-options/resources/previously.png" class="img-fluid figure-img" alt="A greyscale screengrab of the game Pokemon FireRed showing a recap of what the player did before their last save. At the top it says 'previously on your quest'. At the bottom, 'here in Viridian City, Red obtained the item Oak's Parcel. In the centre is an image of the player's sprite in a Pokémart, facing the shopkeeper." width="300"></p>
<figcaption><em>To collect parcels is my real test</em> (thanks <a href="https://www.reddit.com/r/pokemon/comments/f0m0ne/fire_redleaf_green_were_ahead_of_their_time/">Reddit user AIMWSTRN</a>).</figcaption>
</figure>
</div>
</section>
<section id="search" class="level3">
<h3 class="anchored" data-anchor-id="search">2. Search</h3>
<p>The second option is also simple. But much more powerful. It lets you search for words and phrases, powered by <a href="https://docs.python.org/3/library/re.html">re</a>’s regular-expression handling.</p>
<p>A simple example would be to look for a single word, this time with the <code>--search</code> or <code>-s</code> flag<sup>1</sup>:</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode sh code-with-copy"><code class="sourceCode bash"><span id="cb4-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">jot</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-s</span> egg</span></code></pre></div></div>
</div>
<pre><code>[2025-08-28 08:00] fried egg on sourdough toast
[2025-08-25 08:52] wholemeal toast and poached eggs
[2025-08-22 08:13] hash browns and scrambled eggs</code></pre>
<p>But we can use a regular expression to be more specific. We can even specify a date range, thanks to the timestamps.</p>
<p>So, when did I have eggs last work-week, specifically? Let’s use a suitable <del>regeggs</del> regex:</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode sh code-with-copy"><code class="sourceCode bash"><span id="cb6-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> jot <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-s</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2025-08-2([5-9]).*egg"</span></span></code></pre></div></div>
</div>
<pre><code>[2025-08-28 08:00] fried egg on sourdough toast
[2025-08-25 08:52] wholemeal toast and poached eggs</code></pre>
<p>With that regex I’m asking for the string ‘egg’ used in jottings between 25 and 29 August 2025’<sup>2</sup>.</p>
</section>
</section>
<section id="pain-in-the-argparse" class="level2">
<h2 class="anchored" data-anchor-id="pain-in-the-argparse">Pain in the argparse?</h2>
<p>A new learning for me has been <a href="https://docs.python.org/3/library/argparse.html">argparse package</a>, which allows your Python function to accept inputs at the command line.</p>
<p>That’s been crucial for this update to jot (v0.2.3). At the start of the <code>main()</code> function I’ve set up some argparse steps.</p>
<p>To initiate the parser:</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> argparse</span>
<span id="cb8-2"></span>
<span id="cb8-3">parser <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> argparse.ArgumentParser(</span>
<span id="cb8-4">    prog<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"jot"</span>,</span>
<span id="cb8-5">    description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Minimal opinionated Python CLI to jot timestamped thoughts."</span>,</span>
<span id="cb8-6">    epilog<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Source: https://github.com/matt-dray/jot"</span>,</span>
<span id="cb8-7">)</span></code></pre></div></div>
</div>
<p>Then we can add arguments to the parser. Here’s how we do that for the search argument, for which I’ve written a corresponding <code>search_jottings()</code> Python function:</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1">parser.add_argument(</span>
<span id="cb9-2">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"-s"</span>,</span>
<span id="cb9-3">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--search"</span>,</span>
<span id="cb9-4">    nargs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"?"</span>,</span>
<span id="cb9-5">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>,</span>
<span id="cb9-6">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">help</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"search jottings (regex supported)"</span>,</span>
<span id="cb9-7">)</span></code></pre></div></div>
</div>
<p>After these declarations, we can collect the parsed arguments:</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1">args <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> parser.parse_args()</span></code></pre></div></div>
</div>
<p>Later in the body of the <code>main()</code> function we can use the named elements of <code>args</code> to pass into our Python functions. For our search option, that looks like this:</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1">search_jottings(jot_path, args.search)</span></code></pre></div></div>
</div>
<p>As a result of this set up, we also get a nice help file for free with <code>--help</code> or <code>-h</code>, which should be helpful for users:</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode sh code-with-copy"><code class="sourceCode bash"><span id="cb12-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">jot</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-h</span></span></code></pre></div></div>
</div>
<pre><code>usage: jot [-h] [-l [LIST]] [-s [SEARCH]] [text]

Minimal opinionated Python CLI to jot timestamped thoughts.

positional arguments:
  text                  text to write to file

options:
  -h, --help            show this help message and exit
  -l [LIST], --list [LIST]
                        show last n jottings
  -s [SEARCH], --search [SEARCH]
                        search jottings (regex supported)

Source: https://github.com/matt-dray/jot</code></pre>
</section>
<section id="jot-on" class="level2">
<h2 class="anchored" data-anchor-id="jot-on">Jot on</h2>
<p>As I’ve mentioned before, jot was built by me for me. It’s basically feature-complete from my perspective. The plan is to extend it minimally and only as a way to learn more Python. But <a href="https://github.com/matt-dray/jot/issues">I welcome suggestions</a>!</p>

</section>


<div id="quarto-appendix" class="default"><section id="environment" class="level3 appendix"><h2 class="anchored quarto-appendix-heading">Environment</h2><div class="quarto-appendix-contents">

<details>
<summary>
Session info
</summary>
<div class="cell">
<div class="cell-output cell-output-stdout">
<pre><code>[project]
name = "2025-08-30-jot-options"
version = "0.1.0"
requires-python = "&gt;=3.12"
dependencies = []</code></pre>
</div>
</div>
</details>


</div></section><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>In future I want an argument to limit the number of jottings returned, otherwise we might flood the console. With eggs. Which could get messy.↩︎</p></li>
<li id="fn2"><p>More literally, it matches the string ‘2025-08-2’, replacing the final digit of the date with 5, 6, 7, 8 or 9; then allows for any number of any characters until it finds the string ‘egg’.↩︎</p></li>
</ol>
</section><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a></div></div></section></div> ]]></description>
  <category>cli</category>
  <category>jot</category>
  <category>python</category>
  <category>uv</category>
  <guid>https://www.rostrum.blog/posts/2025-08-30-jot-options/</guid>
  <pubDate>Fri, 29 Aug 2025 23:00:00 GMT</pubDate>
  <media:content url="https://www.rostrum.blog/posts/2025-08-30-jot-options/resources/jot.png" medium="image" type="image/png" height="120" width="144"/>
</item>
<item>
  <title>Jotting down some indifferent vibes</title>
  <dc:creator>Matt Dray</dc:creator>
  <link>https://www.rostrum.blog/posts/2025-08-25-jot/</link>
  <description><![CDATA[ 




<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2025-08-25-jot/resources/install-jot.png" class="img-fluid figure-img" style="width:100.0%" alt="A terminal with instructions to install the jot tool from GitHub. The jot command is used to save the note 'sharpened my pencils'. The user is prompted to give a path to a text file where the note will be saved. Successive jot calls are made and the content of the text file is printed to show the notes were saved to file with a timestamp."></p>
</figure>
</div>
<section id="tldr" class="level2">
<h2 class="anchored" data-anchor-id="tldr">tl;dr</h2>
<p>I’ve started a minimal opinionated Python command-line tool to help me remember stuff. It’s called <a href="https://github.com/matt-dray/jot">jot</a>.</p>
</section>
<section id="jot-or-not" class="level2">
<h2 class="anchored" data-anchor-id="jot-or-not">Jot or not</h2>
<p>This tool is for me<sup>1</sup>, but if you want to install it, I recommend you <a href="https://docs.astral.sh/uv/">install uv first</a>. Then run <code>uv tool install git+https://github.com/matt-dray/jot</code> from a terminal to install jot (currently v0.2.1) <a href="https://github.com/matt-dray/jot">from GitHub</a>.</p>
<p>Open a terminal and write your first jotting like <code>jot "sharpened my pencils"</code>. When prompted, enter a text file path where your jottings will be saved. This path will be stored in a <code>.jot-config.json</code> file in your home folder. Each <code>jot</code> call will prepend your your text file with the jotting and a timestamp.</p>
<p>Keep that terminal window open and write new jottings throughout the day, week, month, year.</p>
<p>That’s it.</p>
</section>
<section id="forget-me-jot" class="level2">
<h2 class="anchored" data-anchor-id="forget-me-jot">Forget me jot</h2>
<p>The development of jot is a story in three neurologically-themed parts:</p>
<ol type="1">
<li>Goldfish brain.</li>
<li>Robot brain.</li>
<li>Human brain.</li>
</ol>
<section id="goldfish-brain" class="level3">
<h3 class="anchored" data-anchor-id="goldfish-brain">1. Goldfish brain</h3>
<p>I forget stuff. Work stuff in particular. The fogginess stops me from properly recognising my efforts and reflecting on what I’ve learnt.</p>
<p>Recently I’ve reacquainted myself with <a href="https://gilest.org/doingweeknotes/index.html">weeknotes</a>; appreciated Jeff Huang’s <a href="https://jeffhuang.com/productivity_text_file/">neverending text file</a>; and resonated with Chris Albon’s advice to avoid ‘invisible work’ and to try a <a href="https://bsky.app/profile/chrisalbon.com/post/3ld24aoq4ik2p">lightweight logging system</a>.</p>
<p>So I figured I’d write a Python-powered platform-independent command-line tool to jot notes into an immortal text file<sup>2</sup>. I’m developing <a href="https://www.rostrum.blog/posts/2025-08-11-uv-standalone/">my Python skills</a> after all.</p>
</section>
<section id="robot-brain" class="level3">
<h3 class="anchored" data-anchor-id="robot-brain">2. Robot brain</h3>
<p>As a ‘data scientist’ on ‘the cutting edge’, I wondered if I should try ‘<a href="https://en.wikipedia.org/wiki/Vibe_coding">vibe coding</a>’ on this non-critical personal project<sup>3</sup>.</p>
<p>After some low-effort LLM prompts, I got a thing that did the thing. I figured its existence was the main goal and I could iterate later with more ‘artisanal’ approaches (i.e.&nbsp;engaging my monkey brain and paws).</p>
<p>I released this as <a href="https://github.com/matt-dray/jot/releases/tag/v0.1.0">v0.1.0</a> and started using it. It was just what I wanted (i.e.&nbsp;not very much).</p>
<p>So why then did I rewrite the whole thing from scratch?</p>
</section>
<section id="human-brain" class="level3">
<h3 class="anchored" data-anchor-id="human-brain">3. Human brain</h3>
<p>Inevitably (obviously) the vibes didn’t teach me anything<sup>4</sup>. I knew this would happen, but not how much I’d actually miss the failing and learning that comes with doing a new thing.</p>
<p>So I rewrote it myself using some old-fashioned Duck-Duck-Going, some snotty Stackoverflow comments and, shudder, some documentation.</p>
<p>I learnt things about file handling with <code>with</code>, <a href="https://docs.python.org/3.15/library/pathlib.html">pathlib</a> for easing the pain of filepaths, and <a href="https://docs.python.org/3.15/library/argparse.html">argparse</a> for interpreting inputs on the command line.</p>
<p>Is the resulting <a href="https://github.com/matt-dray/jot/releases/tag/v0.2.0">v0.2.0</a> (human-powered edition) better than v0.1.0? Well, it’s ‘as good’ in the sense it achieves the same outcome. My code is more brittle and my limits are more evident. But I’m okay with that.</p>
</section>
</section>
<section id="human-heart" class="level2">
<h2 class="anchored" data-anchor-id="human-heart">Human heart</h2>
<p>There’s not much of a lesson here, apart from the fact I like tinkering and would prefer to do things that way.</p>
<p>I’ll develop jot a bit more with the aid of ‘traditional’ techniques. The interface will remain minimal, but I’d like to add some really basic options to fetch jottings from the past day/week/month or that match a keyword. <a href="https://github.com/matt-dray/jot/issues">Add some ideas</a> if you like.</p>
<p>And actually, I think LLMs do have a place in this story, just not in the development stage. Before team standups I can consult an LLM to summarise the past week from my jottings. Before appraisals I can summarise the past year.</p>
<p>One particular LLM asked if I’d like some Python code to generate a P*werPoint slide to summarise my week. Wow!</p>
<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2025-08-25-jot/resources/jot-pptx.png" class="img-fluid figure-img" style="width:100.0%" alt="A PowerPoint slide. The title is 'weekly summary' with a sun emoji. There are two columns. The left is titled 'what went well' with a green checkbox emoji. The right is titled 'lessons learnt' with a book emoji. Both columns have three bullet points of text. The text doesn't wrap. It overlaps and runs off the screen."></p>
<figcaption class="figure-caption">Word wrap is for cowards.</figcaption>
</figure>
</div>
<p>Very cool. Judgement day is inevitable.</p>

</section>


<div id="quarto-appendix" class="default"><section id="environment" class="level3 appendix"><h2 class="anchored quarto-appendix-heading">Environment</h2><div class="quarto-appendix-contents">

<details>
<summary>
Session info
</summary>
<div class="cell">
<div class="cell-output cell-output-stdout">
<pre><code>[project]
name = "2025-08-25-jot"
version = "0.1.0"
requires-python = "&gt;=3.12"
dependencies = []</code></pre>
</div>
</div>
</details>


</div></section><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>Though <a href="https://www.rostrum.blog/about#adriana-de-palma">Adriana</a> has been using it too, in a slightly different way to me.↩︎</p></li>
<li id="fn2"><p>You’re thinking ‘yes, but now you have a meta problem: will you actually remember to write in it?’ I don’t think this is a problem for me, personally. A terminal window is always in front of my face and hard to miss. I seem capable of building habits for other positive things.↩︎</p></li>
<li id="fn3"><p>This blog is a non-critical personal project too, but I have not and will not write posts with anything other than my brain and typo-addled fingers. <a href="https://www.rostrum.blog/posts/2024-03-15-ai-garbage/">I’ve written before</a> about my distaste for LLMs as slop manufacturers. I have fewer qualms about using it for boilerplate, as a code helper, and for-fun personal projects. But I’m still not 100% happy with it in general from an ethical standpoint and for the existential dread it can bring.↩︎</p></li>
<li id="fn4"><p>Yes, I could have asked the LLM to walk me through the code and teach me. Would I have remembered any of it? Unlikely. More fool me.↩︎</p></li>
</ol>
</section><section class="quarto-appendix-contents"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div id="quarto-reuse" class="quarto-appendix-contents"><div>CC BY-NC-SA 4.0</div></div></section></div> ]]></description>
  <category>cli</category>
  <category>jot</category>
  <category>python</category>
  <category>uv</category>
  <guid>https://www.rostrum.blog/posts/2025-08-25-jot/</guid>
  <pubDate>Sun, 24 Aug 2025 23:00:00 GMT</pubDate>
  <media:content url="https://www.rostrum.blog/posts/2025-08-25-jot/resources/install-jot.png" medium="image" type="image/png" height="73" width="144"/>
</item>
<item>
  <title>A bank holiday to honour uv?</title>
  <dc:creator>Matt Dray</dc:creator>
  <link>https://www.rostrum.blog/posts/2025-08-11-uv-standalone/</link>
  <description><![CDATA[ 




<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2025-08-11-uv-standalone/resources/bank.png" class="img-fluid figure-img" style="width:100.0%" alt="The command bank.py is run in a macOS terminal Window, yielding the phrase 'Summer bank holiday is on 25 August 2025', with a series of triangle symbols alternating with a black-and-white fill to look like bunting."></p>
</figure>
</div>
<section id="tldr" class="level2">
<h2 class="anchored" data-anchor-id="tldr">tl;dr</h2>
<p>You can use <a href="https://docs.astral.sh/uv/">uv</a> to help turn a Python<sup>1</sup> script into a self-contained executable. Useful for really important stuff, like… discovering the next public holiday from your terminal?</p>
<p>This is my own attempt at something <a href="https://mathspp.com/blog/til/standalone-executable-python-scripts-with-uv">I saw Rodrigo do</a><sup>2</sup>.</p>
</section>
<section id="worth-a-bunt" class="level2">
<h2 class="anchored" data-anchor-id="worth-a-bunt">Worth a bunt</h2>
<p>The UK government maintains <a href="https://www.api.gov.uk/#uk-public-sector-apis">an API catalogue</a>. A very (very) simple API example is <a href="https://www.api.gov.uk/gds/bank-holidays/#bank-holidays">the bank-holidays API</a> run by the Government Digital Service (GDS). It’s perhaps most well-known for <a href="https://www.gov.uk/bank-holidays">the associated page on the GOV.UK website</a>.</p>
<p>All the API does is serve a JSON file with upcoming public holidays. I wrote a ramshackle little Python script that grabs the file, works out what the next holiday is, and prints it out.</p>
<p>Crucially, it also prints <a href="https://en.wikipedia.org/wiki/Bunting_(decoration)">bunting</a> if, according to the JSON, the occasion calls for it.</p>
</section>
<section id="banking-crisis" class="level2">
<h2 class="anchored" data-anchor-id="banking-crisis">Banking crisis</h2>
<p>So, here’s the quite-ordinary code for this demonstration<sup>3</sup>:</p>
<div class="cell">
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>bank.py</strong></pre>
</div>
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> httpx</span>
<span id="cb1-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> datetime <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> date</span>
<span id="cb1-3"></span>
<span id="cb1-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Fetch bank holidays</span></span>
<span id="cb1-5"></span>
<span id="cb1-6">resp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> httpx.get(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"https://www.gov.uk/bank-holidays.json"</span>)</span>
<span id="cb1-7">events <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> resp.json()[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"england-and-wales"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"events"</span>]</span>
<span id="cb1-8"></span>
<span id="cb1-9"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> event <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> events:</span>
<span id="cb1-10">    event[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"date"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> date.fromisoformat(event[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"date"</span>])</span>
<span id="cb1-11"></span>
<span id="cb1-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Find next holiday</span></span>
<span id="cb1-13">today <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> date.today()</span>
<span id="cb1-14">future <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [event <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> event <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> events <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> event[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"date"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> today]</span>
<span id="cb1-15"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">next</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> future[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb1-16"></span>
<span id="cb1-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Print message</span></span>
<span id="cb1-18"></span>
<span id="cb1-19">is_today <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">next</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"date"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> today</span>
<span id="cb1-20">needs_bunting <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">next</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bunting"</span>]</span>
<span id="cb1-21"></span>
<span id="cb1-22"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> is_today:</span>
<span id="cb1-23">    when <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"today!"</span></span>
<span id="cb1-24"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>: </span>
<span id="cb1-25">    when <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"on </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">next</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'date'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>strftime(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%d</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> %B %Y'</span>)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb1-26"></span>
<span id="cb1-27">str_out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">next</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'title'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> is </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>when<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb1-28"></span>
<span id="cb1-29"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> needs_bunting:</span>
<span id="cb1-30">    str_out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> str_out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">" ▼▽▼▽▼▽"</span></span>
<span id="cb1-31"></span>
<span id="cb1-32"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(str_out)</span></code></pre></div>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>Christmas Day is on 25 December 2025 ▼▽▼▽▼▽</code></pre>
</div>
</div>
<p>Very simple. Does the job<sup>4</sup>. But if we want to use the script, maybe we’ll have to clone a repo containing the script, change directory, activate the virtual environment and only then run the script. Every time we want to use it.</p>
<p>Wouldn’t it be better if I could just call it quickly from my macOS Terminal when I need it? To just type <code>bank.py</code> and get a quick response?</p>
</section>
<section id="uv-protection" class="level2">
<h2 class="anchored" data-anchor-id="uv-protection">uv protection</h2>
<p>This is where <a href="https://docs.astral.sh/uv/">uv</a> can help out<sup>5</sup>. You can <a href="https://docs.astral.sh/uv/getting-started/installation/">install</a> via Homebrew with <code>brew install uv</code>, for example.</p>
<p>You can start by adding a block at the top of your file to declare dependencies. Kind of like bundling a <code>pyproject.toml</code> inside your script<sup>6</sup>. No need to worry about a separate dependencies file, lockfile or virtual environment.</p>
<p>To add your dependencies, use <code>uv add</code> with the <code>--script</code> option in the form:</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode sh code-with-copy"><code class="sourceCode bash"><span id="cb3-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">uv</span> add httpx datetime <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--script</span> bank.py</span></code></pre></div>
</div>
<p>Which will add the following section at the top of our <code>bank.py</code> script, including the Python requirement:</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># /// script</span></span>
<span id="cb4-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># requires-python = "&gt;=3.12"</span></span>
<span id="cb4-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># dependencies = [</span></span>
<span id="cb4-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     "datetime",</span></span>
<span id="cb4-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     "httpx",</span></span>
<span id="cb4-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ]</span></span>
<span id="cb4-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ///</span></span></code></pre></div>
</div>
<p>And there you have it: all the information needed to run this file in an isolated manner. You’ll be able to run <code>uv run bank.py</code> from your terminal without any additional files required.</p>
<p>But wait! There’s more. How boring, how <em>painful</em> to have to write the <code>uv run</code> bit. That’s <em>six</em> characters<sup>7</sup>. And you’ll have to provide the full path to the script, yikes.</p>
<p>Instead, you can add a <a href="https://en.wikipedia.org/wiki/Shebang_%28Unix%29">shebang</a><sup>8</sup> at the top of the file to make it executable:</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#!/usr/bin/env -S uv run</span></span></code></pre></div>
</div>
<p>Note the unusual bit on the end… aha! uv again! The <code>-S uv run</code> bit asks the interpreter to let good ol’ uv handle the whole operation.</p>
<p>Finally, you can change the mode (<code>chmod</code>) of the file by adding (<code>+</code>) executable (<code>x</code>) status. Then put the file where you terminal will look if you type the name of the file.</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode sh code-with-copy"><code class="sourceCode bash"><span id="cb6-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">chmod</span> +x bank.py</span>
<span id="cb6-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mv</span> bank.py ~/.local/bin</span></code></pre></div>
</div>
<p>Then you can merely call <code>bank.py</code> from the terminal to know when the next bank holiday is.</p>
<div class="tip">
<p> <b>Note</b></p>
<p>In fact, I discovered later <a href="https://docs.astral.sh/uv/guides/scripts/#using-a-shebang-to-create-an-executable-file">in uv’s docs</a> that you can add the <code>--script</code> flag to the shebang and remove the <code>.py</code> extension from the filename to ensure you only need to type <code>bank</code> into the terminal. Much sleeker.</p>
</div>
</section>
<section id="one-mans-trash" class="level2">
<h2 class="anchored" data-anchor-id="one-mans-trash">One man’s trash</h2>
<p>You can find a <a href="https://gist.github.com/matt-dray/c97603f294fb80736b669141191d827b">GitHub Gist of my code</a><sup>9</sup>, or:</p>
<details>
<summary>
Click to see the complete <code>bank.py</code> script.
</summary>
<div class="cell">
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>bank.py</strong></pre>
</div>
<div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#!/usr/bin/env -S uv run</span></span>
<span id="cb7-2"></span>
<span id="cb7-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># /// script</span></span>
<span id="cb7-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># requires-python = "&gt;=3.12"</span></span>
<span id="cb7-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># dependencies = [</span></span>
<span id="cb7-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     "datetime",</span></span>
<span id="cb7-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     "httpx",</span></span>
<span id="cb7-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ]</span></span>
<span id="cb7-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ///</span></span>
<span id="cb7-10"></span>
<span id="cb7-11"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> httpx</span>
<span id="cb7-12"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> datetime <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> date</span>
<span id="cb7-13"></span>
<span id="cb7-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Fetch bank holidays</span></span>
<span id="cb7-15"></span>
<span id="cb7-16">resp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> httpx.get(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"https://www.gov.uk/bank-holidays.json"</span>)</span>
<span id="cb7-17">events <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> resp.json()[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"england-and-wales"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"events"</span>]</span>
<span id="cb7-18"></span>
<span id="cb7-19"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> event <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> events:</span>
<span id="cb7-20">    event[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"date"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> date.fromisoformat(event[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"date"</span>])</span>
<span id="cb7-21"></span>
<span id="cb7-22"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Find next holiday</span></span>
<span id="cb7-23">today <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> date.today()</span>
<span id="cb7-24">future <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [event <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> event <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> events <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> event[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"date"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> today]</span>
<span id="cb7-25"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">next</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> future[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb7-26"></span>
<span id="cb7-27"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Print message</span></span>
<span id="cb7-28"></span>
<span id="cb7-29">is_today <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">next</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"date"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> today</span>
<span id="cb7-30">needs_bunting <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">next</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bunting"</span>]</span>
<span id="cb7-31"></span>
<span id="cb7-32"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> is_today:</span>
<span id="cb7-33">    when <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"today!"</span></span>
<span id="cb7-34"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>: </span>
<span id="cb7-35">    when <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"on </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">next</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'date'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>strftime(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%d</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> %B %Y'</span>)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb7-36"></span>
<span id="cb7-37">str_out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">next</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'title'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> is </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>when<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb7-38"></span>
<span id="cb7-39"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> needs_bunting:</span>
<span id="cb7-40">    str_out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> str_out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">" ▼▽▼▽▼▽"</span></span>
<span id="cb7-41"></span>
<span id="cb7-42"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(str_out)</span></code></pre></div>
</div>
</div>
</details>
<p>Next, more important goal: write something similar to help me know what bin day it is (we alternate fortnightly between refuse and recycling). I saved a neighbour from getting erroneously binfluenced recently, so this is dear to my heart.</p>

</section>


<div id="quarto-appendix" class="default"><section id="environment" class="level3 appendix"><h2 class="anchored quarto-appendix-heading">Environment</h2><div class="quarto-appendix-contents">

<details>
<summary>
Session info
</summary>
<div class="cell">
<div class="cell-output cell-output-stdout">
<pre><code>[project]
name = "2025-08-11-uv-standalone"
version = "0.1.0"
description = "rostrum.blog post: uv-standalone"
requires-python = "&gt;=3.12"
dependencies = [
    "datetime&gt;=5.5",
    "httpx&gt;=0.28.1",
]</code></pre>
</div>
</div>
</details>


</div></section><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>And thus my 182-post streak without running Python comes to an end.↩︎</p></li>
<li id="fn2"><p>I found this <a href="https://mathspp.com/blog/til/standalone-executable-python-scripts-with-uv">thanks to Rodrigo</a>, who spotted it via <a href="https://simonwillison.net/2024/Aug/21/usrbinenv-uv-run/">Simon</a>, who spotted it <a href="https://github.com/alsuren/sixdofone/pull/8">via David</a>. And now I’m ripping it off.↩︎</p></li>
<li id="fn3"><p>Please feel free to critique my Python code. How could it possibly be worse than my R code?↩︎</p></li>
<li id="fn4"><p>As a more frequent R user: <em>0 days since last zero-indexing mistake.</em>↩︎</p></li>
<li id="fn5"><p>Perhaps you have been living under a rock where uv rays have not struck you. uv is all the rage for doing all-the-Python-setup-things-that-make-Python-setup-a-headache. And it’s super fast. Because Rust. Because carcinisation is coming for all of us.↩︎</p></li>
<li id="fn6"><p><em>The call was coming from inside the script.</em>↩︎</p></li>
<li id="fn7"><p>Likely more, given <a href="https://www.rostrum.blog/posts/2021-02-27-typos/index.html">my terrible typo history</a>.↩︎</p></li>
<li id="fn8"><p>Basically says ‘if I’m called, execute me like a little standalone program’.↩︎</p></li>
<li id="fn9"><p>I’m losing my mind becuase I’ve discovered recently that the pronunciation of ‘gist’ is another ‘how do you pronounce gif?’ situation. To be clear, it should be pronounced ‘gist’.↩︎</p></li>
</ol>
</section><section class="quarto-appendix-contents"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div id="quarto-reuse" class="quarto-appendix-contents"><div>CC BY-NC-SA 4.0</div></div></section></div> ]]></description>
  <category>api</category>
  <category>cli</category>
  <category>python</category>
  <category>uv</category>
  <guid>https://www.rostrum.blog/posts/2025-08-11-uv-standalone/</guid>
  <pubDate>Sun, 10 Aug 2025 23:00:00 GMT</pubDate>
  <media:content url="https://www.rostrum.blog/posts/2025-08-11-uv-standalone/resources/bank.png" medium="image" type="image/png" height="35" width="144"/>
</item>
<item>
  <title>Dependency-light hex stickers with {gex}</title>
  <dc:creator>Matt Dray</dc:creator>
  <link>https://www.rostrum.blog/posts/2025-02-25-gex/</link>
  <description><![CDATA[ 




<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2025-02-25-gex/resources/logo.png" class="img-fluid figure-img" style="width:33.0%" alt="A hexagon logo for the package 'gex'. In the centre is italicised serif text saying the package name. The background is a repeating pattern of a line-drawn frill-winged lizards heading from lower-right to top-left. It has a border. The colours are olivedrab1 and olivedrab4 from base R's named colour palette."></p>
<figcaption class="figure-caption">Would you buy this as a tasteful wallpaper?</figcaption>
</figure>
</div>
<section id="tldr" class="level2">
<h2 class="anchored" data-anchor-id="tldr">tl;dr</h2>
<p>The package {hexbase} has been improved and renamed <a href="https://github.com/matt-dray/gex">{gex}</a>. It’s no longer ‘dependency free’, but has a goal of being ‘gridverse’ only. Sellout!</p>
</section>
<section id="enter-the-gecko" class="level2">
<h2 class="anchored" data-anchor-id="enter-the-gecko">Enter the gecko</h2>
<p><a href="https://www.rostrum.blog/posts/2025-01-31-hexbase/">In my last post</a> I wrote about <a href="https://github.com/matt-dray/hexbase">{hexbase}</a>: a dependency-free hex-logo builder, which was helping me learn the basics of R’s grid-graphics system.</p>
<p>Now the package goal is to stay ‘lightweight’ and import only Prof Murrell and co’s family of small ‘gridverse’ packages (I think I’ve invented this term?).<sup>1</sup></p>
<p>Hence the name change: ‘gex’ is an allusion to grid + hex. I liked ‘grex’ more, but it’s already taken on CRAN. Also, a certain flavour of 90s gamer might appreciate the new name.</p>
<p>A great example of the benefit is for the hex border. In {hexbase}, the ‘border’ was just the gap between a smaller hexagon inside a bigger one. Now, {gex} uses {gridGeometry}‘s <code>polyclipGrob()</code> with <code>op = "minus"</code> to ’cut out’ a smaller from a bigger hexagon to create a polygon that can be applied with <code>add_border()</code>.</p>
</section>
<section id="cast-a-gex-hex" class="level2">
<h2 class="anchored" data-anchor-id="cast-a-gex-hex">Cast a gex hex</h2>
<p>Building a hex with {gex} differs little from {hexbase}. The steps are:</p>
<ol type="1">
<li>Open a PNG graphics device with <code>open_hex()</code>.</li>
<li>Add the hexagon with <code>add_hex()</code>.</li>
<li>Add and arrange as many text and image elements as you like with <code>add_text()</code> and <code>add_image()</code>.</li>
<li>Choose to overlay a border with <code>add_border()</code> (or not).</li>
<li>Close the device and write the file with <code>close_device()</code>.</li>
</ol>
<p>Below is the code used to create the logo for the package itself (how meta), which is shown at the top of this post.<sup>2</sup></p>
<p>First, install the package <a href="https://github.com/matt-dray/gex">from GitHub</a>.</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">install.packages</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"remotes"</span>)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># if not yet installed</span></span>
<span id="cb1-2">remotes<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">install_github</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"matt-dray/gex"</span>)</span></code></pre></div>
</div>
<p>Image manipulation is at the the user’s discretion before being added to the hex. Here I’ll use {magick} to wrangle a line-drawing of a lizard that I want to use (which is bundled in the package), turning it from black to green. I’ll save it as a new PNG and re-read it.</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1">img_tmp <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tempfile</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fileext =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">".png"</span>)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># temp storage for image</span></span>
<span id="cb2-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">system.file</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"images"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"lizard.png"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">package =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gex"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> </span>
<span id="cb2-3">  magick<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">image_read</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb2-4">  magick<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">image_fill</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"olivedrab1"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fuzz =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">refcolor =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"black"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb2-5">  magick<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">image_write</span>(img_tmp)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># write</span></span>
<span id="cb2-6">img <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> png<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">readPNG</span>(img_tmp)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># read the edited image</span></span></code></pre></div>
</div>
<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2025-02-25-gex/resources/lizard-olivedrab1.png" class="img-fluid figure-img" style="width:20.0%" alt="A line drawing of a lizard, mouth agape, with a winglike frill extending from its fore to hind limbs. The linework is in a bright shade of olive drab."></p>
<figcaption class="figure-caption">Charming little fellow.</figcaption>
</figure>
</div>
<p>Now I’ll start building the hex. Here I’m using loops to write multiple images and to add the text twice with an offset and different colours for a drop-shadow effect.</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb3-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Set up write location and graphics device</span></span>
<span id="cb3-2">temp_path <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tempfile</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fileext =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">".png"</span>)</span>
<span id="cb3-3">gex<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">open_device</span>(temp_path)</span>
<span id="cb3-4"></span>
<span id="cb3-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Add same image multiple times in a loop</span></span>
<span id="cb3-6">gex<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">add_hex</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">col =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"olivedrab4"</span>)</span>
<span id="cb3-7"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> (x <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">seq</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.04</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.2</span>)) {  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># add many images in a loop</span></span>
<span id="cb3-8">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> (y <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">seq</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.16</span>)) {</span>
<span id="cb3-9">    gex<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">add_image</span>(img, x, y, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">width =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.3</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">angle =</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">30</span>)</span>
<span id="cb3-10">  }</span>
<span id="cb3-11">}</span>
<span id="cb3-12"></span>
<span id="cb3-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Add text with 'drop shadow'</span></span>
<span id="cb3-14">font <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"IBM Plex Serif"</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># installed on my system</span></span>
<span id="cb3-15">x <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.52</span>; y <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.56</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># starting positions</span></span>
<span id="cb3-16">styles <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># to loop over</span></span>
<span id="cb3-17">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">coords =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> x <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.01</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> y <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.01</span>), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">col =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"olivedrab4"</span>),</span>
<span id="cb3-18">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">coords =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> x, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> y), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">col =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"olivedrab1"</span>)</span>
<span id="cb3-19">)</span>
<span id="cb3-20"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> (style <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> styles) {</span>
<span id="cb3-21">  gex<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">add_text</span>(</span>
<span id="cb3-22">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">string =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gex"</span>,</span>
<span id="cb3-23">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> style[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"coords"</span>]][[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"x"</span>]],</span>
<span id="cb3-24">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> style[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"coords"</span>]][[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"y"</span>]],</span>
<span id="cb3-25">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">60</span>,</span>
<span id="cb3-26">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">col =</span> style[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"col"</span>]],</span>
<span id="cb3-27">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">family =</span> font,</span>
<span id="cb3-28">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">face =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"italic"</span></span>
<span id="cb3-29">  )</span>
<span id="cb3-30">}</span>
<span id="cb3-31"></span>
<span id="cb3-32"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Add border and write file</span></span>
<span id="cb3-33">gex<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">add_border</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">col =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"olivedrab1"</span>)</span>
<span id="cb3-34">gex<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">close_device</span>()</span></code></pre></div>
</div>
<p>As mentioned previously, you build the hex by opening a device and adding elements to it, before closing it to save the file. That may make some people uncomfortable since there’s no ‘object’ as such; you can’t pipe between functions in {gex}. But this comes as no surprise to base and grid-graphics enjoyers, which is kind of the target market. There are dozens of us!</p>
<p>Having said this, I did enjoy <a href="https://kieranhealy.org/blog/archives/2025/02/06/kerning-and-kerning-in-a-widening-gyre/">Kieran’s recent sermon</a> on this style of interaction:</p>
<blockquote class="blockquote">
<p>We have to remember to turn the device off once we’re done with it, like it’s 1997. If you forget, you won’t notice for a while but eventually it’s like your Dad is gonna yell at you because you forgot to turn the lights off downstairs before you went to bed or you left the fridge door open after you went to get a drink of milk or you opened the window while the air conditioning is running in the house what the hell kind of child have I raised.</p>
</blockquote>
<p>I’m (probably) not your dad and you (probably) weren’t raised in a barn; I trust you to <code>close_device()</code> when using {gex}. Also, the package is partly named after a videogame from pre-1997, so it’d be anachronistic to do it any other way, amirite?</p>
</section>
<section id="going-on-grid" class="level2">
<h2 class="anchored" data-anchor-id="going-on-grid">Going <em>on</em> grid</h2>
<p>Since {gex} uses the grid graphics system, other ‘gridverse’ packages can be used to help make your hex. Two others are <a href="https://cran.r-project.org/package=gridBase">{gridBase}</a> to put a base R plot in a viewport and <a href="https://cran.r-project.org/package=gridGraphics">{gridGraphics}</a> to convert that plot to a grob. Handily, Guangchuang Yu (of {hexSticker} fame!) wrapped this up in <code>ggplotify::base2grob()</code>, which does what it says on the tin.</p>
<p>Here’s a plot of multicoloured points applied to a hex. Note all the {grid} calls, which are the type of thing that {gex} hides in its functions so you don’t have to think about it (more on that later in the post).</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb4-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Start with {gex}</span></span>
<span id="cb4-2">temp_path <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tempfile</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fileext =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">".png"</span>)</span>
<span id="cb4-3">gex<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">open_device</span>(temp_path)</span>
<span id="cb4-4">gex<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">add_hex</span>()</span>
<span id="cb4-5"></span>
<span id="cb4-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Add a base plot without {gex}</span></span>
<span id="cb4-7">grid<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">pushViewport</span>(grid<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">viewport</span>())</span>
<span id="cb4-8">base_plot <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> ggplotify<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">base2grob</span>(</span>
<span id="cb4-9">  \() {</span>
<span id="cb4-10">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">par</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">mar =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rep</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>))  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># no plot margin</span></span>
<span id="cb4-11">    n <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1000</span></span>
<span id="cb4-12">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot</span>(</span>
<span id="cb4-13">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">runif</span>(n), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">runif</span>(n),  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 'random' points</span></span>
<span id="cb4-14">      <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">xaxs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"i"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">yaxs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"i"</span>,  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 'edge-to-edge' plot</span></span>
<span id="cb4-15">      <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">axes =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">ann =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>,  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># no chart stuff</span></span>
<span id="cb4-16">      <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">pch =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">col =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">palette.colors</span>(n, , , <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># style points</span></span>
<span id="cb4-17">    )</span>
<span id="cb4-18">  }</span>
<span id="cb4-19">)</span>
<span id="cb4-20">grid<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">grid.draw</span>(base_plot)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># it's a grob now, we can draw it</span></span>
<span id="cb4-21">grid<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">popViewport</span>()</span>
<span id="cb4-22"></span>
<span id="cb4-23"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Finish up with {gex}</span></span>
<span id="cb4-24">gex<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">close_device</span>()</span></code></pre></div>
</div>
<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2025-02-25-gex/resources/base-grob.png" class="img-fluid figure-img" style="width:25.0%" alt="A black hexagon filled with a thousand small circles of varying colours."></p>
</figure>
</div>
<p>Other grid-adjacent packages could be used as well. Trevor has put together <a href="https://trevorldavis.com/R/gridpattern/dev/">{gridpattern}</a> to allow for pattern and gradient fills. For example, you could call <code>grid.pattern_gradient()</code> to add a splendid background to your hex.</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb5-1">temp_path <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tempfile</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fileext =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">".png"</span>)</span>
<span id="cb5-2">gex<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">open_device</span>(temp_path)</span>
<span id="cb5-3">gex<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">add_hex</span>()</span>
<span id="cb5-4">gridpattern<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">grid.pattern_gradient</span>()</span>
<span id="cb5-5">gex<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">close_device</span>()</span></code></pre></div>
</div>
<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2025-02-25-gex/resources/pattern.png" class="img-fluid figure-img" style="width:25.0%" alt="A hexagon with a tasteful colour-gradient background."></p>
</figure>
</div>
<p>Lovely.</p>
</section>
<section id="the-sticky-back-end" class="level2">
<h2 class="anchored" data-anchor-id="the-sticky-back-end">The sticky back-end</h2>
<p>In case you’re wondering what’s going on under the hood, here’s a simplified description. This is more for the nerds. Which is what you are if you’ve read this far.</p>
<section id="grid-reference" class="level3">
<h3 class="anchored" data-anchor-id="grid-reference">Grid reference</h3>
<p>You can find lots of useful material about {grid} and the ‘gridverse’ on <a href="https://www.stat.auckland.ac.nz/~paul/grid/grid.html">the dedicated page</a> of The University of Auckland’s website and on Prof Murrell’s <a href="https://www.stat.auckland.ac.nz/~paul/">home page</a>. Read that first.</p>
<p>But here’s a massive oversimplification anyway. With grid graphics you draw ‘grobs’ (graphics objects, like polygons, lines and text) into little windows on your canvas called ‘viewports’. You create a viewport with various properties (size, position, rotation, etc), ‘push’ to activate it, draw your grobs inside it and then ‘pop’ the viewport to add it to your canvas. Importantly, you can nest viewports for finer control or to inherit characteristics. Note that the default coordinates of your canvas are on ‘npc’ (native parent coordinates) units, meaning a 1 × 1 area with origin [0,0] in the lower left.</p>
<p>{gex} probably doesn’t use {grid} optimally, but I’ve learnt enough for the package to work for my own needs at least.</p>
</section>
<section id="push-pops" class="level3">
<h3 class="anchored" data-anchor-id="push-pops">Push pops</h3>
<p>With {gex}, you start by running <code>open_device()</code> to call a <code>png()</code> graphics device pre-filled with the width and height of the stickers standard, plus a transparent background. The graphics device will be populated with grobs and eventually saved to file in <code>close_device()</code>.</p>
<p>Next, the <code>add_hex()</code> function. It first sets up a <code>polygonGrob</code> of hexagon vertices, which is used in two ways. First, a viewport is pushed with the grob as a <code>clip</code> mask, which means anything falling outside of the hexagon area will be deleted when this viewport is popped. This happens as the end in <code>close_device()</code>. Second, we <code>grid.draw()</code> the grob in its own viewport so its added to the canvas. Since a ‘point-up’ hexagon is slightly narrower than tall, we remove the ‘slivers’ either side of it on the x-axis by setting the enclosing viewport’s <code>xscale</code> to the maximum hexagon width. This ensures that our final PNG file goes ‘edge to edge’. The hex grobs are added with ‘native’ rather than ‘npc’ units so that they’re relative to that ‘sliverless’ viewport.</p>
<p>The <code>add_text()</code> and <code>add_image()</code> functions work in similar fashion: a viewport is opened to house a grob, which is drawn with <code>grid.text()</code> or <code>grid.raster()</code>. Crucially, we put that grob in another viewport in which we can control rotation. We nest the viewports this way so that the user controls the x and y positions or a rotated text or image relative to the hex. If we didn’t do this, the x and y positions would be relative to the element’s angle of rotation.</p>
<p>You would typically use <code>add_border()</code> at this stage, if you want to. The border is just a polygon with the same coordinates as in <code>add_hex()</code>, but with a smaller hexagon cut out of the middle. The <code>width</code> of the border is just the inverse ratio of the inner to the outer hexagon size. By placing this after the text and images, it will overlay them if they inhabit the same coordinates.</p>
<p>Finally, we <code>close_device()</code> to pop the ‘hanging’ viewports we pushed in <code>open_device()</code> at the start. This includes the one that clips out any content that falls outside the extent of the hexagon. The function also contains a call to <code>dev.off()</code> to close the device and save the image to the path specified by <code>file_path</code> in <code>open_device()</code>.</p>
</section>
</section>
<section id="further-gexplorations" class="level2">
<h2 class="anchored" data-anchor-id="further-gexplorations">Further gexplorations?</h2>
<p>As ever, I’m having a nice time. Also as ever, I may choose to continue this or stop working on it entirely. The most important thing is that {gex} will help me towards the utopia of Hex-Driven Development (HDD), the one true method of developing R packages by <em>starting</em> with the sticker.<sup>3</sup></p>

</section>


<div id="quarto-appendix" class="default"><section id="environment" class="level3 appendix"><h2 class="anchored quarto-appendix-heading">Environment</h2><div class="quarto-appendix-contents">

<details>
<summary>
Session info
</summary>
<div class="cell">
<div class="cell-output cell-output-stdout">
<pre><code>Last rendered: 2025-02-25 22:34:12 GMT</code></pre>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>R version 4.4.2 (2024-10-31)
Platform: aarch64-apple-darwin20
Running under: macOS Ventura 13.2.1

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: Europe/London
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
 [1] htmlwidgets_1.6.4 compiler_4.4.2    fastmap_1.2.0     cli_3.6.3.9000   
 [5] tools_4.4.2       htmltools_0.5.8.1 rstudioapi_0.16.0 yaml_2.3.10      
 [9] rmarkdown_2.28    knitr_1.48        jsonlite_1.8.9    xfun_0.48        
[13] digest_0.6.37     rlang_1.1.4       evaluate_1.0.1   </code></pre>
</div>
</div>
</details>


</div></section><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>There a few more packages listed in ‘Suggests’, but these are mostly used for testing or examples. A particular highlight is <a href="https://github.com/markvanderloo/tinytest">{tinytest}</a>, which I’m using for the first time and is very much in the ‘lightweight’ spirit.↩︎</p></li>
<li id="fn2"><p>{gex} has also been used in anger to create the hex for <a href="https://github.com/best-practice-and-impact/aftables">{aftables}</a>, which is the new name for {a11ytables} that you may recall <a href="https://www.rostrum.blog/index.html#category=a11ytables">from earlier posts</a>. It’s also now <a href="https://cran.r-project.org/package=aftables">on CRAN</a> (thanks Olivia!).↩︎</p></li>
<li id="fn3"><p>Could this explain why my packages don’t have millions of downloads? Am I so out of touch? <a href="https://frinkiac.com/caption/S05E20/291123">No, it’s the children who are wrong</a>.↩︎</p></li>
</ol>
</section><section class="quarto-appendix-contents"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div id="quarto-reuse" class="quarto-appendix-contents"><div>CC BY-NC-SA 4.0</div></div></section></div> ]]></description>
  <category>grid</category>
  <category>gex</category>
  <category>r</category>
  <guid>https://www.rostrum.blog/posts/2025-02-25-gex/</guid>
  <pubDate>Tue, 25 Feb 2025 00:00:00 GMT</pubDate>
  <media:content url="https://www.rostrum.blog/posts/2025-02-25-gex/resources/logo.png" medium="image" type="image/png" height="167" width="144"/>
</item>
<item>
  <title>Dependency-free hex stickers with {hexbase}</title>
  <dc:creator>Matt Dray</dc:creator>
  <link>https://www.rostrum.blog/posts/2025-01-31-hexbase/</link>
  <description><![CDATA[ 




<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2025-01-31-hexbase/resources/demo-hex.png" class="img-fluid figure-img" style="width:33.0%" alt="A bisque-coloured hexagon with a hotpink border. The R logo is above centre and tilted at a jaunty angle. The text 'example' writtenin Comic Sans and is copied and shifted to give the effect of a shadow, red on black. On the lower-right edge in blue papyrus font is the text 'visit rostrum.blog ftw'."></p>
<figcaption class="figure-caption">Graphic design is my passion.</figcaption>
</figure>
</div>
<section id="tldr" class="level2">
<h2 class="anchored" data-anchor-id="tldr">tl;dr</h2>
<p>I’ve made <a href="https://github.com/matt-dray/hexbase">{hexbase}</a>, a simple package to create hex stickers in R. The twist: it relies only on base R.</p>
<div class="tip">
<p> <b>Note</b></p>
<p>The package has been renamed to {gex} (loosely ‘grid’ + ‘hex’) and is now, ahem, ‘dependency-light’ to take advantage of other ‘gridverse’ packages (grid-graphics expansion packages made by Prof Murrell and others). The API for {gex} has also been updated relative to the version of {hexbase} shown in this post.</p>
</div>
</section>
<section id="stick-around" class="level2">
<h2 class="anchored" data-anchor-id="stick-around">Stick around</h2>
<p>Hex stickers are obviously popular in the R community <a href="http://hexb.in/">and beyond</a> for advertising your package or other projects<sup>1</sup>. So much so that there are packages to <a href="http://hexsession.liomys.mx/">organise your stickers</a>.</p>
<p>There’s some existing tools to help you make these:</p>
<ul>
<li><a href="https://github.com/GuangchuangYu/hexSticker">{hexSticker}</a> by Guangchuang Yu</li>
<li><a href="https://github.com/dmi3kno/bunny">{bunny}</a> by Dmytro Perepolkin, used in conjunction with {magick}</li>
<li>the <a href="https://github.com/ColinFay/hexmake">hexmake Shiny app</a><sup>2</sup> by Colin Fay</li>
</ul>
<p>Surprise, I’ve made another one: <a href="https://github.com/matt-dray/hexbase">{hexbase}</a>. Why? I wanted to learn about low-level plotting with <a href="https://www.stat.auckland.ac.nz/~paul/grid/grid.html">{grid}</a>, one of R’s built-in packages<sup>3</sup>. The constraint of using only {grid} in a simple, lightweight package seemed a good test (and a good wheeze).</p>
<p>It’s not perfect, but it does what I need and I learnt a lot.</p>
</section>
<section id="do-the-griddy" class="level2">
<h2 class="anchored" data-anchor-id="do-the-griddy">Do the griddy</h2>
<p>I reckon most R users make plots with <a href="https://ggplot2.tidyverse.org/">{ggplot2}</a> otherwise R’s native <code>plot()</code>ting system. I’ll bet {grid}<sup>4</sup> is used less often. It’s lower-level and you have to build everything up yourself, but it is extremely flexible.</p>
<p>To put it extremely simply, {grid} mostly works through a system of nested ‘viewports’ that you push and pop to add and arrange various graphics objects (grobs<sup>5</sup>) onto a graphics device.</p>
<p>This post isn’t a how-to for {grid} because there’s too much to cover, though I might write a separate post to explain how {hexbase} uses {grid}. I found <a href="https://www.stat.auckland.ac.nz/~paul/grid/grid.html">Prof Paul Murrell</a>’s writings really helpful, which is no surprise as Paul is {grid}’s author. See also <a href="https://bookdown.org/rdpeng/RProgDA/the-grid-package.html">Roger’s chapter</a> for an intro.</p>
<p>However, I did learn a few things I wanted to note to myself:</p>
<ul>
<li>the <code>grDevices::png()</code> function has a handy <code>bg</code> argument that you can set to <code>"transparent"</code></li>
<li>you can clip to a grob, which means you could clip out any text or image that falls outside the hexagon boundary</li>
<li>you can rotate a viewport but the x and y coordinates will be relative to the angle, so you need to put it inside another viewport that’s relative to the sticker’s coordinates</li>
<li>the hexagon is not as wide as it is tall, so the x-axis on the default ‘normalised parent coordinate’ system (where the axes are 0 to 1) has to be narrowed by setting the viewport’s <code>xscale</code> to hexagon limits, while the hexagon grob must have <code>default.units = "native"</code> so it’s placed relative to that <code>xscale</code></li>
<li>you can make the border by stacking a smaller hexagin on a bigger one, ensuring that the maximum extent of the sticker will fit inside a unit-1 square</li>
</ul>
</section>
<section id="cast-a-hex" class="level2">
<h2 class="anchored" data-anchor-id="cast-a-hex">Cast a hex</h2>
<p>If you want to give it a go, you can install {hexbase} <a href="https://github.com/matt-dray/hexbase">from GitHub</a> like:</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">install.packages</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"remotes"</span>)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># if not yet installed</span></span>
<span id="cb1-2">remotes<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">install_github</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"matt-dray/hexbase"</span>)</span></code></pre></div>
</div>
<p>At time of writing, the package is at version 0.1.0. You can install that specific version by adding <code>@v0.1.0</code> to the end of the string in <code>install_github()</code>.</p>
<p>As mentioned, the package only imports {grid} and {grDevices} from base R. Otherwise it’s BYOIAF (‘bring your own images and fonts’) by importing with packages like {png} and by installing your fonts locally. Is that cheating? Nah, the <code>Imports</code> section in <a href="https://github.com/matt-dray/hexbase/blob/main/DESCRIPTION">the <code>DESCRIPTION</code> file</a> is clean as far as I’m concerned!</p>
<p>You can then build a sticker additively with a series of {hexbase} function calls:</p>
<ol type="1">
<li><code>open_device()</code> to set up a PNG graphics device with the dimensions of <a href="https://sticker.how/#type-hexagon">the Stickers Standard</a>.</li>
<li><code>add_hex()</code> to add the hexagon and border.</li>
<li><code>add_image()</code> to place an image (run multiple times for more images).</li>
<li><code>add_text()</code> to place and style text (run multiple times for more text).</li>
<li><code>close_device()</code> to close the PNG graphics device and save to file.</li>
</ol>
<p>You can set various text and image properties like position, size, colour and angle. Note that the coordinates are relative to a unit square<sup>6</sup> with axes of 0 to 1, a lower-left origin at [0, 0] and a centre at [0.5, 0.5]. Text and images will be clipped if they exceed the boundary of the hexagon. Check the help files for all the explanation.</p>
<p>Below is an extremely basic example to demonstrate a number of features. Note how you call each function independently (i.e.&nbsp;no pipes), much like writing base plots with successive calls to <code>plot()</code>, <code>points()</code>, <code>text()</code>, etc.</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Somewhere to save it</span></span>
<span id="cb2-2">temp_path <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tempfile</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fileext =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">".png"</span>)</span>
<span id="cb2-3"></span>
<span id="cb2-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 'Bring your own image', e.g. with {png}</span></span>
<span id="cb2-5">image_path <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">system.file</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"img"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Rlogo.png"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">package =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"png"</span>)</span>
<span id="cb2-6">image_png <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> png<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">readPNG</span>(image_path)</span>
<span id="cb2-7"></span>
<span id="cb2-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Build up and write the sticker</span></span>
<span id="cb2-9">hexbase<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">open_device</span>(  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># open with sticker-standard dimensions</span></span>
<span id="cb2-10">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">file_path =</span> temp_path</span>
<span id="cb2-11">)</span>
<span id="cb2-12">hexbase<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">add_hex</span>(</span>
<span id="cb2-13">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">border_width =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.06</span>,</span>
<span id="cb2-14">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">border_col =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"hotpink3"</span>,</span>
<span id="cb2-15">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">bg_col =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bisque"</span></span>
<span id="cb2-16">)</span>
<span id="cb2-17">hexbase<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">add_image</span>(</span>
<span id="cb2-18">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">image_object =</span> image_png,</span>
<span id="cb2-19">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">image_y =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6</span>,  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># y = 0.5 is centre</span></span>
<span id="cb2-20">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">image_angle =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span>,</span>
<span id="cb2-21">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">image_width =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># i.e. half of the npc coord system</span></span>
<span id="cb2-22">)</span>
<span id="cb2-23">hexbase<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">add_text</span>(</span>
<span id="cb2-24">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">text_string =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"example"</span>,</span>
<span id="cb2-25">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">text_x =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.495</span>,</span>
<span id="cb2-26">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">text_y =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.35</span>,</span>
<span id="cb2-27">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">text_col =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#000000"</span>,  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># named colour or hexadecimal</span></span>
<span id="cb2-28">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">text_family =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Comic Sans MS"</span>,  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># must be installed locally</span></span>
<span id="cb2-29">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">text_face =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bold"</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># or 'plain', 'italic', 'bold.italic'</span></span>
<span id="cb2-30">)</span>
<span id="cb2-31">hexbase<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">add_text</span>(  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># add multiple text/images (in a loop?)</span></span>
<span id="cb2-32">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">text_string =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"example"</span>,</span>
<span id="cb2-33">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">text_x =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.505</span>,</span>
<span id="cb2-34">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">text_y =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.34</span>,</span>
<span id="cb2-35">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">text_col =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"red"</span>,</span>
<span id="cb2-36">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">text_family =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Comic Sans MS"</span>,</span>
<span id="cb2-37">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">text_face =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bold"</span></span>
<span id="cb2-38">)</span>
<span id="cb2-39">hexbase<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">add_text</span>(</span>
<span id="cb2-40">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">text_string =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"visit rostrum.blog ftw"</span>,</span>
<span id="cb2-41">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">text_x =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.73</span>, </span>
<span id="cb2-42">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">text_y =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.18</span>,</span>
<span id="cb2-43">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">text_angle =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">30</span>,  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># rotate to match lower-right edge</span></span>
<span id="cb2-44">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">text_size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>,  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># point size</span></span>
<span id="cb2-45">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">text_col =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"blue"</span>, </span>
<span id="cb2-46">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">text_family =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Papyrus"</span></span>
<span id="cb2-47">)</span>
<span id="cb2-48">hexbase<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">close_device</span>()  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># close viewports, write file</span></span></code></pre></div>
</div>
<p>You can then open the image for inspection. A quick way to do this from within R would be like <code>system(paste("open", temp_path))</code>. Note that you can’t rely on plot-window previews (they lie) when you’re developing your sticker. You must inspect the generated PNG file instead.</p>
<p>The (stunning) hex produced by this code can be found at the top of this post. Well done for being able to read this far after you burnt your eyes by looking at it.</p>
</section>
<section id="dont-get-too-hexcited" class="level2">
<h2 class="anchored" data-anchor-id="dont-get-too-hexcited">Don’t get too hexcited</h2>
<p>I might play around with this a bit more if I have time and if I want to delve deeper into {grid}. The {hexbase} package seems to work for my use-case, but it can undoubtedly be improved. <a href="https://github.com/matt-dray/hexbase/issues">Let me know</a> if there’s anything you can unstick. I’d be hexstatic, etc.</p>

</section>


<div id="quarto-appendix" class="default"><section id="environment" class="level3 appendix"><h2 class="anchored quarto-appendix-heading">Environment</h2><div class="quarto-appendix-contents">

<details>
<summary>
Session info
</summary>
<div class="cell">
<div class="cell-output cell-output-stdout">
<pre><code>Last rendered: 2025-10-10 19:22:01 BST</code></pre>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>R version 4.5.1 (2025-06-13)
Platform: aarch64-apple-darwin20
Running under: macOS Sequoia 15.6.1

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.1

locale:
[1] en_GB.UTF-8/en_GB.UTF-8/en_GB.UTF-8/C/en_GB.UTF-8/en_GB.UTF-8

time zone: Europe/London
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
 [1] htmlwidgets_1.6.4 compiler_4.5.1    fastmap_1.2.0     cli_3.6.5        
 [5] tools_4.5.1       htmltools_0.5.8.1 yaml_2.3.10       rmarkdown_2.29   
 [9] knitr_1.50        jsonlite_2.0.0    xfun_0.52         digest_0.6.37    
[13] rlang_1.1.6       fontawesome_0.5.3 evaluate_1.0.4   </code></pre>
</div>
</div>
</details>


</div></section><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>I’ve <a href="https://github.com/matt-dray/stickers">made a few</a> myself.↩︎</p></li>
<li id="fn2"><p>There seems to be <a href="https://github.com/ColinFay/hexmake/issues/22">a problem with accessing the app</a> at time of writing.↩︎</p></li>
<li id="fn3"><p><a href="https://www.rostrum.blog/posts/2023-10-17-nhs-r-2023/">Base slaps</a>, as they say.↩︎</p></li>
<li id="fn4"><p>Though {grid} actually underpins {ggplot2}, so {ggplot2} extension-writers will be familiar.↩︎</p></li>
<li id="fn5"><p>I swear this is the name of a <em>Magic: The Gathering</em> creature or something.↩︎</p></li>
<li id="fn6"><p>Well, the initial plotting area is a unit-1 square, but the width of the hexagon is less than 1 on this scale, so two slivers are removed from the left and right of the hexagon. You can get the vertex point locations of the hexagon with the unexported <code>hexbase:::.get_hex_coords(diameter = 1)</code> function.↩︎</p></li>
</ol>
</section><section class="quarto-appendix-contents"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div id="quarto-reuse" class="quarto-appendix-contents"><div>CC BY-NC-SA 4.0</div></div></section></div> ]]></description>
  <category>grid</category>
  <category>gex</category>
  <category>r</category>
  <guid>https://www.rostrum.blog/posts/2025-01-31-hexbase/</guid>
  <pubDate>Fri, 31 Jan 2025 00:00:00 GMT</pubDate>
  <media:content url="https://www.rostrum.blog/posts/2025-01-31-hexbase/resources/demo-hex.png" medium="image" type="image/png" height="167" width="144"/>
</item>
<item>
  <title>A roguelike sprite randomiser with Shiny</title>
  <dc:creator>Matt Dray</dc:creator>
  <link>https://www.rostrum.blog/posts/2024-12-15-sprite-builder/</link>
  <description><![CDATA[ 




<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2024-12-15-sprite-builder/resources/sprites.gif" class="img-fluid figure-img" style="width:50.0%" alt="A web app titled 'roguelike sprite builder'. A 'reroll' button is pressed several times, which causes a 16-by-16 pixel character sprite to be regenerated with random hair, shirt, weapons, etc. There are additional buttons to download the image. The art is attributed to Kenney."></p>
</figure>
</div>
<section id="tldr" class="level2">
<h2 class="anchored" data-anchor-id="tldr">tl;dr</h2>
<p>I made <a href="https://matt-dray.github.io/roguelike-sprite-builder/">a quick Shiny app</a> that generates randomised characters for your next roguelike game, made with parts designed by <a href="https://www.kenney.nl/">Kenney</a>.</p>
</section>
<section id="standing-on-the-shoulders-of-kenney" class="level2">
<h2 class="anchored" data-anchor-id="standing-on-the-shoulders-of-kenney">Standing on the shoulders of Kenney</h2>
<p><a href="https://www.kenney.nl/">Kenney</a> makes free (<a href="http://creativecommons.org/publicdomain/zero/1.0/">CC0</a>) assets for videogames, like <a href="https://www.kenney.nl/assets?search=tiny&amp;sort=update">the 16×16-pixel sprites</a> I used in <a href="https://www.rostrum.blog/posts/2024-07-14-tilebased/">a previous post</a>, where you could move a little character move around a procedural forest glade while being chased by a mouse.</p>
<p>Kenney also has <a href="https://kenney.nl/assets/roguelike-characters">a roguelike characters pack</a> where you can create your own characters by mixing and matching bodies, clothes and weapons. I’ve had a stab at making <a href="https://github.com/matt-dray/r.oguelike">a toy roguelike in R</a> before, but the ‘art’ and interface were purely composed of text.</p>
<p>With a long-term goal of creating a ‘real’ <a href="https://en.wikipedia.org/wiki/Roguelike">roguelike</a> game (<a href="https://www.rostrum.blog/posts/2023-04-02-splendid-r-games/">maybe in R?</a>), I made a small app to combine sprite parts randomly to create new characters. It’s a pretty simple implementation for now, but I’m recording its current state in case I never come back to it.</p>
</section>
<section id="an-applike" class="level2">
<h2 class="anchored" data-anchor-id="an-applike">An ‘applike’</h2>
<p>The app is made with Shiny, deployed for the browser with <a href="https://posit-dev.github.io/r-shinylive/">{shinylive}</a> and served with GitHub Pages. The source for the app is <a href="https://github.com/matt-dray/roguelike-sprite-builder/">on GitHub</a>. I’ve embedded it below, but you can also visit it in <a href="https://matt-dray.github.io/roguelike-sprite-builder/">a standalone window</a>. It might take a moment to load.</p>
<iframe width="800" height="550" src="https://matt-dray.github.io/roguelike-sprite-builder/" title="roguelike-sprite-builder">
</iframe>
<p>Hit the reroll button to randomise the sprite parts. You can also download as PNG at 16×16 and 1024×1024 pixels or as an R-specific RDS file containing a nativeRaster representation of the sprite.</p>
</section>
<section id="going-rogue" class="level2">
<h2 class="anchored" data-anchor-id="going-rogue">Going rogue</h2>
<p>The basic approach was to:</p>
<ol type="1">
<li><a href="https://github.com/matt-dray/roguelike-sprite-builder/blob/main/data-raw/extract.R">‘Cut out’ and save each individual sprite part</a> from a single ‘spritesheet’.</li>
<li>Select sprite parts at random given some weighting (bodies are always selected, but weapons aren’t).</li>
<li>Store the part names in a <code>reactiveValues()</code> object so they can be called by the draw method (to display the sprite onscreen) and via the download options.</li>
<li>Use <code>image_mosaic()</code> from <a href="https://docs.ropensci.org/magick/articles/intro.html#layers">the {magick package}</a> to print sprite parts on top of each other and store this as a nativeRaster object.</li>
<li>Read the nativeRaster and use {grid} functions to draw it.</li>
</ol>
<p>Why not just draw the <code>image_mosaic()</code> object? Two reasons: I specifically want to download the nativeRaster version of the sprite (for later use in R) and because nativeRaster can then be used to produce an image at any size. The nativeRaster object is just a matrix of colour information, where each cell is a pixel of the image. That means you can re-plot it any size you want.</p>
<p>Of course, I could use <a href="https://coolbutuseless.github.io/package/nara/index.html">{nara} by Mike</a> (coolbutuseless) to ‘blit’ sprite parts to a single object, but I had some trouble using the package in a {shinylive} context as its not on CRAN and the GitHub repo <a href="https://github.com/matt-dray/roguelike-sprite-builder/issues/15">doesn’t have any releases with the necessary WebAssembly binaries</a> (yet?).</p>
<div class="tip">
<p> <b>Note</b></p>
<p>Mike points out that <a href="https://coolbutuseless.r-universe.dev/nara">{nara} is on R-universe</a>, which <a href="https://ropensci.org/blog/2023/11/17/runiverse-wasm/">builds WASM binaries for use by WebR applications</a> including {shinylive}. Totally forgot about this! Will give it a go.</p>
</div>
</section>
<section id="expanding-the-inventory" class="level2">
<h2 class="anchored" data-anchor-id="expanding-the-inventory">Expanding the inventory</h2>
<p>As ever, there were some learning points, like how:</p>
<ul>
<li>I used <code>ignoreNULL = FALSE</code> in <code>shiny::bindEvent()</code> on the step that chooses sprite parts, so that a sprite is created on startup without the need for the user to click the reroll button</li>
<li>there’s <a href="https://github.com/posit-dev/r-shinylive/tree/actions-v1/examples">a {shinylive} deployment GitHub Action</a> that can save the step of having to <code>shinylive::export()</code> your app whenever you make changes</li>
<li>the Chrome browser appears to have a problem downloading the files when the buttons are clicked, so there is a workaround at time of writing that’s used in <a href="https://shinylive.io/r/examples/#r-file-download">one of the examples</a> on the {shinylive} website</li>
</ul>
<p>Even something this simple can be a learning experience.</p>
</section>
<section id="permadeath" class="level2">
<h2 class="anchored" data-anchor-id="permadeath">Permadeath</h2>
<p>Obviously this app is more of a proof-of-concept (as ever on this blog). It’s not particularly special or all that useful outside of my needs.</p>
<p>Next steps might involve letting the user <a href="https://github.com/matt-dray/roguelike-sprite-builder/issues/1">select parts manually</a>, <a href="https://github.com/matt-dray/roguelike-sprite-builder/issues/5">styling and theming the app</a> and giving the sprites <a href="https://github.com/matt-dray/roguelike-sprite-builder/issues/9">a transparent background</a>.</p>
<p>You can add a pull request <a href="https://github.com/matt-dray/roguelike-sprite-builder">to the GitHub repo</a> if this interests you. But beware, adventurer, I can’t promise my code is less labyrinthine than a classic Rogue dungeon.</p>

</section>

<div id="quarto-appendix" class="default"><section id="environment" class="level3 appendix"><h2 class="anchored quarto-appendix-heading">Environment</h2><div class="quarto-appendix-contents">

<details>
<summary>
Session info
</summary>
<div class="cell">
<div class="cell-output cell-output-stdout">
<pre><code>Last rendered: 2024-12-24 08:57:34 GMT</code></pre>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>R version 4.4.2 (2024-10-31)
Platform: aarch64-apple-darwin20
Running under: macOS Ventura 13.2.1

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: Europe/London
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
 [1] htmlwidgets_1.6.4 compiler_4.4.2    fastmap_1.2.0     cli_3.6.3.9000   
 [5] tools_4.4.2       htmltools_0.5.8.1 rstudioapi_0.16.0 yaml_2.3.10      
 [9] rmarkdown_2.28    knitr_1.48        jsonlite_1.8.9    xfun_0.48        
[13] digest_0.6.37     rlang_1.1.4       fontawesome_0.5.2 evaluate_1.0.1   </code></pre>
</div>
</div>
</details>


</div></section><section class="quarto-appendix-contents"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div id="quarto-reuse" class="quarto-appendix-contents"><div>CC BY-NC-SA 4.0</div></div></section></div> ]]></description>
  <category>kenney</category>
  <category>r</category>
  <category>shiny</category>
  <category>shinylive</category>
  <guid>https://www.rostrum.blog/posts/2024-12-15-sprite-builder/</guid>
  <pubDate>Sun, 15 Dec 2024 00:00:00 GMT</pubDate>
  <media:content url="https://www.rostrum.blog/posts/2024-12-15-sprite-builder/resources/sprites.gif" medium="image" type="image/gif"/>
</item>
<item>
  <title>Deploy a Shiny app to itch.io</title>
  <dc:creator>Matt Dray</dc:creator>
  <link>https://www.rostrum.blog/posts/2024-11-16-itch/</link>
  <description><![CDATA[ 




<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2024-11-16-itch/resources/itch.png" class="img-fluid figure-img" style="width:100.0%" alt="The indie game distribution platform, itch.io, showing a pixel-editor tool made with R Shiny."></p>
<figcaption class="figure-caption">Graphic design is my passion.</figcaption>
</figure>
</div>
<section id="tldr" class="level2">
<h2 class="anchored" data-anchor-id="tldr">tl;dr</h2>
<p>As a test, I uploaded an R Shiny app to <a href="https://itch.io/">itch.io</a>, which is a platform for sharing indie games. <a href="https://mattdray.itch.io/little-pixel-fun-zone">It worked</a>.</p>
</section>
<section id="scratching-an-itch.io" class="level2">
<h2 class="anchored" data-anchor-id="scratching-an-itch.io">Scratching an itch.io</h2>
<p>Where can you host your Shiny app for free? Typical options are services like <a href="https://www.shinyapps.io/">shinyapps.io</a> or <a href="https://connect.posit.cloud/">Connect Cloud</a> by Posit.</p>
<p>I have another idea: <a href="https://itch.io/">itch.io</a> is a web-based platform known for hosting indie videogames, assets and other miscellaneous game-adjacent tools<sup>1</sup>. Developers can upload file bundles for users to download, or they can serve their HTML apps directly on the site.</p>
<p>Since <a href="http://localhost:6375/posts/2023-04-02-splendid-r-games/">R is a game engine</a><sup>2</sup>, why aren’t R users uploading their apps to itch.io?</p>
<p>Well, part of the problem is that you didn’t know this was possible. And also because it’s only recently that you can use <a href="https://shiny.posit.co/">{shinylive}</a> to convert your app to be served entirely in the browser with no need for a server<sup>3</sup>.</p>
<p><code>shinylive::export()</code> generates a folder containing your app and all the assets needed for deployment. You just need to zip it up and upload it to itch.io.</p>
<p>Which is <a href="https://mattdray.itch.io/little-pixel-fun-zone">exactly what I did</a> with <a href="https://github.com/matt-dray/little-pixel-fun-zone/">a toy pixel editor</a> that I made and <a href="https://www.rostrum.blog/posts/2024-09-15-shiny-pixel/">wrote about</a> recently.</p>
<p>This requires an itch.io account. When you upload the app you fill in a short pro forma, making sure to set the ‘kind of project’ to HTML and to tick a checkbox to say ‘this game will be played in the browser’. For my example, I also set the ‘classification’ to ‘tools’.</p>
<p>I can’t really think of a catch, but there are limitations around size and complexity: the upload must be under 1 GB and I think there’s a limit of 1000 files (this tripped me up when I tried once before).</p>
</section>
<section id="scratch-my-back" class="level2">
<h2 class="anchored" data-anchor-id="scratch-my-back">Scratch my back</h2>
<p>I did this for the lols, of course, but I do think itch.io is a viable option for certain types of app. It’s a platform for people to share their creativity and no-one cares what language was used if the outcome is fun or useful.</p>
<p>And if that wasn’t enough, you can also use itch.io as a payment platform. Users can leave you a little tip for your efforts.</p>
<p>So, what’s stopping you from becoming the first professional indie R game developer?</p>

</section>


<div id="quarto-appendix" class="default"><section id="environment" class="level3 appendix"><h2 class="anchored quarto-appendix-heading">Environment</h2><div class="quarto-appendix-contents">

<details>
<summary>
Session info
</summary>
<div class="cell">
<div class="cell-output cell-output-stdout">
<pre><code>Last rendered: 2024-11-16 20:28:05 GMT</code></pre>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>R version 4.4.0 (2024-04-24)
Platform: aarch64-apple-darwin20
Running under: macOS Ventura 13.2.1

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: Europe/London
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
 [1] htmlwidgets_1.6.4 compiler_4.4.0    fastmap_1.2.0     cli_3.6.3.9000   
 [5] tools_4.4.0       htmltools_0.5.8.1 rstudioapi_0.16.0 yaml_2.3.10      
 [9] rmarkdown_2.28    knitr_1.48        jsonlite_1.8.9    xfun_0.48        
[13] digest_0.6.37     rlang_1.1.4       evaluate_1.0.1   </code></pre>
</div>
</div>
</details>


</div></section><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>Kinda like <a href="https://store.steampowered.com/">Steam</a>, but with a greater focus on independent developers and the rest of us.↩︎</p></li>
<li id="fn2"><p>Are you sick of me saying this yet?↩︎</p></li>
<li id="fn3"><p>Which makes it possible to deploy apps for free via static-site services like <a href="https://pages.github.com/">GitHub Pages</a> or <a href="https://www.netlify.com/">Netlify</a>.↩︎</p></li>
</ol>
</section><section class="quarto-appendix-contents"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div id="quarto-reuse" class="quarto-appendix-contents"><div>CC BY-NC-SA 4.0</div></div></section></div> ]]></description>
  <category>ich.io</category>
  <category>r</category>
  <category>shiny</category>
  <category>shinylive</category>
  <guid>https://www.rostrum.blog/posts/2024-11-16-itch/</guid>
  <pubDate>Sat, 16 Nov 2024 00:00:00 GMT</pubDate>
  <media:content url="https://www.rostrum.blog/posts/2024-11-16-itch/resources/itch.png" medium="image" type="image/png" height="90" width="144"/>
</item>
<item>
  <title>{sortable} card games in {shiny}</title>
  <dc:creator>Matt Dray</dc:creator>
  <link>https://www.rostrum.blog/posts/2024-10-25-not-balatro/</link>
  <description><![CDATA[ 




<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2024-10-25-not-balatro/resources/cards.gif" class="img-fluid figure-img" style="width:100.0%" alt="Two rows of playing cards labelled 'pool' and 'hand'. Buttons are pressed to order the cards by suit and then by rank. Cards are dragged from the pool to the hand. An ace is dragged and the text updates from 'no hand' to 'high card'. Another ace and it changes to 'a pair'. A 'draw' button is pressed and two new cards are added to the pool. The pool is ordered again by rank. Then two queens are added to the hand and the text changes to 'two pair'."></p>
<figcaption class="figure-caption">You sunk my battleship!</figcaption>
</figure>
</div>
<section id="tldr" class="level2">
<h2 class="anchored" data-anchor-id="tldr">tl;dr</h2>
<p>Use <a href="https://rstudio.github.io/sortable/">{sortable}</a> in a <a href="https://shiny.posit.co/">{shiny}</a> app to build card games, maybe? I mocked up a demo and <a href="https://matt-dray.github.io/not-balatro/">made it available on the web</a> with <a href="https://posit-dev.github.io/r-shinylive/">{shinylive}</a>.</p>
</section>
<section id="balatro" class="level2">
<h2 class="anchored" data-anchor-id="balatro">Balatro</h2>
<p>LocalThunk<sup>1</sup>—the anonymous creator of <a href="https://en.wikipedia.org/wiki/Balatro_(video_game)"><em>Balatro</em></a>, the hit poker-inspired roguelike videogame<sup>2</sup>—revealed recently that 11,000 <em>years</em> of gameplay have been sunk into the game.</p>
<p>The average productivity of the universe has continued to drop now that the game is available on mobile. I am one of the suckers that made the purchase.</p>
<p>As a result, I wondered how viable a card game in R might be. Not just a simple blackjack simulator in the console, but a drag-and-droppable interface in the browser.</p>
<p>It must be possible, since <a href="https://www.rostrum.blog/posts/2023-04-02-splendid-r-games/">R is a game engine (fight me)</a>.</p>
</section>
<section id="badlatro" class="level2">
<h2 class="anchored" data-anchor-id="badlatro">Badlatro</h2>
<p>And so I began experimenting.</p>
<p>I wanted to use {shiny} and existing R packages and to avoid writing much JavaScript and CSS. After all, it’s hard to fit in this frivolity after I’ve finished grinding all day at work and then grinding all night on <em>Balatro</em>.</p>
<p>Luckily, the <a href="https://rstudio.github.io/sortable/">{sortable}</a> package does most of the hard work. The package from Andrie, Barrett and Kent wraps the <a href="https://sortablejs.github.io/Sortable/">SortableJS</a> library and lets you drag list elements around, including into other lists.</p>
<p>Typically, you would use {sortable} to drag little boxes of text. The order can then be read to record preference or perhaps as part of a quiz<sup>3</sup>. But now I’ve hijacked it to show little images of cards that you can drag between a ‘pool’ (randomly drawn cards) and a ‘hand’ (cards selected by the user).</p>
</section>
<section id="app" class="level2">
<h2 class="anchored" data-anchor-id="app">App</h2>
<p>You can check out <a href="https://github.com/matt-dray/not-balatro">the source on GitHub</a> and <a href="https://matt-dray.github.io/not-balatro/">find the app deployed online</a>, thanks to {shinylive} and GitHub Pages. I’ve embedded it below as well<sup>4</sup> (it’ll take a moment to load). I recommend using this on desktop for now, rather than mobile.</p>
<iframe width="800" height="650" src="https://matt-dray.github.io/not-balatro/" title="not-balatro">
</iframe>
</section>
<section id="features" class="level2">
<h2 class="anchored" data-anchor-id="features">Features</h2>
<p>So far it doesn’t do much, but it does enough to prove the concept. Here’s some notes on a few of the technicals.</p>
<section id="counting-cards" class="level3">
<h3 class="anchored" data-anchor-id="counting-cards">Counting cards</h3>
<p>I iterated over all suits and ranks with <a href="https://docs.ropensci.org/magick/">{magick}</a> to apply text and symbols to a blank PNG image. I then read the 52 cards into a <code>shiny::tagList()</code> and passed that to the <code>label</code> argument of <code>sortable::list()</code>. From there, the images could be matched to sampled card names and displayed in the app. This is slightly off-label (ha) compared to ‘normal’ use of the package, which generally involves providing text rather than images.</p>
</section>
<section id="drag-til-you-drop" class="level3">
<h3 class="anchored" data-anchor-id="drag-til-you-drop">Drag ’til you drop</h3>
<p>{sortable} has a nice feature where you can drag list elements between lists within ‘buckets’. I opted instead to use two <code>rank_list()</code>s that shared a <code>group</code> name in their <code>options</code> argument. This meant I could restrict the list size (8 cards in the pool, 5 in hand), thanks to some JavaScript from Barrett in a response to <a href="https://forum.posit.co/t/shiny-sortable-how-to-limit-number-of-items-that-can-be-dropped/69233/2">a Posit community post</a>.</p>
</section>
<section id="a-bit-flushed" class="level3">
<h3 class="anchored" data-anchor-id="a-bit-flushed">A bit flushed</h3>
<p>Detecting poker hands is tricky because you want to recognise that two jacks and three kings is a full house, not a pair of jacks or a three-of-a-kind, for example. I also made things harder by wanting to evaluate poker hands on the fly rather than when the user submits the hand.</p>
<p>I read about lots of very clever algorithms to do this. But I basically just brute-forced it, lol. It’s basically <code>if</code> statements that evaluate and return strongest hands first. So the function will assess a royal flush (i.e.&nbsp;ace, king, queen, jack and 10 of the same suit) and confirm it before it checks for a more basic straight (consecutive ranks of any suit) or a flush (any suit of non-consecutive rank).</p>
</section>
<section id="hit-the-deck" class="level3">
<h3 class="anchored" data-anchor-id="hit-the-deck">Hit the deck</h3>
<p>The deck is stored in ‘dynamic memory’ as a <code>reactieValues()</code> element. When cards are drawn, they’re removed from the deck and can’t be redrawn. This meant I could add a ‘draw’ button that adds previously unseen cards into the pool’s empty slots. Of course, we can take the length of the deck and present this back to the user as well.</p>
</section>
</section>
<section id="improvements" class="level2">
<h2 class="anchored" data-anchor-id="improvements">Improvements</h2>
<p>There’s many features that would improve the demo app. Below are some examples: one seems like it’s not possible, one won’t add that much to what I’ve learnt so far, one is obvious and one is just… bad programming.</p>
<section id="stop-n-swop" class="level3">
<h3 class="anchored" data-anchor-id="stop-n-swop">Stop ‘n’ swop</h3>
<p>It’s satisfying to pick up a card, drag it to a new position and drop it between the cards at that location. This action feels how it would when sorting real cards in your real hand. I’d like the option to be able to directly <em>swap</em> two cards between the pool and hand, though. This would be useful if you change your mind about the hand you’re building as new cards are drawn. As far as I can tell, SortableJS allows <code>swap = TRUE</code> within each rank list and if they are part of the same <code>group</code> then you can swap between them. I haven’t yet found a way to allow swapping to happen <em>only</em> when moving cards between lists, however.</p>
</section>
<section id="diss-card" class="level3">
<h3 class="anchored" data-anchor-id="diss-card">Diss card</h3>
<p>The awkward thing about having a ‘pool’ and a ‘hand’ is that there’s no natural way to discard. I haven’t properly explored solutions for earmarking cards to toss; as it stands, you have to drag the card somewhere for an action to be performed on it. The answer might be to drag these cards to a third area, where they’re binned. Compare this to a game like <em>Balatro</em>, where you first select cards in your hand and then click a button to either play or discard them.</p>
</section>
<section id="u-and-i" class="level3">
<h3 class="anchored" data-anchor-id="u-and-i">U and I</h3>
<p>Of course, as a proof of concept, I haven’t paid much attention to the user interface and experience. You can imagine making prettier cards, a nice green baise to mimic a poker table and even some animations to show cards being dealt. For now, I think the janky plainness is a good indicator that it’s just a demo.</p>
</section>
<section id="card-trick" class="level3">
<h3 class="anchored" data-anchor-id="card-trick">Card trick</h3>
<p>Oh yeah, haha, sometimes the cards disappear. It can happen when you drag from the pool to the hand, then back again and hit the ‘rank’ or ‘suit’ sorting buttons. We know that the card returns correctly to the pool by checking <code>input$pool_list</code> in the app, but there’s some issue with displaying the image, maybe? This is the only true bug that needs fixing, but I’d rather just write this blog post and deal with it later<sup>5</sup>.</p>
</section>
</section>
<section id="a-gamble" class="level2">
<h2 class="anchored" data-anchor-id="a-gamble">A gamble</h2>
<p>Obviously this isn’t yet a ‘game’. There’s a few ways this could go:</p>
<ul>
<li>remake <em>Balatro</em> in R (absolutely no chance)</li>
<li>make a small simulator of something like <a href="https://en.wikipedia.org/wiki/Video_poker">video poker</a> (too basic?)</li>
<li>create a new, simple game to chase high scores with combos of poker hands, discards and chip ‘bets’, perhaps incorporating some <em>Balatro</em>-inspired joker-activated bonuses or wild cards <a href="https://en.wikipedia.org/wiki/Dungeons_%26_Degenerate_Gamblers">like in <em>Dungeons and Degenerate Gamblers</em></a> (might actually be fun)</li>
<li>do nothing (appealing option)</li>
</ul>
<p>Feel free to chip (HA HA HA) in <a href="https://github.com/matt-dray/not-balatro/issues">your ideas</a>.</p>

</section>


<div id="quarto-appendix" class="default"><section id="environment" class="level3 appendix"><h2 class="anchored quarto-appendix-heading">Environment</h2><div class="quarto-appendix-contents">

<details>
<summary>
Session info
</summary>
<div class="cell">
<div class="cell-output cell-output-stdout">
<pre><code>Last rendered: 2024-10-26 21:53:18 BST</code></pre>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>R version 4.4.0 (2024-04-24)
Platform: aarch64-apple-darwin20
Running under: macOS Ventura 13.2.1

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: Europe/London
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
 [1] htmlwidgets_1.6.4 compiler_4.4.0    fastmap_1.2.0     cli_3.6.3.9000   
 [5] tools_4.4.0       htmltools_0.5.8.1 rstudioapi_0.16.0 yaml_2.3.10      
 [9] rmarkdown_2.28    knitr_1.48        jsonlite_1.8.9    xfun_0.48        
[13] digest_0.6.37     rlang_1.1.4       evaluate_1.0.1   </code></pre>
</div>
</div>
</details>


</div></section><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>Apparently LocalThunk’s name <a href="https://youtu.be/iaIu0PT8n_o?t=47">was inspired partly by R</a>.↩︎</p></li>
<li id="fn2"><p>If you’ve been here before, you know I’ve toyed around with roguelikes in R with the concept packages <a href="https://github.com/matt-dray/r.oguelike/">{r.oguelike}</a> and <a href="https://github.com/matt-dray/tilebased">{tilebased}</a>.↩︎</p></li>
<li id="fn3"><p>For example, Rasmus has done this recently in the <a href="https://www.sumsar.net/climate-impact-sorting-challenge/">climate impact sorting challenge</a>. I scored 1 on my first go, ha.↩︎</p></li>
<li id="fn4"><p>Psst, click the joker card at the bottom of the app to see ‘dev mode’ (you’ll have to scroll down in the embedded app to see it).↩︎</p></li>
<li id="fn5"><p>This is basically the entire modus operandi of this blog, lol.↩︎</p></li>
</ol>
</section><section class="quarto-appendix-contents"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div id="quarto-reuse" class="quarto-appendix-contents"><div>CC BY-NC-SA 4.0</div></div></section></div> ]]></description>
  <category>games</category>
  <category>r</category>
  <category>shiny</category>
  <category>shinylive</category>
  <category>sortable</category>
  <guid>https://www.rostrum.blog/posts/2024-10-25-not-balatro/</guid>
  <pubDate>Thu, 24 Oct 2024 23:00:00 GMT</pubDate>
  <media:content url="https://www.rostrum.blog/posts/2024-10-25-not-balatro/resources/cards.gif" medium="image" type="image/gif"/>
</item>
<item>
  <title>Little pixel fun zone</title>
  <dc:creator>Matt Dray</dc:creator>
  <link>https://www.rostrum.blog/posts/2024-09-15-shiny-pixel/</link>
  <description><![CDATA[ 




<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2024-09-15-shiny-pixel/resources/SANIC.png" class="img-fluid figure-img" style="width:50.0%" data-fig.alt="Screenshot of a web app called 'little pixel fun zone'. in the centre is a child's drawing of the fast blue hedgehog rendered in blocky square pixels. Under the image are some buttons to select a colour, undo, fill and downloade outputs."></p>
<figcaption class="figure-caption">Fast blue hedgehog man in Little Pixel Fun Zone, Act 1 (original art by the author).</figcaption>
</figure>
</div>
<section id="tldr" class="level2">
<h2 class="anchored" data-anchor-id="tldr">tl;dr</h2>
<p>I made a <a href="https://matt-dray.github.io/little-pixel-fun-zone/">simple toy pixel-editor for the browser</a>, which helped me learn about reacting to user clicks and implementing undo/redo in {shiny}.</p>
</section>
<section id="just-browsing" class="level2">
<h2 class="anchored" data-anchor-id="just-browsing">Just browsing</h2>
<p>My <a href="https://github.com/matt-dray/pixeltrix">{pixeltrix} package</a> is a local pixel editor you can run from the R console<sup>1</sup>. It’s intentionally simple. You start a graphics device and click the squares (‘pixels’), cycling through your provided colour palette. You’re returned a little pixeltrix-class matrix as a memento, which you can re-edit later.</p>
<p>It might be nice to have a browser-based version, but I’ve never really had the time and figured it would be too fiddly. Well guess what, I had some time and it wasn’t that fiddly.</p>
<p>I’ve been learning a bit more <a href="https://shiny.posit.co/">{shiny}</a> of late. This mini project was a good way to learn a few things that might come in handy later. In particular, how to:</p>
<ol type="1">
<li>Read a clicked point on a plot and react to it.</li>
<li>Implement a basic undo/redo feature.</li>
</ol>
<p>This is a reminder to myself of how to do these things.</p>
</section>
<section id="app" class="level2">
<h2 class="anchored" data-anchor-id="app">App</h2>
<p>The app is available <a href="https://matt-dray.github.io/little-pixel-fun-zone/">via {shinylive} deployed via GitHub Pages</a> and <a href="https://github.com/matt-dray/little-pixel-fun-zone">the source is on GitHub</a> (v0.3.0 at time of writing).</p>
<p>There’s (intentionally) only a few features. You can:</p>
<ul>
<li>click a pixel to toggle it on/off</li>
<li>change the colour, thanks to <a href="https://daattali.com/shiny/colourInput/">Dean Attali’s {colourpicker}</a></li>
<li>undo/redo (with a very short ‘memory’)</li>
<li>flood fill</li>
<li>have an assistant ✨AI✨ draw a picture for you<sup>2</sup></li>
<li>download a matrix representation of your treasured art (pixeltrix-class, for my own needs) as an RDS file</li>
<li>download a png copy of your treasured art</li>
</ul>
<p>Wow!</p>
<p>Here’s an embedded version of the app (may take a moment to load):</p>
<iframe width="800" height="585" src="https://matt-dray.github.io/little-pixel-fun-zone/" title="roguelike-sprite-builder">
</iframe>
<div class="tip">
<p> <b>Note</b></p>
<p>To test the limits of {shinylive}, I also managed to <a href="https://mattdray.itch.io/little-pixel-fun-zone">deploy the app to itch.io</a>, the indie-game dispensation platform! You can read <a href="https://www.rostrum.blog/posts/2024-11-16-itch/">a separate blog post</a> about that.</p>
</div>
</section>
<section id="tricks" class="level2">
<h2 class="anchored" data-anchor-id="tricks">Tricks</h2>
<p>I mentioned there were two things I learnt in particular: handling click-reacts and undo/redo.</p>
<section id="click-react" class="level3">
<h3 class="anchored" data-anchor-id="click-react">Click-react</h3>
<p>Thanks to the <code>locator()</code> function (witchcraft), {pixeltrix} reads the coordinates of a user’s click on the plotting device. The returned values can be used to identify the nearest ‘pixel’ clicked.</p>
<p>With {shiny} we can generate a plot output and then capture an input from it when clicked<sup>3</sup> using the handy <code>click</code> argument to <code>plotOutput()</code>.</p>
<p>In the example but of UI code below, we output a plot with ID <code>pixel_grid</code> that was generated in the server. Then, a <code>click</code> on that plot in the app would be registered as the ID <code>clicked_point</code> for retrieval in the server. In my case, I took the x and y elements of that object and matched them to the nearest pixel (using self-plagiarised {pixeltrix} code).</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1">shiny<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plotOutput</span>(</span>
<span id="cb1-2">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">outputId =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pixel_grid"</span>,  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># the plot of the pixel grid</span></span>
<span id="cb1-3">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">click =</span> shiny<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">clickOpts</span>(</span>
<span id="cb1-4">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">id =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"clicked_point"</span>,  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># to retrieve as input$clicked_point in server</span></span>
<span id="cb1-5">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">clip =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># restrict to plot bounds?</span></span>
<span id="cb1-6">  )</span>
<span id="cb1-7">)</span></code></pre></div>
</div>
<p>I also made the click trigger <code>observeEvent()</code> to refresh the plot given the user’s selection. So, the user clicks the plot and the nearest pixel is toggled.</p>
</section>
<section id="undoredo" class="level3">
<h3 class="anchored" data-anchor-id="undoredo">Undo/redo</h3>
<p>If you misclick a pixel in your picture you could correct it by changing the colour in the colourpicker and re-clicking the errant point. But that takes, ooh, perhaps a couple of seconds. Instead, it seems conceptually more fun to have an undo button. And imagine how exhilarating it would be if that button toggled to redo after it was clicked? I know, calm down.</p>
<p>So, there’s probably lots of ways to do this, but I settled on bookkeeping with ‘memory slots’ in a sort-of cyber-goldfish brain, using <code>reactiveValues()</code>.</p>
<p>The image is stored as a matrix object, with each cell representing the current state of the corresponding pixel when drawn. The current state of the matrix is stored in the first (of two) memory slots, while the second slot is the previous matrix state. Each time you update the drawing, slot 1 overwrites slot 2 and the new matrix takes its place in slot 1.</p>
<p>So, for example, the app starts up with a ‘blank’ grid:</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1">pixel_matrices <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> shiny<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">reactiveValues</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">slot1 =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">.gen_grid</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#E5E5E5"</span>))</span></code></pre></div>
</div>
<p>And then <code>observeEvent()</code> handles things when a click occurs. The click coordinates and currently-selected colour are used to toggle or adjust a pixel in the underlying matrix. The resulting, new, version of the matrix is added to slot 1 and the prior version slips into slot 2.</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb3-1">shiny<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">observeEvent</span>(input<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>clicked_point, {  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># trigger on click</span></span>
<span id="cb3-2">  </span>
<span id="cb3-3">  matrix_updated <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">.gen_updated_pixel_matrix</span>(  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># update current drawing</span></span>
<span id="cb3-4">    shiny<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">isolate</span>(pixel_matrices[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"slot1"</span>]]),  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># the current drawing</span></span>
<span id="cb3-5">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">pixel_coords</span>(),  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># location of clicked point to update</span></span>
<span id="cb3-6">    input<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>selected_colour  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># current user-selected colour</span></span>
<span id="cb3-7">  )</span>
<span id="cb3-8">  </span>
<span id="cb3-9">  pixel_matrices[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"slot2"</span>]] <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> pixel_matrices[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"slot1"</span>]]  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># current is now old</span></span>
<span id="cb3-10">  pixel_matrices[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"slot1"</span>]] <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> matrix_updated  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># new is now current</span></span>
<span id="cb3-11">  </span>
<span id="cb3-12">})</span></code></pre></div>
</div>
<p>Could the so-called cyber-goldfish brain be larger, with more brain slots? No, because then it would be a cyber-elephant and I think that mental image is less funny. But yes, you could probably create slots on the fly and have the ability to undo much further back in history.</p>
<p>So, to perform a do-over, we just need to access the older slot with a button click. The icon for the button is stored in a <code>reactiveVal()</code> that starts as ‘undo’ arrow. When clicked, we flip the memory slots to put the older version in slot 1 and also update the icon to be a ‘redo’ arrow. Another click of the button and the situation will revert again<sup>4</sup>. Voila: time travel.</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb4-1">button_icon <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> shiny<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">reactiveVal</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rotate-left"</span>)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># starting condition</span></span>
<span id="cb4-2"></span>
<span id="cb4-3">shiny<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">observeEvent</span>(input<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>button_undo, {  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># trigger on click</span></span>
<span id="cb4-4">    </span>
<span id="cb4-5">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Switch 'memory' slots</span></span>
<span id="cb4-6">    slot1 <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> pixel_matrices[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"slot1"</span>]]</span>
<span id="cb4-7">    slot2 <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> pixel_matrices[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"slot2"</span>]]</span>
<span id="cb4-8">    pixel_matrices[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"slot2"</span>]] <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> slot1</span>
<span id="cb4-9">    pixel_matrices[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"slot1"</span>]] <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> slot2</span>
<span id="cb4-10">    </span>
<span id="cb4-11">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Invert undo/redo icon</span></span>
<span id="cb4-12">    </span>
<span id="cb4-13">    current_icon <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">undo_button_icon</span>()</span>
<span id="cb4-14">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (current_icon <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rotate-left"</span>) <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">undo_button_icon</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rotate-right"</span>)</span>
<span id="cb4-15">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (current_icon <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rotate-right"</span>) <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">undo_button_icon</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rotate-left"</span>)</span>
<span id="cb4-16">    </span>
<span id="cb4-17">    shiny<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">updateActionButton</span>(</span>
<span id="cb4-18">      <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">inputId =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"button_undo"</span>,</span>
<span id="cb4-19">      <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">icon =</span> shiny<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">icon</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">button_icon</span>())</span>
<span id="cb4-20">    )</span>
<span id="cb4-21">    </span>
<span id="cb4-22">  })</span></code></pre></div>
</div>
<p>Inelegant, perhaps, but manageable<sup>5</sup>.</p>
</section>
</section>
<section id="fiddly-is-money" class="level2">
<h2 class="anchored" data-anchor-id="fiddly-is-money">Fiddly is money</h2>
<p>There’s a number of features I could implement, like arbitrary pixel-grid sizes, uploading a matrix to continue editing it, or maybe even multi-select with click-and-drag (is this even possible?).</p>
<p>If only I could perceive that I have more time and that the fiddlyometer gauge was lower. Yes, this is current me goading future me.</p>
<p>Until then, drop bug reports and how to fix them in <a href="https://github.com/matt-dray/little-pixel-fun-zone/issues">the issues</a>.</p>
<p>Also use the app to draw a classic videogame action scene and send it to me, obviously (see inspiration at top of post).</p>

</section>


<div id="quarto-appendix" class="default"><section id="environment" class="level3 appendix"><h2 class="anchored quarto-appendix-heading">Environment</h2><div class="quarto-appendix-contents">

<details>
<summary>
Session info
</summary>
<div class="cell">
<div class="cell-output cell-output-stdout">
<pre><code>Last rendered: 2025-01-15 21:05:27 GMT</code></pre>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>R version 4.4.2 (2024-10-31)
Platform: aarch64-apple-darwin20
Running under: macOS Ventura 13.2.1

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: Europe/London
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
 [1] htmlwidgets_1.6.4 compiler_4.4.2    fastmap_1.2.0     cli_3.6.3.9000   
 [5] tools_4.4.2       htmltools_0.5.8.1 rstudioapi_0.16.0 yaml_2.3.10      
 [9] rmarkdown_2.28    knitr_1.48        jsonlite_1.8.9    xfun_0.48        
[13] digest_0.6.37     rlang_1.1.4       fontawesome_0.5.2 evaluate_1.0.1   </code></pre>
</div>
</div>
</details>


</div></section><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>You can read about {pixeltrix} in <a href="https://www.rostrum.blog/index.html#category=pixeltrix">other posts</a>.↩︎</p></li>
<li id="fn2"><p>This most often <em>looks</em> like random noise, but I’m sure that’s because of the training set or something. Maybe squint? I’ve been told that robot self-portraits sometimes appear, but I’m sure that’s just a hallucination.↩︎</p></li>
<li id="fn3"><p>Note also that you can perform actions on <code>dblclick</code>, <code>hover</code> and <code>brush</code>, not just a single click.↩︎</p></li>
<li id="fn4"><p>If you undo and then make a new click, then the undo/redo button will continue to show the ‘redo’ icon. Subtle, but to avoid this, you must reset the <code>reactiveVal()</code> to show the ‘undo’ icon if you perform any other actions after undoing.↩︎</p></li>
<li id="fn5"><p>If we messed with time travel and things didn’t go a bit <a href="https://en.wikipedia.org/wiki/Primer_(film)"><em>Primer</em></a>, then I’d say we’ve done well.↩︎</p></li>
</ol>
</section><section class="quarto-appendix-contents"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div id="quarto-reuse" class="quarto-appendix-contents"><div>CC BY-NC-SA 4.0</div></div></section></div> ]]></description>
  <category>pixeltrix</category>
  <category>r</category>
  <category>shiny</category>
  <guid>https://www.rostrum.blog/posts/2024-09-15-shiny-pixel/</guid>
  <pubDate>Sat, 14 Sep 2024 23:00:00 GMT</pubDate>
  <media:content url="https://www.rostrum.blog/posts/2024-09-15-shiny-pixel/resources/SANIC.png" medium="image" type="image/png" height="202" width="144"/>
</item>
<item>
  <title>Tile-style sprite delight</title>
  <dc:creator>Matt Dray</dc:creator>
  <link>https://www.rostrum.blog/posts/2024-07-14-tilebased/</link>
  <description><![CDATA[ 




<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2024-07-14-tilebased/resources/demo.gif" class="img-fluid figure-img" style="width:100.0%" data-fig.alt="A pixel-sprite human character is moving around on some grassy tiles in a clearing of a procedurally generated forest. A rat is chasing him down. This is happening in an R Graphics Device."></p>
<figcaption class="figure-caption">An enemy. Or a friend?</figcaption>
</figure>
</div>
<section id="tldr" class="level2">
<h2 class="anchored" data-anchor-id="tldr">tl;dr</h2>
<p><a href="https://github.com/matt-dray/tilebased">An experiment</a> with <a href="https://coolbutuseless.github.io/package/nara/index.html">graphical advancements</a> in tile-based games made for R.</p>
</section>
<section id="based-tiles" class="level2">
<h2 class="anchored" data-anchor-id="based-tiles">Based tiles</h2>
<p>You know by now that <a href="https://www.rostrum.blog/posts/2023-04-02-splendid-r-games/">R is unequivocally a game engine</a>.</p>
<p>In this vein, Mike Cheng (coolbutuseless) has been hard at work updating the <a href="https://coolbutuseless.github.io/package/nara/index.html">{nara}</a> package, which allows for <a href="https://github.com/coolbutuseless/narademos">fast rendering of pixel-based graphics</a><sup>1</sup>. This can make games easier to develop, especially in tandem with Mike’s <a href="https://coolbutuseless.github.io/package/eventloop/index.html">{eventloop}</a> package for reading continuous input from the player’s keyboard.</p>
<p>As a result, I’ve taken some of the ‘engine’ from <a href="https://github.com/matt-dray/r.oguelike">{r.oguelike}</a>—a toy dungeon-crawling game in an R package<sup>2</sup>—and made it draw graphics instead of just printing to the console<sup>3</sup>. The outcome from this test is the <a href="https://github.com/matt-dray/tilebased">demo {tilebased} package</a>.</p>
<p>It also borrows grass, tree, person and rat sprites from <a href="https://www.kenney.nl/">Kenney’s</a> free and excellent <a href="https://www.kenney.nl/assets?search=tiny&amp;sort=update">‘Tiny’ asset packs</a>. They’re 16×16 pixels, shown here at actual size:</p>
<p><img src="https://www.rostrum.blog/posts/2024-07-14-tilebased/resources/grass.png" class="img-fluid" data-fig.alt="A square of 16 by 16 pixels with a green grass pattern." width="16"> <img src="https://www.rostrum.blog/posts/2024-07-14-tilebased/resources/tree.png" class="img-fluid" data-fig.alt="A pixelated tree with a thick black outline that fits inside limits of 16 by 16 pixels." width="16"> <img src="https://www.rostrum.blog/posts/2024-07-14-tilebased/resources/player.png" class="img-fluid" data-fig.alt="A pixelated human player-character with a thick black outline that fits inside limits of 16 by 16 pixels." width="16"> <img src="https://www.rostrum.blog/posts/2024-07-14-tilebased/resources/rat.png" class="img-fluid" data-fig.alt="A pixelated grey rat enemy-character with a thick black outline that fits inside limits of 16 by 16 pixels." width="16"></p>
<p>Very cute.</p>
</section>
<section id="install" class="level2">
<h2 class="anchored" data-anchor-id="install">Install</h2>
<p>I put this experiment into an R package so it’s easier to develop and play around with.</p>
<p>But note that the package’s functionality is dependent on your operating system. It’ll work on macOS, but not Windows, due to the required graphics device.</p>
<p>You can install <a href="https://github.com/matt-dray/tilebased">from GitHub</a> like:</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1">remotes<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">install_github</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"matt-dray/tilebased"</span>)</span></code></pre></div>
</div>
<p>As well as {nara} and {eventloop}, it depends on my GitHub-only <a href="https://github.com/matt-dray/r.oguelike">{r.oguelike}</a> package.</p>
<p>You can then start a game, which opens a graphics device. Use your arrow keys to move around. This will look like the gif at the top of this page.</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1">tilebased<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">play</span>()</span></code></pre></div>
</div>
<p>As it stands, I haven’t built a proper game-over condition for this experiment. Collide with the enemy and the game will crash (consider this a feature, not a bug!).</p>
</section>
<section id="process" class="level2">
<h2 class="anchored" data-anchor-id="process">Process</h2>
<p>The user just sees graphics in a window. The basic process-loop underlying this is roughly:</p>
<ol type="1">
<li>Create a ‘board mesh’<sup>4</sup>, a matrix object that encodes the play-area layout and content.</li>
<li>Place the player (<code>@</code>) and enemy (<code>e</code>) actors randomly in a cell (<code>.</code>) of the board mesh, avoiding each other and cells that contain obstacles (<code>#</code>).</li>
<li>Generate a ‘navigation mesh’ that labels each cell with a distance value to the player (i.e.&nbsp;how many tiles away from the player).</li>
<li>Map the board mesh to pixels of the graphics device, where the board-mesh cell contents define the tiles to be selected for display. Draw the pixels to the device.</li>
<li>Accept realtime keypress input from the user and move the player, then consequently move the enemy down the distance gradient of the navmesh. Redraw.</li>
<li>Repeat step 5.</li>
</ol>
</section>
<section id="structures" class="level2">
<h2 class="anchored" data-anchor-id="structures">Structures</h2>
<p>I think it’s worth explaining a bit more about how the game board is represented, translated and drawn.</p>
<section id="board-mesh" class="level3">
<h3 class="anchored" data-anchor-id="board-mesh">Board mesh</h3>
<p>The board mesh is a matrix represenation of the board layout and contents.</p>
<p>As a simple example, you could have a 5×6 board-mesh matrix like below, where <code>#</code> is an impenetrable obstacle, <code>.</code> is a traversable floor tile, <code>@</code> is the player and <code>e</code> the enemy:</p>
<pre><code>     [,1] [,2] [,3] [,4] [,5] [,6]
[1,] "#"  "#"  "#"  "#"  "#"  "#" 
[2,] "#"  "@"  "."  "."  "."  "#" 
[3,] "#"  "."  "#"  "#"  "."  "#" 
[4,] "#"  "."  "."  "e"  "."  "#" 
[5,] "#"  "#"  "#"  "#"  "#"  "#" </code></pre>
</section>
<section id="nav-mesh" class="level3">
<h3 class="anchored" data-anchor-id="nav-mesh">Nav mesh</h3>
<p>The navigation (nav) mesh places a value in each cell, which is the distance between that cell and the player’s cell.</p>
<p>The enemy character will travel from high to low distance values, so will travel left on the board mesh from index [4,4] to [4,3], which decreases the distance value from 4 to 3.</p>
<pre><code>     [,1] [,2] [,3] [,4] [,5] [,6]
[1,] Inf  Inf  Inf  Inf  Inf  Inf 
[2,] Inf  0    1    2    3    Inf 
[3,] Inf  1    Inf  Inf  4    Inf 
[4,] Inf  2    3    4    5    Inf 
[5,] Inf  Inf  Inf  Inf  Inf  Inf </code></pre>
</section>
<section id="board" class="level3">
<h3 class="anchored" data-anchor-id="board">Board</h3>
<p>The board the users actually see is a pixel-based translation of the underlying board mesh.</p>
<p>Each cell of the board mesh is drawn to the screen as tiles. If each tile is 16×16 pixels, then our example board is 80×96 pixels. The upper-left corner of the first tile will be placed at pixel position [1,1]. By extension, the enemy-rat tile in board-mesh cell [4,4] will, for example, occupy the square of pixels at 49 to 64 across and 49 to 64 down.</p>
<p><img src="https://www.rostrum.blog/posts/2024-07-14-tilebased/resources/mini-demo.png" class="img-fluid" data-fig.alt="A 5 by 6 tile map. Every tile slot has a grass tile. There's also a pixel-sprite human character at a position 2 tiles along and 2 tiles down and an enemy rat character at a position 4 tiles along and 4 down. There are tree sprites all the way around the edge and two more in the centre, creating a donut shape of grass that the player and enemy can move within."></p>
<p>The tiles themselves are stored as nativeRaster class, which is a matrix format with colour encoded in each cell as integer values.</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb5-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">str</span>(tilebased<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span>grass)</span></code></pre></div>
<div class="cell-output cell-output-stdout">
<pre><code> 'nativeRaster' int [1:16, 1:16] -9845116 -9845116 -9845116 -9845116 -9845116 -9845116 -9845116 -9845116 -9845116 -9845116 ...
 - attr(*, "channels")= int 4</code></pre>
</div>
</div>
<p>These can be overplotted by {nara}, which means we can have grass tiles covering the whole board and partially-transparent tree, player and enemy sprites drawn on top.</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb7-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(nara)</span>
<span id="cb7-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot</span>(tilebased<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span>grass)   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># place grass first</span></span>
<span id="cb7-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot</span>(tilebased<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span>player)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># then player over the top</span></span></code></pre></div>
<div class="cell-output-display">
<p><img src="https://www.rostrum.blog/posts/2024-07-14-tilebased/index_files/figure-html/unnamed-chunk-4-1.png" class="img-fluid" alt="A pixelated human sprite is plotted over a pixelated green grass tile." width="192"></p>
</div>
</div>
<p>To extend this behvaiour for a more exciting adventure, {tilebased} uses the <code>r.oguelike::create_dungeon()</code> function to create a procedural map that’s randomised with each new game. Here’s a 20×32-tile example:</p>
<p><img src="https://www.rostrum.blog/posts/2024-07-14-tilebased/resources/demo.png" class="img-fluid" style="width:100.0%" data-fig.alt="Pixel-sprite human and rat characters are placed on some grassy tiles in a clearing of a procedurally generated forest. There is a forest around the edge of the map and some dotted within the interior. The human is hiding behind a tree."></p>
</section>
</section>
<section id="the-next-level" class="level2">
<h2 class="anchored" data-anchor-id="the-next-level">The next level</h2>
<p>For now, I’ve shown it’s possible to convert a board-mesh matrix into tile-based graphics with user control.</p>
<p>A lot of functionality is missing, which prevents this concept from having a true gameloop. I may work to improve this in future.</p>
<p>For example, {r.oguelike} has auto-battling and a basic inventory system to collect gold and food; you can imagine showing these in a graphical overlay. Perhaps there could be doorways to new areas of a larger map. It could also use a larger tileset and make use of ‘compound tiles’ that fit together to produce larger sprites. I could also make my own sprites with help from <a href="https://github.com/matt-dray/pixeltrix">my {pixeltrix} package</a>.</p>
<p>In addition, I note that Mike is looking at improved sound generation in R. A bangin’ chiptune would complement {tilebased} well, which makes me wonder if a tile-based rhythm game, like <a href="https://en.wikipedia.org/wiki/Crypt_of_the_NecroDancer">Crypt of the Necrodancer</a>, would be possible.</p>

</section>


<div id="quarto-appendix" class="default"><section id="environment" class="level3 appendix"><h2 class="anchored quarto-appendix-heading">Environment</h2><div class="quarto-appendix-contents">

<details>
<summary>
Session info
</summary>
<div class="cell">
<div class="cell-output cell-output-stdout">
<pre><code>Last rendered: 2024-07-14 13:49:17 BST</code></pre>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>R version 4.4.0 (2024-04-24)
Platform: aarch64-apple-darwin20
Running under: macOS Ventura 13.2.1

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: Europe/London
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] nara_0.1.1.9016

loaded via a namespace (and not attached):
 [1] htmlwidgets_1.6.4 compiler_4.4.0    fastmap_1.2.0     cli_3.6.3.9000   
 [5] tools_4.4.0       htmltools_0.5.8.1 yaml_2.3.8        tilebased_0.3.0  
 [9] rmarkdown_2.26    grid_4.4.0        knitr_1.46        jsonlite_1.8.8   
[13] xfun_0.43         digest_0.6.35     rlang_1.1.4.9000  evaluate_0.23    </code></pre>
</div>
</div>
</details>


</div></section><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>And has one of the best hex stickers around.↩︎</p></li>
<li id="fn2"><p>I’ve written <a href="https://www.rostrum.blog/index.html#category=r.oguelike">a few posts</a> about {r.oguelike} if you want to learn more.↩︎</p></li>
<li id="fn3"><p>I <a href="https://www.rostrum.blog/posts/2022-06-28-isometric-dungeon/">played around with this idea</a> once before, rendering a {r.oguelike} dungeon, player and enemy as isometric-view cubes—thanks to Mike’s <a href="https://github.com/coolbutuseless/isocubes">{isocubes}</a> package—that the user could move around with keypresses. But a cube only has so much character!↩︎</p></li>
<li id="fn4"><p>I’ve borrowed this term from game development but Im’ probably not using it quite right. The idea is that the board and nav meshes are theoretical surfaces that contain data to support the final visual output.↩︎</p></li>
</ol>
</section><section class="quarto-appendix-contents"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div id="quarto-reuse" class="quarto-appendix-contents"><div>CC BY-NC-SA 4.0</div></div></section></div> ]]></description>
  <category>eventloop</category>
  <category>kenney</category>
  <category>nara</category>
  <category>r</category>
  <category>r.oguelike</category>
  <category>tilebased</category>
  <guid>https://www.rostrum.blog/posts/2024-07-14-tilebased/</guid>
  <pubDate>Sat, 13 Jul 2024 23:00:00 GMT</pubDate>
  <media:content url="https://www.rostrum.blog/posts/2024-07-14-tilebased/resources/tilebased.gif" medium="image" type="image/gif"/>
</item>
<item>
  <title>Expose CRAN deadlines and DOIs</title>
  <dc:creator>Matt Dray</dc:creator>
  <link>https://www.rostrum.blog/posts/2024-06-12-cran-db/</link>
  <description><![CDATA[ 




<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2024-06-12-cran-db/resources/app.png" class="img-fluid figure-img" style="width:100.0%" data-fig.alt="Screenshot of webpage called 'CRAN Deadlines'. There are individual cards with the names of R packages in them. The header of each card says how many days remain for fixes to be made and is coloured according to the amount of time left (e.g. red for 0 days). The footer says e.g. 'issues need fixing before 2024-06-12' with a link to the CRAN page detailing the errors."></p>
<figcaption class="figure-caption">Red alert!</figcaption>
</figure>
</div>
<section id="tldr" class="level2">
<h2 class="anchored" data-anchor-id="tldr">tl;dr</h2>
<p>You can now programmatically access deadlines and DOIs for CRAN packages. I larked about with them to make <a href="https://matt-dray.github.io/cran-deadlines/">a Shiny app</a> and <a href="https://github.com/matt-dray/badgr">a README-badge-making function</a>.</p>
</section>
<section id="scan-cran" class="level2">
<h2 class="anchored" data-anchor-id="scan-cran">Scan CRAN</h2>
<p>If you didn’t know, base R has functions that return information about packages on CRAN<sup>1</sup>. The main one is probably <code>CRAN_package_db()</code>. Here’s a few columns:</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1">db <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> tools<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">CRAN_package_db</span>()</span>
<span id="cb1-2">db[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Package"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Version"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Maintainer"</span>)]</span></code></pre></div>
<div class="cell-output cell-output-stdout">
<pre><code>        Package Version                                Maintainer
1            A3   1.0.0 Scott Fortmann-Roe &lt;scottfr@berkeley.edu&gt;
2 AalenJohansen     1.0     Martin Bladt &lt;martinbladt@math.ku.dk&gt;
3      AATtools   0.0.2 Sercan Kahveci &lt;sercan.kahveci@sbg.ac.at&gt;
4        ABACUS   1.0.0          Mintu Nath &lt;dr.m.nath@gmail.com&gt;
5   abasequence   0.1.0         Andrew Pilny &lt;andy.pilny@uky.edu&gt;</code></pre>
</div>
</div>
<p>This post is about two new columns that have appeared recently in that dataframe—<code>Deadline</code> and <code>DOI</code>—and a couple of little tools I’ve built around them.</p>
<section id="meet-the-deadline" class="level3">
<h3 class="anchored" data-anchor-id="meet-the-deadline">Meet the deadline</h3>
<p>If your package-builds fail on CRAN then you need to fix them. The CRAN database now contains the date by which fixes need to be made<sup>2</sup>. The simple way to access this information is:</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb3-1">db_dead <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> db[<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">is.na</span>(db<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>Deadline), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Package"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Deadline"</span>)]</span>
<span id="cb3-2">db_dead <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sort_by</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span>Deadline) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">head</span>()</span></code></pre></div>
<div class="cell-output cell-output-stdout">
<pre><code>          Package   Deadline
11555       MuMIn 2024-06-14
16242       rsolr 2024-06-14
16964      semEff 2024-06-14
6901  gghighlight 2024-06-17
9671         lme4 2024-06-17
11800 nestedLogit 2024-06-17</code></pre>
</div>
</div>
<p>You can get this information in an R terminal, sure, but it’s ripe for displaying in a web app that shows the latest data.</p>
<p>This seemed a good excuse for me to learn a couple of (newish-to-me) Shiny things: (a) <a href="https://rstudio.github.io/bslib/">{bslib}</a> as a Shiny framework and (b) a way to create an arbitrary number of UI elements, since the number of packages with deadlines will vary from day to day.</p>
<p>In short, I made a Shiny(live) app, which is embedded below (may take a moment to fully load).</p>
<iframe width="400" height="500" src="https://matt-dray.github.io/cran-deadlines/" title="CRAN deadlines">
</iframe>
<p><a href="https://matt-dray.github.io/cran-deadlines/">You can also visit directly on GitHub Pages</a> or <a href="https://github.com/matt-dray/cran-deadlines">find the source on GitHub</a>. The data refreshes with each visit so it stays up to date.</p>
<p>It’s pretty simple: there’s a card per deadlined package with a link to its CRAN-listing page and check details. For added spice, I calculated days remaining and coloured the card headers by threat level.</p>
<p>If you see a negative number of days remaining, it’s not because the data haven’t updated correctly. A package can have a deadline listed that has already passed, which will manifest as a negative value in the number of days remaining<sup>3</sup>. It also seems that packages can have a deadline listed in <code>CRAN_package_db()</code> even if they’ve been archived or reprieved, so they’ll continue to appear in the app regardless.</p>
<p>Re handling an arbitrary number of UI elements on the fly, I looped over the deadlined packages to build a <code>tagList</code> of <code>bslib::card()</code> elements. This object was then interpreted by <code>renderUI()</code> and the cards were spat out.</p>
<p>Some other new-to-me bits-and-bobs I used were <a href="https://twitter.com/LeaVerou/status/1241619866475474946">an emoji as a favicon</a><sup>4</sup>, {bslib}’s very simple <a href="https://rstudio.github.io/bslib/reference/input_dark_mode.html">dark-mode toggle</a>, and <a href="https://rstudio.github.io/htmltools/reference/tagAppendChild.html"><code>htmltools::tagAppendChild()</code></a> to splice breaks between cards in the <code>tagList</code><sup>5</sup>.</p>
<p>In the end, this was a good excuse to polish some of my Shiny knowledge, but there are simpler approaches to displaying this information. For example, I started with just a basic {reactable}. I see <a href="https://hadley.github.io/cran-deadlines/">Hadley has now done something similar to that</a><sup>6</sup>, in a table that allows you to click rows to expand a package’s check results as well. It’s a better tool and I recommend you bookmark that instead of this Shiny app<sup>7</sup>.</p>
<p>Ah well. By now you’ve come to expect overengineering from this blog, I suspect. At least I had fun.</p>
<div class="tip">
<p> <b>Note</b></p>
<p>The app was available on the web via shinyapps.io when I first published this blog. I actually wanted it to be <a href="https://posit-dev.github.io/r-shinylive/">a Shinylive app</a>, but <a href="https://github.com/matt-dray/cran-deadlines/issues/1">hit a snag</a>. Fortunately, the ever-helpful <a href="https://github.com/rpodcast">Eric Nantz</a> made <a href="https://github.com/matt-dray/cran-deadlines/pull/3">a pull request</a> that solved my problem. I think Shinylive is a good option for this app: it’s small, there’s little user interaction and it’s not worth a precious slot in my free shinyapps.io account. Thanks Eric.</p>
</div>
<br>
<div class="tip">
<p> <b>Note</b></p>
<p>I noticed that Dirk also calculated the number of potential ‘orphans’ that archival would cause. This is useful information to gauge relative ‘danger’ for a given package to fall off CRAN. Hadley has since added this feature to his table and I <a href="https://github.com/matt-dray/cran-deadlines/issues/2">might implement it too</a>.</p>
</div>
</section>
<section id="oi-doi" class="level3">
<h3 class="anchored" data-anchor-id="oi-doi">Oi! DOI!</h3>
<p>A <a href="https://www.doi.org/">DOI</a> is a ‘digital object identifier’. These are used extensively in publishing and can be useful for citation, tracking and gathering usage statistics. CRAN has been adding these to packages recently and the string has been added to the <code>CRAN_package_db()</code> dataframe in the <code>DOI</code> column<sup>8</sup>.</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb5-1">db[<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">is.na</span>(db<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>DOI), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Package"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"DOI"</span>)][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, ]</span></code></pre></div>
<div class="cell-output cell-output-stdout">
<pre><code>        Package                                 DOI
1            A3            10.32614/CRAN.package.A3
2 AalenJohansen 10.32614/CRAN.package.AalenJohansen
3      AATtools      10.32614/CRAN.package.AATtools
4        ABACUS        10.32614/CRAN.package.ABACUS
5   abasequence   10.32614/CRAN.package.abasequence</code></pre>
</div>
</div>
<p><a href="https://fosstodon.org/@eddelbuettel@mastodon.social/112597923361268334">Dirk wondered aloud on Mastodon</a> about making a badge service for DOIs. I don’t know about a ‘service’ but I once wrote a package called <a href="https://github.com/matt-dray/badgr">{badgr}</a> to build customised <a href="https://shields.io/">shields.io</a> metadata badges within R. It’s a small leap to make a {badgr} function that builds CRAN DOI badges.</p>
<p>So, as of of {badgr} v0.2.0, you can use <code>get_cran_doi_badge()</code>. You can download the package from my R-universe:</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb7-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">install.packages</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"badgr"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">repos =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"https://matt-dray.r-universe.dev"</span>)</span></code></pre></div>
</div>
<p>You pass it a CRAN package name. If you want, you can pass other arguments to <code>get_badge()</code> to change its colour, etc (but the default colour is the tasteful blue of the R logo, so you probably want to keep that). For purposes of this blog post, I’ve turned off the arguments that open a browser preview of the badge and copy it to your clipboard.</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb8-1">badgr<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">get_cran_doi_badge</span>(</span>
<span id="cb8-2">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">package_name =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"datapasta"</span>,</span>
<span id="cb8-3">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">browser_preview =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>,</span>
<span id="cb8-4">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">to_clipboard =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span></span>
<span id="cb8-5">)</span></code></pre></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1] "[![](https://img.shields.io/badge/DOI-10.32614/CRAN.package.datapasta-1f57b6?style=flat&amp;link=https://doi.org/10.32614/CRAN.package.datapasta)](https://doi.org/10.32614/CRAN.package.datapasta)"</code></pre>
</div>
</div>
<p>The code outputs a Markdown string that you can paste into your repository’s README, for example. It looks like this when rendered:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="https://doi.org/10.32614/CRAN.package.datapasta"><img src="https://img.shields.io/badge/DOI-10.32614/CRAN.package.datapasta-1f57b6?style=flat&amp;link=https://doi.org/10.32614/CRAN.package.datapasta" class="img-fluid figure-img"></a></p>
</figure>
</div>
<p>You can click it to be taken to the resource, which is the CRAN listing for the package. The whole process looks like this:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2024-06-12-cran-db/resources/doi-badge.gif" class="img-fluid figure-img" style="width:100.0%" data-fig.alt="A gif of the function get_cran_doi_badge running in RStudio with the package name 'datapasta' passed as the only argument. The console prints 'opening browser to display badge preview' and 'badge markdown added to clipboard' The markdown has also printed in the console. The badge opens in a browser window. It says 'DOI' on the left side and the DOIc ode on the right. Clicking it takes you to the CRAN page for hte package."></p>
<figcaption class="figure-caption"><a href="https://milesmcbain.github.io/datapasta/">{datapasta}</a> is great, even if you don’t pronounce it ‘dah-tah pars-tah’.</figcaption>
</figure>
</div>
<p>In retrospect, this function probably doesn’t need to use <code>CRAN_package_db()</code> because the DOIs look fairly standardised in structure (e.g.&nbsp;<code>10.32614/CRAN.package.&lt;package-name&gt;</code>). But at time of writing, not every package has a DOI yet; checking the database means an error can be raised if the DOI isn’t yet active.</p>
<p>Ah well. By now you’ve come to expect overengineering from this blog, I suspect. At least I had fun.</p>
<div class="tip">
<p> <b>Note</b></p>
<p>Dirk mentioned in <a href="https://mastodon.social/@eddelbuettel/112609970727005448">another Mastodon post</a> that he’s added a simple deadline fetcher to {littler} and also pointed out the simple form noted above for a Markdown badge.</p>
</div>
<br>
<div class="tip">
<p> <b>Note</b></p>
<p>Mike <a href="https://fosstodon.org/@coolbutuseless/112611459027946296">started a list</a> of CRAN-related dashboards/search tools and has asked for any that have been missed. Which implies we need a tool to search for such tools, lol?</p>
</div>
</section>

</section>


<div id="quarto-appendix" class="default"><section id="environment" class="level3 appendix"><h2 class="anchored quarto-appendix-heading">Environment</h2><div class="quarto-appendix-contents">

<details>
<summary>
Session info
</summary>
<div class="cell">
<div class="cell-output cell-output-stdout">
<pre><code>Last rendered: 2024-06-17 14:50:18 BST</code></pre>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>R version 4.4.0 (2024-04-24)
Platform: aarch64-apple-darwin20
Running under: macOS Ventura 13.2.1

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: Europe/London
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
 [1] digest_0.6.35     base64enc_0.1-3   fastmap_1.2.0     xfun_0.43        
 [5] fontawesome_0.5.2 knitr_1.46        badgr_0.2         htmltools_0.5.8.1
 [9] rmarkdown_2.26    cli_3.6.2         compiler_4.4.0    rstudioapi_0.16.0
[13] tools_4.4.0       clipr_0.8.0       evaluate_0.23     yaml_2.3.8       
[17] rlang_1.1.4       jsonlite_1.8.8    htmlwidgets_1.6.4</code></pre>
</div>
</div>
</details>


</div></section><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>The Comprehensive R Archive Network, not <a href="https://www.rostrum.blog/posts/2020-09-12-herring-units/">the unit of uncleaned herring</a>.↩︎</p></li>
<li id="fn2"><p>I think it was <a href="https://mastodon.social/@henrikbengtsson/112591630316235677">Henrik’s Mastodon post</a> that first tipped me off to this, thank you.↩︎</p></li>
<li id="fn3"><p>Lluis was kind enough to <a href="https://fosstodon.org/@Lluis_Revilla/112620123453580083">suggest why this might be</a>.↩︎</p></li>
<li id="fn4"><p>This appears when the Shiny app is opened locally in a browser, but does not carry through to the Shinylive version.↩︎</p></li>
<li id="fn5"><p>I’m not convinced my approach is the best one, but it works for now. Please leave an issue in the repo if you have a better idea.↩︎</p></li>
<li id="fn6"><p>The absolute cheek of using the same (completely obvious and utterly non-plagiarised) repository name, can you believe it.↩︎</p></li>
<li id="fn7"><p>Though I chose not to shame the package owners by putting their names in my app, lol.↩︎</p></li>
<li id="fn8"><p>I first noticed this thanks to <a href="https://fosstodon.org/@zeileis/112597049943483012">Achim’s post on Mastodon</a>.↩︎</p></li>
</ol>
</section><section class="quarto-appendix-contents"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div id="quarto-reuse" class="quarto-appendix-contents"><div>CC BY-NC-SA 4.0</div></div></section></div> ]]></description>
  <category>badgr</category>
  <category>bslib</category>
  <category>cran</category>
  <category>r</category>
  <category>shiny</category>
  <category>shinylive</category>
  <guid>https://www.rostrum.blog/posts/2024-06-12-cran-db/</guid>
  <pubDate>Tue, 11 Jun 2024 23:00:00 GMT</pubDate>
  <media:content url="https://www.rostrum.blog/posts/2024-06-12-cran-db/resources/app.png" medium="image" type="image/png" height="69" width="144"/>
</item>
<item>
  <title>The Aesthetics Wiki: an R Addendum</title>
  <dc:creator>Matt Dray</dc:creator>
  <link>https://www.rostrum.blog/posts/2024-05-08-aesthetic/</link>
  <description><![CDATA[ 




<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2024-05-08-aesthetic/resources/passion.png" class="img-fluid figure-img" style="width:100.0%" data-fig.alt="90s-style Word Art saying 'aesethics' in the middle, surrounded by the words 'base R', 'tidyverse' and 'tidy base question' in various egregious fonts and colours."></p>
<figcaption class="figure-caption">They’re doing the Lord’s work over at <a href="https://www.makewordart.com/">makewordart.com</a>.</figcaption>
</figure>
</div>
<section id="tldr" class="level2">
<h2 class="anchored" data-anchor-id="tldr">tl;dr</h2>
<p>Some people look at an R script and see code. But I <em>feel</em> something.</p>
</section>
<section id="a-philosophy" class="level2">
<h2 class="anchored" data-anchor-id="a-philosophy">A philosophy</h2>
<p>Have you experienced the <a href="https://aesthetics.fandom.com/wiki/Aesthetics_Wiki">Aesthetics Wiki</a>?</p>
<p>It’s missing R-related entries for some reason, which definitely fit <a href="https://aesthetics.fandom.com/wiki/Philosophy">the aesthetic philosophy</a> of having:</p>
<blockquote class="blockquote">
<p>perspectives on beauty and the human condition and a political, economic, or social statement</p>
</blockquote>
<p>Tell me you don’t look at an R script and get an immediate vibe.</p>
<p>I want to suggest two obvious aesthetics for submission to the wiki—<em>Basecore</em> and <em>Tidywave</em>—and to introduce the definitely-soon-to-be-mainstreamed <em>V4 Punk</em><sup>1</sup>.</p>
</section>
<section id="the-addendum" class="level2">
<h2 class="anchored" data-anchor-id="the-addendum">The addendum</h2>
<section id="basecore" class="level3">
<h3 class="anchored" data-anchor-id="basecore">Basecore</h3>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2024-05-08-aesthetic/resources/chaotic-academia.png" class="img-fluid figure-img" style="width:100.0%" data-fig.alt="A set of different-sized books haphazardly placed ona bookshelf."></p>
<figcaption class="figure-caption">Adapted from <a href="https://static.wikia.nocookie.net/aesthetics/images/1/15/Chaotic_academia.jpg/revision/latest?cb=20200610213320">Aesthetics Wiki</a>.</figcaption>
</figure>
</div>
<section id="overview" class="level4">
<h4 class="anchored" data-anchor-id="overview">Overview</h4>
<ul>
<li>History: emerged as Base Academic in 1993, mainstreamed as Base Core from 2000 to date.</li>
<li>Visuals: <code>[</code> (square-bracket selector), <code>$</code> (dollar selector), <code>~</code> (formula-form tilde).</li>
<li>Media: <a href="https://cran.r-project.org/doc/manuals/R-exts.html"><em>Writing R Extensions</em></a>, the <a href="https://stat.ethz.ch/mailman/listinfo/r-help">R-help mailing list</a>, that textfile with a <a href="https://fosstodon.org/@mattdray/111125184484188641">Y2K warning</a> that came bundled with R v1.0.</li>
<li>Fashion: cardigans, a wired mouse with a ball in it, the old-school IDE you get when you click ‘R.app’ instead of ‘RStudio.app’.</li>
<li>Palette: <span style="background-color:lightgrey;">grey</span>.</li>
<li>Nearest aesthetic: <a href="https://aesthetics.fandom.com/wiki/Chaotic_Academia">Chaotic Academia</a>, which ‘acknowledges the pretentiousness of classic academia, subtly mocking it at times’.</li>
</ul>
</section>
<section id="sample" class="level4">
<h4 class="anchored" data-anchor-id="sample">Sample</h4>
<p>Classic stuff. Typical base-R code uses a lot of intermediate assignment to create temporary objects for further manipulation. Many actions make use of square bracket notation to indicate some action over rows and columns of a data.frame. Data.frame columns have to be called within the context of the data.frame they belong to, using dollar or square-bracket notation. The tilde is used for formula notation (‘this given that’).</p>
<details>
<summary>
Set up demo data
</summary>
<div class="cell">
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">suppressPackageStartupMessages</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(dplyr))</span>
<span id="cb1-2">sw_a <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> starwars[, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"name"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"height"</span>)]</span>
<span id="cb1-3">sw_b <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> starwars[, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"name"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"homeworld"</span>)]</span></code></pre></div>
</div>
</details>
<div class="cell">
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1">x <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> sw_a[sw_a<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>species <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Human"</span>, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">names</span>(sw_a) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span>]</span>
<span id="cb2-2">x <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">merge</span>(x, sw_b, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">by =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"name"</span>)</span>
<span id="cb2-3">x<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>height <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> x<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>height <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span></span>
<span id="cb2-4">x<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>homeworld <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ifelse</span>(</span>
<span id="cb2-5">  <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!</span>x<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>homeworld <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%in%</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Tatooine"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Naboo"</span>),</span>
<span id="cb2-6">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Other"</span>,</span>
<span id="cb2-7">  x<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>homeworld</span>
<span id="cb2-8">)</span>
<span id="cb2-9">x <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aggregate</span>(x, height <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span> homeworld, mean, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">na.rm =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)</span>
<span id="cb2-10">x <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> x[<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">order</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>x<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>height), ]</span>
<span id="cb2-11">x</span></code></pre></div>
<div class="cell-output cell-output-stdout">
<pre><code>  homeworld   height
3  Tatooine 1.792500
2     Other 1.776471
1     Naboo 1.772000</code></pre>
</div>
</div>
</section>
</section>
<section id="tidywave" class="level3">
<h3 class="anchored" data-anchor-id="tidywave">Tidywave</h3>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2024-05-08-aesthetic/resources/corporate-memphis.png" class="img-fluid figure-img" style="width:100.0%" data-fig.alt="Colourful stylised cartoon of faceless humans with small heads and large limbs and hands."></p>
<figcaption class="figure-caption">Adapted from <a href="https://static.wikia.nocookie.net/aesthetics/images/6/62/Blob-web-exercise.webp/revision/latest?cb=20210410215136">Aesthetics Wiki</a>.</figcaption>
</figure>
</div>
<section id="overview-1" class="level4">
<h4 class="anchored" data-anchor-id="overview-1">Overview</h4>
<ul>
<li>History: humble academic origins in 2008, later popularised with the creation of the {tidyverse} in 2016.</li>
<li>Visuals: <code>%&gt;%</code> ({magrittr} pipe), <code>.</code> (data placeholder), <code>~</code> (lambda function).</li>
<li>Media: <a href="https://r4ds.hadley.nz/"><em>R for Data Science</em></a>, conflict warnings in the console when you do <code>library(tidyverse)</code>, that one gif of Hadley tapping merrily on an invisible keyboard (‘just typing R code!’).</li>
<li>Fashion: RStudio, hex stickers, rapid deprecation.</li>
<li>Palette: Posit <span style="background-color:#1a162d;color:white;">blue</span>, <span style="background-color:#fdeba4;">yellow</span>, and <span style="background-color:#767381;color:white;">grey</span>.</li>
<li>Nearest aesthetic: <a href="https://aesthetics.fandom.com/wiki/Corporate_Memphis">Corporate Memphis</a> because I’ve literally seen it in Posit marketing.</li>
</ul>
</section>
<section id="sample-1" class="level4">
<h4 class="anchored" data-anchor-id="sample-1">Sample</h4>
<p>The script reads from left-to-right and top-to-bottom, recipe style, using pipes: ‘take dataset, then do a thing, then do a thing’, etc. Each function is verb that indicates its action. Non-standard evaluation is rampant. A tilde replaces the tedious typing of <kbd>f</kbd><kbd>u</kbd><kbd>n</kbd><kbd>c</kbd><kbd>t</kbd><kbd>i</kbd><kbd>o</kbd><kbd>n</kbd><kbd>(</kbd><kbd>)</kbd>. A titchy inconspicuous <code>.</code> acts as a data placeholder on the right-hand side of a pipe.</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb4-1">sw_a <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span></span>
<span id="cb4-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filter</span>(species <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Human"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span></span>
<span id="cb4-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">select</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>species) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span></span>
<span id="cb4-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">left_join</span>(sw_b, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">by =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"name"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span></span>
<span id="cb4-5">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mutate</span>(</span>
<span id="cb4-6">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">height =</span> height <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>,</span>
<span id="cb4-7">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">homeworld =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">if_else</span>(</span>
<span id="cb4-8">      <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!</span>homeworld <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%in%</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Tatooine"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Naboo"</span>),</span>
<span id="cb4-9">      <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Other"</span>,</span>
<span id="cb4-10">      homeworld</span>
<span id="cb4-11">    )</span>
<span id="cb4-12">  ) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span></span>
<span id="cb4-13">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">summarise</span>(</span>
<span id="cb4-14">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">height =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mean</span>(height, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">na.rm =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>),</span>
<span id="cb4-15">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">.by =</span> homeworld</span>
<span id="cb4-16">  ) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span></span>
<span id="cb4-17">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">arrange</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">desc</span>(height))</span></code></pre></div>
<div class="cell-output cell-output-stdout">
<pre><code># A tibble: 3 × 2
  homeworld height
  &lt;chr&gt;      &lt;dbl&gt;
1 Tatooine    1.79
2 Other       1.78
3 Naboo       1.77</code></pre>
</div>
</div>
</section>
<section id="v4-punk" class="level4">
<h4 class="anchored" data-anchor-id="v4-punk">V4 Punk</h4>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2024-05-08-aesthetic/resources/vacation-dadcore.png" class="img-fluid figure-img" style="width:100.0%" data-fig.alt="Tom Sellick in a Hawaiian shirt at the beach, leaning against a palm tree. He is smiling."></p>
<figcaption class="figure-caption">Adapted from <a href="https://static.wikia.nocookie.net/aesthetics/images/d/d1/Magnum-pi-hawaiian-shirt.jpg/revision/latest?cb=20200507215616">Aesthetics Wiki</a>.</figcaption>
</figure>
</div>
</section>
<section id="overview-2" class="level4">
<h4 class="anchored" data-anchor-id="overview-2">Overview</h4>
<ul>
<li>History: incremental emergence following R version 4.0 in 2020, boosted greatly in 2022 with the release of the base pipe. The future of R.</li>
<li>Visuals: <code>|&gt;</code> (base pipe), <code>_</code> (data placeholder), <code>\()</code> (lambda function).</li>
<li>Media: meme blogs, cheeky Fosstodon posts.</li>
<li>Fashion: hubris, evil moustaches, troll-face emojis.</li>
<li>Palette: <span style="background-color:#ff0000;">r</span><span style="background-color:#ff00ff;">a</span><span style="background-color:#00ffff;">i</span><span style="background-color:#00ff00;">n</span><span style="background-color:#0000ff;color:white;">b</span><span style="background-color:#4b0082;color:white;">o</span><span style="background-color:#ee82ee;">w</span>?</li>
<li>Nearest aesthetic: <a href="https://aesthetics.fandom.com/wiki/Vacation_Dadcore">Vacation Dadcore</a>, so you can ‘escape to a simpler time without sacrificing any of the fun’.</li>
</ul>
</section>
<section id="sample-2" class="level4">
<h4 class="anchored" data-anchor-id="sample-2">Sample</h4>
<p>Hear me out: what if tidyverse, but made entirely of base R functions<sup>2</sup>? This is now possible with the base pipe and by using obscure functions that help you avoid square brackets<sup>3</sup>. It’s also sketchy as heck; just see the help files for <code>subset()</code> (‘unanticipated consequences’) and <code>transform()</code> (‘you deserve whatever you get!’). The death of Basecore and Tidywave, for sure.</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb6-1">sw_a <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb6-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">subset</span>(</span>
<span id="cb6-3">    species <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Human"</span>,</span>
<span id="cb6-4">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">select =</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>species</span>
<span id="cb6-5">  ) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb6-6">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">merge</span>(sw_b, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">by =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"name"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb6-7">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">transform</span>(</span>
<span id="cb6-8">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">height =</span> height <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>,</span>
<span id="cb6-9">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">homeworld =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ifelse</span>(</span>
<span id="cb6-10">      <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!</span>homeworld <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%in%</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Tatooine"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Naboo"</span>),</span>
<span id="cb6-11">      <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Other"</span>,</span>
<span id="cb6-12">      homeworld</span>
<span id="cb6-13">    )</span>
<span id="cb6-14">  ) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb6-15">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aggregate</span>(</span>
<span id="cb6-16">    height <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span> homeworld, </span>
<span id="cb6-17">    \(x) <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mean</span>(x, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">na.rm =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)</span>
<span id="cb6-18">  ) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb6-19">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sort_by</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span> height, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">decreasing =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># hot off the v4.4 press</span></span></code></pre></div>
<div class="cell-output cell-output-stdout">
<pre><code>  homeworld   height
3  Tatooine 1.792500
2     Other 1.776471
1     Naboo 1.772000</code></pre>
</div>
</div>
<p>Can’t wait to see this roll out into production code.</p>
</section>
</section>

</section>


<div id="quarto-appendix" class="default"><section id="environment" class="level3 appendix"><h2 class="anchored quarto-appendix-heading">Environment</h2><div class="quarto-appendix-contents">

<details>
<summary>
Session info
</summary>
<div class="cell">
<div class="cell-output cell-output-stdout">
<pre><code>Last rendered: 2024-05-09 09:04:29 BST</code></pre>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>R version 4.4.0 (2024-04-24)
Platform: aarch64-apple-darwin20
Running under: macOS Ventura 13.2.1

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: Europe/London
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] dplyr_1.1.4

loaded via a namespace (and not attached):
 [1] digest_0.6.35     utf8_1.2.4        R6_2.5.1          fastmap_1.1.1    
 [5] tidyselect_1.2.1  xfun_0.43         magrittr_2.0.3    glue_1.7.0       
 [9] tibble_3.2.1      knitr_1.46        pkgconfig_2.0.3   htmltools_0.5.8.1
[13] rmarkdown_2.26    generics_0.1.3    lifecycle_1.0.4   cli_3.6.2        
[17] fansi_1.0.6       vctrs_0.6.5       withr_3.0.0       compiler_4.4.0   
[21] rstudioapi_0.16.0 tools_4.4.0       pillar_1.9.0      evaluate_0.23    
[25] yaml_2.3.8        rlang_1.1.3       jsonlite_1.8.8   </code></pre>
</div>
</div>
</details>


</div></section><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>These <a href="https://aesthetics.fandom.com/wiki/Suffix_Meaning">suffixes are meaningful</a>: core ‘implies a system, a set of rules’, wave ‘a significant shift within a genre’ and punk ‘reject[s] social norms’.↩︎</p></li>
<li id="fn2"><p>I called this ‘The Modern Base Aesthetic’ in <a href="https://www.rostrum.blog/posts/2023-10-17-nhs-r-2023/">my talk, ‘Base Slaps!’</a>, at the NHS-R conference 2023. But now I realise the aesthetics run deeper.↩︎</p></li>
<li id="fn3"><p>If you truly want something that looks like {dplyr} code but depends only on base R, then check out <a href="https://nathaneastwood.github.io/poorman/">the {poorman} package</a>.↩︎</p></li>
</ol>
</section><section class="quarto-appendix-contents"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div id="quarto-reuse" class="quarto-appendix-contents"><div>CC BY-NC-SA 4.0</div></div></section></div> ]]></description>
  <category>base</category>
  <category>r</category>
  <category>tidyverse</category>
  <guid>https://www.rostrum.blog/posts/2024-05-08-aesthetic/</guid>
  <pubDate>Tue, 07 May 2024 23:00:00 GMT</pubDate>
  <media:content url="https://www.rostrum.blog/posts/2024-05-08-aesthetic/resources/passion.png" medium="image" type="image/png" height="66" width="144"/>
</item>
<item>
  <title>Stop Jenny committing arson</title>
  <dc:creator>Matt Dray</dc:creator>
  <link>https://www.rostrum.blog/posts/2024-04-01-perpetual-restart/</link>
  <description><![CDATA[ 




<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2024-04-01-perpetual-restart/resources/arson.png" class="img-fluid figure-img" style="width:100.0%" data-fig.alt="Posit employee Jenny Bryan on stage at a lectern. Her eyes are emoji flames. In red Comic Sans around her are the words 'fire', 'computer' and 'burn', as well as the R code 'rm(list=ls())'. There is a flame effect along the bottom of the image."></p>
<figcaption class="figure-caption">This image has been edited. With apologies.</figcaption>
</figure>
</div>
<section id="tldr" class="level2">
<h2 class="anchored" data-anchor-id="tldr">tl;dr</h2>
<p>Make RStudio restart itself. Perpetually.</p>
<div class="tip">
<p> <b>Note</b></p>
<p>This post contains April Fools’ Day content.</p>
</div>
</section>
<section id="passing-the-torch" class="level2">
<h2 class="anchored" data-anchor-id="passing-the-torch">Passing the torch</h2>
<p><a href="https://www.tidyverse.org/blog/2017/12/workflow-vs-script/">Jenny Bryan once said</a>:</p>
<blockquote class="blockquote">
<p>If the first line of your R script is <code>rm(list = ls())</code> I will come into your office and SET YOUR COMPUTER ON FIRE 🔥.</p>
</blockquote>
<p>The objects in your R environment are wicked little imps whose only motivation is to deceive you. They must be destroyed. Frequently.</p>
<p>But <code>rm(list = ls())</code> isn’t enough to start a new session with a completely clean slate. It doesn’t detach packages, for example. Reproducibility is at stake here, folks!</p>
<p>The only salvation is to completely restart R. All the time.</p>
<p>I don’t think you heard me. Come closer. I said <em>all the time</em>.</p>
</section>
<section id="an-extinguisher" class="level2">
<h2 class="anchored" data-anchor-id="an-extinguisher">An extinguisher</h2>
<p>I’ve been <a href="https://www.rostrum.blog/posts/2020-04-17-r-self-shame/index.html#env">woke to this thought technology</a> for a while. You’re right, it’s time I gave back to the community.</p>
<p>I can’t trust you to restart R voluntarily, so I must help you to help yourself.</p>
<p>So, I have for you a gift. A precious snippet of R code that came to me in a fever dream.</p>
<div class="cell">
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">setHook</span>(</span>
<span id="cb1-2">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rstudio.sessionInit"</span>,</span>
<span id="cb1-3">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(...) {</span>
<span id="cb1-4">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">message</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"⏳ Restarting in 30 seconds..."</span>)</span>
<span id="cb1-5">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Sys.sleep</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">30</span>)</span>
<span id="cb1-6">    rstudioapi<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">executeCommand</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"restartR"</span>)</span>
<span id="cb1-7">  }</span>
<span id="cb1-8">)</span></code></pre></div>
</div>
<p>Simply open your <code>.Rprofile</code><sup>1</sup> (<a href="https://support.posit.co/hc/en-us/articles/360047157094-Managing-R-with-Rprofile-Renviron-Rprofile-site-Renviron-site-rsession-conf-and-repos-conf">a special file</a> that’s executed at start up) with <code>usethis::edit_r_profile()</code><sup>2</sup>, paste in the code<sup>3</sup> and then restart R for it to take effect<sup>4</sup>.</p>
<p>What does it do? It waits for RStudio to start up<sup>5</sup> and then restarts R<sup>6</sup> after 30 seconds<sup>7</sup>.</p>
<p>…Which prompts another restart after 30 more seconds.</p>
<p>…And another.</p>
<p>…Forever<sup>8</sup>.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://www.rostrum.blog/posts/2024-04-01-perpetual-restart/resources/restart.png" class="img-fluid figure-img" style="width:100.0%" data-fig.alt="The R console in RStudio. Repeated several times is the message 'restarting in 30 seconds...' then 'restarting R session'."></p>
<figcaption class="figure-caption">trapped in a loop help I’m</figcaption>
</figure>
</div>
<p>I know what you’re thinking: doesn’t this block the console? Kind of. You can still type stuff and it’ll be executed before the next restart. Only to disappear immediately.</p>
<p>Complete panic. Just like Jenny wanted! I think?</p>
</section>
<section id="rise-from-the-ashes" class="level2">
<h2 class="anchored" data-anchor-id="rise-from-the-ashes">Rise from the ashes</h2>
<p>How can you thank me for this? I am humble (extremely), but you’re welcome to <a href="https://github.com/scrogster/rickrollR/">support my Patreon</a>.</p>
<p>Happy March 32nd, colleagues.</p>

</section>


<div id="quarto-appendix" class="default"><section id="environment" class="level3 appendix"><h2 class="anchored quarto-appendix-heading">Environment</h2><div class="quarto-appendix-contents">

<details>
<summary>
Session info
</summary>
<div class="cell">
<div class="cell-output cell-output-stdout">
<pre><code>Last rendered: 2024-04-03 08:14:51 BST</code></pre>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>R version 4.3.1 (2023-06-16)
Platform: aarch64-apple-darwin20 (64-bit)
Running under: macOS Ventura 13.2.1

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.11.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: Europe/London
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
 [1] htmlwidgets_1.6.2 compiler_4.3.1    fastmap_1.1.1     cli_3.6.2        
 [5] tools_4.3.1       htmltools_0.5.8   rstudioapi_0.16.0 yaml_2.3.8       
 [9] rmarkdown_2.26    knitr_1.45        jsonlite_1.8.8    xfun_0.43        
[13] digest_0.6.35     rlang_1.1.3       fontawesome_0.5.2 evaluate_0.23    </code></pre>
</div>
</div>
</details>


</div></section><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>Don’t.↩︎</p></li>
<li id="fn2"><p>Don’t.↩︎</p></li>
<li id="fn3"><p>Don’t.↩︎</p></li>
<li id="fn4"><p>Fine, <a href="https://www.youtube.com/watch?v=lCscYsICvoA">dew it</a>.↩︎</p></li>
<li id="fn5"><p>The code depends on the {rstudioapi} package, but it needs to load before it can be used. This seems to happen <em>after</em> the .Rprofile has been read. This is why we have to use <code>setHook()</code> with <code>rstudio.sessionInit</code> so the restart command can be executed once RStudio has initiated.↩︎</p></li>
<li id="fn6"><p>I also tried <code>rsudioapi::restartSession()</code> and <code>.rs.restartR()</code> here, but they didn’t work quite as I expected. Let me know any non-RStudio ways to do this, of course.↩︎</p></li>
<li id="fn7"><p>If you’re feeling extra spicy, maybe randomise the wait period!↩︎</p></li>
<li id="fn8"><p>Maybe you’ll have to force quit RStudio? Use <kbd>Cmd</kbd>+<kbd>Option</kbd>+<kbd>Esc</kbd> on a Macintosh or <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>Delete</kbd> on Michaelsoft Binbows. I assume Linux users know what they’re doing.↩︎</p></li>
</ol>
</section><section class="quarto-appendix-contents"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div id="quarto-reuse" class="quarto-appendix-contents"><div>CC BY-NC-SA 4.0</div></div></section></div> ]]></description>
  <category>r</category>
  <category>reproducibility</category>
  <guid>https://www.rostrum.blog/posts/2024-04-01-perpetual-restart/</guid>
  <pubDate>Sun, 31 Mar 2024 23:00:00 GMT</pubDate>
  <media:content url="https://www.rostrum.blog/posts/2024-04-01-perpetual-restart/resources/arson.png" medium="image" type="image/png" height="66" width="144"/>
</item>
</channel>
</rss>
