<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.2">Jekyll</generator><link href="https://michaelchadwick.info/feed.xml" rel="self" type="application/atom+xml" /><link href="https://michaelchadwick.info/" rel="alternate" type="text/html" /><updated>2024-02-12T14:54:21-08:00</updated><id>https://michaelchadwick.info/feed.xml</id><title type="html">MC.Info</title><subtitle>code, audio, and life developments by michael chadwick</subtitle><author><name>Michael Chadwick</name></author><entry><title type="html">About This Computer</title><link href="https://michaelchadwick.info/blog/2024/01/09/about-this-computer/" rel="alternate" type="text/html" title="About This Computer" /><published>2024-01-09T00:00:00-08:00</published><updated>2024-01-09T00:00:00-08:00</updated><id>https://michaelchadwick.info/blog/2024/01/09/about-this-computer</id><content type="html" xml:base="https://michaelchadwick.info/blog/2024/01/09/about-this-computer/"><![CDATA[<p>Have you ever wanted to just get a quick print out (to the screen) of all the tech that’s on your computer? I’ve been working on a shell script for years that does just that.</p>

<!--more-->

<h2 id="first-things-first">FIRST THINGS FIRST</h2>

<p>Note, this post is only concerned with macOS/Linux and its various flavors, as that’s what I largely use on a day-to-day basis. I’m sure Windows has its own stuff (like <code class="language-plaintext highlighter-rouge">systeminfo</code>), but that’s for a different post. Also, I’m only concerned with tools that you access from the <em>command line</em>, as GUIs will most likely have their own menu or button to tell you about your computer, but that can’t be included in a script with other stuff.</p>

<p>My personal favorite command line tool to get information about my Mac (or Linux VM) is <a href="https://github.com/dylanaraps/neofetch">neofetch</a>. It’s a rad <code class="language-plaintext highlighter-rouge">bash</code> script that creates a cool, screenshot-able display of useful information alongside either a picture of your OS’s logo, or a custom image. Since it runs on <code class="language-plaintext highlighter-rouge">bash</code>, it runs on virtually any <code class="language-plaintext highlighter-rouge">bash</code>-compatible shell system, which is a lot of potential operating systems.</p>

<p>Before I found <code class="language-plaintext highlighter-rouge">neofetch</code>, I used to use <a href="https://github.com/HorlogeSkynet/archey4">archey</a> (which is a maintained fork of the original found <a href="https://github.com/djmelik/archey">here</a>). It’s written in Python, which is another dependency that may not exist on the system you’re checking.</p>

<h2 id="script-time">SCRIPT TIME</h2>

<p>All this being said, not every system has (or <em>can be installed with</em>) the cool tools described above, so I decided a more <em>progressive enhancement</em> flow of attempts worked best.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function </span>about<span class="o">()</span> <span class="o">{</span>
  <span class="c"># OS info</span>
  <span class="k">if </span><span class="nb">hash </span>neofetch 2&gt;/dev/null<span class="p">;</span> <span class="k">then
    </span>neofetch
  <span class="k">else
    if </span><span class="nb">hash </span>archey 2&gt;/dev/null<span class="p">;</span> <span class="k">then
      </span>archey <span class="nt">-c</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span>
    <span class="k">else
      if</span> <span class="o">[</span> <span class="nt">-f</span> /etc/os-release <span class="o">]</span><span class="p">;</span> <span class="k">then
        </span><span class="nb">cat</span> /etc/os-release
      <span class="k">else
        if</span> <span class="o">[</span> <span class="nt">-f</span> /proc/version <span class="o">]</span><span class="p">;</span> <span class="k">then
          </span><span class="nb">cat</span> /proc/version
        <span class="k">else
          </span><span class="nb">uname</span> <span class="nt">-a</span>
        <span class="k">fi
      fi
    fi
  fi</span>

  ...
<span class="o">}</span>
</code></pre></div></div>

<p>Essentially, start with the best option, and if it does not exist, try the next best option. If <code class="language-plaintext highlighter-rouge">uname</code> doesn’t exist then you’re probably not running macOS/Linux, and something is very wrong.</p>

<p>While this if-else chain is useful, it’s not very noteworthy for a post. Thus, over time I’ve slowly expanded this script to contain other things I care to know about a computer I’m using.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># colors</span>
<span class="nv">_RED</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="nb">tty</span> <span class="nt">-s</span> <span class="o">&amp;&amp;</span> tput setaf 1<span class="si">)</span><span class="s2">"</span>
<span class="nv">_YELLOW</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="nb">tty</span> <span class="nt">-s</span> <span class="o">&amp;&amp;</span> tput setaf 3<span class="si">)</span><span class="s2">"</span>
<span class="nv">_WHITE</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="nb">tty</span> <span class="nt">-s</span> <span class="o">&amp;&amp;</span> tput setaf 7<span class="si">)</span><span class="s2">"</span>
<span class="nv">_GRAY</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="nb">tty</span> <span class="nt">-s</span> <span class="o">&amp;&amp;</span> tput setaf 8<span class="si">)</span><span class="s2">"</span>
<span class="nv">_BOLD</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="nb">tty</span> <span class="nt">-s</span> <span class="o">&amp;&amp;</span> tput bold<span class="si">)</span><span class="s2">"</span>
<span class="nv">_RESET</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="nb">tty</span> <span class="nt">-s</span> <span class="o">&amp;&amp;</span> tput sgr0<span class="si">)</span><span class="s2">"</span>

...other functions...

<span class="k">function </span>about<span class="o">()</span> <span class="o">{</span>
  <span class="c"># OS info</span>

  ...

  <span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">_BOLD</span><span class="k">}${</span><span class="nv">_WHITE</span><span class="k">}</span><span class="s2">--------------------------------</span><span class="k">${</span><span class="nv">_RESET</span><span class="k">}</span><span class="s2">"</span>
  <span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">_BOLD</span><span class="k">}${</span><span class="nv">_WHITE</span><span class="k">}</span><span class="s2">| programming langs            |</span><span class="k">${</span><span class="nv">_RESET</span><span class="k">}</span><span class="s2">"</span>
  <span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">_BOLD</span><span class="k">}${</span><span class="nv">_WHITE</span><span class="k">}</span><span class="s2">--------------------------------</span><span class="k">${</span><span class="nv">_RESET</span><span class="k">}</span><span class="s2">"</span>

  ...

  <span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">_BOLD</span><span class="k">}${</span><span class="nv">_WHITE</span><span class="k">}</span><span class="s2">--------------------------------</span><span class="k">${</span><span class="nv">_RESET</span><span class="k">}</span><span class="s2">"</span>
  <span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">_BOLD</span><span class="k">}${</span><span class="nv">_WHITE</span><span class="k">}</span><span class="s2">| package managers             |</span><span class="k">${</span><span class="nv">_RESET</span><span class="k">}</span><span class="s2">"</span>
  <span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">_BOLD</span><span class="k">}${</span><span class="nv">_WHITE</span><span class="k">}</span><span class="s2">--------------------------------</span><span class="k">${</span><span class="nv">_RESET</span><span class="k">}</span><span class="s2">"</span>

  ...

  <span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">_BOLD</span><span class="k">}${</span><span class="nv">_WHITE</span><span class="k">}</span><span class="s2">--------------------------------</span><span class="k">${</span><span class="nv">_RESET</span><span class="k">}</span><span class="s2">"</span>
  <span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">_BOLD</span><span class="k">}${</span><span class="nv">_WHITE</span><span class="k">}</span><span class="s2">| web servers                  |</span><span class="k">${</span><span class="nv">_RESET</span><span class="k">}</span><span class="s2">"</span>
  <span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">_BOLD</span><span class="k">}${</span><span class="nv">_WHITE</span><span class="k">}</span><span class="s2">--------------------------------</span><span class="k">${</span><span class="nv">_RESET</span><span class="k">}</span><span class="s2">"</span>

  ...

  <span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">_BOLD</span><span class="k">}${</span><span class="nv">_WHITE</span><span class="k">}</span><span class="s2">--------------------------------</span><span class="k">${</span><span class="nv">_RESET</span><span class="k">}</span><span class="s2">"</span>
  <span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">_BOLD</span><span class="k">}${</span><span class="nv">_WHITE</span><span class="k">}</span><span class="s2">| webdev tools                 |</span><span class="k">${</span><span class="nv">_RESET</span><span class="k">}</span><span class="s2">"</span>
  <span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">_BOLD</span><span class="k">}${</span><span class="nv">_WHITE</span><span class="k">}</span><span class="s2">--------------------------------</span><span class="k">${</span><span class="nv">_RESET</span><span class="k">}</span><span class="s2">"</span>

  ...

  <span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">_BOLD</span><span class="k">}${</span><span class="nv">_WHITE</span><span class="k">}</span><span class="s2">--------------------------------</span><span class="k">${</span><span class="nv">_RESET</span><span class="k">}</span><span class="s2">"</span>
  <span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">_BOLD</span><span class="k">}${</span><span class="nv">_WHITE</span><span class="k">}</span><span class="s2">| database systems             |</span><span class="k">${</span><span class="nv">_RESET</span><span class="k">}</span><span class="s2">"</span>
  <span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">_BOLD</span><span class="k">}${</span><span class="nv">_WHITE</span><span class="k">}</span><span class="s2">--------------------------------</span><span class="k">${</span><span class="nv">_RESET</span><span class="k">}</span><span class="s2">"</span>

  ...
<span class="o">}</span>
</code></pre></div></div>

