<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>gecko's site</title><link href="https://exelo.tl/" rel="alternate"></link><link href="https://exelo.tl/feed/atom.xml" rel="self"></link><id>https://exelo.tl/</id><updated>2024-02-25T00:00:00+00:00</updated><entry><title>Poop Sweeper</title><link href="https://exelo.tl/poopsweeper.html" rel="alternate"></link><published>2023-12-24T00:00:00+00:00</published><updated>2023-12-24T00:00:00+00:00</updated><author><name>gecko@exelo.tl</name></author><id>tag:exelo.tl,2023-12-24:/poopsweeper.html</id><summary type="html">&lt;p&gt;Now that the game is out, I can finally talk about this!&lt;/p&gt;
&lt;p&gt;My magnum opus:&lt;/p&gt;
&lt;img alt="A photo of a blue Game Boy Advance running Poop Sweeper, a Minesweeper clone in the pause menu of Goodboy Galaxy, set against a Windows XP style background." class="align-center" src="https://exelo.tl/media/2023/sweep.jpg" style="width: 520px;" /&gt;
&lt;p&gt;Poop Sweeper is a faithful clone of Minesweeper&lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt; in the pause menu of &lt;a class="reference external" href="https://goodboygalaxy.com/"&gt;Goodboy Galaxy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It has the following features:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Persistent state (board remains intact when you navigate away from the app)&lt;/li&gt;
&lt;li&gt;Extremely low …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;Now that the game is out, I can finally talk about this!&lt;/p&gt;
&lt;p&gt;My magnum opus:&lt;/p&gt;
&lt;img alt="A photo of a blue Game Boy Advance running Poop Sweeper, a Minesweeper clone in the pause menu of Goodboy Galaxy, set against a Windows XP style background." class="align-center" src="https://exelo.tl/media/2023/sweep.jpg" style="width: 520px;" /&gt;
&lt;p&gt;Poop Sweeper is a faithful clone of Minesweeper&lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt; in the pause menu of &lt;a class="reference external" href="https://goodboygalaxy.com/"&gt;Goodboy Galaxy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It has the following features:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Persistent state (board remains intact when you navigate away from the app)&lt;/li&gt;
&lt;li&gt;Extremely low memory footprint (96 bytes of RAM while the app is closed)&lt;/li&gt;
&lt;li&gt;Supports flagging and 'chording'&lt;/li&gt;
&lt;li&gt;Status bar w/ cute animations&lt;/li&gt;
&lt;li&gt;Saves the player's best time and number of wins&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This was really fun to make and I got to take a deep dive into Minesweeper mechanics while doing so, so I wanted to talk a bit about how I did it!&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;div class="contents topic" id="contents"&gt;
&lt;p class="topic-title"&gt;&lt;a class="reference internal" href="#top"&gt;Contents&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#recap" id="toc-entry-1"&gt;Recap!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#data-representation" id="toc-entry-2"&gt;Data representation&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#ugly-macro-usage" id="toc-entry-3"&gt;Ugly macro usage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#board-utilities" id="toc-entry-4"&gt;Board utilities&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#populating-the-board" id="toc-entry-5"&gt;Populating the board&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#the-floodfill-algorithm" id="toc-entry-6"&gt;The floodfill algorithm&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#detonation-hack" id="toc-entry-7"&gt;Detonation hack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#auto-removal-of-false-flags" id="toc-entry-8"&gt;Auto-removal of false flags&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#player-input" id="toc-entry-9"&gt;Player input&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#rendering-the-board" id="toc-entry-10"&gt;Rendering the board&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#chording" id="toc-entry-11"&gt;Chording&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#conclusion" id="toc-entry-12"&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;hr class="docutils" /&gt;
&lt;div class="section" id="recap"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#recap"&gt;Recap!&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Minesweeper is a game of logic and probability. There are a number of mines hidden on the board. The goal is to reveal all squares that don't have mines.&lt;/p&gt;
&lt;p&gt;All the while, there's a timer counting up. The faster you win, the better!&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="An animated gif demonstrating Poop Sweeper gameplay. A few mistakes are made along the way but I notice and correct them, and end up winning the game in 31 seconds." class="crisp" src="https://exelo.tl/media/2023/gbg174b.gif" style="height: 320px;" /&gt;
&lt;p class="caption"&gt;You're doin' it! You're winnin' the video game!&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Each turn, you reveal a square of your choice:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;If the square contains a mine, then you lose!&lt;/li&gt;
&lt;li&gt;If the square is touching one or more mines, it tells you how many mines it's touching.&lt;/li&gt;
&lt;li&gt;If the square isn't touching any mines, then all squares around it are revealed too.&lt;ul&gt;
&lt;li&gt;This happens recursively, so revealing a safe square in an ocean of safe squares can cause a large portion of the board to be revealed, akin to using the 'floodfill' tool in a painting program.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can also 'flag' a square if you believe it contains a mine. The game then won't let you reveal it by accident. You can unflag it at any time.&lt;/p&gt;
&lt;p&gt;If you lose, the game will reveal all the other mines on the field. The mine that killed you will be marked in red. Additionally, any incorrectly flagged squares will be marked by an 'X'.&lt;/p&gt;
&lt;p&gt;Finally, there's a lesser-known technique known as &lt;cite&gt;chording&lt;/cite&gt;. If you click an already-revealed numbered square &lt;em&gt;and&lt;/em&gt; it's touching the correct number of flags, then the game will reveal all squares neighbouring it (except for the ones that were flagged). This is a neat time-saver, but it can kill you if you flagged any of the neighbours incorrectly.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="An animated gif of Poop Sweeper gameplay in which I place a flag on two squares, one of which is incorrect. I then click on a square numbered '2' and accidentally detonate a nearby mine because it wasn't flagged." class="crisp" src="https://exelo.tl/media/2023/gbg174c.gif" style="height: 320px;" /&gt;
&lt;p class="caption"&gt;Me losing by chording near a falsely flagged cell.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;On PC versions of Minesweeper, chording requires you to click with both mouse buttons at the same time, which is probably why it's not well-known by casual players.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="data-representation"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#data-representation"&gt;Data representation&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The trick to having a small global memory footprint is to cram the state for each square into 1 byte.&lt;/p&gt;
&lt;p&gt;To do this I used Nim's bitsize pragma, which corresponds to C's bitfield structs. I don't actually like these anymore&lt;a class="footnote-reference" href="#footnote-2" id="footnote-reference-2"&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;, would rather do bitwise ops on integers. But oh well, here we are.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Square&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;object&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;isMine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sx"&gt;{.bitsize&lt;/span&gt;:&lt;span class="sx"&gt;1.}&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="c"&gt;# there&amp;#39;s a mine here&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;isRevealed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sx"&gt;{.bitsize&lt;/span&gt;:&lt;span class="sx"&gt;1.}&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="c"&gt;# this square has been revealed&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;isFlagged&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sx"&gt;{.bitsize&lt;/span&gt;:&lt;span class="sx"&gt;1.}&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c"&gt;# this square is flagged&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;isDetonated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sx"&gt;{.bitsize&lt;/span&gt;:&lt;span class="sx"&gt;1.}&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c"&gt;# colour us red (we killed the player)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;mineNeighbours&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sx"&gt;{.bitsize&lt;/span&gt;:&lt;span class="sx"&gt;4.}&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uint8&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c"&gt;# how many mines are we touching&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;GameState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;enum&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;gsFresh&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;gsPlaying&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;gsDead&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;gsWon&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;gsResetting&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;MinesweeperBoard&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;object&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;squares&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;array&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;BoardWidth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BoardHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Square&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;gameState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GameState&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;gotNewRecord&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;gameTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uint16&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;cursorPoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Vec2i&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c"&gt;# position on the board of squares&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sx"&gt;{.ewdata.}&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MinesweeperBoard&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c"&gt;# declare the board (always in memory)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Each &lt;tt class="docutils literal"&gt;Square&lt;/tt&gt; holds everything needed to draw itself correctly, which happens to be almost everything needed by the game logic too.&lt;/p&gt;
&lt;!-- (Well, except for one thing I'll explain when we get to the floodfill algorithm.) --&gt;
&lt;p&gt;There's also a load of data used only while the app is actually open (dw about it too much!):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;SquarePos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;object&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sx"&gt;{.bitsize&lt;/span&gt;:&lt;span class="sx"&gt;4.}&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uint8&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sx"&gt;{.bitsize&lt;/span&gt;:&lt;span class="sx"&gt;4.}&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uint8&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;MinesweeperState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;enum&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;psInactive&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;psBegin&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;psCopyPopupTiles&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;psReady&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;psAnimateIn&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;psShowing&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;psAnimateOut&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;psDone&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;MinesweeperVars&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;object&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;shouldHide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MinesweeperState&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;nextTid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AppTransition&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;squaresFlagged&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;squaresRevealed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pointsToCheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;BoardWidth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BoardHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SquarePos&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c"&gt;# a stack for the floodfill&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;squareObj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ObjAttr&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;maxwell&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Sprite&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Sprite&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;timerWidget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TimerWidget&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;cursorPressed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;cursorPressRangeX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Slice&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;cursorPressRangeY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Slice&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;timeUntilReset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;resetWipeX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;resetWipeTicker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;messageTid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;messageTextBuffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;array&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;char&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;messageTextDirty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ptr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MinesweeperVars&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c"&gt;# heap-allocated, uses no memory while app is closed&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="ugly-macro-usage"&gt;
&lt;h3&gt;&lt;a class="headerlink" href="#ugly-macro-usage"&gt;Ugly macro usage&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To save keystrokes while working with all this game state, I use a macro called &lt;tt class="docutils literal"&gt;extract&lt;/tt&gt; which I stole from PHP (my enemy!!). PHP's version takes a hash table and magically turns all the fields into local variables (at runtime! disgusting!)&lt;/p&gt;
&lt;p&gt;My version is a bit different, and can be reasoned about statically:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c"&gt;# now, let&amp;#39;s pretend these fields&lt;/span&gt;
&lt;span class="n"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c"&gt;#  are all top-level variables!&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It &lt;a class="reference external" href="https://nim-lang.org/docs/manual_experimental.html#symbols-as-templateslashmacro-calls-alias-syntax"&gt;aliases&lt;/a&gt; all fields of an object, allowing e.g. &lt;tt class="docutils literal"&gt;squares&lt;/tt&gt; as a shorthand for &lt;tt class="docutils literal"&gt;board.squares&lt;/tt&gt;. Neatly this means I can heap-allocate &lt;tt class="docutils literal"&gt;MinesweeperVars&lt;/tt&gt; without making the code any less readable.&lt;/p&gt;
&lt;p&gt;The downside is I have to remember that &lt;tt class="docutils literal"&gt;vars&lt;/tt&gt; could be &lt;tt class="docutils literal"&gt;nil&lt;/tt&gt;, and the danger is hidden&lt;a class="footnote-reference" href="#footnote-3" id="footnote-reference-3"&gt;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt; because it doesn't look like anything is being dereferenced.&lt;/p&gt;
&lt;p&gt;Personally I think the footgun is worth it for the productivty boost, but ymmv. Rust programmers please don't kill me :')&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="board-utilities"&gt;
&lt;h3&gt;&lt;a class="headerlink" href="#board-utilities"&gt;Board utilities&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;squares&lt;/tt&gt; is a 1D array that's intended to be treated as 2D, like &lt;tt class="docutils literal"&gt;squares[x + y*w]&lt;/tt&gt;. I made some convenience procs to access a square by vector or by individual coordinates. These have a &lt;cite&gt;var&lt;/cite&gt; return value, so you can mutate the board with them like &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;get(x,y).isMine&lt;/span&gt; = true&lt;/tt&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;proc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Vec2i&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Square&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;squares&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;BoardWidth&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;proc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Square&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;squares&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;BoardWidth&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Also, I commonly needed to act on all 8 neighbours of a square. This is more annoying than it sounds, because you have to consider the edges of the board too. I ended up with a template which substitutes the body of code 8 times...&lt;/p&gt;
&lt;p&gt;&lt;em&gt;While writing this post I realised an iterator would be a more idiomatic way to achieve the exact same thing, so let's pretend I did that instead:&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;iterator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;neighbours&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;tuple&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ny&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BoardWidth&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BoardHeight&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BoardWidth&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BoardHeight&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BoardWidth&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BoardHeight&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="populating-the-board"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#populating-the-board"&gt;Populating the board&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When the player reveals their first square, it would suck if they immediately got a mine and game-overed. Also it'd be unfair if the first square was already neighbouring a mine.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="Animated gif where I start a game of Poop Sweeper and immediately get a square numbered '1', which means I now know almost nothing about the board! I then get another '1' and on the 3rd move I lose the game due to pure bad luck." class="crisp" src="https://exelo.tl/media/2023/poopsweeper_bad_start.gif" style="height: 320px;" /&gt;
&lt;p class="caption"&gt;Wow, this sucks!&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;For this reason, we wait for the player to make their first move before populating the board with mines. While placing mines, we pick random coordinates, but re-roll if we landed within the 3x3 area surrounding the first square. On later iterations it's also possible to pick a square that already has a mine, so we have to re-roll in that case too.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;proc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;startGame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Vec2i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;squares&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c"&gt;# setup board:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NumMines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BoardWidth&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BoardHeight&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;isMine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c"&gt;# Put a mine only if not near the starting square and not already a mine.&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;isMine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ny&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;neighbours&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;inc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ny&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;mineNeighbours&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;inc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;gameTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;gameState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gsPlaying&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;gotNewRecord&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;squaresFlagged&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;squaresRevealed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="the-floodfill-algorithm"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#the-floodfill-algorithm"&gt;The floodfill algorithm&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When you reveal a square in Minesweeper, as long as that square is not neighbouring any mines, the game should recursively reveal all neighbouring squares.&lt;/p&gt;
&lt;p&gt;Recursion is kinda expensive though! Fortunately, we have our 1st year Computer Science knowledge to fall back on: &lt;em&gt;any time you have a recursive algorithm, you can rewrite it without recursion by using a loop and a stack&lt;/em&gt;&lt;a class="footnote-reference" href="#footnote-4" id="footnote-reference-4"&gt;&lt;sup&gt;4&lt;/sup&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="An animated gif where instead of revealing the entire safe area at once, you can see the floodfill algorithm gradually revealing 1 square at a time." class="crisp" src="https://exelo.tl/media/2023/poopsweeper_floodfill.gif" style="height: 320px;" /&gt;
&lt;p class="caption"&gt;Here's it working in slow motion!&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;So first up, the stack, which I named &lt;tt class="docutils literal"&gt;pointsToCheck&lt;/tt&gt;. It holds the coordinates of squares to be revealed (whose neighbours will then be added, and so on). As the board is only 14x6, you can use 4 bits per coordinate (see &lt;tt class="docutils literal"&gt;SquarePos&lt;/tt&gt; further up), so it ends up very compact!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;proc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pushPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SquarePos&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uint8&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uint8&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;pointsToCheck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;proc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pushPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Vec2i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;pushPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;proc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;popPoint&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Vec2i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pointsToCheck&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pointsToCheck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;dec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pointsToCheck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If you process all neighbours of a square, then all the neighbours' neighbours, you'll find many of them overlap. This is a problem! If you allow squares to be processed more than once, the algorithm will never end (until you eventually run out of stack space).&lt;/p&gt;
&lt;p&gt;So a strategy is needed to ensure each square is only checked once. In my case I noticed &lt;tt class="docutils literal"&gt;isDetonated&lt;/tt&gt; is never true for any square at this moment in the game&lt;a class="footnote-reference" href="#footnote-5" id="footnote-reference-5"&gt;&lt;sup&gt;5&lt;/sup&gt;&lt;/a&gt;, so I aliased it to &lt;tt class="docutils literal"&gt;checking&lt;/tt&gt; for this code only.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c"&gt;# repurpose `isDetonated` as a way to prevent a square&lt;/span&gt;
&lt;span class="c"&gt;# from being added twice to the floodfill.&lt;/span&gt;
&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;checking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Square&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ptr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Square&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isDetonated&lt;/span&gt;
&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;`checking=`&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Square&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ptr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Square&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isDetonated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With that out the way, here's the code to reveal a square!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;proc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;revealSquare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Vec2i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;isMine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;isDetonated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;gameOver&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;# Floodfill&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pushPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pointsToCheck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;popPoint&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;addr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isMine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mineNeighbours&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ny&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;neighbours&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;neighbour&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;addr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ny&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;neighbour&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isRevealed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;neighbour&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checking&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;neighbour&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checking&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;pushPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ny&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isRevealed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;inc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;squaresRevealed&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isRevealed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c"&gt;# auto-remove false flags (this behaviour is like GNOME Mines)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isFlagged&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isFlagged&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;dec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;squaresFlagged&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mitems&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;squares&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isMine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;discard&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="c"&gt;# keep mines marked as &amp;quot;detonated&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checking&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It basically just reveals whatever square is at the top of the stack, and if it's a blank square, adds all its neighbours to the stack. This is repeated until the stack is empty.&lt;/p&gt;
&lt;p&gt;There are two discrepancies which I'll now explain.&lt;/p&gt;
&lt;div class="section" id="detonation-hack"&gt;
&lt;h3&gt;&lt;a class="headerlink" href="#detonation-hack"&gt;Detonation hack&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Right at the end, the &lt;tt class="docutils literal"&gt;checking&lt;/tt&gt; flags are cleared in preparation for the next move.&lt;/p&gt;
&lt;p&gt;I said &lt;tt class="docutils literal"&gt;isDetonated&lt;/tt&gt; will never be true during a floodfill, but that turned out to be not quite right! &lt;em&gt;Chording&lt;/em&gt; can cause more cells to be revealed after a mine was detonated.&lt;/p&gt;
&lt;p&gt;Since &lt;tt class="docutils literal"&gt;checking&lt;/tt&gt; uses the same bit as &lt;tt class="docutils literal"&gt;isDetonated&lt;/tt&gt;, this meant there was a subtle visual bug where mines could become un-detonated. Luckily a mine can never be &lt;tt class="docutils literal"&gt;checking&lt;/tt&gt;, so we can just not clear that bit for mines.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="auto-removal-of-false-flags"&gt;
&lt;h3&gt;&lt;a class="headerlink" href="#auto-removal-of-false-flags"&gt;Auto-removal of false flags&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Something not mentioned in any of my reading material was how the floodfill algorithm should interact with falsely-flagged squares. I tested in a few Minesweeper clones, and got different results in each:&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;video style="height:240px" muted controls&gt;
  &lt;source src="https://exelo.tl/media/2023/minesweeper_false_flags_1.mp4"&gt;