<p>An example of a programming language would be <code class="language-plaintext highlighter-rouge">ruby</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function </span>about<span class="o">()</span> <span class="o">{</span>

  ...

  <span class="k">if </span><span class="nb">hash </span>ruby 2&gt;/dev/null<span class="p">;</span> <span class="k">then
    </span><span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">_BOLD</span><span class="k">}${</span><span class="nv">_YELLOW</span><span class="k">}</span><span class="s2">ruby</span><span class="k">${</span><span class="nv">_RESET</span><span class="k">}</span><span class="s2">:       </span><span class="k">${</span><span class="nv">_BOLD</span><span class="k">}${</span><span class="nv">_WHITE</span><span class="k">}</span><span class="si">$(</span>ruby <span class="nt">-v</span> | <span class="nb">cut</span> <span class="nt">-d</span><span class="s1">' '</span> <span class="nt">-f2</span><span class="si">)</span><span class="k">${</span><span class="nv">_RESET</span><span class="k">}</span><span class="s2"> </span><span class="si">$(</span>which ruby<span class="si">)</span><span class="s2">"</span> | _anon

    <span class="k">if </span><span class="nb">hash </span>rbenv 2&gt;/dev/null<span class="p">;</span> <span class="k">then
      </span><span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">_BOLD</span><span class="k">}${</span><span class="nv">_YELLOW</span><span class="k">}</span><span class="s2">- rbenv</span><span class="k">${</span><span class="nv">_RESET</span><span class="k">}</span><span class="s2">     </span><span class="k">${</span><span class="nv">_BOLD</span><span class="k">}${</span><span class="nv">_WHITE</span><span class="k">}</span><span class="si">$(</span>rbenv <span class="nt">-v</span> | <span class="nb">cut</span> <span class="nt">-d</span><span class="s1">' '</span> <span class="nt">-f2</span><span class="si">)</span><span class="k">${</span><span class="nv">_RESET</span><span class="k">}</span><span class="s2">; using: </span><span class="si">$(</span>rbenv global<span class="si">)</span><span class="s2">"</span> | _anon
    <span class="k">else
      </span><span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">_BOLD</span><span class="k">}${</span><span class="nv">_GRAY</span><span class="k">}</span><span class="s2">- rbenv not installed</span><span class="k">${</span><span class="nv">_RESET</span><span class="k">}</span><span class="s2">"</span>
    <span class="k">fi
  else
    </span><span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">_BOLD</span><span class="k">}${</span><span class="nv">_GRAY</span><span class="k">}</span><span class="s2">- ruby not installed</span><span class="k">${</span><span class="nv">_RESET</span><span class="k">}</span><span class="s2">"</span>
  <span class="k">fi</span>

  ...

<span class="o">}</span>
</code></pre></div></div>

<p>I check if it exists on the system, and if it does I print out the version (deftly cut up to get the most basic display of it possible, as every program does it differently and in different precision). Everything has proper spacing so that when a bunch of them are printed out they line up nicely.</p>

<p>Here’s an excerpt from the programming languages section to get the gist of all this:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python3:    3.11.7 /usr/local/bin/python3
- pyenv     2.3.35<span class="p">;</span> using: system
ruby:       3.2.2 <span class="nv">$HOME</span>/.rbenv/shims/ruby
- rbenv     1.2.0<span class="p">;</span> using: 3.2.2
rust:       1.75.0 /usr/local/bin/rustc
tsc:        5.3.3 /usr/local/bin/tsc
</code></pre></div></div>

<p>You may have noticed some of the version printing lines ending in <code class="language-plaintext highlighter-rouge">_anon</code>. That’s piping the output to a special sub-function I wrote to anonymize any line that may have my home directory explicitly displayed. If I want to share this kind of thing publicly, it’s nice to be able to keep it a little private.</p>

<h2 id="for-the-road">FOR THE ROAD</h2>

<p>To be honest, I don’t use this script very much, especially on a computer I use every day. I just kind of know what’s on it after a while, so I don’t need the reminder. I <em>can</em> forget, though, so it’s there to save me if I need it. Also, I can just copy this script to a server I’m accessing and run it to get a quick survey of its wares. As with most scripts like this, the most fun is in writing it, not using it.</p>

<p>The full thing is on <a href="https://github.com/michaelchadwick/dotfiles/blob/master/_functions#L34-L294">Github</a> if you wish to use it (or expand it! (or mangle it!)).</p>]]></content><author><name>Michael Chadwick</name></author><category term="blog" /><category term="about" /><category term="bash" /><category term="documentation" /><category term="linux" /><category term="macos" /><category term="neofetch" /><category term="python" /><category term="scripting" /><category term="shell" /><category term="sysadmin" /><category term="tool" /><category term="unix" /><summary type="html"><![CDATA[Have you ever wanted to just get a quick print out (to the screen) of all the tech that’s on your computer? I’ve been working on a shell script for years that does just that.]]></summary></entry><entry><title type="html">Advent of Code 2023</title><link href="https://michaelchadwick.info/blog/2023/12/22/advent-of-code-2023/" rel="alternate" type="text/html" title="Advent of Code 2023" /><published>2023-12-22T00:00:00-08:00</published><updated>2023-12-22T00:00:00-08:00</updated><id>https://michaelchadwick.info/blog/2023/12/22/advent-of-code-2023</id><content type="html" xml:base="https://michaelchadwick.info/blog/2023/12/22/advent-of-code-2023/"><![CDATA[<p>December is almost over, bringing us the new year of 2024. However, the last few years have ended with something fun, challenging, and educational for me: a digital advent calendar of puzzles.</p>

<!--more-->

<h2 id="what-is-the-advent-of-code">WHAT IS THE ADVENT OF CODE?</h2>

<p>Much like other advent calendars, <a href="https://adventofcode.com">Advent of Code</a> is a daily puzzle challenge that happens each day in December until (and including) the 25th, Christmas itself. Each year has a story of sorts, usually involving helping elves solve problems so that Christmas goes off without a hitch.</p>

<p>Each day is a two-part word problem: you solve the first part and unlock the second part. Part one is often easier and has some connection with part two. Days get harder as the challenge progresses. Regardless of the complexity, each part is solved with a single integer value. Solve each day in a year and you get 50 stars.</p>

<p>A puzzle consists of a word problem, some sample data, and then a question to answer using the actual input data they give you (tied to your login, so it’s unique). The common approach is to parse some textual input data, create some kind of data structure out of it, build an engine of sorts to process that data structure, and spit out an answer. Just following the directions, paying close to attention to what it says (and doesn’t say), and methodically working through all that will usually result in a correct solution. The reality, of course, is wildly variable day-to-day, though. One day you might be sending Santa Claus up and down an elevator, and the next you might be evaluating growing fish populations.</p>

<p>Since it <em>is</em> a challenge, there is a <a href="https://adventofcode.com/leaderboard">leaderboard</a> so you can see who solved each day’s puzzle first and in what time. However, all of the <a href="https://adventofcode.com/events">released challenges</a> are available at any time, regardless of the year in which they first became available, so you can do them at your leisure for completion’s sake.</p>

<h2 id="how-it-is-going">HOW IT IS GOING</h2>

<p>This is my third year actively competing in the challenge. Since I started in 2021, I’ve gone back to previous years and done a few of their challenges, but the meat of my output is in 2021, 2022, and 2023 (thus far).</p>

<p>I really enjoy AoC (as I will now refer to it), but it definitely challenges me! Despite using computers, and programming for them in some way, over the last 25+ years, AoC is often best solved with a strong background in algorithms and classic <em>computer science</em> chops. Except for a single class in high school in C, and a few quarters of college in Java, my programming knowledge is largely self-taught and centered around web development technologies like HTML, CSS, Javascript, SQL, and the like. My frontend has always been better than my backend, shall we say.</p>

<p>My approach to most challenges is straightforward and naive. What that means is that I don’t really analyze what the problem is asking for in a meta sense (e.g. a grid of numbers is given, and I’m asked to navigate through it, but what popular algorithm is this <em>really</em> modeling?) until after I just start parsing data left-to-right, top-to-down, and realizing that I’m hopelessly muddled in an endless string manipulation exercise with no idea how to get it to output an answer. Another very real result of this approach is an <strong>unoptimized algorithm</strong>. When you run it, it <em>may</em> get a solution, but only after it runs until the <em>heat death of the universe</em>, which means your algorithm is probably not correct <em>enough</em>.</p>

<p>Beyond the difficulty of any individual day’s puzzle, there are two things that always happen each year to me, compacting the overall frustration. The <strong>first thing</strong> is that I invariably end up not finishing a challenge on the day it’s released. This will usually occur around the second week of the month. That then makes me not finish the next day, too. And the next. The missed days pile up, and it becomes like a Steam backlog I know I’ll never get to. It hurts my confidence in even attempting later days, and it’s all just bleh. The <strong>second thing</strong> is related: <strong><em>mental fatigue</em></strong>. Even if I <em>know</em> what I need to do to solve a puzzle, sometimes it’s just tedious to accomplish. I work slowly on these things, and knowing I work slowly on them makes me not want to even try sometimes.</p>

<p>By the end of this year’s challenge (only a few days to go), I’m about mentally taxed to the extreme, and I just let the day’s releases go by like dust in the wind. I read them, but I rarely even attempt them. Sigh. Time to clean up my code, and maybe work on previous year’s puzzles, I guess.</p>

<p>One thing I’m doing differently this year is using a template for each day. Thanks to my friend Matt, I now have a script that generates the subdirectory and files for each day’s challenge so I just have to fill them in. It’s not necessary, but it makes the whole workflow run more smoothly.</p>

<h2 id="the-advent-of-code-meta-experience">THE ADVENT OF CODE META EXPERIENCE</h2>

<p>As one may find about events, there’s a lot of things going on <em>around</em> the event that can be interesting, too. This year, I’m trying to use the <a href="https://reddit.com/r/adventofcode">subreddit</a> that is chock full o’ people chatting about the event, giving hints, making memes, and just venting. I’ve found some helpful stuff there, and while it can still feel like “cheating” I’m <em>trying</em> not to care.</p>

<p>Also, I’ve decided to make a <a href="https://aoc.neb.host">web app</a> chronicling my journey. It’s built in Ruby on Rails (I’m doing all the problems this year in Ruby), and it’s a chance to rekindle my interest in both. I messed around with Ruby on Rails in the past, but never really finished a project worth putting up for posterity. I may go back and add my past year’s output at some point, but for now it’s just 2023.</p>

<p>My friend Matt and I are doing a few episodes of our podcast <a href="https://hackingthegrepson.com">Hacking the Grepson</a> on this year’s Advent of Code. There should be some interesting commentary on each day’s challenges for anyone interested.</p>

<h2 id="for-the-road">FOR THE ROAD</h2>

<p>While there are some other programming advent calendars out there, AoC is truly unique. The quality of the challenges, the website, the commentary on Reddit…it’s a very cool and important entity. Even if you don’t know how to program, you could ostensibly solve stuff on paper or in, like, Excel, so that’s pretty neat.</p>

<p>As this year’s challenge draws to a close, I’ve gotten more stars than any other year, and that’s progress!</p>]]></content><author><name>Michael Chadwick</name></author><category term="blog" /><category term="advent-of-code" /><category term="aoc" /><category term="aoc-2023" /><category term="ruby" /><category term="ruby-on-rails" /><summary type="html"><![CDATA[December is almost over, bringing us the new year of 2024. However, the last few years have ended with something fun, challenging, and educational for me: a digital advent calendar of puzzles.]]></summary></entry><entry><title type="html">Client-Side Ruby with Web Assembly</title><link href="https://michaelchadwick.info/blog/2023/11/22/client-side-ruby-with-web-assembly/" rel="alternate" type="text/html" title="Client-Side Ruby with Web Assembly" /><published>2023-11-22T00:00:00-08:00</published><updated>2023-11-22T00:00:00-08:00</updated><id>https://michaelchadwick.info/blog/2023/11/22/client-side-ruby-with-web-assembly</id><content type="html" xml:base="https://michaelchadwick.info/blog/2023/11/22/client-side-ruby-with-web-assembly/"><![CDATA[<p>I’ve never tried to do anything with Web Assembly before, even though it seems quite exciting. However, while searching the Internet for a way to showcase some old Ruby scripts I wrote once in my early programming days, I came upon this Javascript library that lets you run client-side Ruby in a web page.</p>

<p>And folks…I just had to try it out.</p>

<!--more-->

<h2 id="ruby">RUBY</h2>

<p><a href="https://www.ruby-lang.org">Ruby</a> is a dynamic programming language, often used with something called <a href="https://rubyonrails.org">Rails</a> to make dynamic websites. My attempts at making a Rails site never quite came to fruition, but I’ve made many scripts to do various things, like producing random text or <a href="https://github.com/michaelchadwick/gemwarrior-ruby">making a whole game</a>.</p>

<p>That being said, Ruby code largely runs on the command line and doesn’t have a strong GUI game. That is, until you bring <a href="https://webassembly.org">Web Assembly</a> (WASM) into the mix.</p>

<h2 id="web-assembly">WEB ASSEMBLY</h2>

<p>To be honest, I don’t think I understand it well enough to explain in any detail, other than to say it’s a way to write things in non-web languages (e.g. C/C++, Go, Ruby) so that they can be run on the web (via Javascript, for example). It’s a whole <em>thing</em>, and my little experiment here is just barely touching the edge of what it can do.</p>

<p>Here’s a <a href="https://web.dev/articles/what-is-webassembly">decent introduction</a>, here’s a related technology called <a href="https://emscripten.org/">Emscripten</a>, here’s a <a href="https://timdaub.github.io/wasm-synth/">synth</a> someone built with WASM, and here’s a <a href="https://innovation.ebayinc.com/tech/engineering/webassembly-at-ebay-a-real-world-use-case/">use case from ebay</a> to wet your whistle for now.</p>

<h2 id="gluing-it-all-together">GLUING IT ALL TOGETHER</h2>

<p>One way to use WASM is to translate Ruby code into something a web browser can understand on the client-side. In the past, you could have some frontend Javascript call a backend Ruby script and then return its output to the frontend to display on a website. With WASM, you can do <strong>all</strong> of this on the frontend.</p>

<p>Remember this tag?</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;script </span><span class="na">type=</span><span class="s">"text/javascript"</span><span class="nt">&gt;</span>
  <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">hello world</span><span class="dl">'</span><span class="p">)</span>
<span class="nt">&lt;/script&gt;</span>
</code></pre></div></div>

<p>Standard use case, right? You can also do this:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;script </span><span class="na">type=</span><span class="s">"text/ruby"</span><span class="nt">&gt;</span>
  <span class="nx">puts</span> <span class="dl">"</span><span class="s2">hello world from ruby</span><span class="dl">"</span>
<span class="nt">&lt;/script&gt;</span>
</code></pre></div></div>

<p>Of course, if you do <em>just</em> that, it won’t do anything. Add the following library to your <code class="language-plaintext highlighter-rouge">&lt;head&gt;</code> tag: <code class="language-plaintext highlighter-rouge">&lt;script src="https://cdn.jsdelivr.net/npm/ruby-3_2-wasm-wasi@1.0.1/dist/browser.script.iife.js"&gt;&lt;/script&gt;</code>, then refresh the page. You should see something like the following:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ruby 3.2.0 <span class="o">(</span>2022-12-25 revision a528908271<span class="o">)</span> <span class="o">[</span>wasm32-wasi] browser.script.iife.js:2871
hello world from ruby                                     browser.script.iife.js:2871
</code></pre></div></div>

<p>If you <code class="language-plaintext highlighter-rouge">require</code> the <code class="language-plaintext highlighter-rouge">js</code> module, then you can target web elements just like in Javascript:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;body&gt;</span>
  <span class="nt">&lt;label</span> <span class="na">for=</span><span class="s">"button"</span><span class="nt">&gt;</span>Click this button, then check dev console!<span class="nt">&lt;/label&gt;</span>
  <span class="nt">&lt;button</span> <span class="na">id=</span><span class="s">"button"</span><span class="nt">&gt;</span>Boop<span class="nt">&lt;/button&gt;</span>
<span class="nt">&lt;/body&gt;</span>

<span class="nt">&lt;script </span><span class="na">type=</span><span class="s">"text/ruby"</span><span class="nt">&gt;</span>
  <span class="nx">require</span> <span class="dl">"</span><span class="s2">js</span><span class="dl">"</span>

  <span class="nb">document</span> <span class="o">=</span> <span class="nx">JS</span><span class="p">.</span><span class="nb">global</span><span class="p">[:</span><span class="nb">document</span><span class="p">]</span>
  <span class="nx">button</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">button</span><span class="dl">'</span><span class="p">)</span>
  <span class="nx">button</span><span class="p">.</span><span class="nx">addEventListener</span> <span class="dl">"</span><span class="s2">click</span><span class="dl">"</span> <span class="k">do</span> <span class="o">|</span><span class="nx">e</span><span class="o">|</span>
    <span class="nx">puts</span> <span class="dl">"</span><span class="s2">you hit the boop!</span><span class="dl">"</span>
  <span class="nx">end</span>
<span class="nt">&lt;/script&gt;</span>
</code></pre></div></div>

<h2 id="for-the-road">FOR THE ROAD</h2>

<p>My examples don’t truly show the real power of WASM, because you could have easily written the Ruby in actual Javascript, but for something more complex (and pre-written), this could be very helpful.</p>

<p>Also, there’s one fundamental issue when writing client-side Ruby I should note: Ruby is couched in the terminal’s event loop, but to do anything interactive on a web page, like an input parser, you have to re-think how you’re checking for that input (<code class="language-plaintext highlighter-rouge">gets</code> vs <code class="language-plaintext highlighter-rouge">&lt;input&gt;</code>) and displaying output (<code class="language-plaintext highlighter-rouge">puts</code> vs <code class="language-plaintext highlighter-rouge">outputElement.value</code>), which definitely takes some re-writing of your original Ruby script. I suppose this is unavoidable as the two paradigms are fundamentally different.</p>

<p>Regardless, I thought this was a cool thing to discover, and I plan to rewrite some of my old scripts for showcasing on a website soon!</p>]]></content><author><name>Michael Chadwick</name></author><category term="blog" /><category term="ruby" /><category term="wasm" /><category term="web" /><category term="web-assembly" /><summary type="html"><![CDATA[I’ve never tried to do anything with Web Assembly before, even though it seems quite exciting. However, while searching the Internet for a way to showcase some old Ruby scripts I wrote once in my early programming days, I came upon this Javascript library that lets you run client-side Ruby in a web page.]]></summary></entry><entry><title type="html">Share New Jekyll Blog Post on Mastodon Using Github Actions</title><link href="https://michaelchadwick.info/blog/2023/11/17/share-new-jekyll-blog-post-on-mastodon-using-github-actions/" rel="alternate" type="text/html" title="Share New Jekyll Blog Post on Mastodon Using Github Actions" /><published>2023-11-17T00:00:00-08:00</published><updated>2023-11-17T00:00:00-08:00</updated><id>https://michaelchadwick.info/blog/2023/11/17/share-new-jekyll-blog-post-on-mastodon-using-github-actions</id><content type="html" xml:base="https://michaelchadwick.info/blog/2023/11/17/share-new-jekyll-blog-post-on-mastodon-using-github-actions/"><![CDATA[<p>Now that I use <a href="https://masto.neb.host">Mastodon</a> as my daily social media outlet, I want to automagically share any new posts on my <a href="https://michaelchadwick.info">blog</a> as a new toot. My blog is kept in a <code class="language-plaintext highlighter-rouge">git</code> repo, and shared on <a href="https://github.com">Github</a>, so I figured I could use <a href="https://docs.github.com/en/actions">Github Actions</a> to do this.</p>

<p>Thanks to some digital friends along the way, I was able to create one!</p>

<!--more-->

<h2 id="github-actions">GITHUB ACTIONS?</h2>

<p>Github Actions are YAML config files you write to trigger some kind of event to take place when a Github-related action occurs (e.g. push, pull request, etc.). You can even write Github Actions that others can use, and I used <a href="https://github.com/cbrgm/mastodon-github-action">one of them</a> to make the Mastodon part much easier to write.</p>