&lt;/video&gt;
&lt;video style="height:240px" muted controls&gt;
  &lt;source src="https://exelo.tl/media/2023/minesweeper_false_flags_2.mp4"&gt;
&lt;/video&gt;
&lt;/div&gt;&lt;p&gt;I figured this would be worth asking a Minesweeper veteran about. I stumbled across the &lt;a class="reference external" href="https://minesweepergame.com/"&gt;minesweepergame.com&lt;/a&gt; IRC channel on NewNet. Would anyone still be there?&lt;/p&gt;
&lt;pre class="literal-block"&gt;
exelotl (quassel&amp;#64;newnet-iis.p62.16.154.IP) has joined #minesweeper
Mode #minesweeper +nt by nyc.newnet.net
Channel #minesweeper created on 2022-05-29 17:54:30 UTC
&amp;lt;exelotl&amp;gt; hi!
&amp;lt;exelotl&amp;gt; I'm making a minesweeper clone, and was wondering if falsely flagged tiles are
          supposed to become automatically unflagged if they get uncovered by the flood-
          fill algorithm?
&amp;lt;exelotl&amp;gt; I've played 2 different games and each seems to handle this case differently
&amp;lt;aradesh&amp;gt; hi
&amp;lt;aradesh&amp;gt; they remain flagged and unopened
&amp;lt;aradesh&amp;gt; but the squares around it open
&amp;lt;aradesh&amp;gt; if you go to the minesweeper rankings website and to official clones
&amp;lt;aradesh&amp;gt; you'll see they all behave the same way
&amp;lt;exelotl&amp;gt; ah, thanks! yep that seems to be the most common behaviour by far
&lt;/pre&gt;
&lt;p&gt;Yes, it turns out! There were people, and I got an answer within a couple of hours! Yay for IRC!&lt;/p&gt;
&lt;p&gt;The verdict is, official Minesweeper games will never remove your flags, even if the flags are clearly wrong.&lt;/p&gt;
&lt;p&gt;Ultimately I decided to &lt;em&gt;ignore&lt;/em&gt; this rule and let the floodfill un-flag them, because spotting and removing them yourself is just busywork. Your speed is already hindered when playing on a GBA with a d-pad, so I figured it won't hurt to cut the player some slack here.&lt;/p&gt;
&lt;p&gt;This behaviour also matches the Mines app available in my system package manager, so there you have it. Poop Sweeper may not be a 100% faithful Minesweeper clone but it &lt;em&gt;is&lt;/em&gt; a faithful GNOME Mines clone. ;)&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="player-input"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#player-input"&gt;Player input&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There's some nuance to the controls, the code isn't all that interesting but basically you have to wait until the player releases (A) before making the move. While (A) is held, the avatar in the top displays a 'thinking' face. If another button is pressed while holding (A), it cancels the move, so releasing (A) then does nothing.&lt;/p&gt;
&lt;p&gt;For chording, I thought about requiring the player to press both (A)+(B), like on PC, but this felt unnecessarily obtuse.&lt;/p&gt;
&lt;p&gt;Therefore, releasing (A) can cause 3 things to happen:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;if this is the first move, populate the board then reveal the square&lt;/li&gt;
&lt;li&gt;if the square is already revealed, attempt chording&lt;/li&gt;
&lt;li&gt;otherwise, just reveal the square&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gameState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gsFresh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;startGame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cursorPoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;revealSquare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cursorPoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cursorPoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isRevealed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;tryChording&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cursorPoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;elif&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cursorPoint&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;isFlagged&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;revealSquare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cursorPoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="rendering-the-board"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#rendering-the-board"&gt;Rendering the board&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I just used a hundred sprites, keep it simple. x)&lt;/p&gt;
&lt;p&gt;Ok I'll elaborate: The GBA supports tilemaps, which could in principle work for a game like this. We use one for the backing board:&lt;/p&gt;
&lt;img alt="A picture of the blank Poop Sweeper GUI with no text or mines on it. This is the actual asset that's used in the game." class="crisp align-center" src="https://exelo.tl/media/2023/pda_window_poopsweep.png" style="width: 512px;" /&gt;
&lt;p&gt;However, we can't easily use tiles for mines, numbers, etc. as GBA tiles are 8x8px, but our squares are 12x12px. There's a trick you can do with layering two maps slightly offset from each other to construct a map with 12x12px tiles. &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Puyo_Pop_(video_game)"&gt;Puyo Pop&lt;/a&gt; does this &lt;em&gt;(amazing game btw!)&lt;/em&gt;, but it seemed like an overcomplicated solution here.&lt;/p&gt;
&lt;p&gt;The GBA supports up to 128 sprites. Rendering the entire board requires 84 sprites, and a few more are needed for text labels and some other UI elements. It's tight, but we have just enough resources to pull it off!&lt;/p&gt;
&lt;div class="twocol docutils container"&gt;
&lt;div class="leftside docutils container"&gt;
&lt;div class="figure align-left"&gt;
&lt;img alt="The NO$GBA OAM viewer window, which displays a grid of 128 thumbnails, one for each sprite drawn on the GBA screen. Some of the boxes are struck-through, to indicate that those sprites are currently unused/invisible." src="https://exelo.tl/media/2023/poopsweeper_oam2.png" style="width: 582px;" /&gt;
&lt;p class="caption"&gt;Screenshot from NO$GBA showing the contents of &lt;abbr title="Object Attribute Memory"&gt;OAM&lt;/abbr&gt;. &lt;br/&gt; Each box corresponds to a single sprite on-screen.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="rightside docutils container"&gt;
&lt;div class="figure align-right"&gt;
&lt;img alt="A series of 8x8 pixel tiles arranged in a grid (2 tiles wide and 13 tiles high). Every cluster of 2x2 tiles has a single minesweeper square on it (with padding as the square doesn't use the entire 16x16 pixel area)." src="https://exelo.tl/media/2023/poopsweeper_vram.png" style="height: 443px;" /&gt;
&lt;p class="caption"&gt;Tile arrangement &lt;br/&gt; in Obj-VRAM.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;div style="clear: both;"&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Finally, rendering the title and status bar text is done with &lt;a class="reference external" href="https://gbadev.net/tonc/tte.html"&gt;Tonc Text Engine&lt;/a&gt; which expects you to prepare regions of the map to hold unique tiles. Here's an illustration of how the tiles are arranged:&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="A close-up of the game's UI showing where the title and status bar text are rendered. The relevant tiles where text would go are numbered in a column-major order (so first column is 1,2,3, second is 4,5,6, and so on)." src="https://exelo.tl/media/2023/poopsweeper_tte_layout.png" /&gt;
&lt;p class="caption"&gt;How tile IDs are arranged for rendering text. (the unlabeled tiles have IDs too, but for &lt;br/&gt; them it's ok if the same tile is reused multiple times, and their order doesn't matter)&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Later in Goodboy development I made it possible to define these tile regions during asset conversion, so you don't have to deal with preparing the map at runtime (and can save a bit of VRAM in the process!). Unfortunately Poop Sweeper was too early to benefit from this change, but it's no big deal.&lt;/p&gt;
&lt;!-- Some regions on the map are defined such that they always hold unique tiles. This allows us to render the "Poop Sweeper" title text and the contents of the status bar.  [oh, this isn't true, pda_window_poopsweep doesn't use regions.] :( --&gt;
&lt;!-- And that's about everything! I guess I didn't cover saving, or how the pause menu itself works, --&gt;
&lt;/div&gt;
&lt;div class="section" id="chording"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#chording"&gt;Chording&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Aw yeah, finally the secret technique to becoming a Minesweeper pro.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="An animated gif showing a Poop Sweeper game where the player is clicking on the numbered tiles. The neighbouring tiles depress, to show that the game is attempting to do chording, but no new tiles are revealed." class="crisp" src="https://exelo.tl/media/2023/poopsweeper_chording.gif" style="height: 320px;" /&gt;
&lt;p class="caption"&gt;Nothing happens, because we didn't put down the right number of flags...&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Let's review the rules on chording:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;When you click a square with a number on it...&lt;/li&gt;
&lt;li&gt;If the number of flagged neighbours matches the number on the square&lt;/li&gt;
&lt;li&gt;Then the game should reveal all neighbouring squares (except for those flagged)&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;proc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;tryChording&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Vec2i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;addr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c"&gt;# 1. Check that the tile is revealed and has a number on it.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isRevealed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mineNeighbours&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;# 2. Verify number of flags == number of neighbours displayed.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;numFlags&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ny&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;neighbours&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ny&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;isFlagged&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;inc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;numFlags&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;numFlags&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mineNeighbours&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c"&gt;# 3. For each neighbour, force reveal it, as long as it&amp;#39;s not flagged.&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ny&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;neighbours&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ny&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;isFlagged&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="n"&gt;revealSquare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vec2i&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ny&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There was another small bug here. As mentioned, I allowed the floodfill to uncover false flags. &lt;em&gt;But you actually don't want that, if the false flag was the reason you lost&lt;/em&gt; - you wanna see where you went wrong!&lt;/p&gt;
&lt;p&gt;To fix this, before revealing the neighbours, I record the positions of any neighbours that are flagged. Afterwards, if we lost, I set them to flagged just in case their flag got removed:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c"&gt;# Small workaround to preserve falsely-flagged tiles if we lost.&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;falseFlags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SquarePos&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ny&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;neighbours&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ny&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;isFlagged&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ny&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;isMine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;falseFlags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SquarePos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ny&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# 3. For each neighbour, force reveal it, as long as it&amp;#39;s not flagged.&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ny&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;neighbours&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ny&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;isFlagged&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;revealSquare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vec2i&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ny&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c"&gt;# Apply aforementioned workaround.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gameState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gsDead&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;falseFlags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;isFlagged&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;One could argue I shouldn't allow &lt;em&gt;any&lt;/em&gt; flags to be uncovered on the losing move. But this solution was good enough for me, so that's how it is!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I hope you enjoyed this deep dive into 1% of Goodboy Galaxy!&lt;/p&gt;
&lt;p&gt;For the most part it's a game about exploring, shooting beasties and making friends, but we did our best to fill it with little surprises like this along the way x)&lt;/p&gt;
&lt;p&gt;If that sounds like your cup of tea then please &lt;a class="reference external" href="https://goodboygalaxy.itch.io/goodboy-galaxy-gba"&gt;buy the game&lt;/a&gt;, tell your friends, and wishlist it on &lt;a class="reference external" href="https://store.steampowered.com/app/2705890/Goodboy_Galaxy/"&gt;Steam&lt;/a&gt;, so we can keep doing more stuff like this!&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;p&gt;&lt;strong&gt;Testimonials&lt;/strong&gt;&lt;/p&gt;
&lt;div class="footnotes docutils container"&gt;
&lt;p&gt;&lt;em&gt;Poop Sweeper is too addictive. It's perfect.&lt;/em&gt; — e9&lt;/p&gt;
&lt;p&gt;&lt;em&gt;poopy sweeper is goty on its own already xD&lt;/em&gt; — Crystal&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Shouldn't you be working on the actual game?&lt;/em&gt; — my Dad&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This game got me addicted to minesweeper!&lt;/em&gt; — Clipper152&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Goodboy Galaxy and Witch'n'wiz are not enough for me, even together. But Poop Sweeper is changing everything.&lt;/em&gt; — Michal Bernat&lt;/p&gt;
&lt;/div&gt;
&lt;hr class="docutils" /&gt;
&lt;p&gt;Comment on the &lt;a class="reference external" href="https://forum.gbadev.net/topic/42-how-i-made-poop-sweeper"&gt;GBAdev Forums&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Footnotes&lt;/strong&gt;&lt;/p&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-1"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Actually a faithful clone of &lt;a class="reference external" href="https://gitlab.gnome.org/GNOME/gnome-mines"&gt;GNOME Mines&lt;/a&gt; due to one subtlety which I'll explain later.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-2" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-2"&gt;[2]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Compared to bitwise operators, C compilers &lt;a class="reference external" href="https://lwn.net/Articles/478657/"&gt;aren't very good&lt;/a&gt; at optimising bitfields. Also, Nim has trouble getting the &lt;a class="reference external" href="https://github.com/nim-lang/Nim/issues/19040"&gt;size of a bitfield struct&lt;/a&gt; at compile time. I could achieve readable code without these problems by using &lt;a class="reference external" href="https://nim-lang.org/docs/manual.html#types-distinct-type"&gt;distinct types&lt;/a&gt; with getters/setters for each bit.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-3" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-3"&gt;[3]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;If you think this is terrible, maybe you wanna try &lt;a class="reference external" href="https://ziglang.org/"&gt;Zig&lt;/a&gt; or &lt;a class="reference external" href="https://harelang.org/"&gt;Hare&lt;/a&gt; which go out of their way to ensure such spooky-action-at-a-distance is impossible.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-4" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-4"&gt;[4]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;After all, that's what recursion is — running the same code repeatedly while using the call stack to retain state from previous invocations.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-5" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-5"&gt;[5]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;The purpose of &lt;cite&gt;isDetonated&lt;/cite&gt; is only to colour a mine red, to indicate it was the one that killed you.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="blog"></category></entry><entry><title>Wow, a note!</title><link href="https://exelo.tl/2022-12-24.html" rel="alternate"></link><published>2022-12-24T00:00:00+00:00</published><updated>2022-12-24T00:00:00+00:00</updated><author><name>gecko@exelo.tl</name></author><id>tag:exelo.tl,2022-12-24:/2022-12-24.html</id><summary type="html">&lt;p&gt;Well, I finally redid my website!&lt;/p&gt;
&lt;p&gt;It's gonna be like a blog, but also a place for short updates, and anyone can subscribe via the &lt;a class="reference external" href="/feed/atom.xml"&gt;atom feed&lt;/a&gt; if they'd like to hear what I'm up to.&lt;/p&gt;
&lt;p&gt;I dug up some old articles of mine from around the web, but they …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Well, I finally redid my website!&lt;/p&gt;
&lt;p&gt;It's gonna be like a blog, but also a place for short updates, and anyone can subscribe via the &lt;a class="reference external" href="/feed/atom.xml"&gt;atom feed&lt;/a&gt; if they'd like to hear what I'm up to.&lt;/p&gt;
&lt;p&gt;I dug up some old articles of mine from around the web, but they were kinda long, and it felt like whatever I posted next would have to live up to them! But maybe if I were to make a completely underwhelming post like this one, that might break the ice.&lt;/p&gt;
</content><category term="note"></category></entry><entry><title>Nim Tilengine Bindings</title><link href="https://exelo.tl/nim-tilengine.html" rel="alternate"></link><published>2022-11-01T00:00:00+00:00</published><updated>2022-11-01T00:00:00+00:00</updated><author><name>gecko@exelo.tl</name></author><id>tag:exelo.tl,2022-11-01:/nim-tilengine.html</id><summary type="html">&lt;p&gt;&lt;a class="reference external" href="https://www.tilengine.org/"&gt;Tilengine&lt;/a&gt; by Megamarc is a free, cross-platform software rendering engine for retro-style 2D games using tilemaps, sprites and palettes. Many oldschool tricks are supported such as palette-based animation and per-scanline raster effects. The implementation is efficient and can run well even on a Raspberry Pi.&lt;/p&gt;
&lt;p&gt;Last month I put together …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="https://www.tilengine.org/"&gt;Tilengine&lt;/a&gt; by Megamarc is a free, cross-platform software rendering engine for retro-style 2D games using tilemaps, sprites and palettes. Many oldschool tricks are supported such as palette-based animation and per-scanline raster effects. The implementation is efficient and can run well even on a Raspberry Pi.&lt;/p&gt;
&lt;p&gt;Last month I put together &lt;a class="reference external" href="https://sr.ht/~exelotl/nim-tilengine/"&gt;nim-tilengine&lt;/a&gt;, allowing you to use Tilengine with the Nim programming language.&lt;/p&gt;
&lt;p&gt;These bindings strip away all the cruft from the C API, leading to code that's shorter and more readable than even the official Python bindings, while keeping native performance!&lt;/p&gt;
&lt;div class="section" id="quick-example"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#quick-example"&gt;Quick Example&lt;/a&gt;&lt;/h2&gt;
&lt;img alt="Screenshot of a cave level from Goodboy Galaxy running in an SDL window." class="align-center" src="https://exelo.tl/media/2022/tileengine.png" style="width: 420px;" /&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tilengine&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;320&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;numLayers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;numSprites&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;numAnimations&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loadTilemap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;assets/map.tmx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Layer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;setTilemap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;createWindow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;processWindow&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;drawFrame&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;deleteWindow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;deinit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
</content><category term="blog"></category></entry><entry><title>Tools behind the Goodboy Galaxy demo</title><link href="https://exelo.tl/goodboy-demo-tooling.html" rel="alternate"></link><published>2021-08-28T00:00:00+01:00</published><updated>2022-12-29T00:00:00+00:00</updated><author><name>gecko@exelo.tl</name></author><id>tag:exelo.tl,2021-08-28:/goodboy-demo-tooling.html</id><summary type="html">&lt;a class="reference external image-reference" href="https://hotpengu.itch.io/goodboy-galaxy-demo"&gt;
&lt;img alt="A screenshot of Goodboy Galaxy Chapter Zero gameplay. Maxwell is in the caverns surrounded by three enemies, with a floating orange and some gems up in the top-left." class="align-right" src="https://exelo.tl/media/2021/goodboy-demo-en-8.png" /&gt;
&lt;/a&gt;
&lt;p&gt;When I posted about Goodboy Galaxy on the &lt;a class="reference external" href="https://forum.nim-lang.org/t/8375"&gt;Nim forums&lt;/a&gt;, enthus1ast asked:&lt;/p&gt;
&lt;blockquote&gt;
Can you give a little insight what technologies you've used?&lt;/blockquote&gt;
&lt;p&gt;Here's a repost of my answer:&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;hr class="docutils" /&gt;
&lt;p&gt;Our Nim GBA library is called &lt;a class="reference external" href="https://natu.exelo.tl"&gt;Natu&lt;/a&gt;, which includes bindings for various C/asm libraries such as:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/gbadev-org/libtonc"&gt;libtonc&lt;/a&gt; for interacting with the …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;a class="reference external image-reference" href="https://hotpengu.itch.io/goodboy-galaxy-demo"&gt;
&lt;img alt="A screenshot of Goodboy Galaxy Chapter Zero gameplay. Maxwell is in the caverns surrounded by three enemies, with a floating orange and some gems up in the top-left." class="align-right" src="https://exelo.tl/media/2021/goodboy-demo-en-8.png" /&gt;
&lt;/a&gt;
&lt;p&gt;When I posted about Goodboy Galaxy on the &lt;a class="reference external" href="https://forum.nim-lang.org/t/8375"&gt;Nim forums&lt;/a&gt;, enthus1ast asked:&lt;/p&gt;
&lt;blockquote&gt;
Can you give a little insight what technologies you've used?&lt;/blockquote&gt;
&lt;p&gt;Here's a repost of my answer:&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;hr class="docutils" /&gt;
&lt;p&gt;Our Nim GBA library is called &lt;a class="reference external" href="https://natu.exelo.tl"&gt;Natu&lt;/a&gt;, which includes bindings for various C/asm libraries such as:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/gbadev-org/libtonc"&gt;libtonc&lt;/a&gt; for interacting with the hardware and rendering text&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://www.danposluns.com/gbadev/posprintf/index.html"&gt;posprintf&lt;/a&gt; for efficient string formatting&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://maxmod.devkitpro.org/"&gt;maxmod&lt;/a&gt; for music and sound.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We used the devkitARM&lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt; cross compiler toolchain (though plain old embedded ARM GCC can work fine too, so we'll probably switch to that in the future).&lt;/p&gt;
&lt;p&gt;The library itself started as a straight-up C wrapper, but has become more idiomatic since then (more type safe, better naming, using sets instead of ORing together constants, etc.). I'm still trying to clean it up and make it more usable for others, when I can. While a bit outdated already, you can check out my NimConf &lt;a class="reference external" href="https://www.youtube.com/watch?v=sZUM7MhWr88&amp;amp;list=PLxLdEZg8DRwTIEzUpfaIcBqhsj09mLWHx&amp;amp;index=9"&gt;video&lt;/a&gt; from last year if you'd like to see it in action. :)&lt;/p&gt;
&lt;p&gt;Natu works with both &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--gc:none&lt;/span&gt;&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--gc:arc&lt;/span&gt;&lt;/tt&gt;, though arc didn't exist when I started working on Goodboy Galaxy, but I've had success with it for other small projects. Right now everything in the game is on the stack or statically allocated&lt;a class="footnote-reference" href="#footnote-2" id="footnote-reference-2"&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;, which is nice for performance, so the choice of memory management strategy doesn't matter all that much for me. However, I'm envious of the benefits that destructors could bring if I'd written the engine with arc in mind… No more leaks would be great!&lt;/p&gt;
&lt;p&gt;While building the game we implemented many of the systems described in &lt;a class="reference external" href="https://www.gamedeveloper.com/programming/gameboy-advance-resource-management"&gt;Game Boy Advance Resource Management&lt;/a&gt;, such as a tile engine and Obj (sprite) VRAM allocator. This article is absolute gold, I don't know where I'd be without it!&lt;/p&gt;
&lt;p&gt;For level design we use &lt;a class="reference external" href="https://www.mapeditor.org/"&gt;Tiled&lt;/a&gt;. But we can't just parse the XML files at runtime - custom tooling is needed. We devised a level format (in Nim types), and wrote a tool which takes the levels, processes and converts all the maps, tileset graphics etc. and spits out C code and Nim code, allowing that data to be used in the game. Earlier this year I discovered Nim's &lt;a class="reference external" href="https://nim-lang.github.io/Nim/filters.html#available-filters-stdtmpl-filter"&gt;stdtmpl&lt;/a&gt; filter which makes this kind of codegen much more pleasant, so I've started using that where I can.&lt;/p&gt;
&lt;p&gt;One aspect of the game I'm really happy with is the dialogue and cutscene system. I really wanted some sort of coroutines&lt;a class="footnote-reference" href="#footnote-3" id="footnote-reference-3"&gt;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt; so that I could sequence a cutscene using simple procedural code:&lt;/p&gt;
&lt;pre class="code nim literal-block"&gt;
&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runLeft&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="c"&gt;# give control back to the main thread&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="c"&gt;# then resume from here in 2 seconds.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stopRunning&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;chMaxwell&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Oh... Heck.&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;# show a speech box.&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;To achieve this initially I made my own hacked-together version of &lt;abbr title="Continuation Passing Style"&gt;CPS&lt;/abbr&gt; - a macro that splits a block of code into a chain of functions. It was ok at first, but ended up being a buggy nightmare to maintain. Then, by chance a member of the GBAdev community went and implemented &lt;a class="reference external" href="https://github.com/felixjones/agbabi#context-switching"&gt;context switching&lt;/a&gt; for the GBA, which allowed me to adapt Zevv's &lt;a class="reference external" href="https://github.com/zevv/nimcoro"&gt;nimcoro&lt;/a&gt; to get true Lua-style coroutines. After getting this working, I never looked back. it's perfect!&lt;/p&gt;
&lt;p&gt;Oh yeah, I guess I should mention some pain points:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Nim's methods are too heavy for use on embedded platforms - RTTI eats up a ton of RAM and the performance degrades as more subclasses are added. So we had to roll our own VTables instead for things like the entity system.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/nim-lang/RFCs/issues/257#issuecomment-805816584"&gt;Addressable contant data&lt;/a&gt; in Nim is still pretty limited. The improvements to &lt;tt class="docutils literal"&gt;let&lt;/tt&gt; were nice but only work for arrays of ordinal types. So we've mostly resorted to generating all our data as C code to work around this, which is fine once you get used to it.&lt;/li&gt;
&lt;li&gt;Lack of circular imports is super painful, as we need things to talk to each other and can't afford to use event busses and other high-level decoupling techniques that would usually solve this. We often have to resort to hacks involving &lt;tt class="docutils literal"&gt;{.exportc.}&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;{.importc.}&lt;/tt&gt; to work around this problem.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That said, if you sent me back in time by 2 years I would absolutely choose Nim for this project again! It's a joy to work with and I can't think of any other language I'd rather be using today.&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-1"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Later we switched to vanilla embedded ARM GCC (available on most package managers), with the help of &lt;a class="reference external" href="https://github.com/AntonioND/gba-bootstrap"&gt;gba-bootstrap&lt;/a&gt; by AntonioND. I'm so happy with this change, because it simplified installation and allowed us to distance ourselves from devkitPro, who are too gatekeepy for my taste nowadays.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-2" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-2"&gt;[2]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;This quickly changed after the demo, as we kept running out of RAM. So we started to allocate data for minigames, menus etc. on the heap so they'd take up zero memory when not in use. Newlib's malloc was too heavy so we used &lt;a class="reference external" href="https://codeberg.org/pgimeno/ACSL"&gt;ACSL's&lt;/a&gt; instead.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-3" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-3"&gt;[3]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Check my old post &lt;a class="reference external" href="https://exelo.tl/lua-coroutines.html"&gt;using Lua coroutines to create an RPG dialogue system&lt;/a&gt; to see why this is so awesome.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content><category term="blog"></category></entry><entry><title>Things I've learned since 'Goodboy Advance'</title><link href="https://exelo.tl/goodboy-advance.html" rel="alternate"></link><published>2019-12-30T00:00:00+00:00</published><updated>2022-12-31T00:00:00+00:00</updated><author><name>gecko@exelo.tl</name></author><id>tag:exelo.tl,2019-12-30:/goodboy-advance.html</id><summary type="html">&lt;img alt="" class="align-right" src="https://exelo.tl/media/2019/1e09a.png" /&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.gbadev.org/demos.php?showinfo=1486"&gt;Goodboy Advance&lt;/a&gt; was the original jam version of Goodboy Galaxy, which Rik and I hacked together for Ludum Dare one weekend in December 2018.&lt;/p&gt;
&lt;p&gt;It's not that good really... well it was kinda impressive at the time, but it's really missing enemies, characters, and story. If you're looking for a …&lt;/p&gt;</summary><content type="html">&lt;img alt="" class="align-right" src="https://exelo.tl/media/2019/1e09a.png" /&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.gbadev.org/demos.php?showinfo=1486"&gt;Goodboy Advance&lt;/a&gt; was the original jam version of Goodboy Galaxy, which Rik and I hacked together for Ludum Dare one weekend in December 2018.&lt;/p&gt;
&lt;p&gt;It's not that good really... well it was kinda impressive at the time, but it's really missing enemies, characters, and story. If you're looking for a cool GBA homebrew game about a dog in space, you should definitely try &lt;a class="reference external" href="https://hotpengu.itch.io/goodboy-galaxy-demo"&gt;Goodboy Galaxy: Chapter Zero&lt;/a&gt; instead.&lt;/p&gt;
&lt;p&gt;However, if you'd like to know what we used to make the original jam game, and all the things we've learned since that allowed us to go on to make Goodboy Galaxy, then this is the page for you!&lt;/p&gt;
&lt;div class="section" id="how-we-made-it"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#how-we-made-it"&gt;How we made it&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The game was written in C using the devkitARM&lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt; toolchain, with libraries provided by this excellent free book called &lt;a class="reference external" href="http://www.coranac.com/projects/tonc/"&gt;Tonc&lt;/a&gt; which can teach you almost everything you need to know about GBA programming.&lt;/p&gt;
&lt;p&gt;We used &lt;a class="reference external" href="https://maxmod.devkitpro.org/"&gt;Maxmod&lt;/a&gt; for music/sfx playback. Music was composed in MilkyTracker and MadTracker. i.e. we have different weapons of choice, but it doesn't matter because both support the XM format which Maxmod can use.&lt;/p&gt;
&lt;p&gt;Aseprite was used for graphics/animations (having to be mindful of size and palette restrictions etc.). To convert the graphics into the raw formats which can be used by the hardware, we used &lt;a class="reference external" href="http://www.coranac.com/projects/grit/"&gt;Grit&lt;/a&gt; which is powerful but quite difficult to work with because there are so many options and sometimes it's hard to tell which ones do what you want&lt;a class="footnote-reference" href="#footnote-2" id="footnote-reference-2"&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And finally, we made the maps in &lt;a class="reference external" href="https://www.mapeditor.org/"&gt;Tiled&lt;/a&gt;, but it seemed like parsing XML or JSON in C on such limited hardware would be a terrible experience. So instead we made a Node.js script which goes over the Tiled data and generates C code for a given level. That may sound like a big hack but it was really a life saver!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="learnings"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#learnings"&gt;Learnings&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The source code for the jam game can be found on &lt;a class="reference external" href="https://github.com/exelotl/goodboy-advance"&gt;GitHub&lt;/a&gt;. This code was born from the ashes of a Super Crate Box &lt;a class="reference external" href="https://youtu.be/sZUM7MhWr88?t=119"&gt;clone&lt;/a&gt; that I was working on many years ago.&lt;/p&gt;
&lt;p&gt;Here are some things it does poorly, which we've fixed in Goodboy Galaxy and/or you may want to do differently if you're aiming to make something bigger than a jam game:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;strong&gt;All declarations are in 1 header file&lt;/strong&gt;. This was convenient for the jam but wouldn't cut it for a larger project. (Actually for Galaxy we rewrote everything in &lt;a class="reference external" href="https://nim-lang.org/"&gt;Nim&lt;/a&gt; which has a decent module system instead of header files)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;strong&gt;Misuse of VBlank&lt;/strong&gt;. No separation between update &amp;amp; draw means that sometimes the game will perform visual changes (scrolling the background, fading the palette, modifying sprites) while the &lt;abbr title="Picture Processing Unit"&gt;PPU&lt;/abbr&gt; is in the middle of updating the display, leading to tearing artifacts.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Writing a decent GBA main loop is a topic that deserves its own article, but at the very least: &lt;em&gt;&amp;quot;Game logic&amp;quot; -&amp;gt; &amp;quot;Wait for VBlank&amp;quot; -&amp;gt; &amp;quot;Visual updates&amp;quot;&lt;/em&gt; would be a much better structure than what I did here (&lt;em&gt;&amp;quot;Wait for VBlank&amp;quot;&lt;/em&gt; -&amp;gt; &lt;em&gt;&amp;quot;Do everything&amp;quot;&lt;/em&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;strong&gt;Tile/map/palette locations are hardcoded&lt;/strong&gt; or allocated by simple counters. Instead, you might want to use allocators such as the ones presented in &lt;a class="reference external" href="https://www.gamedeveloper.com/programming/gameboy-advance-resource-management"&gt;Gameboy Advance Resource Management&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;strong&gt;Statically allocated arrays&lt;/strong&gt; for each type of entity. Consequently there's a maximum of 20 breakable blocks in a level, 8 bullets alive at a time, etc. This is fine for smaller games (keep it simple!) but doesn't scale well: if the current level doesn't have any breakables, that array of 20 breakables is still taking up RAM for no good reason.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p class="first"&gt;Consider putting all entities in 1 array (an &lt;a class="reference external" href="https://gameprogrammingpatterns.com/object-pool.html"&gt;object pool&lt;/a&gt;) and using some mechanism such as dispatch tables to give different behaviours to different entities.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;On modern hardware, dynamic dispatch can be considered a thing to avoid in the interest of &lt;a class="reference external" href="https://gameprogrammingpatterns.com/data-locality.html"&gt;cache-friendliness&lt;/a&gt;, but on the GBA it turns out that &lt;em&gt;chasing pointers isn't particularly slow&lt;/em&gt; compared to the speed of the processor itself.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;div class="first line-block"&gt;
&lt;div class="line"&gt;More generally, if you're running out of memory you can try moving global variables from &lt;abbr title="Internal Work RAM"&gt;IWRAM&lt;/abbr&gt; into &lt;abbr title="External Work RAM"&gt;EWRAM&lt;/abbr&gt; which is slower to access but more plentiful.&lt;/div&gt;
&lt;div class="line"&gt;e.g. &lt;tt class="docutils literal"&gt;EWRAM_DATA u16 myarray[100];&lt;/tt&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;It's also OK to do dynamic allocation via &lt;tt class="docutils literal"&gt;malloc&lt;/tt&gt;, but try to only use it for data that sticks around for a while but isn't needed permanently, e.g. the state of a boss fight on a particular level. The only trouble is, the default &lt;tt class="docutils literal"&gt;malloc&lt;/tt&gt; implementation from newlib is pretty wasteful (&amp;gt;1KiB static IWRAM usage), so you might want to consider replacing it with a version that's tailored to the GBA, such as the one from &lt;a class="reference external" href="https://codeberg.org/pgimeno/ACSL"&gt;ACSL&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;strong&gt;The wrapping level mechanic&lt;/strong&gt; introduces some nasty bugs. When the player goes beyond the right side of the level, they are teleported to the left side. This can cause objects to vanish or appear out of nowhere. It can also lead to getting stuck in walls or ceiling depending on placement.&lt;/p&gt;
&lt;p&gt;For the jam, we designed the levels to minimise the problem. In Galaxy, we fixed it with more thorough checking and a proper spawn system (which ties into the '1 array of entities' mentioned above).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;strong&gt;Streaming larger level maps&lt;/strong&gt; (bigger than 2x2 screenblocks) - this code works but it's not that good. It uses 4 whole screenblocks which are copied every frame even if nothing changed. Probably need to be smarter about that...&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The way to do this properly is to take advantage of the fact that a single screenblock (tile map region) on the GBA is 2 tiles wider than the size of the GBA screen (and plenty of tiles higher). This means as the camera scrolls you can copy in new rows &amp;amp; columns of map data, to give you an infinitely large map from just a single screenblock. The Tonc &lt;a class="reference external" href="https://github.com/gbadev-org/libtonc-examples/tree/master/lab/bigmap"&gt;bigmap&lt;/a&gt; example demonstrates this. There's also an &lt;a class="reference external" href="https://pastebin.com/rXWxjYiF"&gt;annotated version&lt;/a&gt; of the same code by ColonelSalt which is easier to read.&lt;/li&gt;
&lt;li&gt;You may eventually run into the problem that there are too many unique tile graphics in your level. The maximum is 1024 (as tile IDs in the map are 10-bit). But you might run into problems before then, because 1024 tiles takes up a lot of VRAM! To solve this, you'll need a means of dynamically loading tiles as they scroll onto the screen, and unloading them as they scroll off. Once again, &lt;a class="reference external" href="https://www.gamedeveloper.com/programming/gameboy-advance-resource-management"&gt;GBA Resource Management&lt;/a&gt; explains how to do this.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;strong&gt;Too much DMA&lt;/strong&gt; - the game occasionally crashes on screen transitions on real hardware. I suspect this might be due to me using DMA copies to load absolutely everything. As Tonc says, &lt;a class="reference external" href="https://www.coranac.com/tonc/text/dma.htm#ssec-func-use"&gt;“don't wear it out”&lt;/a&gt;. Perhaps this would cause the game to miss interrupts or fire them too late (Maxmod in particular is picky about VBlank IRQ timing). I'd recommend using memcpy32 for most things instead. (&lt;em&gt;If somebody knows more about this, I'd really like to know!&lt;/em&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;strong&gt;Tooling&lt;/strong&gt;: We have a script that converts Tiled level data into C code. Besides that, we used grit for all our image data, including the level maps themselves. In Galaxy we realised we need more control, so we ditched grit and ended up making a lot more scripts that generate all kinds of data from various config files. It takes time to get this stuff working, but it makes adding content to the game much quicker and less error-prone!&lt;/p&gt;
&lt;p&gt;tl;dr Don't be afraid to write code that spits out more code.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;strong&gt;Polish&lt;/strong&gt;: The game has some niceties such as muzzle flash / impact fx / screenshake, but the position-locked camera and the static background let things down a little. In Galaxy we use HBlank DMA to get some gorgeous &lt;a class="reference external" href="https://youtu.be/sZUM7MhWr88?t=1740"&gt;multi-layer parallax&lt;/a&gt; from just a single background.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;img alt="A gif showing gameplay footage of Goodboy Advance." class="align-center" src="https://exelo.tl/media/2019/1e071.gif" /&gt;
&lt;hr class="docutils" /&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-1"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;devkitARM can be installed from &lt;a class="reference external" href="https://devkitpro.org/wiki/Getting_Started"&gt;here&lt;/a&gt; but nowadays I'd recommend something like &lt;a class="reference external" href="https://github.com/LunarLambda/sdk-seven"&gt;sdk-seven&lt;/a&gt;, &lt;a class="reference external" href="https://github.com/felixjones/gba-toolchain"&gt;gba-toolchain&lt;/a&gt; or &lt;a class="reference external" href="https://github.com/AntonioND/gba-bootstrap"&gt;gba-bootstrap&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-2" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-2"&gt;[2]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="https://github.com/Optiroc/SuperFamiconv"&gt;SuperFamiconv&lt;/a&gt; is often recommended as a more user-friendly and less buggy alternative to grit. I'd recommend trying that first.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="blog"></category></entry><entry><title>Flappy Delivery Co.</title><link href="https://exelo.tl/flappy-delivery-co.html" rel="alternate"></link><published>2018-01-02T00:00:00+00:00</published><updated>2018-01-02T00:00:00+00:00</updated><author><name>gecko@exelo.tl</name></author><id>tag:exelo.tl,2018-01-02:/flappy-delivery-co.html</id><content type="html">&lt;p&gt;&lt;em&gt;A bird delivers a letter - what happens next will shock you!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A little thing I made for a job interview.&lt;/p&gt;
&lt;iframe id="game" src="https://exelo.tl/misc/flappy-delivery-co/" width="660" height="380"&gt;
&lt;/iframe&gt;
&lt;script&gt;
function fwd(e) {
  document.getElementById('game').contentWindow.document.postMessage(JSON.parse(JSON.stringify(e)))
}
document.ontouchstart = fwd
document.onmousedown = fwd
document.onkeydown = fwd
&lt;/script&gt;</content><category term="game"></category></entry><entry><title>An Introduction to Metatables</title><link href="https://exelo.tl/intro-to-metatables.html" rel="alternate"></link><published>2016-06-12T13:06:00+01:00</published><updated>2022-12-26T00:00:00+00:00</updated><author><name>gecko@exelo.tl</name></author><id>tag:exelo.tl,2016-06-12:/intro-to-metatables.html</id><summary type="html">&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;Originally posted on the &lt;a class="reference external" href="https://www.lexaloffle.com/bbs/?tid=3342"&gt;forums&lt;/a&gt; of PICO-8, a 'fantasy console' with limitations inspired by classic 8-bit computers, which uses a modified flavour of Lua 5.2.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Hi folks, this post aims to offer a clear introduction to the topic of
metatables in Lua for those who are not yet …&lt;/p&gt;</summary><content type="html">&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;Originally posted on the &lt;a class="reference external" href="https://www.lexaloffle.com/bbs/?tid=3342"&gt;forums&lt;/a&gt; of PICO-8, a 'fantasy console' with limitations inspired by classic 8-bit computers, which uses a modified flavour of Lua 5.2.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Hi folks, this post aims to offer a clear introduction to the topic of
metatables in Lua for those who are not yet familiar with them.&lt;/p&gt;
&lt;p&gt;Without further ado, let's go!&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;p&gt;A &lt;strong&gt;table&lt;/strong&gt; is a mapping of keys to values. They're explained quite well
in the PICO-8 manual and the Lua reference manual so I won't go into
more detail. In particular you should know that &lt;tt class="docutils literal"&gt;t.foo&lt;/tt&gt; is just a
nicer way of writing &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;t[&amp;quot;foo&amp;quot;]&lt;/span&gt;&lt;/tt&gt; and also that &lt;tt class="docutils literal"&gt;t:foo()&lt;/tt&gt; is a nicer
way of calling the function &lt;tt class="docutils literal"&gt;t.foo(t)&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;metatable&lt;/strong&gt; is a table with some specially named properties defined
inside. You apply a metatable to any other table to change the way that
table behaves. This can be used to:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;define custom operations for your table (+, -, etc.)&lt;/li&gt;
&lt;li&gt;define what should happen when somebody tries to look up a key that
doesn't exist&lt;/li&gt;
&lt;li&gt;specify how your table should be converted to a string (e.g. for
printing)&lt;/li&gt;
&lt;li&gt;change the way the garbage collector treats your table (e.g. &lt;a class="reference external" href="https://www.lua.org/pil/17.html"&gt;tables
with weak keys&lt;/a&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Point #2 is especially powerful because it allows you to set default
values for missing properties, or specify a
&lt;a class="reference external" href="https://en.wikipedia.org/wiki/Prototype-based_programming"&gt;prototype&lt;/a&gt;
object which contains methods shared by many tables.&lt;/p&gt;
&lt;p&gt;You can attach a metatable to any other table using the
&lt;a class="reference external" href="http://www.lua.org/manual/5.2/manual.html#pdf-setmetatable"&gt;setmetatable&lt;/a&gt;
function.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;All possible metatable events are explained on the lua-users wiki:&lt;/div&gt;
&lt;div class="line"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;a class="reference external" href="http://lua-users.org/wiki/MetatableEvents"&gt;list of metatable
events&lt;/a&gt; &amp;lt;&amp;lt;&amp;lt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;which is, as far as I'm aware, the best reference for everything that
metatables can be used for.&lt;/p&gt;
&lt;p&gt;And that's really all you need to know!&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;div class="section" id="vectors-example"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#vectors-example"&gt;Vectors Example&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I'll now demonstrate how metatables could be used to make a &amp;quot;2D
point/vector&amp;quot; type, with custom operators.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;-- define a new metatable to be shared by all vectors&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;mt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="c1"&gt;-- function to create a new vector&lt;/span&gt;
&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;makevec2d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nb"&gt;setmetatable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;-- define some vector operations such as addition, subtraction:&lt;/span&gt;
&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;mt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;__add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;makevec2d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;mt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;__sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;makevec2d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;-- more fancy example, implement two different kinds of multiplication:&lt;/span&gt;
&lt;span class="c1"&gt;-- number*vector -&amp;gt; scalar product&lt;/span&gt;
&lt;span class="c1"&gt;-- vector*vector -&amp;gt; cross product&lt;/span&gt;
&lt;span class="c1"&gt;-- don&amp;#39;t worry if you&amp;#39;re not a maths person, this isn&amp;#39;t important :)&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;mt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;__mul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;number&amp;quot;&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;makevec2d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;elseif&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;number&amp;quot;&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;makevec2d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;-- check if two vectors with different addresses are equal to each other&lt;/span&gt;
&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;mt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;__eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;-- custom format when converting to a string:&lt;/span&gt;
&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;mt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;__tostring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;(&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;, &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;)&amp;quot;&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we can use our newly defined 'vector' type like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;makevec2d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;-- calls __tostring internally, so this prints &amp;quot;(3, 4)&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;-- (6, 8)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;-- (9, 12)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Pretty neat right?&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="object-orientation"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#object-orientation"&gt;Object Orientation&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I mentioned that metatables can be used to define what should happen
when a key lookup fails, and that this can be used to create custom
methods shared by many tables. For example we might want to be able to
do this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;makevec2d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;magnitude&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;-- calculate the length of the vector, returning 5&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In Lua this is not always necessary, for example, we could define an
ordinary function to do the job for us:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;magnitude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="n"&gt;magnitude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;-- returns 5&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In fact, for PICO-8 I would recommend that approach, because it's as
efficient as you can get, and it uses the least number of tokens (PICO-8
cartridges are limited in code size).&lt;/p&gt;
&lt;p&gt;But I think it's educational to see how metatables can make it possible
to use Lua in a more OOP style.&lt;/p&gt;
&lt;p&gt;First off, we define all our methods in a table somewhere. Note, you can
define them in the metatable itself (this is a common convention), but
I'll put them in a different table to prevent confusion.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;magnitude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now back to our metatable, &lt;tt class="docutils literal"&gt;mt&lt;/tt&gt;:&lt;/p&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;__index&lt;/tt&gt; property of a metatable is referred to when you try to
look up a key &lt;tt class="docutils literal"&gt;k&lt;/tt&gt; which is not present in the original table &lt;tt class="docutils literal"&gt;t&lt;/tt&gt;.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;If &lt;tt class="docutils literal"&gt;__index&lt;/tt&gt; is a function, it is called like &lt;tt class="docutils literal"&gt;mt.__index(t, k)&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;If &lt;tt class="docutils literal"&gt;__index&lt;/tt&gt; is a table, a lookup is performed like &lt;tt class="docutils literal"&gt;mt.__index[k]&lt;/tt&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So we can add the magnitude function, along any other methods we may
have defined, to all our existing vector objects by simply setting the
&lt;tt class="docutils literal"&gt;__index&lt;/tt&gt; property to our table of methods:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;mt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;And now, as we wanted, we can call &lt;tt class="docutils literal"&gt;a:magnitude()&lt;/tt&gt;&lt;/div&gt;
&lt;div class="line"&gt;Which is a shortcut for &lt;tt class="docutils literal"&gt;a.magnitude(a)&lt;/tt&gt;&lt;/div&gt;
&lt;div class="line"&gt;Which is a shortcut for &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;a[&amp;quot;magnitude&amp;quot;](a)&lt;/span&gt;&lt;/tt&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Hopefully given all this information, it's clear what's happening: We
never defined a &lt;tt class="docutils literal"&gt;&amp;quot;magnitude&amp;quot;&lt;/tt&gt; property in &lt;tt class="docutils literal"&gt;a&lt;/tt&gt;, so when we try to lookup the
string &lt;tt class="docutils literal"&gt;&amp;quot;magnitude&amp;quot;&lt;/tt&gt;, the lookup fails and Lua refers to the metatable's
&lt;tt class="docutils literal"&gt;__index&lt;/tt&gt; property instead.&lt;/p&gt;
&lt;p&gt;Since &lt;tt class="docutils literal"&gt;__index&lt;/tt&gt; is a table, it looks in there for any property called
&lt;tt class="docutils literal"&gt;&amp;quot;magnitude&amp;quot;&lt;/tt&gt; and finds the magnitude function that we defined. This
function is then called with the parameter &lt;tt class="docutils literal"&gt;a&lt;/tt&gt; which we implicitly
passed when we used the &lt;tt class="docutils literal"&gt;:&lt;/tt&gt; operator.&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;p&gt;Well, that's it from me! I hope somebody finds this post useful, and
please let me know if there is something you don't understand, or
something that I left out or could have explained better. If you'd like
to see more examples of metatable usage and OOP, I recommend chapters
13, 16 and 17 of &lt;a class="reference external" href="https://www.lua.org/pil/contents.html"&gt;Programming in
Lua&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
</content><category term="blog"></category></entry><entry><title>Using Lua coroutines to create an RPG dialogue system</title><link href="https://exelo.tl/lua-coroutines.html" rel="alternate"></link><published>2016-03-28T00:00:00+01:00</published><updated>2022-12-26T00:00:00+00:00</updated><author><name>gecko@exelo.tl</name></author><id>tag:exelo.tl,2016-03-28:/lua-coroutines.html</id><summary type="html">&lt;p&gt;Recently I've been working on an RPG&lt;a class="footnote-reference" href="#rpg" id="footnote-reference-1"&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt; with a friend, for whom coding is
not their strong point. We're using the excellent
&lt;a class="reference external" href="https://love2d.org/"&gt;LÖVE&lt;/a&gt; framework, so the whole game is written
in Lua.&lt;/p&gt;
&lt;p&gt;Scripting of dialogues, animations and in-game events is a hugely
important aspect for any RPG, and I …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Recently I've been working on an RPG&lt;a class="footnote-reference" href="#rpg" id="footnote-reference-1"&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt; with a friend, for whom coding is
not their strong point. We're using the excellent
&lt;a class="reference external" href="https://love2d.org/"&gt;LÖVE&lt;/a&gt; framework, so the whole game is written
in Lua.&lt;/p&gt;
&lt;p&gt;Scripting of dialogues, animations and in-game events is a hugely
important aspect for any RPG, and I wanted to build a scripting system
that's easy to use and doesn't require any knowledge about the rest of
the game's engine, but is also powerful enough for me to extend with new
functionality as needed. This post aims to show how a few simple Lua
features can be combined to create a scripting environment that's
pleasant to use.&lt;/p&gt;
&lt;p&gt;First, let's take a look at the XSE language used in Pokémon modding, as
this was probably my main point of inspiration. It has a very
straightforward, imperative style, even though each instruction doesn't
correspond to a single function in-game.&lt;/p&gt;
&lt;p&gt;By this I mean, the whole game engine doesn't freeze just because you
are talking to an NPC, however there are points at which the dialogue
script cannot progress until the text animations have finished and the
player has pressed the [A] button.&lt;/p&gt;
&lt;img alt="A screenshot of a text editor window. A series of commands have been written to tell an NPC to turn to face the player and say &amp;quot;Hello world!&amp;quot;. A modified Pokemon Emerald is running, demonstrating the script in action." src="https://exelo.tl/media/2016/xse.png" /&gt;
&lt;p&gt;Another interesting tool is Yarn&lt;a class="footnote-reference" href="#yarn" id="footnote-reference-2"&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;, a dialogue editor in
which you connect nodes of text together to form complete conversations.
It has variables, conditionals and custom commands which you can hook up
to different parts of your engine to trigger animations and such. I'd
say it's definitely worth checking out especially if you're using Unity
or similar.&lt;/p&gt;
&lt;p&gt;So how would we go about creating such a system in LÖVE without creating
our own language or writing an interpreter for an existing language such
as Yarn?&lt;/p&gt;
&lt;div class="section" id="part-1-chaining-callbacks-together"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#part-1-chaining-callbacks-together"&gt;Part 1: Chaining Callbacks Together&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The first thing we need is the ability to 'say' some text from inside a
script, which boils down to setting a string and then waiting for the
user to press a button before we resume execution of the script. The
game should still be updating on every frame, even when text is being
displayed.&lt;/p&gt;
&lt;p&gt;In true JavaScript fashion&lt;a class="footnote-reference" href="#async" id="footnote-reference-3"&gt;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt;, we could create an asynchronous API that
looks a bit like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="n"&gt;callback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;callback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cb&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Our game logic &amp;amp; rendering code could look something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;love&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="c1"&gt;-- player movement code&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;love&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;-- code to draw the world goes here&lt;/span&gt;

    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;love&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;graphics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;love&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keypressed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isRepeat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;space&amp;quot;&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
        &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
            &lt;span class="c1"&gt;-- execute the next part of the script&lt;/span&gt;
            &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="kr"&gt;end&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then we could write a dialogue script that looks like this, potentially
fetching it at runtime with a call to &lt;tt class="docutils literal"&gt;dofile()&lt;/tt&gt; or something:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello there!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;How&amp;#39;s it going?&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Well, nice talking to you!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This kind of code grows unwieldy very quickly. It's confusing for
non-coders and also error prone (many places to miss out a comma or a
closing bracket). You could try some variations such as giving a name to
each function, but it still turns out quite unpleasant to work with
because managing all those functions gets in the way of what matters:
writing good dialogue and scenes. At this point we'd surely be better
off writing a Yarn interpreter or using some other existing solution.&lt;/p&gt;
&lt;p&gt;But this is not JavaScript, and we can do better!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="part-2-using-coroutines"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#part-2-using-coroutines"&gt;Part 2: Using Coroutines&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For the uninitiated, coroutines are chunks of code that can be jumped to
much like functions. A coroutine can suspend itself (&lt;tt class="docutils literal"&gt;yield&lt;/tt&gt;) at will,
returning to the point at which it was called. At a later stage, the
program can jump back into the coroutine and &lt;tt class="docutils literal"&gt;resume&lt;/tt&gt; where it left
off.&lt;/p&gt;
&lt;p&gt;I suppose this puts them in a sort of middle ground between functions
and threads. They are more powerful than functions, but you still have
to manage them explicitly - you can't just leave them running in the
background to do their own thing. Typically they are used to break up an
intensive task into small bursts, so that the program can still function
as normal (receive user input, print to console, etc.)&lt;/p&gt;
&lt;p&gt;Hang on a minute, doesn't this sound a lot like what we want from the
dialogue scripting system? Executing a single line and then suspending
the script while we give control back to the game loop?&lt;/p&gt;
&lt;p&gt;Let's see how we could achieve the same result as Part 1, only using a
coroutine instead of a chain of callbacks.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="n"&gt;routine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;str&lt;/span&gt;
    &lt;span class="nb"&gt;coroutine.yield&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;-- load the script and wrap it in a coroutine&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;loadfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;routine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;coroutine.create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;-- begin execution of the script&lt;/span&gt;
    &lt;span class="nb"&gt;coroutine.resume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;routine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The important difference here is the implementation of the &lt;tt class="docutils literal"&gt;say&lt;/tt&gt;
function. Instead of setting a callback for later use, we tell the
current coroutine to yield. This means we can't call say directly from
the main program, only from inside a coroutine. Also there is now a
loader function which creates a new coroutine and tells it to run the
script.&lt;/p&gt;
&lt;p&gt;Next we need to rewrite &lt;tt class="docutils literal"&gt;love.keypressed&lt;/tt&gt; to make it resume the
coroutine on the press of the space bar.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;love&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keypressed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isRepeat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;space&amp;quot;&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;routine&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;coroutine.status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;routine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;~=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;dead&amp;quot;&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
            &lt;span class="c1"&gt;-- execute the next part of the script&lt;/span&gt;
            &lt;span class="nb"&gt;coroutine.resume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;routine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kr"&gt;end&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And finally, we can write a script that looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello there!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                &lt;span class="c1"&gt;-- the script suspends once here&lt;/span&gt;
&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;How&amp;#39;s it going?&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;             &lt;span class="c1"&gt;-- it suspends again here&lt;/span&gt;
&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Well, nice talking to you!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;-- it suspends for the 3rd time here&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="part-3-sandboxing-and-advanced-usage"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#part-3-sandboxing-and-advanced-usage"&gt;Part 3: Sandboxing and Advanced Usage&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If we declare a global variable, 'n', we can create an NPC that
remembers how many times the player has spoken to it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hey kid, I&amp;#39;m Mr. Red!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
    &lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;I don&amp;#39;t believe we&amp;#39;ve met before!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;You have spoken to me &amp;quot;&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot; times!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It's great that this works, because it does exactly what you would
expect and it's super easy to use. However, there are some problems.&lt;/p&gt;
&lt;p&gt;If all the variables are stored in the global environment, we risk
running into naming collisions which at best will cause scripts to
behave incorrectly and at worst could replace key functionality and
crash the game.&lt;/p&gt;
&lt;p&gt;Additionally, having our game's state scattered across a ton of globals
makes things very difficult when we want to think about serialising the
gamestate to produce a save file.&lt;/p&gt;
&lt;p&gt;Fortunately Lua makes it easy to swap out the environment of a function
for any table, using &lt;tt class="docutils literal"&gt;setfenv&lt;/tt&gt; in Lua 5.1 or &lt;tt class="docutils literal"&gt;_ENV&lt;/tt&gt; in Lua 5.2 or
greater. We don't need to change our scripts at all, we just need to
make sure that they still have access to the &lt;tt class="docutils literal"&gt;say&lt;/tt&gt; function, by
placing it in their environment (the &lt;tt class="docutils literal"&gt;game&lt;/tt&gt; table below).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;game&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;str&lt;/span&gt;
    &lt;span class="nb"&gt;coroutine.yield&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;loadfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;setfenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;routine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;coroutine.create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;-- begin execution of the script&lt;/span&gt;
    &lt;span class="nb"&gt;coroutine.resume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;routine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It also might be helpful to have a script that is called once at
startup, to initialise all the game variables to default values, or load
them from a save file.&lt;/p&gt;
&lt;p&gt;As far as animation goes, we can drop in a tweening solution like
&lt;a class="reference external" href="https://github.com/rxi/flux"&gt;flux&lt;/a&gt;, along with a few helper
functions which will allow us to pause the script until the animation
completes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flux&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;flux&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pause&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;coroutine.yield&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;game&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nb"&gt;coroutine.resume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;routine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;and then we could tween a character to x = 800 with a script like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;flux&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;myNpc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt; &lt;span class="p"&gt;}):&lt;/span&gt;&lt;span class="n"&gt;ease&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;linear&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="n"&gt;oncomplete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;which yes, is a mouthful for non-coders, and it introduces an
asynchronous aspect back into the scripting API. We would probably
benefit from a custom animation system that's more more tailored to our
game, but this hopefully goes to show how easy it is to make scripts
that can interact with any other part of the engine.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="what-next"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#what-next"&gt;What Next?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I hope I was able to teach some interesting ideas here! I wanted to
share this because coroutines are something I've known about for a
while, but until now I've never had a good reason to use them. I would
be interested to know which other languages can be used to create a
system like this.&lt;/p&gt;
&lt;p&gt;Here are some things you might want to do next, to create a more
full-featured RPG engine:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Add &lt;tt class="docutils literal"&gt;lock()&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;release()&lt;/tt&gt;, so it's possible to display text
while the player is moving, or stop the player from moving even when
there is no text.&lt;/li&gt;
&lt;li&gt;Add an &lt;tt class="docutils literal"&gt;ask(str, &lt;span class="pre"&gt;...)&lt;/span&gt;&lt;/tt&gt; function whereby the player can choose from
a list of options (e.g. yes/no)&lt;/li&gt;
&lt;li&gt;Download a level editor such as Tiled, or create your own. Try
attaching some scripts to game objects such as buttons and NPCs.
&lt;a class="reference external" href="http://lua.space/gamedev/using-tiled-maps-in-love"&gt;Relevant tutorial on using Tiled with
LÖVE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Create an easy-to-use animation system with commands such as 'face X
direction' or 'move N steps'&lt;/li&gt;
&lt;li&gt;Add character portraits so that the player knows who's speaking (this
might require you to add an extra parameter to &lt;tt class="docutils literal"&gt;say()&lt;/tt&gt; or some new
functions)&lt;/li&gt;
&lt;li&gt;Consider how you would go about handling save data. How to
distinguish it from data which is part of the gamestate but does not
need to be saved permanently?&lt;/li&gt;
&lt;/ul&gt;
&lt;hr class="docutils" /&gt;
&lt;table class="docutils footnote" frame="void" id="rpg" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-1"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;We never finished the RPG, but I did make a little &lt;a class="reference external" href="https://www.lexaloffle.com/bbs/?tid=3833"&gt;PICO-8 example&lt;/a&gt; to demonstrate the techniques from this article.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="yarn" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-2"&gt;[2]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Now &lt;a class="reference external" href="https://yarnspinner.dev/"&gt;Yarn Spinner&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="async" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-3"&gt;[3]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;JS async/await wasn't much of a thing back when this article was written.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="blog"></category></entry><entry><title>Museum City Boat - Post Mortem</title><link href="https://exelo.tl/museum-city-boat.html" rel="alternate"></link><published>2015-09-11T00:00:00+01:00</published><updated>2024-02-25T00:00:00+00:00</updated><author><name>gecko@exelo.tl</name></author><id>tag:exelo.tl,2015-09-11:/museum-city-boat.html</id><summary type="html">&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;Recovered from &lt;a class="reference external" href="https://web.archive.org/web/20210304215544/http://ludumdare.com/compo/2015/09/11/museum-city-boat-post-mortem/"&gt;ludumdare.com/compo/2015/09/11/museum-city-boat-post-mortem&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Hi! It's slightly late but I figured I'd finish up the post mortem for my entry, &lt;a class="reference external" href="https://web.archive.org/web/20210304215544/http://ludumdare.com/compo/ludum-dare-33/?action=preview&amp;amp;uid=5246"&gt;Museum City Boat&lt;/a&gt;.&lt;/p&gt;
&lt;img alt="" class="align-center" src="https://exelo.tl/media/2015/capture.gif" /&gt;
&lt;p&gt;It's a small, charming side-scroller where you get to go on a rampage as a monster, smash some lamps, flip some …&lt;/p&gt;</summary><content type="html">&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;Recovered from &lt;a class="reference external" href="https://web.archive.org/web/20210304215544/http://ludumdare.com/compo/2015/09/11/museum-city-boat-post-mortem/"&gt;ludumdare.com/compo/2015/09/11/museum-city-boat-post-mortem&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Hi! It's slightly late but I figured I'd finish up the post mortem for my entry, &lt;a class="reference external" href="https://web.archive.org/web/20210304215544/http://ludumdare.com/compo/ludum-dare-33/?action=preview&amp;amp;uid=5246"&gt;Museum City Boat&lt;/a&gt;.&lt;/p&gt;
&lt;img alt="" class="align-center" src="https://exelo.tl/media/2015/capture.gif" /&gt;
&lt;p&gt;It's a small, charming side-scroller where you get to go on a rampage as a monster, smash some lamps, flip some cars and generally do monster things.&lt;/p&gt;
&lt;p&gt;The main tools used were Lua/LOVE, GraphicsGale, Fireworks, Renoise, and a Zoom H1 Handy Recorder.&lt;/p&gt;
&lt;p&gt;Shout outs to the friendly participants on the #love channel (irc.oftc.net) who were really supportive and helpful as usual :)&lt;/p&gt;
&lt;div class="section" id="what-went-well"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#what-went-well"&gt;What went well&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="pre-compo-planning"&gt;
&lt;h3&gt;&lt;a class="headerlink" href="#pre-compo-planning"&gt;Pre-compo planning&lt;/a&gt;&lt;/h3&gt;
&lt;!-- would be nice to find the original version of this --&gt;
&lt;img alt="" class="align-center" src="https://exelo.tl/media/2015/ideas-550x268.png" style="width: 420px;" /&gt;
&lt;p&gt;It was my 5th time entering, and I've noticed that the theme always catches me by surprise. This time, I wanted to be ready! So during the voting, I tried to make notes on possible game ideas for &lt;em&gt;every single theme&lt;/em&gt;. Because thinking up game ideas is much easier when you are not dealing with jam stress.&lt;/p&gt;
&lt;p&gt;This took up a lot of time and creative thinking, and I gave up on it half way through Round 3. It still paid off though: the theme 'You Are The Monster' did not give me any good ideas when it was announced, so I simply looked back at my notes and chose my favourite idea from there.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="style-and-gameplay"&gt;
&lt;h3&gt;&lt;a class="headerlink" href="#style-and-gameplay"&gt;Style and Gameplay&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This is possibly the first time I tried to make a game that evokes simple, classic arcade vibes rather than focusing on ambience and emotion. Sometimes I feel like there is a fine line between 'silly' and 'amateur'&lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt;. I wanted to make something cute, fun and silly, but still high quality. It was only in the later stages of development that I realised what I was making might achieve those goals and not be garbage.&lt;/p&gt;
&lt;p&gt;Friends and participants commented on the smooth camera movement, simple mouse controls, cute graphics and satisfying sound effects, so I feel like I succeeded here!&lt;/p&gt;
&lt;p&gt;Another interesting thing I managed was to add replay value through score categories (number of structures destroyed, number of times seen, etc.) and just writing some challenges in the game's description. Like &lt;em&gt;'hey, can you beat the game without being seen by cameras or people?'&lt;/em&gt; - If the game was bigger or I had more time, this kind of thing would be put in an achievements system of some kind, but people will make their own fun if you tell them how!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="coding"&gt;
&lt;h3&gt;&lt;a class="headerlink" href="#coding"&gt;Coding&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The last time I made a game in Lua, I was using cmd and a general text editor, which meant I was missing out on some important workflow aspects such as quick-launch, error checking, rename variables etc. This time I used ZeroBrane Studio, which is a truly excellent Lua IDE that comes with love2d support built in. It made my life so much easier!&lt;/p&gt;
&lt;p&gt;My code turned out clean, for a jam at least. I spent &lt;em&gt;very little&lt;/em&gt; time tracking down bugs, even though I had plenty of globals that the other classes in the game depended on. I think this is because I stuck to some simple rules: initialise globals early, put them where they make sense (e.g. main or assets), be aware that their value might not be what you expect… and also because I was able to assume I didn't need to do any dangerous things like removing the player from the world (which could be disastrous because so many things access the player directly for AI or collision checking).&lt;/p&gt;
&lt;p&gt;I had to throw in some rendering hacks to make sure things like water and game-over text displayed correctly (just stick them in the global drawing function), but this was OK because it was added quite late in development, so didn't get a chance to turn into spaghetti code.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="music-and-audio"&gt;
&lt;h3&gt;&lt;a class="headerlink" href="#music-and-audio"&gt;Music and Audio&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Electronic music is a passion of mine, so this part is always fun and usually turns out well! However, I spent the majority of my time drawing and coding, so audio was quite a rushed thing in the end.&lt;/p&gt;
&lt;p&gt;I spent about 4 hours on the last day of the Jam to write a cheery 80s/90s track. I have a Korg M1 plugin which is &lt;em&gt;perfect&lt;/em&gt; for this kind of stuff, combined with some great virtual analogue synths like FXPansion Strobe. The main challenge I had to overcome was my tendency to write really emotional chords and melodies which don't fit the vibe I was going for. It took a couple of attempts but eventually I came up with something that fit just right.&lt;/p&gt;
&lt;div style="text-align: center"&gt;
&lt;audio controls&gt;
  &lt;source src="https://exelo.tl/media/2015/ld33_4.ogg"&gt;