<p>I’ll go into detail on some specifics, but first here’s the full thing:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Blog2Masto</span>
<span class="na">on</span><span class="pi">:</span>
  <span class="na">push</span><span class="pi">:</span>
    <span class="na">paths</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s1">'</span><span class="s">blog/_posts/*.md'</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">post</span><span class="pi">:</span>
    <span class="na">if</span><span class="pi">:</span> <span class="s">contains(github.event.head_commit.message, 'new blog post:')</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Checkout code</span>
      <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v2</span>
      <span class="na">with</span><span class="pi">:</span>
        <span class="na">fetch-depth</span><span class="pi">:</span> <span class="m">0</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Get Commit SHA</span>
      <span class="na">id</span><span class="pi">:</span> <span class="s">get_commit_sha</span>
      <span class="na">if</span><span class="pi">:</span> <span class="s">${{ !env.ACT }}</span>
      <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
        <span class="s">echo "Running on Github"</span>
        <span class="s">echo "sha=${{ github.sha }}" &gt;&gt; $GITHUB_ENV</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Identify Added Blog Post</span>
      <span class="na">id</span><span class="pi">:</span> <span class="s">identify_added_blog_post</span>
      <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
        <span class="s">if [ -z ${{ env.sha }} ]; then</span>
          <span class="s">echo "using secret local SHA for 'act' testing"</span>
          <span class="s">sha=${{ secrets.TEST_SHA }}</span>
        <span class="s">else</span>
          <span class="s">sha=${{ env.sha }}</span>
        <span class="s">fi</span>

        <span class="s">if [ -n "$post_file" ]; then</span>
          <span class="s">new_post_file=$(echo "$post_file" | head -n 1)</span>

          <span class="s"># Extract information from file's front matter using awk and grep</span>
          <span class="s">title=$(awk '/^title:/ {$1=""; sub(/^[ \t]+/, ""); print}' "$new_post_file" | tr -d '"')</span>
          <span class="s">date=$(echo "$new_post_file" | grep -oP '\d{4}-\d{2}-\d{2}' | sed 's/-/\//g')</span>
          <span class="s">tags=$(awk '/^tags:/ {for (i=2; i&lt;=NF; i++) print "#" $i}' "$new_post_file" | tr -d '[,]' | tr '\n' ' ')</span>

          <span class="s"># Construct the URL based on Jekyll site's URL structure</span>
          <span class="s">base_url="${{ secrets.BLOG_URL }}"</span>
          <span class="s">url=$(echo "$title" | tr '[:upper:]' '[:lower:]' | tr -d '[:punct:]' | sed -E 's/[[:space:]]+/-/g' | sed -E 's/^-//;s/-$//')</span>
          <span class="s">post_url="$base_url/$date/$url"</span>

          <span class="s"># Add pertinent Github environment variables</span>
          <span class="s">echo "post_title=$title" &gt;&gt; $GITHUB_ENV</span>
          <span class="s">echo "post_url=$post_url" &gt;&gt; $GITHUB_ENV</span>
          <span class="s">echo "post_tags=$tags" &gt;&gt; $GITHUB_ENV</span>
        <span class="s">fi</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Post to Mastodon</span>
      <span class="na">id</span><span class="pi">:</span> <span class="s">post_to_mastodon</span>
      <span class="na">if</span><span class="pi">:</span> <span class="s">${{ env.post_title != '' &amp;&amp; env.post_url != '' }}</span>
      <span class="na">uses</span><span class="pi">:</span> <span class="s">cbrgm/mastodon-github-action@v1.0.3</span>
      <span class="na">with</span><span class="pi">:</span>
        <span class="na">message</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">New blog: "${{ env.post_title }}"</span>
          <span class="s">${{ env.post_url }}</span>

          <span class="s">${{ env.post_tags }} #blog</span>
        <span class="na">visibility</span><span class="pi">:</span> <span class="s2">"</span><span class="s">public"</span>
      <span class="na">env</span><span class="pi">:</span>
        <span class="na">MASTODON_URL</span><span class="pi">:</span> <span class="s">${{ secrets.MASTODON_URL }}</span>
        <span class="na">MASTODON_ACCESS_TOKEN</span><span class="pi">:</span> <span class="s">${{ secrets.MASTODON_ACCESS_TOKEN }}</span>
</code></pre></div></div>

<h2 id="artificial-intellihelp">ARTIFICIAL INTELLIHELP</h2>

<p>I <em>would</em> say that this Github Action I have written is a Github Action I have written, except for the fact that a lot of it was written by <a href="https://chat.openai.com">ChatGPT3</a>.</p>

<p>My initial stab at writing a Github Action was a lot less complicated than the final one, didn’t use <a href="https://github.com/actions/checkout">Github’s repo checkout action</a>, and didn’t have a long shell script to create variables. The resulting Mastodon post was just a link to my blog, and a simple “New blog post” title. I figured I could do better than this, but it would take some real effort.</p>

<p>Poring through the docs yielded some half-hearted and nonfunctional results (creating actions are not as simple as I would hope), so I did something I’ve done very little of thus far: I asked ChatGPT.</p>

<p>And it went well!</p>

<p>It’s no hot take at this point to say that ChatGPT is kind of revolutionary, if for nothing else than to be able to ask questions in conversational language and get actual working results! I did have to iterate a few times on certain lines that needed some tweaking or threw an error, but it was actually fun, way more fun than my Googling attempts.</p>

<h2 id="local-development">LOCAL DEVELOPMENT</h2>

<p>The main issue with writing and testing a Github Action is the…Github Action part. My action is predicated on a <code class="language-plaintext highlighter-rouge">git push</code> with a certain commit message, so every time I needed to make a change I had to do a whole <strong>thing</strong>:</p>

<ul>
  <li>Edit the action on Github</li>
  <li>Commit that change</li>
  <li>Pull that change to my local version</li>
  <li>Make a change to a post</li>
  <li>Commit that change</li>
  <li>Do a <code class="language-plaintext highlighter-rouge">git push</code> to trigger the action</li>
</ul>

<p>That workflow is not very efficient, eh?</p>

<p>Thankfully, I found <a href="https://github.com/nektos/act">act</a>. This awesome tool allows you to edit your action to your heart’s content, and run it from your local computer, instead of Github’s server. Make change, save file, run command, see results. Just like regular web development!</p>

<p>Downsides to using <code class="language-plaintext highlighter-rouge">act</code> vs Github itself were mainly centered around creating some secrets or server variables that don’t automatically exist locally, so it was sort of like writing configuration for unit tests.</p>

<h2 id="putting-it-all-together">PUTTING IT ALL TOGETHER</h2>

<p>It’s not exaggeration to claim that without the previous online help, I would have probably just given up and went back to manually posting on Mastodon like a pleb. But that’s no fun!</p>

<p>Thus, my <code class="language-plaintext highlighter-rouge">.github/workflows/post-to-mastodon.yml</code> action was truly born, and now we will break it down.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Blog2Masto</span>
<span class="na">on</span><span class="pi">:</span>
  <span class="na">push</span><span class="pi">:</span>
    <span class="na">paths</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s1">'</span><span class="s">blog/_posts/*.md'</span>
</code></pre></div></div>

<p>Github Actions are referred to as <em>workflows</em> that are triggered by some kind of <em>event</em> that occurs on a repository. My <em>event</em> is simple: when I do a <code class="language-plaintext highlighter-rouge">git push</code>, check if there is a file within the commit that matches a path. That path is where my blog posts live. If so, run a <em>job</em>.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">jobs</span><span class="pi">:</span>
  <span class="na">post</span><span class="pi">:</span>
    <span class="na">if</span><span class="pi">:</span> <span class="s">contains(github.event.head_commit.message, 'new blog post:')</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
</code></pre></div></div>

<p>Github Actions consist of <em>jobs</em> that consist of <em>steps</em>. I only have one job I need to accomplish: post to my Mastodon instance. I only want the job to run if a <code class="language-plaintext highlighter-rouge">git push</code> is adding a new blog post, so I figured the easiest way was to just put that in the commit message and check for it. There may be a way to do this by checking for “only when a Markdown post is <em>added</em>”, but this works for my purpose.</p>

<p>Also, the <code class="language-plaintext highlighter-rouge">runs-on</code> value is there because a small VM gets spun up to run your action, which is kind of cool.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">steps</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Checkout code</span>
      <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v2</span>
      <span class="na">with</span><span class="pi">:</span>
        <span class="na">fetch-depth</span><span class="pi">:</span> <span class="m">0</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Get Commit SHA</span>
      <span class="na">id</span><span class="pi">:</span> <span class="s">get_commit_sha</span>
      <span class="na">if</span><span class="pi">:</span> <span class="s">${{ !env.ACT }}</span>
      <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
        <span class="s">echo "Running on Github"</span>
        <span class="s">echo "sha=${{ github.sha }}" &gt;&gt; $GITHUB_ENV</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Identify Added Blog Post</span>
    <span class="s">...</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Post to Mastodon</span>
    <span class="s">...</span>
</code></pre></div></div>

<p>My one job has four steps:</p>

<ol>
  <li>Checkout code using a default Github Action</li>
  <li>If we are not using <code class="language-plaintext highlighter-rouge">act</code> on local (i.e. on Github), use the latest SHA</li>
  <li>Parse the commit to get the title, url, and tags from a Markdown file</li>
  <li>Post to Mastodon</li>
</ol>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>      <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
        <span class="s">if [ -z ${{ env.sha }} ]; then</span>
          <span class="s">echo "using secret local SHA for 'act' testing"</span>
          <span class="s">sha=${{ secrets.TEST_SHA }}</span>
        <span class="s">else</span>
          <span class="s">sha=${{ env.sha }}</span>
        <span class="s">fi</span>
</code></pre></div></div>

<p>The above part is in step 3 and sets a environment variable for our SHA (the long alphanumeric string that identifies your commit) if it hasn’t been set in step 2. This <code class="language-plaintext highlighter-rouge">secrets.TEST_SHA</code> is a hand-picked identifier from the git repo’s history that I know has the correct commit message and file added, and is used for local testing. The action uses the SHA later to extract information about the blog post to use in my Mastodon post.</p>

<p>The rest of the <code class="language-plaintext highlighter-rouge">identify_added_blog_post</code> step consists mainly of grabbing info from the Markdown file using *nix tools like <code class="language-plaintext highlighter-rouge">awk</code> and <code class="language-plaintext highlighter-rouge">grep</code> and <code class="language-plaintext highlighter-rouge">sed</code>, and was helpfully provided by ChatGPT3. The variables I need are added to the <code class="language-plaintext highlighter-rouge">$GITHUB_ENV</code> variable to be used in the second step.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Post to Mastodon</span>
      <span class="na">id</span><span class="pi">:</span> <span class="s">post_to_mastodon</span>
      <span class="na">if</span><span class="pi">:</span> <span class="s">${{ env.post_title != '' &amp;&amp; env.post_url != '' }}</span>
      <span class="na">uses</span><span class="pi">:</span> <span class="s">cbrgm/mastodon-github-action@v1.0.3</span>
      <span class="na">with</span><span class="pi">:</span>
        <span class="na">message</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">New blog: "${{ env.post_title }}"</span>
          <span class="s">${{ env.post_url }}</span>

          <span class="s">${{ env.post_tags }} #blog</span>
        <span class="na">visibility</span><span class="pi">:</span> <span class="s2">"</span><span class="s">public"</span>
      <span class="na">env</span><span class="pi">:</span>
        <span class="na">MASTODON_URL</span><span class="pi">:</span> <span class="s">${{ secrets.MASTODON_URL }}</span>
        <span class="na">MASTODON_ACCESS_TOKEN</span><span class="pi">:</span> <span class="s">${{ secrets.MASTODON_ACCESS_TOKEN }}</span>
</code></pre></div></div>

<p>Thanks to <a href="https://github.com/cbrgm/mastodon-github-action">this Github Action</a>, I could bring those juicy variables to my final step and plug them into the <code class="language-plaintext highlighter-rouge">with: message:</code> value. Finally, a worthy payload is sent off to my Mastodon instance, and the people rejoiced.</p>

<h2 id="for-the-road">FOR THE ROAD</h2>

<p>For how little I actually write blog posts, the time spent on getting a Github Action to work was most likely overkill. However, as a programmer, just trying to see if it can be done is a valid reason for anything.</p>

<p>Now that this blog post is done, committing its finality and pushing it to Github will hopefully, and accurately, create a Mastodon post linking to it. The meta of all this is truly overwhelming.</p>

<p><em>POSTSCRIPT</em>: Guess what? Running this on Github did not quite work, so more fiddling was necessary: Apparently, the Github checkout action needed some additional configuration (the code above has been updated) to actually see the commit that is crucial for getting the filename for anything else to work. This wasn’t necessary for <code class="language-plaintext highlighter-rouge">act</code> to work, so it was confusing for sure. But now it works! Yaaaaaaaay!</p>]]></content><author><name>Michael Chadwick</name></author><category term="blog" /><category term="automation" /><category term="chatgpt" /><category term="chatgpt3" /><category term="github" /><category term="github-actions" /><category term="jekyll" /><category term="mastodon" /><summary type="html"><![CDATA[Now that I use Mastodon as my daily social media outlet, I want to automagically share any new posts on my blog as a new toot. My blog is kept in a git repo, and shared on Github, so I figured I could use Github Actions to do this.]]></summary></entry><entry><title type="html">Slay the TIC-80</title><link href="https://michaelchadwick.info/blog/2023/10/23/slay-the-tic-80/" rel="alternate" type="text/html" title="Slay the TIC-80" /><published>2023-10-23T00:00:00-07:00</published><updated>2023-10-23T00:00:00-07:00</updated><id>https://michaelchadwick.info/blog/2023/10/23/slay-the-tic-80</id><content type="html" xml:base="https://michaelchadwick.info/blog/2023/10/23/slay-the-tic-80/"><![CDATA[<p>So, <a href="/blog/2023/09/13/back-to-basics-with-nebyooskate">NebyooSkate</a> kind of petered out, inspiration-wise (lost interest in gameplay), and technical-wise (not sure how to do the stuff I want to do with it), but it’s available to <a href="https://lowresnx.inutilis.com/topic.php?id=3143">try out</a> if you’d like.</p>

<p>In its place, a different idea has sprung anew: a roguelike deckbuilding idea.</p>

<!--more-->

<h2 id="why">WHY?</h2>

<p>The <a href="https://hackingthegrepson.com">podcast</a> I do with my friend Matt recently explored <a href="https://www.megacrit.com">Slay the Spire</a>, a roguelike deckbuilder available for most gaming platforms that exist. It came out several years ago, but I finally got sucked into it via Apple Arcade. Since then, I’ve put 40-50 hours into dozens and dozens and dozens of runs (still no wins yet -_-).</p>

<p>In the episode, we discuss how we think StS is made, from a gamedev viewpoint. I’m not one of the developers, and neither is Matt, but we both have made simple games before, so we have an educated guess on how StS might be constructed from component parts.</p>

<p>That being said, I’ve never made a game like it. That doesn’t mean I’m not now inspired to do so, though :-D</p>

<h2 id="what">WHAT?</h2>

<p>My preferred game engines tend to be retro consoles, mainly because they have all the parts you need to make simple games with proper gameplay, sprite graphics, and synth sounds. I’ve made a few finished games in <a href="https://www.lexaloffle.com/pico-8.php">PICO-8</a> and <a href="https://lowresnx.inutilis.com">Lowres NX</a> thus far (and at least poked at <a href="https://love2d.org">LÖVE</a>), but there’s one I learned about a year ago or so that I have not actually attempted to use yet: <a href="https://tic80.com">TIC-80</a>.</p>

<p>Much like the other retro consoles, TIC-80 features a command-line interface that can load and run applications, kind of like an old Commodore 64 or something. They are viewed through a low resolution display, and there are apps you can use to construct graphics, sound, and game logic for them. My main reason for using it is just to try something new, but there some significant points in TIC-80’s favor: slightly larger resolution than PICO-8, support for more than just the Lua scripting language, support for more button inputs, and completely open-source (PICO-8 is still very cheap to purchase).</p>

<h2 id="how">HOW?</h2>

<p>My first goal is to make a working TIC-80 cartridge, i.e. will load, print some text to the screen, and react to button presses. Next, I will create a player sprite, an enemy sprite, and display them. Then I will create an attack card, and display that to the screen, as well. Then I will create some kind of state logic for the enemy so it knows what to do on its turn. If I get that far, I think that’s some significant progress!</p>

<p>My end goal is to get the bare-bones, turn-based event loop of “there is a player, an enemy, some cards, and the ability for the player to use them against the enemy” working, followed by a win state if you beat the enemy, and a fail state if the enemy beats you. Beyond that, it’s a matter of making more enemies, more cards, and some kind of chain of battles that lead somewhere.</p>

<p>If I’m still interested in the game after all of that, then it would behoove me to make better graphics for everything, a title screen, and some sound effects and background music. There’s no way it’ll be anywhere near as cool as Slay the Spire, but RogueyooDeck could still be a cool thing to add to my <a href="https://neb.host/#games">portfolio</a>.</p>

<h2 id="for-the-road">FOR THE ROAD</h2>

<p>Learning a new system and all of its quirks is always fun, so even if this doesn’t go anywhere, I can still say I learned TIC-80. Who knows, maybe this will blossom into something bigger and I’ll try <a href="https://godotengine.org">Godot</a> someday!</p>]]></content><author><name>Michael Chadwick</name></author><category term="blog" /><category term="deckbuilding" /><category term="gamedev" /><category term="lowresnx" /><category term="lua" /><category term="pico8" /><category term="roguelike" /><category term="tic80" /><summary type="html"><![CDATA[So, NebyooSkate kind of petered out, inspiration-wise (lost interest in gameplay), and technical-wise (not sure how to do the stuff I want to do with it), but it’s available to try out if you’d like.]]></summary></entry><entry><title type="html">Back to BASICs with NebyooSkate</title><link href="https://michaelchadwick.info/blog/2023/09/13/back-to-basics-with-nebyooskate/" rel="alternate" type="text/html" title="Back to BASICs with NebyooSkate" /><published>2023-09-13T00:00:00-07:00</published><updated>2023-09-13T00:00:00-07:00</updated><id>https://michaelchadwick.info/blog/2023/09/13/back-to-basics-with-nebyooskate</id><content type="html" xml:base="https://michaelchadwick.info/blog/2023/09/13/back-to-basics-with-nebyooskate/"><![CDATA[<p>Inspiration has hit me again, and so I’ve dove back into BASIC programming via LowRes NX to make a new game. This time, I’m making an ice skating game somewhat inspired by Tony Hawk’s Pro Skater.</p>

<!--more-->

<h2 id="some-backstory">SOME BACKSTORY</h2>

<p>My daughter started ice skating at the beginning of 2023. She was a bit shaky at first, like we all were, but she has progressed impressively and quickly. She’s taken many public and private lessons, practiced for many hours, and is even on a synchronized skating team!</p>

<p>All this is to say that ice skating was not really in my mind grapes pre-2023, but is now in full force. As someone who dabbles in game development from time to time, the idea to make an ice skating game was not that farfetched.</p>

<h2 id="game-ide">GAME IDE</h2>

<p>There are a lot of ways to make a game these days. My preference is to use a retro console app like <a href="https://www.lexaloffle.com/pico-8.php">PICO-8</a> or <a href="https://tic80.com">TIC-80</a>, mainly because they have all the tools in them to make the code, graphics, and sound. The integrated package makes all the parts talk to each other easily, and is just the right level of quality for my “programmer art” mindset.</p>