&lt;/audio&gt;
&lt;/div&gt;&lt;p&gt;My two main inspirations here were Mitch Murder and Bacalao. I was listening to them almost all weekend, their music is so great!&lt;/p&gt;
&lt;!-- links to music --&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://mitchmurder.bandcamp.com/track/breakazoid"&gt;Mitch Murder - Breakazoid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://bacalao.bandcamp.com/track/cheat-codes-fluomix"&gt;Bacalao - Cheat Codes (Fluomix)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The sound effects were pretty much done in the last 2 hours! Most of them came from the kitchen. I was literally standing there at 1AM with a mic, knocking glasses together and making growling noises. They were all trimmed, pitched and normalised in Renoise and Audacity.&lt;/p&gt;
&lt;p&gt;Some sounds were more creative, for example the explosion was made by taking the muffled 'thud' of a cupboard closing, then pitching it down further, adding white noise and running it through a distortion plugin. The water hiss and camera beeps were generated with SunVox.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="what-could-have-gone-better"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#what-could-have-gone-better"&gt;What could have gone better&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="design-process-confidence"&gt;
&lt;h3&gt;&lt;a class="headerlink" href="#design-process-confidence"&gt;Design Process, Confidence&lt;/a&gt;&lt;/h3&gt;
&lt;img alt="" class="align-center" src="https://exelo.tl/media/2015/IMG_20150911_223202.jpg" style="width: 480px;" /&gt;
&lt;p&gt;The picture above was the closest thing I had to a 'design doc'. Not really a huge problem, because I never churn out pages for any of my small projects, I'm kinda used to improvising as I go along.&lt;/p&gt;
&lt;p&gt;The main issue was settling on a graphical style. I never made a game with vector or high-res graphics before, so I figured given that I have Fireworks (hybrid vector/bitmap editing) and there are easy skeletal animation tools out there such as Spriter - how hard could it be to make a game using those? Or perhaps I could draw the monster with code, since the love2d splashes seem to have these delicious vector graphics? Though, how would I relate the vector graphics to the physics engine?&lt;/p&gt;
&lt;p&gt;So I spent all afternoon contemplating and trying different ways to draw my monster and animate him, and the results were awful! At this point I just felt like giving up, or maybe switching to a different system altogether like PICO-8. It wasn't until Saturday night that I went back to GraphicsGale, drew some pixels and actually got the monster running around the screen. I eventually realised that if I wanted to make a game I was happy with, I'd have to enter the Jam to make up for all that lost time.&lt;/p&gt;
&lt;p&gt;One good decision I made was to use &lt;a class="reference external" href="https://androidarts.com/palette/16pal.htm"&gt;Arne's 16-colour Palette&lt;/a&gt;. If I'd had more skill and time to put towards pixel-art, I could have chosen a more detailed style and &lt;em&gt;really&lt;/em&gt; made the palette shine, but anyway it served me really well! In my previous entry I spent far too much time worrying about clashing colours, but this time I could forget all that and get on with pixelling. I settled on a clean flat style with 1px outlines and no shading.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="level-creation"&gt;
&lt;h3&gt;&lt;a class="headerlink" href="#level-creation"&gt;Level Creation&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When I was getting started, I drew the whole level as a huge image in Fireworks, and hard-coded the player and other entity spawns. I was planning on switching to a proper editor like Tiled, but I didn't feel like setting up and traversing all that exported data, so it never really happened.&lt;/p&gt;
&lt;p&gt;I can't say whether or not I lost much time to this, but it was certainly inconvenient sometimes, and the fact that I chose to work this way makes my game much less scalable in its current state.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="what-next"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#what-next"&gt;What next?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I'd love to take this game further and turn it into a deeper, longer and more interesting adventure (with boss battles!). Right now I'm experimenting with development for the Gameboy Advance, so maybe I'll ditch the rigid body physics and port the game to GBA? It will make it a far bigger technical challenge but it's a really rewarding platform to develop for.&lt;/p&gt;
&lt;p&gt;At the same time I have some commitment to older projects that I want to get finished, but I'm worried that if I work on those then I will lose my motivation to work on this game? So in the long term I'm not quite sure what will happen.&lt;/p&gt;
&lt;p&gt;As long as I'm doing GBA development I'd like to try blogging about it, because it's an interesting field but most of the reading material is from 2007 and earlier. Maybe it would help people to have some modern articles on the subject from someone who is still learning.&lt;/p&gt;
&lt;p&gt;This has become quite a wall of text, so if you've made it this far, thanks for reading, and best of luck to all those awaiting LD33 results!&lt;/p&gt;
&lt;p&gt;Until next time,&lt;/p&gt;
&lt;p&gt;Gecko&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;p&gt;&lt;strong&gt;Footnotes&lt;/strong&gt;&lt;/p&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-1"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;p class="first"&gt;in 2024 I'd describe this differently - it's like, there's a difference between &lt;em&gt;confidently&lt;/em&gt; amateur (this is scrappy but there's intent, it works and I'm proud of it) and &lt;em&gt;aimlessly&lt;/em&gt; amateur (having huge gaps in my vision and being unable to pull together a compelling work by the end). The latter doesn't make for a desirable end result, but can still be a valuable learning experience.&lt;/p&gt;
&lt;p class="last"&gt;Tangentially, you should check out &lt;a class="reference external" href="https://farawaytimes.blogspot.com/2023/02/how-to-make-good-small-games.html"&gt;How To Make Good Small Games&lt;/a&gt; by John Thyer, it's really important!&lt;/p&gt;
&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="blog"></category></entry><entry><title>Curiosity - Post Mortem</title><link href="https://exelo.tl/curiosity.html" rel="alternate"></link><published>2013-05-10T00:00:00+01:00</published><updated>2013-05-10T00:00:00+01:00</updated><author><name>gecko@exelo.tl</name></author><id>tag:exelo.tl,2013-05-10:/curiosity.html</id><summary type="html">&lt;!-- TODO figure out what timezone the LD servers were in? --&gt;
&lt;p&gt;About time I got around to writing a post mortem for my entry, here goes!&lt;/p&gt;
&lt;img alt="Screenshot of a platform game. A little black critter with a neutral face is grappling up to the ceiling inside a minty green 'garden' room. The scene is made of abstract shapes with flat colours, and a white 'vignette' effect around the edges of the screen gives it a sort of heavenly feeling." src="https://exelo.tl/media/2013/31.png" /&gt;
&lt;p&gt;Curiosity is a little ambient exploration game written in the ooc programming language. You can &lt;a class="reference external" href="https://exelotl.itch.io/curiosity"&gt;play the game here&lt;/a&gt; (comments &lt;a class="reference external" href="https://web.archive.org/web/20210419205457/http://ludumdare.com/compo/ludum-dare-26/?action=preview&amp;amp;uid=5246"&gt;here&lt;/a&gt;) if you're interested.&lt;/p&gt;
&lt;div class="section" id="the-idea"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#the-idea"&gt;The Idea&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the UK, Ludum Dare starts and ends at …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;!-- TODO figure out what timezone the LD servers were in? --&gt;
&lt;p&gt;About time I got around to writing a post mortem for my entry, here goes!&lt;/p&gt;
&lt;img alt="Screenshot of a platform game. A little black critter with a neutral face is grappling up to the ceiling inside a minty green 'garden' room. The scene is made of abstract shapes with flat colours, and a white 'vignette' effect around the edges of the screen gives it a sort of heavenly feeling." src="https://exelo.tl/media/2013/31.png" /&gt;
&lt;p&gt;Curiosity is a little ambient exploration game written in the ooc programming language. You can &lt;a class="reference external" href="https://exelotl.itch.io/curiosity"&gt;play the game here&lt;/a&gt; (comments &lt;a class="reference external" href="https://web.archive.org/web/20210419205457/http://ludumdare.com/compo/ludum-dare-26/?action=preview&amp;amp;uid=5246"&gt;here&lt;/a&gt;) if you're interested.&lt;/p&gt;
&lt;div class="section" id="the-idea"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#the-idea"&gt;The Idea&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the UK, Ludum Dare starts and ends at 3AM. I decided to stay up into the early hours of Saturday, and fell asleep with minimalism floating around in my head. I think I had a dream about a game idea, but unfortunately I couldn't remember it.&lt;/p&gt;
&lt;p&gt;I thought about a game that starts of super minimalistic, and gets progressively more detailed and lush as the player progresses. Of course, this was silly and vague. In such a short space of time just having a finished project is a challenge, but I hope the game resembles my intentions a little.&lt;/p&gt;
&lt;p&gt;I admit I didn't really like the theme at first - any game made in 48 hours is going to be minimal, so minimalism seemed to be a wildcard. I changed my mind once I got started.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="design"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#design"&gt;Design&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My graphics software hasn't changed since the last time I entered. Even though I'm 5 major versions behind now, and running it 2 operating systems ahead of what it was developed for, Fireworks is still my general purpose graphics editor of choice. Having .png as the native project format is really handy, along with the variety of non-destructive image processing options.&lt;/p&gt;
&lt;p&gt;I'd heard good things about Ogmo Editor, and watched a tutorial on using it in FlashPunk. Considering this was my first time using it in a project, it worked amazingly! When designing the world, I tried to make sure there was more than one way to solve each challenge (though not everyone who played the game noticed this). Some people said the game reminds them of the Knytt series, which is very cool to hear! Nifflas was certainly a source of inspiration for me.&lt;/p&gt;
&lt;img alt="" src="https://exelo.tl/media/2013/ogmo.png" /&gt;
&lt;p&gt;A few people found the game too difficult, but I don't think there were any major flaws in my level design this time. My old entry for LD21 had lots of blind jumps, no checkpoints, and you could fall off the world by going left. I've definitely improved in that respect! Personally I thought the difficulty level was fine, especially after playing some other awesome yet insanely hard entries.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="programming"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#programming"&gt;Programming&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My choice of language, &lt;a class="reference external" href="http://ooc-lang.org/"&gt;ooc&lt;/a&gt;, served me incredibly well! It's a modern object-oriented language that compiles into C99, and therefore works on any platform with pthreads and a C compiler. I picked it up in the last few months, created quite a lot of bindings to existing C libraries, and have been working on a FlashPunk inspired game engine called &lt;a class="reference external" href="https://github.com/exelotl/vamos"&gt;Vamos&lt;/a&gt; using SDL 2.0's hardware accelerated rendering API, which I used to create this entry.&lt;/p&gt;
&lt;p&gt;The majority of development issues were all tackled before the compo started, so I had a pretty smooth ride on my own framework. The day before, I bound a small XML library (MiniXML) to ooc, so I was able to parse Ogmo Editor's level data. On the first day I remembered I still hadn't implemented depth-sorted rendering, and that ate up a little bit of time, but was quite painless.&lt;/p&gt;
&lt;p&gt;The main problem was that my game engine didn't have sound effects. I'd been trying to write my own audio mixer before the compo (without relying on SDL_mixer), but it had huge latency and I couldn't figure out how to avoid sounds being synchronized to the buffer size. The music playback (which uses stb-vorbis for decoding) was working fine, and I found a hacky solution last-minute to create smooth crossfades between tracks. That hopefully compensated for the lack of sfx.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="music"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#music"&gt;Music&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This has always been a strong point for me. I wanted to include just as much musical content as last time, which meant I had to make 4 tracks! In my last entry, my soundtrack was ruined for some people by streaming/stuttering issues. That wasn't a problem this time, because I was making a desktop game.&lt;/p&gt;
&lt;img alt="" src="https://exelo.tl/media/2013/renoise.png" /&gt;
&lt;p&gt;I'm primarily a Renoise user now (though SunVox is still awesome, and I highly recommend it if you're looking for a free music program). I've got a nice collection of free VSTs and samples and had fun creating some ambient songs and soundscapes. It gave me a nice break from the intense coding! :)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="overall"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#overall"&gt;Overall&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I'm really happy with how this turned out! I've since made some Linux binaries, and it also runs nicely on OSX (though I don't have a mac to test or package it). I'd love to develop Vamos further and make some more ooc games in the future.&lt;/p&gt;
&lt;p&gt;I'd also like to thank everyone for their encouraging feedback so far. Thanks guys, and well done on all your finished projects!&lt;/p&gt;
&lt;/div&gt;
</content><category term="blog"></category></entry><entry><title>Raspberry Pi Arrived</title><link href="https://exelo.tl/raspberry-pi-arrived.html" rel="alternate"></link><published>2012-10-21T23:55:00+01:00</published><updated>2022-09-12T17:16:00+01:00</updated><author><name>gecko@exelo.tl</name></author><id>tag:exelo.tl,2012-10-21:/raspberry-pi-arrived.html</id><summary type="html">&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;This is an ancient article from my short-lived Posterous blog, before they got bought out by Twitter and &lt;a class="reference external" href="https://ourincrediblejourney.tumblr.com/post/43736846682/posterous"&gt;discontinued&lt;/a&gt;. Most of the information here is outdated, but I thought it'd be fun to publish for prosterity.&lt;/p&gt;
&lt;p class="last"&gt;The Pi was super useful overall! At school I'd remote into it via …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;This is an ancient article from my short-lived Posterous blog, before they got bought out by Twitter and &lt;a class="reference external" href="https://ourincrediblejourney.tumblr.com/post/43736846682/posterous"&gt;discontinued&lt;/a&gt;. Most of the information here is outdated, but I thought it'd be fun to publish for prosterity.&lt;/p&gt;
&lt;p class="last"&gt;The Pi was super useful overall! At school I'd remote into it via PuTTY and do my computing classwork in the terminal. This spared me the pain of having to use the Turbo Pascal IDE in 2013 (yeah wtf). But probably the coolest thing I did was to run a small Pokémon Showdown server for my friends, modified to support random &lt;abbr title="Little Cup, i.e. battles using Lv.5 baby pokemon only"&gt;LC&lt;/abbr&gt; battles.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;It was my 17th birthday recently and my grandma was kind enough to buy me a Raspberry Pi (256mb Model B), though she didn't know what the heck it was. Here's a little explanation on what I've done with it so far:&lt;/p&gt;
&lt;div class="section" id="initial-setup"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#initial-setup"&gt;Initial Setup&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I'm still relatively new to Linux/Unix, although I've had some experience with the terminal on my phone and iPod Touch, and running lightweight distros on VMs on my laptop, so I learned tons even in the first few steps. Having flashed Raspbian Wheezy onto my SD card with win32diskimager, I found a spare USB keyboard and mouse and connected the Pi to my TV with an RCA cable.&lt;/p&gt;
&lt;img alt="" src="https://exelo.tl/media/2012/IMG_20121019_154537.jpg" style="height: 340px;" /&gt;
&lt;img alt="" src="https://exelo.tl/media/2012/IMG_20121019_154558.jpg" style="height: 340px;" /&gt;
&lt;p&gt;The startup guides had a lot of warnings about using at least 700mA power supplies - the specs on the side of my phone charger were confusing but it worked fine. I powered the Pi up and was welcomed by the raspi-config screen, where I set a password and resized the OS to fill the whole SD card. After that was done, I restarted the unit and was welcomed by the terminal. The &lt;tt class="docutils literal"&gt;startx&lt;/tt&gt; command is used to initialize a desktop, although I haven't needed to use it much yet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="remote-access"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#remote-access"&gt;Remote Access&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The latest Raspbian has SSH enabled by default, so this was pretty painless. I'm using PuTTY on my laptop, and my iPod Touch and Android phone both have SSH clients on them, so I can pretty much get onto the Pi from anywhere in my house. In order to remotely access the desktop, I installed tightvncserver with &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;apt-get&lt;/span&gt;&lt;/tt&gt;. Conveniently this starts up its own X desktop instance in the background, so I can start up tightvncserver over SSH, then connect with a VNC client. I've ordered a wireless dongle so hopefully I won't have this ethernet cable stretching across the house for much longer.&lt;/p&gt;
&lt;img alt="" src="https://exelo.tl/media/2012/Capture.png" /&gt;
&lt;p&gt;One of my main uses for the Pi is to run my Lua IRC bot on the &lt;a class="reference external" href="https://bonuslevel.org/"&gt;bonuslevel&lt;/a&gt; chat, so I installed Lua and transferred the source via SFTP. The bot ran fine, but I had trouble launching it over SSH because it requires some initial input, but it must then run in the background even after the session has ended. The solution for this was to use a tool called dtach, which is perfectly suited for situations like this.&lt;/p&gt;
&lt;p&gt;The command to create a new session and attach to it is&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;dtach&lt;span class="w"&gt; &lt;/span&gt;-A&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;socket&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;command...&amp;gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This confused me initially, as I thought that 'socket' was supposed to be a network socket, which was wrong. It is actually a Unix domain socket, which is a local means of communication between programs, bound to a temporary file which exists during the socket's lifetime. So, I can run&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;dtach&lt;span class="w"&gt; &lt;/span&gt;-A&lt;span class="w"&gt; &lt;/span&gt;bot_sock&lt;span class="w"&gt; &lt;/span&gt;lua&lt;span class="w"&gt; &lt;/span&gt;Main.lua
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;When I'm done responding to the bot's initial prompts, I can hit ctrl+\ to detach from the session. Now I have my Lua process running continuously in the background, and I can end the SSH session and go to France and have a McDonalds, while my IRC bot remains functional, running off a tiny board at home. Pretty cool.&lt;/p&gt;
&lt;/div&gt;
</content><category term="blog"></category></entry><entry><title>The Great Unescape - Post Mortem</title><link href="https://exelo.tl/the-great-unescape.html" rel="alternate"></link><published>2011-08-27T00:00:00+01:00</published><updated>2011-08-27T00:00:00+01:00</updated><author><name>gecko@exelo.tl</name></author><id>tag:exelo.tl,2011-08-27:/the-great-unescape.html</id><summary type="html">&lt;div class="admonition note first"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;This was the first jam game I ever made, when I was 15 years old. Originally posted on the &lt;a class="reference external" href="https://web.archive.org/web/20210419223243/http://ludumdare.com/compo/2011/08/27/the-great-unescape-post-mortem/"&gt;old Ludum Dare site&lt;/a&gt;, I'm putting it here for prosterity!&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Looks like people are still writing post-mortems a week later, so here's mine!&lt;/p&gt;
&lt;p class="last"&gt;...&lt;/p&gt;
</summary><content type="html">&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;This was the first jam game I ever made, when I was 15 years old. Originally posted on the &lt;a class="reference external" href="https://web.archive.org/web/20210419223243/http://ludumdare.com/compo/2011/08/27/the-great-unescape-post-mortem/"&gt;old Ludum Dare site&lt;/a&gt;, I'm putting it here for prosterity!&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Looks like people are still writing post-mortems a week later, so here's mine!&lt;/p&gt;
&lt;div class="section" id="development"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#development"&gt;Development&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I woke up last Saturday and checked the theme on twitter. I didn't really expect the theme to be 'escape', but I had a think over breakfast and got a cool story idea, sort of inspired by an episode of an old TV show called Porridge. I started off with a dull blue wall tileset and made a prison cell in DAME.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="" src="https://exelo.tl/media/2011/3.png" /&gt;
&lt;p class="caption"&gt;Drawing tilesets in Graphics Gale.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Once the character graphics were done, I threw together a system which scans the DAME project file for game entities then places them on the map, since I didn't have time to get to grips with the complex export system in DAME. Eventually I had little Rick running and jumping around in his cell, and I spent the rest of the day composing in SunVox and using Tweener to arrange the introduction text and events.&lt;/p&gt;
&lt;p&gt;I didn't actually start on the gameplay or levels until Sunday, when I decided to construct a small jungle full of spiders and spikes. It took most of the daytime to create the enemies, tiles, and music which left me with the evening to work out how to bring everything to a close. I stayed up right until the deadline designing the last area and finishing the game off, but I managed to squeeze in another tune, bringing me to a total of four songs!&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="" src="https://exelo.tl/media/2011/4.png" /&gt;
&lt;p class="caption"&gt;Making the soundtrack in SunVox.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="what-went-right"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#what-went-right"&gt;What Went Right&lt;/a&gt;&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Considering this was my first time using DAME, creating the levels and making them work in Flixel was surprisingly painless.&lt;/li&gt;
&lt;li&gt;I've practised a lot with SunVox, so I can churn out decent music fast!&lt;/li&gt;
&lt;li&gt;I had a nice storyline idea and managed to keep it short without ruining it.&lt;/li&gt;
&lt;li&gt;I've been practising with Flixel and FlashDevelop for quite a long time. Even though I hadn't released any Flixel games before this one, I was very comfortable with my choice of language and library.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="what-could-have-gone-better"&gt;
&lt;h2&gt;&lt;a class="headerlink" href="#what-could-have-gone-better"&gt;What Could Have Gone Better&lt;/a&gt;&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;I missed a few details, for example there is no wall on the left side of the jungle, so you can fall off the screen and be stuck forever.&lt;/li&gt;
&lt;li&gt;Quite a lot of people heard strange hiccups in the music playback. I have no idea what's causing this and I can't hear them myself, but I added a standalone download which should hopefully fix any audio problems.&lt;/li&gt;
&lt;li&gt;I made a couple of bad design decisions, there are quite a lot of blind drops into enemies. I thought the levels were short enough not to need checkpoints, but it looks like I was wrong.&lt;/li&gt;
&lt;/ul&gt;
&lt;img alt="" class="align-center" src="https://exelo.tl/media/2011/5.png" /&gt;
&lt;p&gt;I'm really pleased with the amount I managed to get done in two days! If you're interested, &lt;a class="reference external" href="https://web.archive.org/web/20210419223243/http://www.ludumdare.com/compo/ludum-dare-21/?action=rate&amp;amp;uid=5246"&gt;check out the entry itself here&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
</content><category term="blog"></category></entry></feed>