<p>Unfortunately, there is no iOS version of either, so if I want to work on the game I have to be at a computer. I’m not <em>always</em> at a computer, and it’s super useful to use my iPhone to do game development as well, if possible.</p>

<p>Thus, I went back to <a href="https://lowresnx.inutilis.com">LowRes NX</a>, a retro console app I used for a bit earlier in the year. It works on macOS, iOS, and Windows. I can edit the code, graphics, and sound on my phone, but also on my Mac, with the helpful niceties that a desk (large monitor, full keyboard/mouse) and VS Code provides (syntax highlighting, code folding, and auto-indentation via <a href="https://marketplace.visualstudio.com/items?itemName=schraf.lowres-nx&amp;ssr=false#overview">this plugin</a>, etc.).</p>

<p>One unfortunate result of using LowRes NX is that it uses the BASIC programming language (but without line numbers, thankfully). Obviously, one <em>can</em> make a game with it, but BASIC was originally created in the 60s and does not do OOP, nor does it feature the modern creature features programmers have come to know and love since then (like, I dunno, functions that return a value). It’s best to set your expectations to “someone making a game in 1980 for the Atari” or something.</p>

<p>Regardless, for the kind of simple stuff I’m making, BASIC is fine, even if it means I have to kind of de-program my brain from what I normally work in to make headway. The low-resolution graphics and audio are fine, too, as I love chiptune and retro games.</p>

<h2 id="progress-so-far">PROGRESS SO FAR</h2>

<p>The initial seed of an idea was to create an ice rink with some walls, and a skater that you control. Move the skater around the rink and then…uh, I’ll figure that out later. This involved making a bunch of sprites for the four cardinal directions, as well as alternate versions for when you’re moving. I also had to add a simple acceleration/deceleration model so it actually looks like the avatar is <em>skating</em> and not simply <em>moving</em>.</p>

<p>The most significant thing I have figured out so far is creating a little Tony Hawk-esque “hold button to crouch and then release to jump” move.</p>

<video src="/assets/video/posts/nebyooskate_jump-2023-09-13.mp4" controls="controls" width="480" height="320"></video>

<pre><code class="language-basic">GLOBAL B_PRESSED
B_PRESSED=0

IF BUTTON TAP(0,1) THEN
  B_PRESSED=1
END IF

IF BUTTON(0,1) AND B_PRESSED=1 THEN
  'CHANGE SPRITE, ETC.
ELSE
  IF B_PRESSED=1 THEN
    B_PRESSED=0
    CALL JUMP
  END IF
END IF
</code></pre>

<p>The way I handle jumping is hacky (it uses <code>WAIT</code>) and needs to be updated to an upward velocity thing, but I’ll get to that next.</p>

<h2 id="challenges-ahead">CHALLENGES AHEAD</h2>

<p>I want this game to involve doing tricks to get a high score, so I’m going to continue with the THPS idea, and figure out how best to map all the moves I want to be able to perform onto two buttons and a D-pad. I think trying to figure out the notion of “combos” is going to be rough.</p>

<p>Since I’m a musician, I gotta think of a cool soundtrack and some sound effects, too, but that’s the real fun part.</p>

<h2 id="for-the-road">FOR THE ROAD</h2>

<p>Stay slick!</p>]]></content><author><name>Michael Chadwick</name></author><category term="blog" /><category term="basic" /><category term="gamedev" /><category term="lowresnx" /><category term="nebyooskate" /><category term="pico8" /><category term="tic80" /><summary type="html"><![CDATA[Inspiration has hit me again, and so I’ve dove back into BASIC programming via LowRes NX to make a new game. This time, I’m making an ice skating game somewhat inspired by Tony Hawk’s Pro Skater.]]></summary></entry><entry><title type="html">AWStats Hacking, or How I Inserted HTML into a 20K+ LOC Perl Script</title><link href="https://michaelchadwick.info/blog/2023/08/28/awstats-hacking-or-how-i-inserted-html-into-a-20K-loc-perl-script/" rel="alternate" type="text/html" title="AWStats Hacking, or How I Inserted HTML into a 20K+ LOC Perl Script" /><published>2023-08-28T00:00:00-07:00</published><updated>2023-08-28T00:00:00-07:00</updated><id>https://michaelchadwick.info/blog/2023/08/28/awstats-hacking-or-how-i-inserted-html-into-a-20K-loc-perl-script</id><content type="html" xml:base="https://michaelchadwick.info/blog/2023/08/28/awstats-hacking-or-how-i-inserted-html-into-a-20K-loc-perl-script/"><![CDATA[<p>Besides some half-hearted Google Analytics embeds on a couple of my sites set up years ago, I’ve never really kept track of who (or what) visits my websites.</p>

<p>THAT ENDS NOW!</p>

<p>Ahem.</p>

<!--more-->

<p>Since I’ve been a on self-hosted kick, slowly replacing external services with internal services that I set up and administer, I decided I would finally get some kind of analytics going on my web places.</p>

<p>There are many options for this kind of thing nowadays, but I remembered an old friend from my past work experiences, and figured I could look them up again to see how they’re doing.</p>

<p>Enter <a href="http://www.awstats.org">AWStats</a>.</p>

<p>AWStats, which stands for <strong>A</strong>dvanced <strong>W</strong>eb <strong>Stat</strong>istic<strong>S</strong>, is pretty simple: you give it a configuration file that points to a server log file, it parses a <a href="https://github.com/eldy/AWStats/blob/develop/wwwroot/cgi-bin/awstats.pl">HUMONGOUS Perl script</a> (this software was first released over 20 years ago, but hasn’t changed much in its basic structure), and spits out a web page (using <code class="language-plaintext highlighter-rouge">&lt;frameset&gt;</code>!) that gives you a bunch of stats about your website and its visitors. It’s not exactly pretty, and it isn’t what you’d expect in a modern web application, but it works, more or less.</p>

<p>Of course, as a web developer, it’s near-impossible for me to not want to tweak it. Just a little bit.</p>

<p>After looking at the AWStats interface for about 5 seconds, I realized I wanted two (2) things:</p>

<ol>
  <li>A <code class="language-plaintext highlighter-rouge">&lt;select&gt;</code> to choose from my multiple domains, as an easy switch, instead of having multiple bookmarks or manually changing the part after <code class="language-plaintext highlighter-rouge">config=</code> in the URL</li>
  <li>A favicon.</li>
</ol>

<p>Now, most web applications these days have templates you can customize, as they’ve been built with this kind of hackery in mind. AWStats, though? NOPE.</p>

<h2 id="frame-time">FRAME TIME</h2>

<p>First things first, how is the web page that the GINORMOUS Perl script spits out structured? It still uses that darling of the early 2000s, <code class="language-plaintext highlighter-rouge">&lt;frameset&gt;</code>. There’s one of them, with two <code class="language-plaintext highlighter-rouge">&lt;frame&gt;</code> elements inside (I’m using pseudocode below, so it’s not 100% accurate, but you get the idea):</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;html</span> <span class="na">lang=</span><span class="s">"en"</span><span class="nt">&gt;</span>
<span class="nt">&lt;head&gt;</span>
  ...
<span class="nt">&lt;/head&gt;</span>
<span class="nt">&lt;frameset</span> <span class="na">cols=</span><span class="s">"240, *"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;frame</span> <span class="na">name=</span><span class="s">"left"</span> <span class="na">src=</span><span class="s">"nav"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;frame</span> <span class="na">name=</span><span class="s">"right"</span> <span class="na">src=</span><span class="s">"stats"</span><span class="nt">&gt;</span>
<span class="nt">&lt;/frameset&gt;</span>
<span class="nt">&lt;/html&gt;</span>
</code></pre></div></div>

<p>It’s easy to see that this is how the <em>rendered</em> website looks, but how is the HTML created? Buried inside somewhere, an intrepid coder finds the following:</p>

<div class="language-perl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">...</span>
<span class="k">print</span> <span class="p">"</span><span class="s2">&lt;frameset cols=</span><span class="se">\"</span><span class="si">$FRAMEWIDTH</span><span class="s2">,*</span><span class="se">\"</span><span class="s2">&gt;</span><span class="se">\n</span><span class="p">";</span>
<span class="k">print</span> <span class="p">"</span><span class="s2">&lt;frame name=</span><span class="se">\"</span><span class="s2">mainleft</span><span class="se">\"</span><span class="s2"> src=</span><span class="se">\"</span><span class="p">"</span>
  <span class="o">.</span> <span class="nv">XMLEncode</span><span class="p">("</span><span class="si">$AWScript${NewLinkParams}</span><span class="s2">framename=mainleft</span><span class="p">")</span>
  <span class="o">.</span> <span class="p">"</span><span class="se">\"</span><span class="s2"> frameborder=</span><span class="se">\"</span><span class="s2">0</span><span class="se">\"</span><span class="s2"> /&gt;</span><span class="se">\n</span><span class="p">";</span>
<span class="k">print</span> <span class="p">"</span><span class="s2">&lt;frame name=</span><span class="se">\"</span><span class="s2">mainright</span><span class="se">\"</span><span class="s2"> src=</span><span class="se">\"</span><span class="p">"</span>
  <span class="o">.</span> <span class="nv">XMLEncode</span><span class="p">("</span><span class="si">$AWScript${NewLinkParams}</span><span class="s2">framename=mainright</span><span class="p">")</span>
  <span class="o">.</span> <span class="p">"</span><span class="se">\"</span><span class="s2"> scrolling=</span><span class="se">\"</span><span class="s2">yes</span><span class="se">\"</span><span class="s2"> frameborder=</span><span class="se">\"</span><span class="s2">0</span><span class="se">\"</span><span class="s2"> /&gt;</span><span class="se">\n</span><span class="p">";</span>
<span class="k">print</span> <span class="p">"</span><span class="s2">&lt;noframes&gt;&lt;body&gt;</span><span class="p">";</span>
<span class="o">...</span>
<span class="k">print</span> <span class="p">"</span><span class="s2">&lt;/body&gt;&lt;/noframes&gt;</span><span class="se">\n</span><span class="p">";</span>
<span class="k">print</span> <span class="p">"</span><span class="s2">&lt;/frameset&gt;</span><span class="se">\n</span><span class="p">";</span>
<span class="k">print</span> <span class="p">"</span><span class="s2">&lt;/frameset&gt;</span><span class="se">\n</span><span class="p">";</span>
<span class="o">...</span>
</code></pre></div></div>

<p>It took me some time on MDN to remember how to use HTML frames again, as I hadn’t used them in decades, but I finally came up with this little hack:</p>

<div class="language-perl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">...</span>
<span class="c1"># mikehack start</span>
<span class="k">print</span> <span class="p">"</span><span class="s2">&lt;frameset rows=</span><span class="se">\"</span><span class="s2">50,*</span><span class="se">\"</span><span class="s2">&gt;</span><span class="se">\n</span><span class="p">";</span>
<span class="k">print</span> <span class="p">"</span><span class="s2">&lt;frame name=</span><span class="se">\"</span><span class="s2">domainselector</span><span class="se">\"</span><span class="s2"> src=</span><span class="se">\"</span><span class="p">"</span>
  <span class="o">.</span> <span class="nv">XMLEncode</span><span class="p">("</span><span class="s2">./domainselector.html?framename=maintop</span><span class="p">")</span>
  <span class="o">.</span> <span class="p">"</span><span class="se">\"</span><span class="s2"> frameborder=</span><span class="se">\"</span><span class="s2">0</span><span class="se">\"</span><span class="s2"> /&gt;</span><span class="se">\n</span><span class="p">";</span>
<span class="c1"># mikehack end</span>
<span class="k">print</span> <span class="p">"</span><span class="s2">&lt;frameset cols=</span><span class="se">\"</span><span class="si">$FRAMEWIDTH</span><span class="s2">,*</span><span class="se">\"</span><span class="s2">&gt;</span><span class="se">\n</span><span class="p">";</span>
<span class="k">print</span> <span class="p">"</span><span class="s2">&lt;frame name=</span><span class="se">\"</span><span class="s2">mainleft</span><span class="se">\"</span><span class="s2"> src=</span><span class="se">\"</span><span class="p">"</span>
  <span class="o">.</span> <span class="nv">XMLEncode</span><span class="p">("</span><span class="si">$AWScript${NewLinkParams}</span><span class="s2">framename=mainleft</span><span class="p">")</span>
  <span class="o">.</span> <span class="p">"</span><span class="se">\"</span><span class="s2"> frameborder=</span><span class="se">\"</span><span class="s2">0</span><span class="se">\"</span><span class="s2"> /&gt;</span><span class="se">\n</span><span class="p">";</span>
<span class="k">print</span> <span class="p">"</span><span class="s2">&lt;frame name=</span><span class="se">\"</span><span class="s2">mainright</span><span class="se">\"</span><span class="s2"> src=</span><span class="se">\"</span><span class="p">"</span>
  <span class="o">.</span> <span class="nv">XMLEncode</span><span class="p">("</span><span class="si">$AWScript${NewLinkParams}</span><span class="s2">framename=mainright</span><span class="p">")</span>
  <span class="o">.</span> <span class="p">"</span><span class="se">\"</span><span class="s2"> scrolling=</span><span class="se">\"</span><span class="s2">yes</span><span class="se">\"</span><span class="s2"> frameborder=</span><span class="se">\"</span><span class="s2">0</span><span class="se">\"</span><span class="s2"> /&gt;</span><span class="se">\n</span><span class="p">";</span>
<span class="k">print</span> <span class="p">"</span><span class="s2">&lt;noframes&gt;&lt;body&gt;</span><span class="p">";</span>
<span class="o">...</span>
<span class="k">print</span> <span class="p">"</span><span class="s2">&lt;/body&gt;&lt;/noframes&gt;</span><span class="se">\n</span><span class="p">";</span>
<span class="k">print</span> <span class="p">"</span><span class="s2">&lt;/frameset&gt;</span><span class="se">\n</span><span class="p">";</span>
<span class="k">print</span> <span class="p">"</span><span class="s2">&lt;/frameset&gt;</span><span class="se">\n</span><span class="p">";</span>
<span class="o">...</span>

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

<p>This essentially puts the whole page into another <code class="language-plaintext highlighter-rouge">&lt;frameset&gt;</code> so I can insert a new <code class="language-plaintext highlighter-rouge">&lt;frame&gt;</code> that pulls in another HTML file with a domain selector chunk:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;form</span> <span class="na">id=</span><span class="s">"domain-selector"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;select</span> <span class="na">id=</span><span class="s">"domain"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;option</span> <span class="na">value=</span><span class="s">""</span><span class="nt">&gt;</span>- Choose Domain -<span class="nt">&lt;/option&gt;</span>
    <span class="nt">&lt;option</span> <span class="na">value=</span><span class="s">"michaelchadwick.info"</span><span class="nt">&gt;</span>michaelchadwick.info<span class="nt">&lt;/option&gt;</span>
    <span class="c">&lt;!-- add other &lt;option&gt;s here for more domains --&gt;</span>
  <span class="nt">&lt;/select&gt;</span>
  <span class="nt">&lt;button</span> <span class="na">type=</span><span class="s">"button"</span> <span class="na">onclick=</span><span class="s">"changeDomain()"</span><span class="nt">&gt;</span>Change Domain<span class="nt">&lt;/button&gt;</span>
<span class="nt">&lt;/form&gt;</span>

<span class="nt">&lt;script </span><span class="na">type=</span><span class="s">"text/javascript"</span><span class="nt">&gt;</span>
  <span class="kd">function</span> <span class="nf">changeDomain</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">dropdown</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">querySelector</span><span class="p">(</span><span class="dl">'</span><span class="s1">#domain-selector select</span><span class="dl">'</span><span class="p">)</span>
    <span class="kd">const</span> <span class="nx">domain</span> <span class="o">=</span> <span class="nx">dropdown</span><span class="p">.</span><span class="nx">value</span>

    <span class="k">if </span><span class="p">(</span><span class="nx">domain</span> <span class="o">!=</span> <span class="dl">''</span><span class="p">)</span> <span class="p">{</span>
      <span class="c1">// top.location to reload whole frameset</span>
      <span class="c1">// self.location and document.location did not work</span>
      <span class="nx">top</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">href</span> <span class="o">=</span> <span class="s2">`/cgi-bin/awstats.pl?config=</span><span class="p">${</span><span class="nx">domain</span><span class="p">}</span><span class="s2">`</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="nb">window</span><span class="p">.</span><span class="nx">onload</span> <span class="o">=</span> <span class="kd">function</span> <span class="nf">loadDomainSelector</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">searchParams</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">URLSearchParams</span><span class="p">(</span><span class="k">new</span> <span class="nc">URL</span><span class="p">(</span><span class="nx">top</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">href</span><span class="p">).</span><span class="nx">search</span><span class="p">)</span>

    <span class="kd">const</span> <span class="nx">dropdown</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">domain</span><span class="dl">'</span><span class="p">)</span>
    <span class="kd">const</span> <span class="nx">domain</span> <span class="o">=</span> <span class="nx">searchParams</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">config</span><span class="dl">'</span><span class="p">)</span>

    <span class="nx">dropdown</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">domain</span>
  <span class="p">}</span>
<span class="nt">&lt;/script&gt;</span>
</code></pre></div></div>

<p>This bit of HTML and JS makes a <code class="language-plaintext highlighter-rouge">&lt;select&gt;</code> with all my domains, and when the button is clicked the site reloads with the new config value, giving me sweet, luscious statistics.</p>

<h2 id="favicon">FAVICON</h2>

<p>Inserting a favicon into AWStats was a lot easier, but still necesitated trawling through the code until I found where the <code class="language-plaintext highlighter-rouge">&lt;/head&gt;</code> tag was inserted. I put a simple <code class="language-plaintext highlighter-rouge">&lt;link rel="icon" href="/path/to/favicon.ico"&gt;</code> right before it, and now I have a custom image on the pinned tab for AWStats for easy scanning amongst other pinned tabs that are not AWStats.</p>

<h2 id="for-the-road">FOR THE ROAD</h2>

<p>The takeaway from all this is thus: AWStats is ancient by Internet standards, and not at all how I, or probably most people, would write an app like this anymore. That being said, it’s kind of impressive how much this software has not changed, and yet is still useful. Clean, beautiful code is certainly an aim when developing, but in the end, the end user either concludes it works or not, so even something like a 20K line script can still do the trick.</p>]]></content><author><name>Michael Chadwick</name></author><category term="blog" /><category term="awstats" /><category term="hacking" /><category term="html" /><category term="open-source" /><category term="perl" /><category term="self-hosting" /><summary type="html"><![CDATA[Besides some half-hearted Google Analytics embeds on a couple of my sites set up years ago, I’ve never really kept track of who (or what) visits my websites.]]></summary></entry><entry><title type="html">Mastodon Embedding for Toot’s Sake</title><link href="https://michaelchadwick.info/blog/2023/08/15/mastodon-embedding-for-toots-sake/" rel="alternate" type="text/html" title="Mastodon Embedding for Toot’s Sake" /><published>2023-08-15T00:00:00-07:00</published><updated>2023-08-15T00:00:00-07:00</updated><id>https://michaelchadwick.info/blog/2023/08/15/mastodon-embedding-for-toots-sake</id><content type="html" xml:base="https://michaelchadwick.info/blog/2023/08/15/mastodon-embedding-for-toots-sake/"><![CDATA[<p>I’ve been doing a lot of posting on my <a href="https://masto.neb.host/@nebyoolae">Mastodon instance</a> lately. It’s been fun just throwing a few hundred characters up every now and then, and interacting with a new crowd of artists, programmers, and geeks I’ve amassed over the last ~10 months.</p>

<p>Media embeds work pretty seamlessly in a Mastodon post, but I’ve never tried to embed a Mastodon post <em>somewhere else</em>.</p>

<p>Let’s try it!</p>

<!--more-->

<p>So, I made a track recently by doing a <a href="https://support.apple.com/guide/garageband-ipad/live-loops-overview-chsca7ff9ced/ipados">Live Loops</a> session in Garageband iOS on my phone. I was inspired by the <strong>Neon Metropolis</strong> sound pack, which is full of fun chiptune beats, rhythms, arps, and leads. While on a recent vacation, I loaded up the sound pack, started a session, and then just went to town for a few minutes, starting and stopping loops, and doing DJ-style effects here and there.</p>

<p>Once I was happy with what I’d done, I imported the Garageband iOS file into Logic Pro X onto a Mac to clean up my mistakes and do some more customization. Then I exported it to an MP3, put it up on my VPS, and made a post on Mastodon about it. Hopefully, you should see that below.</p>

<iframe src="https://masto.neb.host/@nebyoolae/110881230681905053/embed" class="mastodon-embed" style="max-width: 100%; border: 0" width="400" allowfullscreen="allowfullscreen"></iframe>
<script src="https://masto.neb.host/embed.js" async="async"></script>

<p>If you don’t see the Mastodon post above, with a further-embedded media player, then just go to the post directly: <a href="https://masto.neb.host/@nebyoolae/110881230681905053">direct link to Mastodon post</a></p>

<p>If <strong>that</strong> link doesn’t work, then try this link: <a href="https://bits.neb.host/assets/audio/misc/magnificent-megalopolis.mp3">direct link to Magnificent Megalopolis</a>.</p>

<p>If <strong><em>that</em></strong> link doesn’t work, then just, uh, imagine a cool song in your head and call it a day.</p>]]></content><author><name>Michael Chadwick</name></author><category term="blog" /><category term="fun" /><category term="garageband" /><category term="garageband-ios" /><category term="live-loops" /><category term="logic" /><category term="mastodon" /><category term="music" /><summary type="html"><![CDATA[I’ve been doing a lot of posting on my Mastodon instance lately. It’s been fun just throwing a few hundred characters up every now and then, and interacting with a new crowd of artists, programmers, and geeks I’ve amassed over the last ~10 months.]]></summary></entry><entry><title type="html">Blogging All the Blogs</title><link href="https://michaelchadwick.info/blog/2023/08/04/blogging-all-the-blogs/" rel="alternate" type="text/html" title="Blogging All the Blogs" /><published>2023-08-04T00:00:00-07:00</published><updated>2023-08-04T00:00:00-07:00</updated><id>https://michaelchadwick.info/blog/2023/08/04/blogging-all-the-blogs</id><content type="html" xml:base="https://michaelchadwick.info/blog/2023/08/04/blogging-all-the-blogs/"><![CDATA[<p>I’ve been blogging on the web for over 20 years. I started with <a href="https://livejournal.com">LiveJournal</a>, moved to a personal <a href="https://wordpress.org">Wordpress</a>, and then created a <a href="https://jekyllrb.com">Jekyll</a> blog inside my public site.</p>

<p>Moving all of that cruft from one system to another is non-trivial, and I left a lot of old stuff in a format that couldn’t be used…<em>until now</em>.</p>

<p><em>Note</em>: the post header image was created using <a href="https://nightcafe.studio">NightCafe</a>, but the words are my own (I swear!).</p>

<!--more-->

<h2 id="my-process">MY PROCESS</h2>

<p>Thankfully, both LiveJournal and Wordpress possess ways to export content, and so I did. Unfortunately, Wordpress exports not only the final version of a post, but also “autosave” and “revision” versions, so I had to spend some time wading through them all to get the canonical version of each post. There were 4000+ files, so it took <em>a bit</em>.</p>

<p>Jekyll uses a simple filename-based post format tucked into a <code>_posts</code> directory, so a blog post written in today would be something like <code>_posts/2023-08-04-blogging-all-the-blogs.md</code> (<code>.html</code> is also OK). My exported blog posts did not have that filename format, so through the combination of a couple shell scripts and <a href="https://manytricks.com/namemangler">Name Mangler</a> I was able to grab a <code>date</code> meta line from the file itself and rename each file accordingly. I also needed to add a specific tag to some posts so I could hide them publicly (not everything I wrote in the past is germane to this blog, but I still want to be able to read them!).</p>

<h2 id="hiccups">HICCUPS</h2>

<p>Besides re-learning how to do string manipulation in <code>bash</code> and how to actually use <code>sed</code> (I’ve basically almost never used it before), the main hiccup was just dealing with a large number of files (which I segmented into folders based on the first letter in the file) and trial-and-error with the logic in my shell scripts.</p>

<p>However, one Jekyll-related thing that came up (that I’ve solved in the past but forgot how to do until now) was this warning when serving up my site:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Conflict: The following destination is shared by multiple files
          The written file may end up with unexpected contents.
          /path/to/jekyll/_site/blog/tags/[tag-name]/index.html
           - blog/tags/[tag-name]/index.html
           - blog/tags/[tag-name]/index.html
</code></pre></div></div>

<p>Most of the fixes I found on the web did not address it for me (e.g. duplicate <code>page.html</code> and <code>page.md</code>), but once I searched for the offending tag name it was obvious: <code>tag name</code> vs. <code>tag-name</code> was resulting in the same page, which is not ideal.</p>

<p>The fix? Make sure you only use one format or the other. I just renamed any tags that had spaces to use hyphens instead and <em>voilà</em>.</p>

<h2 id="in-the-end">IN THE END</h2>

<p>Now I have a way to read everything I’ve ever blogged in the past 20 years in one place, and I control all of the content and how it’s displayed. This is the dream for all of us who, while still living in a corporate-controlled web where all your content gets locked up in a few companies’ coffers, don’t, like, actually want that to be true.</p>

<p>I’m sure many don’t care about that, and will continue to post their stuff on the Facebooks, Instagrams, Twitters/Xs, Threads, and their ilk, and so be it. Maybe I’m too precious with my words and their archival, but I figure I can have it this way and still link to my blog on those spaces if I want, so it’s a win-win.</p>]]></content><author><name>Michael Chadwick</name></author><category term="blog" /><category term="blogging" /><category term="site-maintenance" /><summary type="html"><![CDATA[I’ve been blogging on the web for over 20 years. I started with LiveJournal, moved to a personal Wordpress, and then created a Jekyll blog inside my public site.]]></summary></entry><entry><title type="html">Nebyoodle is Live</title><link href="https://michaelchadwick.info/blog/2023/07/05/nebyoodle-is-live/" rel="alternate" type="text/html" title="Nebyoodle is Live" /><published>2023-07-05T00:00:00-07:00</published><updated>2023-07-05T00:00:00-07:00</updated><id>https://michaelchadwick.info/blog/2023/07/05/nebyoodle-is-live</id><content type="html" xml:base="https://michaelchadwick.info/blog/2023/07/05/nebyoodle-is-live/"><![CDATA[<p>First of all, RIP <a href="https://heardle.app">Heardle</a> :(</p>

<p>Second of all, I have a big enough ego to attempt my own Heardle, but using only my music. And so, I did.</p>

<!--more-->

<p>Using the same codebase as my <a href="https://bogdle.neb.host">Bogdle</a> game, which is also itself a play on the popular Wordle game, I was able to get a PoC of <a href="https://guess.nebyoolae.com">Nebyoodle</a> done fairly quickly.</p>

<h2 id="fun-new-challenges">FUN NEW CHALLENGES</h2>

<p>As with all new web applications, there’s usually at least one or two challenges that are new to me.</p>

<h3 id="drupal-json-api">DRUPAL JSON API</h3>

<p>The logical data source for all my music is <a href="https://music.nebyoolae.com">NebyooMusic</a>, a Drupal-based web app that has all the music I’ve ever done, organized, and filled with metadata.</p>

<p>I enabled the core module <a href="https://www.drupal.org/docs/core-modules-and-themes/core-modules/jsonapi-module">JSON:API</a>, and some of its submodules, and began configuring it to work with views of tracks. Then I could query an API endpoint and get a, for example, random track.</p>

<p>For a random-track-guessing web game, that’s pretty useful!</p>

<h3 id="web-audio-api">WEB AUDIO API</h3>

<p><a href="https://ah.neb.host">This</a> <a href="https://keebord.neb.host">is</a> <a href="https://soundlister.neb.host">something</a> I’ve dealt with plenty of times before, so I’m not neophyte to making audio occur on a website, but the Web Audio API is still something that is non-trivial to deal with beyond the following:</p>

<audio controls="">
  <source src="/assets/audio/posts/sound.mp3" type="audio/mpeg" />
No &lt;audio&gt; support in your browser found.
</audio>

<p>The main issue was handling skips vs incorrect guesses vs correct guesses and how they affect the UI and the overall game status. When it’s just text or images, it’s one thing, but when it’s audio, it’s a bit different.</p>

<h2 id="for-the-road">FOR THE ROAD</h2>

<p>I learn and organize as I go, which means a lot of backtracking on code and redoing things I’ve already done to make them more sense. I also jump between projects a fair amount, usually due to being stuck or burnt out, and any progress I make in one project often influences others. I should really use a framework or something (or make my own…?), but instead I stubbornly keep making my own each time, and consistency across projects takes a hit.</p>

<p>That being said, the meat of this project was done in Bogdle, so it didn’t take nearly as long to get done. Now I have a fun way to go through my catalog, which is nearly 600(!) tracks large. No one else would have the domain knowledge to play this game, and yet it was still fun to build, which is my bar of success.</p>]]></content><author><name>Michael Chadwick</name></author><category term="blog" /><category term="audio" /><category term="bogdle" /><category term="game" /><category term="music" /><category term="nebapp" /><category term="nebyoolae" /><category term="nebyoomusic" /><category term="web" /><category term="wordle-like" /><summary type="html"><![CDATA[First of all, RIP Heardle :(]]></summary></entry></feed>