<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.2.1">Jekyll</generator><link href="https://mdcramer.github.io/feed/apple-2-blog.xml" rel="self" type="application/atom+xml" /><link href="https://mdcramer.github.io/" rel="alternate" type="text/html" /><updated>2026-04-27T11:57:27-07:00</updated><id>https://mdcramer.github.io/feed/apple-2-blog.xml</id><title type="html">Hackin’ and Tinkerin’ | Apple-2-blog</title><subtitle>A collection of blogs related to some of my work on GitHub and elsewhere</subtitle><author><name>Mark Cramer</name></author><entry><title type="html">K-means by another means</title><link href="https://mdcramer.github.io/apple-2-blog/k-means/" rel="alternate" type="text/html" title="K-means by another means" /><published>2025-09-20T00:00:00-07:00</published><updated>2025-10-06T00:00:00-07:00</updated><id>https://mdcramer.github.io/apple-2-blog/k-means</id><content type="html" xml:base="https://mdcramer.github.io/apple-2-blog/k-means/"><![CDATA[<p>Wait. Does k-means count as machine learning? Yes. Yes, it does.</p>

<p><a href="https://cs229.stanford.edu/" target="_blank">CS229</a> is the graduate-level machine learning course I took at Stanford as part of the <a href="https://www.linkedin.com/pulse/graduate-certificate-ai-achievement-unlocked-mark-cramer/" target="_blank">Graduate Certificate in AI</a> which I completed in 2021. While k-means is my choice as the easiest to understand algorithm in machine learning, it was taught as the introductory clustering algorithm for unsupervised learning. As a TA for <a href="https://online.stanford.edu/courses/xcs229-machine-learning" target="_blank">XCS229</a>, which I have been doing since 2022 and most recently did this Spring, I know that it is still being taught as part of this seminal course in AI.</p>

<h2 id="we-have-liftoff">We have liftoff!</h2>

<p>Unlike <a href="/apple-2-blog/synthesizing-data/">previously</a> where I saved the result for the end, let’s start by taking a look at the algorithm in action!</p>

<p><a href="https://youtube.com/shorts/Cy0wMMLObVA?autoplay=1" title="Video of Apple ]\[+ running k-means" target="_blank"><img src="https://img.youtube.com/vi/Cy0wMMLObVA/0.jpg" alt="Video of Apple 2+ running k-means" /></a></p>

<p>Here is a screenshot of the decision boundary after convergence.</p>

<p><img src="/assets/images/apple2/k-means-convergence.jpg" alt="K-means decision boundary after convergence" title="K-means decision boundary after convergence" /></p>

<p>Accuracy is 90% because 1 of the 10 observations is on the incorrect side of the decision boundary.</p>

<p>For debugging purposes, to speed up execution, I reduced the number of samples in each class to 5. (You might note that, on the graph, there are only 4 points in class 1, which are the □s. That’s because one of the points is at <code class="language-plaintext highlighter-rouge">(291, 90)</code>, which is off the edge of the screen. Gaussian distributions can generate extreme outliers, so I decided to preserve those points rather than clip them to the edge of the screen.) That’s obviously pretty small but you can see the algorithm iterating.</p>

<p>At the end of each loop I draw a line between the latest estimates of cluster centroids. The perpendicular bisector of these segments are the decision boundaries between the classes, so I draw them, too. Some of the code was written to handle more than two classes but here there are only two which makes this relatively easy.</p>

<h2 id="k-means-explained">K-means explained</h2>

<p><a href="https://en.wikipedia.org/wiki/K-means_clustering" target="_blank">K-means clustering</a> is an iterative algorithm that aims to partition \(n\) observations into \(k\) clusters in which each observation belongs to the cluster with the nearest mean, called the cluster centroid.</p>

<table>
  <thead>
    <tr>
      <th>Step</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Initialize</td>
      <td>Produce and initial set of k cluster centroids. This can be done by randomly choosing k observations from the dataset.</td>
    </tr>
    <tr>
      <td>Step 1 - Assignment</td>
      <td>Using Euclidean distance to the centroids, assign each observation to a cluster.</td>
    </tr>
    <tr>
      <td>Step 2 - Update</td>
      <td>For each cluster, recompute the centroid using the newly assigned observations. If the centroids change (outside of a certain tolerance), go back to step 1 and repeat.</td>
    </tr>
  </tbody>
</table>

<p>Ezpz.</p>

<p>The math is also simple. In step 1, the distance between two points, \(x\) and \(y\), is simply \(\sqrt{(x_0 - y_0)^2 + (x_1 - y_1)^2 + \cdots + (x_{d-1} - y_{d-1})^2}\), where \(d\) is the dimensionality of the observations. In our case \(d=2\) which is why we only have \(x_0\) and \(x_1\). Also, since we’re only using the distances for comparative purposes, it’s not even necessary to take the square root. In step 2, the centroid is simply the sum of all the points divided by the number of points.</p>

<h2 id="implementation">Implementation</h2>

<p>First, a little housekeeping before getting to the implementation of the algorithm.</p>

<div class="language-bbcbasic highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="m">10</span>  <span class="nv">HOME</span> <span class="p">:</span> <span class="nv">VTAB</span> <span class="m">21</span>
<span class="m">20</span>  <span class="nv">PI</span> <span class="o">=</span> <span class="m">3.14159265</span>
<span class="m">30</span>  <span class="k">GOSUB</span> <span class="m">1000</span> <span class="p">:</span> <span class="c">REM  DRAW AXIS</span>
<span class="m">40</span>  <span class="k">GOSUB</span> <span class="m">100</span> <span class="p">:</span> <span class="c">REM  GENERATE DATA</span>
<span class="m">50</span>  <span class="k">GOSUB</span> <span class="m">900</span> <span class="p">:</span> <span class="c">REM  WAIT FOR KEY</span>
<span class="m">60</span>  <span class="k">GOSUB</span> <span class="m">2000</span> <span class="p">:</span> <span class="c">REM  RUN K-MEANS</span>
<span class="m">70</span>  <span class="k">END</span>

<span class="m">100</span> <span class="c">REM  == HYPERPARAMETERS ==</span>
<span class="m">...</span>
<span class="m">450</span> <span class="k">DIM</span> <span class="nv">P%</span><span class="o">(</span><span class="m">2</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="p">:</span> <span class="c">REM  RANDOM POINTS</span>
<span class="m">460</span> <span class="c">REM  == K-MEANS DATA TABLES ==</span>
<span class="m">470</span> <span class="k">DIM</span> <span class="nv">DI</span><span class="o">(</span><span class="nv">NS</span> <span class="o">-</span> <span class="m">1</span><span class="p">,</span><span class="nv">KN</span> <span class="o">-</span> <span class="m">1</span><span class="o">)</span>
<span class="m">480</span> <span class="c">REM  -- K - MU-XO, MU-X1, N-K --</span>
<span class="m">490</span> <span class="k">DIM</span> <span class="nv">KM</span><span class="o">(</span><span class="nv">KN</span> <span class="o">-</span> <span class="m">1</span><span class="p">,</span><span class="m">2</span><span class="o">)</span>
<span class="m">500</span> <span class="c">REM  -- K - OLD MU-X0, OLD MU-X1 --</span>
<span class="m">510</span> <span class="k">DIM</span> <span class="nv">KO</span><span class="o">(</span><span class="nv">KN</span> <span class="o">-</span> <span class="m">1</span><span class="p">,</span><span class="m">1</span><span class="o">)</span>
<span class="m">...</span>

<span class="m">900</span> <span class="c">REM  == WAIT FOR KEYSTROKE ==</span>
<span class="m">910</span> <span class="nv">POKE</span> <span class="m">49168</span><span class="p">,</span><span class="m">0</span> <span class="p">:</span> <span class="c">REM  CLEAR BUFFER</span>
<span class="m">920</span> <span class="k">IF</span> <span class="nv">PEEK</span><span class="o">(</span><span class="m">49152</span><span class="o">)</span> <span class="o">&lt;</span> <span class="m">128</span> <span class="k">GOTO</span> <span class="m">920</span>
<span class="m">930</span> <span class="nv">POKE</span> <span class="m">49168</span><span class="p">,</span><span class="m">0</span>
<span class="m">940</span> <span class="k">RETURN</span>
</code></pre></div></div>

<p>At the very top of the program I decided to organize everything into subroutines. The idea here is to enable expansion into other ML algorithms.</p>

<p>The “wait for key” subroutine is the APPLESOFT BASIC method for simply waiting for any keystroke before continuing. (<code class="language-plaintext highlighter-rouge">PEEK</code> and <code class="language-plaintext highlighter-rouge">POKE</code> are commands for directly accessing addresses in memory. I had those numbers memorized in high school but, naturally, I had to look them up.) I thought it’d be nice to add this pause after generating the data but I might take it out later.</p>

<p>Lastly, at the end of the “hyperparameters” section I declare a convenience array, <code class="language-plaintext highlighter-rouge">P%(2,1)</code> to keep track of 3 random points as well as a few arrays I’m going to use in the k-means algorithm. The reason I do this here is because in APPLESOFT BASIC you get an error if you declare an array that already exists. Should at some point I want to call the k-means algorithm multiple times, this won’t be a problem.</p>

<h3 id="initialize">Initialize</h3>

<p>Getting started, the first thing to do is initialize the algorithm by generating \(k\) cluster centroids. (\(k\) is a hyperparameter that specifies the number of clusters to be “found.” I set it previously with <code class="language-plaintext highlighter-rouge">KN = 2</code>.)</p>

<div class="language-bbcbasic highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="m">2000</span> <span class="c">REM  == K-MEANS ==</span>
<span class="m">2010</span> <span class="k">PRINT</span> <span class="s">"RUN K-MEANS"</span>
<span class="m">2020</span> <span class="c">REM  -- CLEAR PREDICTIONS --</span>
<span class="m">2030</span> <span class="k">FOR</span> <span class="nv">I</span> <span class="o">=</span> <span class="m">0</span> <span class="k">TO</span> <span class="nv">NS</span> <span class="o">-</span> <span class="m">1</span>
<span class="m">2040</span>   <span class="nv">DS%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">3</span><span class="o">)</span> <span class="o">=</span> <span class="m">0</span>
<span class="m">2050</span> <span class="k">NEXT</span> <span class="nv">I</span>
<span class="m">2100</span> <span class="c">REM  -- INITIALIZE CENTROIDS --</span>
<span class="m">2110</span> <span class="k">FOR</span> <span class="nv">I</span> <span class="o">=</span> <span class="m">0</span> <span class="k">TO</span> <span class="nv">KN</span> <span class="o">-</span> <span class="m">1</span>
<span class="m">2120</span>   <span class="nv">J</span> <span class="o">=</span> <span class="nb">INT</span><span class="o">(</span><span class="nb">RND</span><span class="o">(</span><span class="m">1</span><span class="o">)</span> <span class="o">*</span> <span class="nv">NS</span><span class="o">)</span>
<span class="m">2130</span>   <span class="k">IF</span> <span class="nv">DS%</span><span class="o">(</span><span class="nv">J</span><span class="p">,</span><span class="m">3</span><span class="o">)</span> <span class="o">=</span> <span class="m">1</span> <span class="k">GOTO</span> <span class="m">2120</span>
<span class="m">2140</span>   <span class="nv">KM</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">=</span> <span class="nv">DS%</span><span class="o">(</span><span class="nv">J</span><span class="p">,</span><span class="m">1</span><span class="o">)</span>
<span class="m">2150</span>   <span class="nv">KM</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">2</span><span class="o">)</span> <span class="o">=</span> <span class="nv">DS%</span><span class="o">(</span><span class="nv">J</span><span class="p">,</span><span class="m">2</span><span class="o">)</span>
<span class="m">2160</span>   <span class="nv">DS%</span><span class="o">(</span><span class="nv">J</span><span class="p">,</span><span class="m">3</span><span class="o">)</span> <span class="o">=</span> <span class="m">1</span>
<span class="m">2170</span> <span class="k">NEXT</span> <span class="nv">I</span>
<span class="m">2200</span> <span class="c">REM  -- DRAW LINES BETWEEN CENTROIDS --</span>
<span class="m">2210</span> <span class="k">FOR</span> <span class="nv">I</span> <span class="o">=</span> <span class="m">1</span> <span class="k">TO</span> <span class="nv">KN</span> <span class="o">-</span> <span class="m">1</span>
<span class="m">2220</span>   <span class="nv">HPLOT</span> <span class="nv">KM</span><span class="o">(</span><span class="nv">I</span><span class="o">-</span><span class="m">1</span><span class="p">,</span><span class="m">0</span><span class="o">)</span><span class="p">,</span> <span class="m">159</span><span class="o">-</span><span class="nv">KM</span><span class="o">(</span><span class="nv">I</span><span class="o">-</span><span class="m">1</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="k">TO</span> <span class="nv">KM</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">0</span><span class="o">)</span><span class="p">,</span> <span class="m">159</span><span class="o">-</span><span class="nv">KM</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">1</span><span class="o">)</span>
<span class="m">2230</span> <span class="k">NEXT</span> <span class="nv">I</span>
<span class="m">2240</span> <span class="k">GOSUB</span> <span class="m">3000</span><span class="p">:</span> <span class="c">REM  DRAW DECISION BOUNDARY</span>
</code></pre></div></div>

<p>I start by clearing out the prediction column, \(y\), of the dataset table, <code class="language-plaintext highlighter-rouge">DS%(NS - 1,3)</code> because I’m going to use this to make sure I don’t randomly pick the same point twice. Then for each class I randomly pick a point from the dataset. If it’s already been used I randomly pick another. <code class="language-plaintext highlighter-rouge">KM(KN - 1, 2)</code> is where I store the means for each cluster along with a count of the number of points in each cluster.</p>

<p>Finally, I draw a line between the cluster centroids. This loop does not take into account all combinations of centroids (it works fine if \(k=2\)) and generates an error if a centroid is off the screen, which is possible, so I might just get rid of this later, since it’s not really necessary, rather than try to fix it.</p>

<h3 id="step-1---assignment">Step 1 - Assignment</h3>

<p>The fist step is to assign every data point to the nearest cluster centroid.</p>

<div class="language-bbcbasic highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="m">2300</span> <span class="c">REM  -- COMPUTE ASSIGNMENTS --</span>
<span class="m">2310</span> <span class="k">FOR</span> <span class="nv">I</span> <span class="o">=</span> <span class="m">0</span> <span class="k">TO</span> <span class="nv">NS</span> <span class="o">-</span> <span class="m">1</span>
<span class="m">2320</span>   <span class="k">PRINT</span> <span class="s">"POINT "</span><span class="p">;</span><span class="nv">I</span><span class="p">;</span><span class="s">" AT "</span><span class="p">;</span><span class="nv">DS%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">0</span><span class="o">)</span><span class="p">;</span><span class="s">","</span><span class="p">;</span><span class="nv">DS%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">1</span><span class="o">)</span><span class="p">;</span>
<span class="m">2330</span>   <span class="nv">DS%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">3</span><span class="o">)</span> <span class="o">=</span> <span class="m">0</span>
<span class="m">2340</span>   <span class="k">FOR</span> <span class="nv">J</span> <span class="o">=</span> <span class="m">0</span> <span class="k">TO</span> <span class="nv">KN</span> <span class="o">-</span> <span class="m">1</span>
<span class="m">2350</span>     <span class="nv">DI</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="nv">J</span><span class="o">)</span> <span class="o">=</span> <span class="o">(</span><span class="nv">DS%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">0</span><span class="o">)-</span><span class="nv">KM</span><span class="o">(</span><span class="nv">J</span><span class="p">,</span><span class="m">0</span><span class="o">))^</span><span class="m">2</span> <span class="o">+</span> <span class="o">(</span><span class="nv">DS%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">1</span><span class="o">)-</span><span class="nv">KM</span><span class="o">(</span><span class="nv">J</span><span class="p">,</span><span class="m">1</span><span class="o">))^</span><span class="m">2</span>
<span class="m">2360</span>     <span class="k">IF</span> <span class="nv">J</span> <span class="o">&gt;</span><span class="m">0</span> <span class="o">AND</span> <span class="o">(</span><span class="nv">DI</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="nv">J</span><span class="o">)</span> <span class="o">&lt;</span> <span class="nv">DI</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="nv">DS%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">3</span><span class="o">)))</span> <span class="k">THEN</span> <span class="nv">DS%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">3</span><span class="o">)</span> <span class="o">=</span> <span class="nv">J</span>
<span class="m">2370</span>   <span class="k">NEXT</span> <span class="nv">J</span>
<span class="m">2380</span>   <span class="k">PRINT</span> <span class="s">" -&gt; "</span><span class="p">;</span><span class="nv">DS%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">3</span><span class="o">)</span><span class="p">;</span><span class="s">" Y^="</span><span class="p">;</span><span class="nv">DS%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">2</span><span class="o">)</span>
<span class="m">2390</span> <span class="k">NEXT</span> <span class="nv">I</span>
<span class="m">2500</span> <span class="c">REM  -- COMPUTE ACCURACY --</span>
<span class="m">2510</span> <span class="nv">CT</span> <span class="o">=</span> <span class="m">0</span>
<span class="m">2520</span> <span class="k">FOR</span> <span class="nv">I</span> <span class="o">=</span> <span class="m">0</span> <span class="k">TO</span> <span class="nv">NS</span> <span class="o">-</span> <span class="m">1</span>
<span class="m">2530</span>   <span class="k">IF</span> <span class="nv">DS%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">2</span><span class="o">)</span> <span class="o">=</span> <span class="nv">DS%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">3</span><span class="o">)</span> <span class="k">THEN</span> <span class="nv">CT</span> <span class="o">=</span> <span class="nv">CT</span> <span class="o">+</span> <span class="m">1</span>
<span class="m">2540</span> <span class="k">NEXT</span> <span class="nv">I</span>
<span class="m">2550</span> <span class="nv">A</span> <span class="o">=</span> <span class="nv">CT</span> <span class="o">/</span> <span class="nv">NS</span>
<span class="m">2560</span> <span class="k">IF</span> <span class="nv">A</span> <span class="o">&lt;</span> <span class="m">0.5</span> <span class="k">THEN</span> <span class="nv">A</span> <span class="o">=</span> <span class="m">1</span> <span class="o">-</span> <span class="nv">A</span>
<span class="m">2570</span> <span class="k">PRINT</span> <span class="s">"ACCURACY = "</span><span class="p">;</span> <span class="nb">INT</span><span class="o">(</span><span class="nv">A</span><span class="o">*</span><span class="m">10000</span><span class="o">+</span><span class="m">0.5</span><span class="o">)/</span><span class="m">100</span><span class="p">;</span><span class="s">"%"</span>
</code></pre></div></div>

<p>The assignment step is also quite easy. I loop through all the data points, computing the Euclidean distance to each cluster centroid. (Since <code class="language-plaintext highlighter-rouge">SQRT()</code> is expensive, and unnecessary here since we’re just comparing, I actually just compute the square of the Euclidean distance.) If the distance is less than the previous minimum distance, <code class="language-plaintext highlighter-rouge">DI(I,DS%(I,3))</code>, I update the assignment, <code class="language-plaintext highlighter-rouge">DS%(I,3) = J</code>.</p>

<p>At the end, I compute the accuracy of the computed assignments by simply counting the number of assignments, <code class="language-plaintext highlighter-rouge">DS%(I,3)</code>, that match the actual labels, <code class="language-plaintext highlighter-rouge">DS%(I,2)</code>. Here, however, there’s an interesting wrinkle: with two classes, half the time the label I choose for the assignment is the opposite of the label from the original dataset. K-means doesn’t require the distinction, so at times I was seeing a perfect classification reporting 0% accuracy. The line <code class="language-plaintext highlighter-rouge">IF A &lt; 0.5 THEN A = 1 - A</code> addresses this, however, it only works for 2 classes. I’ll need something more robust should I want this to work for \(k &gt; 2\).</p>

<h3 id="step-2---update">Step 2 - Update</h3>

<p>The second step is to recompute the cluster centroids based on the assigned data points. Convergence occurred if the centroids don’t change (within a tolerance) from the previous iteration.</p>

<div class="language-bbcbasic highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="m">2600</span> <span class="c">REM  -- COMPUTE CENTROIDS --</span>
<span class="m">2610</span> <span class="k">FOR</span> <span class="nv">J</span> <span class="o">=</span> <span class="m">0</span> <span class="k">TO</span> <span class="nv">KN</span> <span class="o">-</span> <span class="m">1</span>
<span class="m">2620</span>   <span class="nv">K0</span><span class="o">(</span><span class="nv">J</span><span class="p">,</span><span class="m">0</span><span class="o">)</span> <span class="o">=</span> <span class="nv">KM</span><span class="o">(</span><span class="nv">J</span><span class="p">,</span><span class="m">0</span><span class="o">)</span>
<span class="m">2630</span>   <span class="nv">K0</span><span class="o">(</span><span class="nv">J</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">=</span> <span class="nv">KM</span><span class="o">(</span><span class="nv">J</span><span class="p">,</span><span class="m">1</span><span class="o">)</span>
<span class="m">2640</span>   <span class="nv">KM</span><span class="o">(</span><span class="nv">J</span><span class="p">,</span><span class="m">0</span><span class="o">)</span> <span class="o">=</span> <span class="m">0</span><span class="p">:</span> <span class="nv">KM</span><span class="o">(</span><span class="nv">J</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">=</span> <span class="m">0</span>
<span class="m">2650</span>   <span class="nv">KM</span><span class="o">(</span><span class="nv">J</span><span class="p">,</span><span class="m">2</span><span class="o">)</span> <span class="o">=</span> <span class="m">0</span>
<span class="m">2660</span> <span class="k">NEXT</span>
<span class="m">2670</span> <span class="k">FOR</span> <span class="nv">I</span> <span class="o">=</span> <span class="m">0</span> <span class="k">TO</span> <span class="nv">NS</span> <span class="o">-</span> <span class="m">1</span>
<span class="m">2680</span>   <span class="nv">Y</span> <span class="o">=</span> <span class="nv">DS%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">3</span><span class="o">)</span>
<span class="m">2690</span>   <span class="nv">KM%</span><span class="o">(</span><span class="nv">Y</span><span class="p">,</span><span class="m">0</span><span class="o">)</span> <span class="o">=</span> <span class="nv">KM%</span><span class="o">(</span><span class="nv">Y</span><span class="p">,</span><span class="m">0</span><span class="o">)</span> <span class="o">+</span> <span class="nv">DS%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">0</span><span class="o">)</span>
<span class="m">2700</span>   <span class="nv">KM%</span><span class="o">(</span><span class="nv">Y</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">=</span> <span class="nv">KM%</span><span class="o">(</span><span class="nv">Y</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">+</span> <span class="nv">DS%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">1</span><span class="o">)</span>
<span class="m">2710</span>   <span class="nv">KM%</span><span class="o">(</span><span class="nv">Y</span><span class="p">,</span><span class="m">2</span><span class="o">)</span> <span class="o">=</span> <span class="nv">KM%</span><span class="o">(</span><span class="nv">Y</span><span class="p">,</span><span class="m">2</span><span class="o">)</span> <span class="o">+</span> <span class="m">1</span>
<span class="m">2720</span> <span class="k">NEXT</span>
<span class="m">2730</span> <span class="k">FOR</span> <span class="nv">I</span> <span class="o">=</span> <span class="m">0</span> <span class="k">TO</span> <span class="nv">KN</span> <span class="o">-</span> <span class="m">1</span>
<span class="m">2740</span>   <span class="nv">KM%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">0</span><span class="o">)</span> <span class="o">=</span> <span class="nv">KM%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">0</span><span class="o">)</span> <span class="o">/</span> <span class="nv">KM%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">2</span><span class="o">)</span>
<span class="m">2750</span>   <span class="nv">KM%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">=</span> <span class="nv">KM%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">/</span> <span class="nv">KM%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">2</span><span class="o">)</span>
<span class="m">2760</span> <span class="k">NEXT</span>
<span class="m">2800</span> <span class="c">REM  -- DETERMINE CONVERGENCE --</span>
<span class="m">2810</span> <span class="nv">DI</span> <span class="o">=</span> <span class="m">0</span>
<span class="m">2820</span> <span class="k">FOR</span> <span class="nv">I</span> <span class="o">=</span> <span class="m">0</span> <span class="k">TO</span> <span class="nv">KN</span> <span class="o">-</span> <span class="m">1</span>
<span class="m">2830</span>   <span class="nv">DI</span> <span class="o">=</span> <span class="nv">DI</span> <span class="o">+</span> <span class="o">(</span><span class="nv">KM%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">0</span><span class="o">)</span> <span class="o">-</span> <span class="nv">KO%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">0</span><span class="o">))</span> <span class="o">^</span> <span class="m">2</span> <span class="o">+</span> <span class="o">(</span><span class="nv">KM%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">-</span> <span class="nv">KO%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">1</span><span class="o">))</span> <span class="o">^</span> <span class="m">2</span>
<span class="m">2840</span> <span class="k">NEXT</span>
<span class="m">2850</span> <span class="k">IF</span> <span class="nv">DI</span> <span class="o">&gt;</span> <span class="m">0.01</span> <span class="k">THEN</span> <span class="k">GOTO</span> <span class="m">2200</span>
<span class="m">2860</span> <span class="k">PRINT</span> <span class="s">"K-MEANS CONVERGED"</span>
<span class="m">2900</span> <span class="c">REM  -- CLEAR GRAPHICS AND REDRAW WITH DECISION BOUNDARY --</span>
<span class="m">2910</span> <span class="k">GOSUB</span> <span class="m">1000</span>
<span class="m">2920</span> <span class="k">FOR</span> <span class="nv">I</span> <span class="o">=</span> <span class="m">0</span> <span class="k">TO</span> <span class="nv">NS</span> <span class="o">-</span> <span class="m">1</span>
<span class="m">2930</span>   <span class="nv">X0%</span> <span class="o">=</span> <span class="nv">DS%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">0</span><span class="o">)</span>
<span class="m">2940</span>   <span class="nv">X1%</span> <span class="o">=</span> <span class="nv">DS%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">1</span><span class="o">)</span>
<span class="m">2950</span>   <span class="nv">K</span> <span class="o">=</span> <span class="nv">DS%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">2</span><span class="o">)</span>
<span class="m">2960</span>   <span class="k">ON</span> <span class="nv">K</span> <span class="o">+</span> <span class="m">1</span> <span class="k">GOSUB</span> <span class="m">1200</span><span class="p">,</span><span class="m">1300</span>
<span class="m">2970</span> <span class="k">NEXT</span>
<span class="m">2980</span> <span class="k">GOSUB</span> <span class="m">3000</span>
<span class="m">2990</span> <span class="k">RETURN</span>
</code></pre></div></div>

<p>I start by saving the cluster centroids to <code class="language-plaintext highlighter-rouge">KO(KN - 1,1)</code>. This is used later to determine convergence. I then iterate through ever data point, adding it’s values to the cluster to which it belongs while keeping track of the number of data points in each cluster. Next I iterate through each cluster and compute the mean of each dimension by dividing by the number of data point in that cluster.</p>

<p>Lastly, I determine if there’s convergence by measuring how far all the centroid have moved. (Again, I don’t bother with the <code class="language-plaintext highlighter-rouge">SQRT()</code>.) If the answer is more than the specified tolerance, \(0.01\), I go back to Step #1. Otherwise, I clear the graphics, redraw the axis and data points and finish by drawing the decision boundary.</p>

<h3 id="drawing-the-decision-boundary">Drawing the decision boundary</h3>

<p>This code is a slog and it’s not really critical to understanding ML but I thought it’d be cool to drawn a decision boundary while k-means is iterating and then again at the end. Given a point (the midpoint on the segment between two cluster centroids) and a slope (which is perpendicular to that segment), the challenge is to drawn a line inside the ‘box’ of the screen, assuming the line intersects that box.</p>

<div class="language-bbcbasic highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="m">3000</span> <span class="c">REM  -- DRAW DECISION BOUNDARY --</span>
<span class="m">3010</span> <span class="k">FOR</span> <span class="nv">I</span> <span class="o">=</span> <span class="m">1</span> <span class="k">TO</span> <span class="nv">KN</span> <span class="o">-</span> <span class="m">1</span>
<span class="m">3020</span>   <span class="nv">M</span> <span class="o">=</span> <span class="m">1</span><span class="nv">E6</span>
<span class="m">3030</span>   <span class="k">IF</span> <span class="nv">KM%</span><span class="o">(</span><span class="nv">I</span> <span class="o">-</span> <span class="m">1</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">-</span> <span class="nv">KM%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">&lt;&gt;</span> <span class="m">0</span> <span class="k">THEN</span> <span class="nv">M</span> <span class="o">=</span> <span class="o">-</span><span class="m">1</span> <span class="o">*</span> <span class="o">(</span><span class="nv">KM%</span><span class="o">(</span><span class="nv">I</span> <span class="o">-</span> <span class="m">1</span><span class="p">,</span><span class="m">0</span><span class="o">)</span> <span class="o">-</span> <span class="nv">KM%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">0</span><span class="o">))</span> <span class="o">/</span> <span class="o">(</span><span class="nv">KM%</span><span class="o">(</span><span class="nv">I</span> <span class="o">-</span> <span class="m">1</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">-</span> <span class="nv">KM%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">1</span><span class="o">))</span>
<span class="m">3040</span>   <span class="nv">P%</span><span class="o">(</span><span class="m">0</span><span class="p">,</span><span class="m">0</span><span class="o">)</span> <span class="o">=</span> <span class="o">(</span><span class="nv">KM%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">0</span><span class="o">)</span> <span class="o">-</span> <span class="nv">KM%</span><span class="o">(</span><span class="nv">I</span> <span class="o">-</span> <span class="m">1</span><span class="p">,</span><span class="m">0</span><span class="o">))</span> <span class="o">/</span> <span class="m">2</span> <span class="o">+</span> <span class="nv">KM%</span><span class="o">(</span><span class="nv">I</span> <span class="o">-</span> <span class="m">1</span><span class="p">,</span><span class="m">0</span><span class="o">)</span>
<span class="m">3050</span>   <span class="nv">P%</span><span class="o">(</span><span class="m">0</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">=</span> <span class="o">(</span><span class="nv">KM%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">-</span> <span class="nv">KM%</span><span class="o">(</span><span class="nv">I</span> <span class="o">-</span> <span class="m">1</span><span class="p">,</span><span class="m">1</span><span class="o">))</span> <span class="o">/</span> <span class="m">2</span> <span class="o">+</span> <span class="nv">KM%</span><span class="o">(</span><span class="nv">I</span> <span class="o">-</span> <span class="m">1</span><span class="p">,</span><span class="m">1</span><span class="o">)</span>
<span class="m">3060</span>   <span class="k">GOSUB</span> <span class="m">3080</span>
<span class="m">3070</span> <span class="k">NEXT</span>
<span class="m">3080</span> <span class="c">REM  -- DRAW LINE FROM SLOPE AND POINT --</span>
<span class="m">3090</span> <span class="nv">NX</span> <span class="o">=</span> <span class="m">1</span> <span class="p">:</span> <span class="c">REM  -- REM NUMBER OF INTERSECTIONS --</span>
<span class="m">3100</span> <span class="k">IF</span> <span class="nb">ABS</span><span class="o">(</span><span class="nv">M</span><span class="o">)</span> <span class="o">&gt;</span> <span class="m">1</span><span class="nv">E5</span> <span class="k">THEN</span> <span class="k">GOSUB</span> <span class="m">3240</span> <span class="p">:</span> <span class="k">GOTO</span> <span class="m">3210</span> <span class="p">:</span> <span class="c">REM  VERTICAL LINE</span>
<span class="m">3110</span> <span class="nv">P%</span><span class="o">(</span><span class="nv">NX</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">=</span> <span class="nv">M</span> <span class="o">*</span> <span class="o">(</span><span class="m">10</span> <span class="o">-</span> <span class="nv">P%</span><span class="o">(</span><span class="m">0</span><span class="p">,</span><span class="m">0</span><span class="o">))</span> <span class="o">+</span> <span class="nv">P%</span><span class="o">(</span><span class="m">0</span><span class="p">,</span><span class="m">1</span><span class="o">)</span>
<span class="m">3120</span> <span class="k">IF</span> <span class="nv">P%</span><span class="o">(</span><span class="nv">NX</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">&gt;</span> <span class="m">10</span> <span class="o">AND</span> <span class="nv">P%</span><span class="o">(</span><span class="nv">NX</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">&lt;</span> <span class="m">149</span> <span class="k">THEN</span> <span class="nv">P%</span><span class="o">(</span><span class="nv">NX</span><span class="p">,</span><span class="m">0</span><span class="o">)</span> <span class="o">=</span> <span class="m">10</span> <span class="p">:</span> <span class="nv">NX</span> <span class="o">=</span> <span class="nv">NX</span> <span class="o">+</span> <span class="m">1</span>
<span class="m">3130</span> <span class="nv">P%</span><span class="o">(</span><span class="nv">NX</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">=</span> <span class="nv">M</span> <span class="o">*</span> <span class="o">(</span><span class="m">269</span> <span class="o">-</span> <span class="nv">P%</span><span class="o">(</span><span class="m">0</span><span class="p">,</span><span class="m">0</span><span class="o">))</span> <span class="o">+</span> <span class="nv">P%</span><span class="o">(</span><span class="m">0</span><span class="p">,</span><span class="m">1</span><span class="o">)</span>
<span class="m">3140</span> <span class="k">IF</span> <span class="nv">P%</span><span class="o">(</span><span class="nv">NX</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">&gt;</span> <span class="m">10</span> <span class="o">AND</span> <span class="nv">P%</span><span class="o">(</span><span class="nv">NX</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">&lt;</span> <span class="m">149</span> <span class="k">THEN</span> <span class="nv">P%</span><span class="o">(</span><span class="nv">NX</span><span class="p">,</span><span class="m">0</span><span class="o">)</span> <span class="o">=</span> <span class="m">269</span> <span class="p">:</span> <span class="nv">NX</span> <span class="o">=</span> <span class="nv">NX</span> <span class="o">+</span> <span class="m">1</span>
<span class="m">3150</span> <span class="k">IF</span> <span class="nv">NX</span> <span class="o">=</span> <span class="m">3</span> <span class="k">THEN</span> <span class="k">GOTO</span> <span class="m">3210</span>
<span class="m">3160</span> <span class="k">IF</span> <span class="nv">M</span> <span class="o">&lt;&gt;</span> <span class="m">0</span> <span class="k">THEN</span> <span class="nv">P%</span><span class="o">(</span><span class="nv">NX</span><span class="p">,</span><span class="m">0</span><span class="o">)</span> <span class="o">=</span> <span class="o">(</span><span class="m">10</span> <span class="o">-</span> <span class="nv">P%</span><span class="o">(</span><span class="m">0</span><span class="p">,</span><span class="m">1</span><span class="o">))</span> <span class="o">/</span> <span class="nv">M</span> <span class="o">+</span> <span class="nv">P%</span><span class="o">(</span><span class="m">0</span><span class="p">,</span><span class="m">0</span><span class="o">)</span>
<span class="m">3170</span> <span class="k">IF</span> <span class="nv">M</span> <span class="o">&lt;&gt;</span> <span class="m">0</span> <span class="o">AND</span> <span class="nv">P%</span><span class="o">(</span><span class="nv">NX</span><span class="p">,</span><span class="m">0</span><span class="o">)</span> <span class="o">&gt;</span> <span class="m">10</span> <span class="o">AND</span> <span class="nv">P%</span><span class="o">(</span><span class="nv">NX</span><span class="p">,</span><span class="m">0</span><span class="o">)</span> <span class="o">&lt;</span> <span class="m">269</span> <span class="k">THEN</span> <span class="nv">P%</span><span class="o">(</span><span class="nv">NX</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">=</span> <span class="m">10</span> <span class="p">:</span> <span class="nv">NX</span> <span class="o">=</span> <span class="nv">NX</span> <span class="o">+</span> <span class="m">1</span>
<span class="m">3180</span> <span class="k">IF</span> <span class="nv">NX</span> <span class="o">=</span> <span class="m">3</span> <span class="k">THEN</span> <span class="k">GOTO</span> <span class="m">3210</span>
<span class="m">3190</span> <span class="k">IF</span> <span class="nv">M</span> <span class="o">&lt;&gt;</span> <span class="m">0</span> <span class="k">THEN</span> <span class="nv">P%</span><span class="o">(</span><span class="nv">NX</span><span class="p">,</span><span class="m">0</span><span class="o">)</span> <span class="o">=</span> <span class="o">(</span><span class="m">149</span> <span class="o">-</span> <span class="nv">P%</span><span class="o">(</span><span class="m">0</span><span class="p">,</span><span class="m">1</span><span class="o">))</span> <span class="o">/</span> <span class="nv">M</span> <span class="o">+</span> <span class="nv">P%</span><span class="o">(</span><span class="m">0</span><span class="p">,</span><span class="m">0</span><span class="o">)</span>
<span class="m">3200</span> <span class="k">IF</span> <span class="nv">M</span> <span class="o">&lt;&gt;</span> <span class="m">0</span> <span class="o">AND</span> <span class="nv">P%</span><span class="o">(</span><span class="nv">NX</span><span class="p">,</span><span class="m">0</span><span class="o">)</span> <span class="o">&gt;</span> <span class="m">10</span> <span class="o">AND</span> <span class="nv">P%</span><span class="o">(</span><span class="nv">NX</span><span class="p">,</span><span class="m">0</span><span class="o">)</span> <span class="o">&lt;</span> <span class="m">269</span> <span class="k">THEN</span> <span class="nv">P%</span><span class="o">(</span><span class="nv">NX</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">=</span> <span class="m">149</span> <span class="p">:</span> <span class="nv">NX</span> <span class="o">=</span> <span class="nv">NX</span> <span class="o">+</span> <span class="m">1</span>
<span class="m">3210</span> <span class="c">REM  -- DRAW LINE --</span>
<span class="m">3220</span> <span class="k">IF</span> <span class="nv">NX</span> <span class="o">=</span> <span class="m">3</span> <span class="k">THEN</span> <span class="nv">HPLOT</span> <span class="nv">P%</span><span class="o">(</span><span class="m">1</span><span class="p">,</span><span class="m">0</span><span class="o">)</span><span class="p">,</span><span class="m">159</span> <span class="o">-</span> <span class="nv">P%</span><span class="o">(</span><span class="m">1</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="k">TO</span> <span class="nv">P%</span><span class="o">(</span><span class="m">2</span><span class="p">,</span><span class="m">0</span><span class="o">)</span><span class="p">,</span><span class="m">159</span> <span class="o">-</span> <span class="nv">P%</span><span class="o">(</span><span class="m">2</span><span class="p">,</span><span class="m">1</span><span class="o">)</span>
<span class="m">3230</span> <span class="k">RETURN</span>
<span class="m">3240</span> <span class="c">REM  -- VERTICAL LINE --</span>
<span class="m">3250</span> <span class="nv">P%</span><span class="o">(</span><span class="m">1</span><span class="p">,</span><span class="m">0</span><span class="o">)</span> <span class="o">=</span> <span class="nv">P%</span><span class="o">(</span><span class="m">0</span><span class="p">,</span><span class="m">0</span><span class="o">)</span>
<span class="m">3260</span> <span class="nv">P%</span><span class="o">(</span><span class="m">2</span><span class="p">,</span><span class="m">0</span><span class="o">)</span> <span class="o">=</span> <span class="nv">P%</span><span class="o">(</span><span class="m">0</span><span class="p">,</span><span class="m">0</span><span class="o">)</span>
<span class="m">3270</span> <span class="nv">P%</span><span class="o">(</span><span class="m">1</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">=</span> <span class="m">10</span>
<span class="m">3280</span> <span class="nv">P%</span><span class="o">(</span><span class="m">2</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">=</span> <span class="m">269</span>
<span class="m">3290</span> <span class="k">RETURN</span>
</code></pre></div></div>

<p>Without delving too far into the details, this routine relies heavily on the convenience array, <code class="language-plaintext highlighter-rouge">P%(2,1)</code>, that I declared during the “hyperparameters” routine. I start by computing the slope of the perpendicular segment connecting two centroids. I then find the midpoint of that segment. (By the way, this routine also does not account for all combinations of centroids, but it works when \(k=2\).) I accommodate for when the slope is vertical and use <code class="language-plaintext highlighter-rouge">P%(0,0)</code> and <code class="language-plaintext highlighter-rouge">P%(0,1)</code> to store the midpoint between the two centroids and <code class="language-plaintext highlighter-rouge">M</code> for the slope.</p>

<p>I then iterate through the 4 sides of the ‘box’ on the screen, using the corners <code class="language-plaintext highlighter-rouge">(10,10)</code> and <code class="language-plaintext highlighter-rouge">(269,149)</code> so that the decision boundary isn’t drawn all the way to the edges of the screen. I thought that would look prettier this way. I next determine if the decision boundary intersects, respectively, the left, right, top and bottom edges of the box. I use <code class="language-plaintext highlighter-rouge">NX</code> to keep track of the number of sides of the box intersected by the decision boundary and <code class="language-plaintext highlighter-rouge">P%(NX,0)</code> and <code class="language-plaintext highlighter-rouge">P%(NX,1)</code> to keep track of those intersections. If <code class="language-plaintext highlighter-rouge">NX = 3</code>, which means there are two intersections, I draw the line because it’s inside the box.</p>

<h2 id="can-we-do-better">Can we do better?</h2>

<p>Yes! Yes, we can.</p>

<p>While k-means is simple, it does not take advantage of our knowledge of the Gaussian nature of the data. If we know that the distributions are at least approximately Gaussian, which is frequently the case, we can employ a more powerful application of the Expectation Maximization (EM) framework (k-means is a specific implementation of centroid-based clustering that uses an iterative approach similar to EM with ‘hard’ clustering) that takes advantage of this. This post is already long enough, so we’ll deal with that another day. Eventually, perhaps, we’ll also get to deep learning, although developing back propagation for an arbitrary size neural net using APPLESOFT BASIC on an <span class="no-break">Apple ][+</span> is not going to be easy.</p>

<h2 id="social-media-sharing">Social media sharing</h2>
<p>I shared this post on <a href="https://news.ycombinator.com/item?id=45415510" target="_blank">Hacker News</a> and, to my surprise and delight, got a decent amount of feedback, including some helpful corrections.</p>

<p>My <a href="https://www.facebook.com/markdcramer/posts/pfbid0eG3AuPSxFTqBm4nggDAjqJrQe8VFv5VUEKf5SQ8qtnDjVFZ4qaSZvauDBcKRxSoFl" target="_blank">post on Facebook</a> received <em>significantly</em> less reaction. My friends are apparently a lot less interested in ML algorithms on retro hardware than randos on Hacker News. My share requests to Facebook groups <a href="https://www.facebook.com/groups/5251478676" target="_blank">Apple II Enthusiasts</a> and <a href="https://www.facebook.com/groups/retrocomputers" target="_blank">Retro Microcomputers</a> were both rejected, which is a bit disappointing. Still, those are fun groups to follow. My <a href="https://forum.vcfed.org/index.php?threads/ml-on-an-apple.1254709/" target="_blank">post on the VCF forum</a> also got zilch. (I attend the <a href="https://vcfed.org/events/vintage-computer-festival-west/" target="_blank">Vintage Computer Festival</a> at the <a href="https://computerhistory.org/" target="_blank">Computer History Museum</a>, which was a good time.) Perhaps I’ll try Reddit and LinkedIn next to see what happens…</p>]]></content><author><name>Mark Cramer</name></author><category term="K-means" /><category term="machine learning" /><summary type="html"><![CDATA[Success! There is machine learning happening on my Apple ][+.]]></summary></entry><entry><title type="html">No Cursor, no emulator</title><link href="https://mdcramer.github.io/apple-2-blog/emulator/" rel="alternate" type="text/html" title="No Cursor, no emulator" /><published>2025-08-16T00:00:00-07:00</published><updated>2025-08-23T00:00:00-07:00</updated><id>https://mdcramer.github.io/apple-2-blog/emulator</id><content type="html" xml:base="https://mdcramer.github.io/apple-2-blog/emulator/"><![CDATA[<p>A few weeks ago, after reading this <a href="/apple-2-blog/refactoring">blog</a>, a good friend sent an email with some interesting questions:</p>

<blockquote>
  <p>Are you actually writing and running your code on the real physical Apple ][, or are you using an emulator? If you’re using a real one, how are you transferring the code back and forth from the Apple ][ to the blog? I’m guessing you’re not using generative AI to write the code, since it sounds like writing it by hand is part of the fun. Is that right?</p>
</blockquote>

<p>I sent him the answers but I thought I might share here, too.</p>

<h2 id="running-on-the-real-hardware">Running on the real hardware</h2>

<p>He might have missed my post about <a href="/apple-2-blog/revive">bringing the clunker back to life</a> but I’m running on the real hardware from early ’80s, which I recently refurbished with new capacitors in the power supply and new RAM chips on the motherboard.</p>

<p>I can’t remember exactly when my parents ponied up a bought me the Apple ][+ but it was after years of me hassling them relentlessly. In their defense, no one had a personal computer back then and it wasn’t entirely clear what one would do with one. The machine was <em>very</em> expensive and they didn’t want to pay that kind of money for a video game machine. I vaguely recall the Apple ][+ with a pair of floppy drives costing ~$2000 plus a few hundred for the extra 16k RAM on the expansion memory card and ~$500 for the dot matrix printer. That would be ~$9000 today. (For comparison, we had a <a href="https://www.youtube.com/watch?v=DYVH2IHnH8U&amp;t=300s" target="_blank">Magnavox Odyssey<sup>2</sup></a> which was under $200. The cartridges were ~$25.) My father was an aircraft mechanic and my mother stayed home to raise two children, so they paid close attention to finances.</p>

<p>My obsession with the Apple computer began during the summer after the 6<sup>th</sup> grade. My mother signed me up for computer camp where we learned how to write code on the Apple ][. These machines didn’t even have floppies, so we used cassette tape recorders. You’d press ‘record’ on the tape recorder before entering <code class="language-plaintext highlighter-rouge">SAVE</code> to store the program. To get it back you’d type <code class="language-plaintext highlighter-rouge">LOAD</code> and then press ‘play’ on the tape recorder. It wasn’t punch cards but that’s wild. My program was a lowres picture of a Star Wars battle (another childhood obsession).</p>

<p>From that moment, I had to have one. Every single birthday or holiday from that point forward, when anyone asked me what I wanted, to everyone’s dismay, I’d say, “Money.” My plan was to save up enough to get one. In the meantime, since these computers were not readily available, I would write BASIC programs on notebook paper (to do what, I can’t remember), ride my bike to the mall and then try them out on the <a href="https://en.wikipedia.org/wiki/TRS-80_Color_Computer" target="_blank">TRS-80 Color Computers</a> at Radio Shack. As I recognized at the time, the sales people there were extremely cool to let me do that.</p>

<p>Saving up that kind of money took years but sometime around the 9<sup>th</sup> grade I had enough to get a TRS-80, which was significantly less expensive at about half the cost. My parents, realizing that I was about to drop every penny I had on a computer inferior to the one I really wanted, stepped up and surprised me. I can still remember the boxes sitting in the living room when I got home.</p>

<h2 id="suffering-on-the-real-hardware">Suffering on the real hardware</h2>

<p>While I know that there are <a href="https://www.applefritter.com/content/apple-ii-online-emulator-0" target="_blank">emulators</a>, which would probably make life easier, I am not using them. While writing Applesoft BASIC on the original hardware feels <em>authentic</em>, it comes with some challenges:</p>

<ul>
  <li>The keyboard works but it’s 40+ years old and isn’t particularly ergonomic.</li>
  <li>The monitor is only 40 characters wide because, for some reason, I don’t have the <a href="https://en.wikipedia.org/wiki/Apple_80-Column_Text_Card" target="_blank">80-column card</a>. (I vaguely recall having an 80-character wide display but perhaps that was on the school’s computers. I also remember having a color monitor but that must have been the school as well.)</li>
  <li>Reading the code requires the <code class="language-plaintext highlighter-rouge">LIST</code> command but without specifying line numbers the entire program will quickly scroll by. Therefore, I have to painstakingly enter the line numbers each time.</li>
  <li>There isn’t a particularly effective way to edit lines of text. This super enthusiastic dude <a href="https://youtu.be/PHfKCxjsmos?si=LbrDEIzWVNBPsF8K&amp;t=108">demonstrates</a> how to use the <code class="language-plaintext highlighter-rouge">ESC</code> character to edit a line of code but it’s a real hassle. I’ve used this (although on my machine, which I found in the manual, you have to use a, b, c and d, which is confusing, plus hold down <code class="language-plaintext highlighter-rouge">ESC</code> the entire time) but it’s almost always easier to just retype the line. (<strong>Update</strong>: The I, J, K, M works and <code class="language-plaintext highlighter-rouge">ESC</code> toggles if you use those keys. It takes a little getting used to but it’s much better.)</li>
  <li>Applesoft BASIC has a “debug mode” with <code class="language-plaintext highlighter-rouge">TRACE</code>, which essentially prints out line numbers as they’re executed, but I’ve never used it. It sounds like a nightmare.</li>
</ul>

<h2 id="transferring-applesoft-basic-code-to-this-blog">Transferring Applesoft BASIC code to this blog</h2>

<p>While there’s probably a way to connect the Apple ][+ to my Windows machine, I solve this problem by taking a picture of the monitor with my phone and then asking <a href="https://chatgpt.com/share/684f9647-c438-8010-86d6-a3eae68a7b7d" target="_blank">chatGPT</a> to extract the code. The first attempts weren’t too bad, but I had to iterate quite a few times to get it to output correctly. For example, if a single line of code wrapped on the screen, chatGPT would add extra line numbers. It also messed up the counting on occasion. It sometimes interpreted the percent sign as a dollar sign. That being said, it saves me a <em>ton</em> of work. Interestingly, I think I ‘trained’ chatGPT on how to read the Applesoft BASIC code because outputs have become increasingly higher quality.</p>

<p align="middle">
  <img src="/assets/images/apple2/code-pic-1.jpg" width="32%" />
  <img src="/assets/images/apple2/code-pic-2.jpg" width="32%" /> 
  <img src="/assets/images/apple2/code-pic-3.jpg" width="32%" />
</p>

<p>I tried taking a video of the entire program scrolling by but the quality was not good enough for OCR.</p>

<h2 id="no-generative-ai">No generative AI</h2>

<p>Or not much, at any rate. GenAI is amazing and Cursor is beyond fantastic, but, as my friend said, part of the fun is writing the code myself. I’ll admit to getting assistance with <a href="/apple-2-blog/refactoring#generating-the-samples">adpative quota sampling</a>, which isn’t particularly difficult. I’ve also asked about how some commands work (e.g. <code class="language-plaintext highlighter-rouge">RND()</code>) although I’ve been relying primarily on the original <a href="/apple-2-blog/synthesizing-data">reference manual</a>. The rest, however, is me.</p>]]></content><author><name>Mark Cramer</name></author><category term="Cursor" /><category term="emulator" /><summary type="html"><![CDATA[While the modern tools are at my disposal, I am, for the most part, not using them.]]></summary></entry><entry><title type="html">Refactoring</title><link href="https://mdcramer.github.io/apple-2-blog/refactoring/" rel="alternate" type="text/html" title="Refactoring" /><published>2025-07-13T00:00:00-07:00</published><updated>2025-07-13T00:00:00-07:00</updated><id>https://mdcramer.github.io/apple-2-blog/refactoring</id><content type="html" xml:base="https://mdcramer.github.io/apple-2-blog/refactoring/"><![CDATA[<p>Well, that didn’t take too long. When I was developing <a href="https://mortalwayfare.com/" target="_blank">Mortal Wayfare</a>, I recall frequently passing hours just looking at the code, trying to figure out how to better represent what I was trying to accomplish while improving the organization. (Should this method go in a different class? Is there a better name for this variable? Should I break this code out into a function?…) Some days I would do nothing but this. Having never been a professional software developer, I’m not sure to what extent this is normal, but I found the process to be very helpful.</p>

<p>I suppose there’s a balance between planning out what you’re going to do and just getting started. For my this project, I leaned toward the latter. As such, after the first pass I already decided to change it up. More specifically, the first thing I decided to do was, rather than have a different array for each class of data, move everything into a single array. The reason is that, during training, this will facilitate looping over all the data.</p>

<p>Quick note on line numbers: refactoring BASIC almost <em>always</em> consists of changing line numbers. As such, this code is not going to match up with previous posts. Hopefully that won’t be confusing.</p>

<h2 id="refactoring-the-hyperparameters">Refactoring the hyperparameters</h2>

<p>To make that happen, I start by moving all of my generator hyperparameters into a single array, <code class="language-plaintext highlighter-rouge">GE(..,..)</code>, where the first axis is the class. The second axis contains, respectively, \(u_{x_0}, u_{x_1}, \sigma_{x_0}, \sigma_{x_1}, \rho\) and \(\sqrt{1 - \rho^2}\). (See <a href="/apple-2-blog/synthesizing-data#box-muller-to-the-rescue">Synthesizing data - Box-Muller</a> for details.) The last element is purely for performance since this quantity will be needed frequently. (Upon further refection, I could have used \(\sqrt{1 - \rho^2} \sigma_{x_1}\) but this is a small optimization for later.)</p>

<div class="language-bbcbasic highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="m">100</span> <span class="c">REM == HYPERPARAMETERS ==</span>
<span class="m">110</span> <span class="nv">KN</span> <span class="o">=</span> <span class="m">2</span><span class="p">:</span> <span class="c">REM # OF CLASSES</span>
<span class="m">120</span> <span class="k">DIM</span> <span class="nv">KN%</span><span class="o">(</span><span class="nv">KN</span> <span class="o">-</span> <span class="m">1</span><span class="o">)</span><span class="p">:</span> <span class="c">REM # OF SAMPLES FROM EACH CLASS</span>
<span class="m">130</span> <span class="nv">KN%</span><span class="o">(</span><span class="m">0</span><span class="o">)</span> <span class="o">=</span> <span class="m">100</span>
<span class="m">140</span> <span class="nv">KN%</span><span class="o">(</span><span class="m">1</span><span class="o">)</span> <span class="o">=</span> <span class="m">50</span>

<span class="m">150</span> <span class="c">REM -- GENERATORS --</span>
<span class="m">160</span> <span class="c">REM -- CLASS - MU-X0, MU-X1, SIG-X0, SIG-X1, RHO --</span>
<span class="m">170</span> <span class="k">DIM</span> <span class="nv">GE</span><span class="o">(</span><span class="nv">KN</span> <span class="o">-</span> <span class="m">1</span><span class="p">,</span><span class="m">5</span><span class="o">)</span>

<span class="m">180</span> <span class="c">REM -- CLASS 0 --</span>
<span class="m">190</span> <span class="nv">GE</span><span class="o">(</span><span class="m">0</span><span class="p">,</span><span class="m">0</span><span class="o">)</span> <span class="o">=</span> <span class="m">100</span><span class="p">:</span> <span class="c">REM MU-X0</span>
<span class="m">200</span> <span class="nv">GE</span><span class="o">(</span><span class="m">0</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">=</span> <span class="m">50</span><span class="p">:</span> <span class="c">REM MU-X1</span>
<span class="m">210</span> <span class="nv">GE</span><span class="o">(</span><span class="m">0</span><span class="p">,</span><span class="m">2</span><span class="o">)</span> <span class="o">=</span> <span class="m">30</span><span class="p">:</span> <span class="c">REM SIG-X0</span>
<span class="m">220</span> <span class="nv">GE</span><span class="o">(</span><span class="m">0</span><span class="p">,</span><span class="m">3</span><span class="o">)</span> <span class="o">=</span> <span class="m">10</span><span class="p">:</span> <span class="c">REM SIG-X1</span>
<span class="m">230</span> <span class="nv">GE</span><span class="o">(</span><span class="m">0</span><span class="p">,</span><span class="m">4</span><span class="o">)</span> <span class="o">=</span> <span class="m">0.5</span><span class="p">:</span> <span class="c">REM RHO</span>
<span class="m">240</span> <span class="nv">GE</span><span class="o">(</span><span class="m">0</span><span class="p">,</span><span class="m">5</span><span class="o">)</span> <span class="o">=</span> <span class="nb">SQR</span><span class="o">(</span><span class="m">1</span> <span class="o">-</span> <span class="nv">GE</span><span class="o">(</span><span class="m">0</span><span class="p">,</span><span class="m">4</span><span class="o">)</span> <span class="o">^</span> <span class="m">2</span><span class="o">)</span>

<span class="m">250</span> <span class="c">REM -- CLASS 1 --</span>
<span class="m">260</span> <span class="nv">GE</span><span class="o">(</span><span class="m">1</span><span class="p">,</span><span class="m">0</span><span class="o">)</span> <span class="o">=</span> <span class="m">200</span><span class="p">:</span> <span class="c">REM MU-X0</span>
<span class="m">270</span> <span class="nv">GE</span><span class="o">(</span><span class="m">1</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">=</span> <span class="m">100</span><span class="p">:</span> <span class="c">REM MU-X1</span>
<span class="m">280</span> <span class="nv">GE</span><span class="o">(</span><span class="m">1</span><span class="p">,</span><span class="m">2</span><span class="o">)</span> <span class="o">=</span> <span class="m">50</span><span class="p">:</span> <span class="c">REM SIG-X0</span>
<span class="m">290</span> <span class="nv">GE</span><span class="o">(</span><span class="m">1</span><span class="p">,</span><span class="m">3</span><span class="o">)</span> <span class="o">=</span> <span class="m">25</span><span class="p">:</span> <span class="c">REM SIG-X1</span>
<span class="m">300</span> <span class="nv">GE</span><span class="o">(</span><span class="m">1</span><span class="p">,</span><span class="m">4</span><span class="o">)</span> <span class="o">=</span> <span class="o">-</span><span class="m">0.5</span><span class="p">:</span> <span class="c">REM RHO</span>
<span class="m">310</span> <span class="nv">GE</span><span class="o">(</span><span class="m">1</span><span class="p">,</span><span class="m">5</span><span class="o">)</span> <span class="o">=</span> <span class="nb">SQR</span><span class="o">(</span><span class="m">1</span> <span class="o">-</span> <span class="nv">GE</span><span class="o">(</span><span class="m">1</span><span class="p">,</span><span class="m">4</span><span class="o">)</span> <span class="o">^</span> <span class="m">2</span><span class="o">)</span>
<span class="m">320</span> <span class="c">REM == END HYPERPARAMETERS ==</span>
</code></pre></div></div>
<p>Note: here is a funny thing with Applesoft BASIC arrays. I couldn’t remember if there was 0- or 1-indexed. Turns out, they are both! <code class="language-plaintext highlighter-rouge">DIM A(3)</code> will create an array of length 4 which goes from index 0 to 3. I can’t recall seeing anything like that anywhere else. So, I use <code class="language-plaintext highlighter-rouge">DIM GE(KN-1,4)</code> because I want the first axis to have <code class="language-plaintext highlighter-rouge">KN</code> elements and the second axis to have 5. Funny.</p>

<p>Another note: Applesoft BASIC only uses the first two characters of a variable names, however, <code class="language-plaintext highlighter-rouge">KN</code> and <code class="language-plaintext highlighter-rouge">KN%(..)</code> are considered different.</p>

<h2 id="generating-the-samples">Generating the samples</h2>

<p>After counting up the total number of samples, I then create <code class="language-plaintext highlighter-rouge">DS%(..,..)</code> which replaces <code class="language-plaintext highlighter-rouge">AX%(..)</code> and <code class="language-plaintext highlighter-rouge">BX%(..)</code>. The first axis contains the samples, of which there are <code class="language-plaintext highlighter-rouge">NS</code> in total. The second axis is, respectively, \(x_0, x_1, \hat{y}\) and \(y\). \(\hat{y}\) is the label (i.e., the class) and \(y\) is the prediction, which we’ll get to later. Having all the data in a single array will facilitate generating and training.</p>

<div class="language-bbcbasic highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="m">330</span> <span class="nv">NS</span> <span class="o">=</span> <span class="m">0</span><span class="p">:</span> <span class="c">REM TOTAL # SAMPLES</span>
<span class="m">340</span> <span class="k">FOR</span> <span class="nv">I</span> <span class="o">=</span> <span class="m">0</span> <span class="k">TO</span> <span class="nv">KN</span> <span class="o">-</span> <span class="m">1</span>
<span class="m">350</span>   <span class="nv">NS</span> <span class="o">=</span> <span class="nv">NS</span> <span class="o">+</span> <span class="nv">KN%</span><span class="o">(</span><span class="nv">I</span><span class="o">)</span>
<span class="m">360</span> <span class="k">NEXT</span>

<span class="m">370</span> <span class="c">REM -- DATA TABLE --</span>
<span class="m">380</span> <span class="c">REM -- # - X0, X1, Y-HAT, Y --</span>
<span class="m">390</span> <span class="k">DIM</span> <span class="nv">DS%</span><span class="o">(</span><span class="nv">NS</span> <span class="o">-</span> <span class="m">1</span><span class="p">,</span><span class="m">3</span><span class="o">)</span>
</code></pre></div></div>
<p>Since I’d like for my data to be randomly sampled, rather than scrambled after the fact, I decided to use ‘adaptive quota sampling’ to generate them randomly while ending up with the appropriate quantities. I could have simply taken the end proportion of each class and use that to create probabilities during generation but then there would be no guarantee then the final number of samples for each class would be correct.</p>

<div class="language-bbcbasic highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="m">400</span> <span class="c">REM -- REMAINING SAMPLES COUNT --</span>
<span class="m">410</span> <span class="k">DIM</span> <span class="nv">RK%</span><span class="o">(</span><span class="nv">KN</span> <span class="o">-</span> <span class="m">1</span><span class="o">)</span>
<span class="m">420</span> <span class="k">FOR</span> <span class="nv">I</span> <span class="o">=</span> <span class="m">0</span> <span class="k">TO</span> <span class="nv">KN</span> <span class="o">-</span> <span class="m">1</span>
<span class="m">430</span>   <span class="nv">RK%</span><span class="o">(</span><span class="nv">I</span><span class="o">)</span> <span class="o">=</span> <span class="nv">KN%</span><span class="o">(</span><span class="nv">I</span><span class="o">)</span>
<span class="m">440</span> <span class="k">NEXT</span>

<span class="m">450</span> <span class="c">REM == GENERATE SAMPLES ==</span>
<span class="m">460</span> <span class="k">FOR</span> <span class="nv">I</span> <span class="o">=</span> <span class="m">0</span> <span class="k">TO</span> <span class="nv">NS</span> <span class="o">-</span> <span class="m">1</span>
<span class="m">470</span>   <span class="c">REM -- ADAPTIVE QUOTA SAMPLING --</span>
<span class="m">480</span>   <span class="c">REM -- PICK CLASS BASED ON REMAINING SAMPLES TO GENERATE --</span>
<span class="m">490</span>   <span class="nv">P%</span> <span class="o">=</span> <span class="nb">RND</span><span class="o">(</span><span class="m">1</span><span class="o">)</span> <span class="o">*</span> <span class="o">(</span><span class="nv">NS</span> <span class="o">-</span> <span class="nv">I</span><span class="o">)</span>
<span class="m">500</span>   <span class="nv">CS</span> <span class="o">=</span> <span class="m">0</span>
<span class="m">510</span>   <span class="nv">J</span> <span class="o">=</span> <span class="m">0</span>
<span class="m">520</span>   <span class="nv">CS</span> <span class="o">=</span> <span class="nv">CS</span> <span class="o">+</span> <span class="nv">RK%</span><span class="o">(</span><span class="nv">J</span><span class="o">)</span>
<span class="m">530</span>   <span class="nv">K</span> <span class="o">=</span> <span class="nv">J</span>
<span class="m">540</span>   <span class="nv">J</span> <span class="o">=</span> <span class="nv">J</span> <span class="o">+</span> <span class="m">1</span>
<span class="m">550</span>   <span class="k">IF</span> <span class="nv">P%</span> <span class="o">&gt;=</span> <span class="nv">CS</span> <span class="k">GOTO</span> <span class="m">520</span>
<span class="m">560</span>   <span class="nv">RK%</span><span class="o">(</span><span class="nv">K</span><span class="o">)</span> <span class="o">=</span> <span class="nv">RK%</span><span class="o">(</span><span class="nv">K</span><span class="o">)</span> <span class="o">-</span> <span class="m">1</span>
<span class="m">570</span>   <span class="c">REM -- GENERATE RANDOM NORMAL VARIABLES Z0, Z1 --</span>
<span class="m">580</span>   <span class="k">GOSUB</span> <span class="m">1400</span>
<span class="m">590</span>   <span class="c">REM -- GENERATE SAMPLE FROM Z0, Z1 --</span>
<span class="m">600</span>   <span class="c">REM X0 = MU-X0 + SIG-X0 * Z0</span>
<span class="m">610</span>   <span class="nv">X0%</span> <span class="o">=</span> <span class="nv">GE</span><span class="o">(</span><span class="nv">K</span><span class="p">,</span><span class="m">0</span><span class="o">)</span> <span class="o">+</span> <span class="nv">GE</span><span class="o">(</span><span class="nv">K</span><span class="p">,</span><span class="m">2</span><span class="o">)</span> <span class="o">*</span> <span class="nv">Z0</span>
<span class="m">620</span>   <span class="c">REM X1 = MU-X1 + RHO * SIG-X1 * Z0 + SQR(1 - RHO^2) * SIG-X1 * Z1</span>
<span class="m">630</span>   <span class="nv">X1%</span> <span class="o">=</span> <span class="nv">GE</span><span class="o">(</span><span class="nv">K</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">+</span> <span class="nv">GE</span><span class="o">(</span><span class="nv">K</span><span class="p">,</span><span class="m">4</span><span class="o">)</span> <span class="o">*</span> <span class="nv">GE</span><span class="o">(</span><span class="nv">K</span><span class="p">,</span><span class="m">3</span><span class="o">)</span> <span class="o">*</span> <span class="nv">Z0</span> <span class="o">+</span> <span class="nv">GE</span><span class="o">(</span><span class="nv">K</span><span class="p">,</span><span class="m">5</span><span class="o">)</span> <span class="o">*</span> <span class="nv">GE</span><span class="o">(</span><span class="nv">K</span><span class="p">,</span><span class="m">3</span><span class="o">)</span> <span class="o">*</span> <span class="nv">Z1</span>
<span class="m">640</span>   <span class="nv">DS%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">0</span><span class="o">)</span> <span class="o">=</span> <span class="nv">X0%</span>
<span class="m">650</span>   <span class="nv">DS%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">=</span> <span class="nv">X1%</span>
<span class="m">660</span>   <span class="nv">DS%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">2</span><span class="o">)</span> <span class="o">=</span> <span class="nv">K</span>
<span class="m">670</span>   <span class="k">PRINT</span> <span class="nv">I</span> <span class="o">+</span> <span class="m">1</span><span class="p">;</span> <span class="nv">TAB</span><span class="o">(</span><span class="m">5</span><span class="o">)</span><span class="p">;</span> <span class="s">"SAMPLE "</span><span class="p">;</span> <span class="nv">KN%</span><span class="o">(</span><span class="nv">K</span><span class="o">)</span> <span class="o">-</span> <span class="nv">RK%</span><span class="o">(</span><span class="nv">K</span><span class="o">)</span><span class="p">;</span>
<span class="m">680</span>   <span class="k">PRINT</span> <span class="nv">TAB</span><span class="o">(</span><span class="m">16</span><span class="o">)</span><span class="p">;</span> <span class="s">"IN CLASS "</span><span class="p">;</span> <span class="nv">K</span><span class="p">;</span>
<span class="m">690</span>   <span class="k">PRINT</span> <span class="s">" AT ("</span><span class="p">;</span> <span class="nv">X0%</span><span class="p">;</span> <span class="s">","</span><span class="p">;</span> <span class="nv">X1%</span><span class="p">;</span> <span class="s">")"</span>
<span class="m">700</span>   <span class="k">ON</span> <span class="nv">K</span> <span class="o">+</span> <span class="m">1</span> <span class="k">GOSUB</span> <span class="m">1200</span><span class="p">,</span><span class="m">1300</span><span class="p">:</span> <span class="c">REM PLOT SAMPLE</span>
<span class="m">710</span> <span class="k">NEXT</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">RK%(..)</code> holds the remaining number of samples to be generated for each class. Lines 490 to 550 then produce a random number <code class="language-plaintext highlighter-rouge">P%</code> which is from 0 to the number of remaining samples to generate and then figures out which class this should be based on the remaining number of samples to be generated for each. The beauty of this is that the probability of generating a sample in each class will be a function of how many remaining samples need to be generated. It took a while to debug, but in the end this produces exactly the number of samples in each class as specified in the hyperparameters.</p>

<p>The code from lines 580 to 690 is the same as previously, however, the class-specific variables have been replaced by <code class="language-plaintext highlighter-rouge">GE(..,..)</code>, as mentioned above. Also, I changed <code class="language-plaintext highlighter-rouge">X%</code> and <code class="language-plaintext highlighter-rouge">Y%</code> to <code class="language-plaintext highlighter-rouge">XO%</code> and <code class="language-plaintext highlighter-rouge">X1%</code> to avoid confusion and be more consistent with the \(Y = \theta^T X\) literature.</p>

<p>Line 700 is nifty. I don’t recall using <code class="language-plaintext highlighter-rouge">ON..GOSUB</code> in high school but it enables branching based on the argument. I use <code class="language-plaintext highlighter-rouge">K+1</code> since the first class is \(0\) and a zero argument doesn’t branch. For the moment, this only supports two classes.</p>

<h3 id="counting-the-samples">Counting the samples</h3>

<p>For debugging purposes, I added some code to count the number of samples in each class to make sure it matched the hyperparameters. In the end, I decided to leave it. The loop is actually a bit slow but it’s only run once.</p>

<div class="language-bbcbasic highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="m">720</span> <span class="c">REM == COUNT SAMPLES OF EACH CLASS TO VERIFY RESULT ==</span>
<span class="m">730</span> <span class="k">DIM</span> <span class="nv">CT%</span><span class="o">(</span><span class="nv">KN</span> <span class="o">-</span> <span class="m">1</span><span class="o">)</span>
<span class="m">740</span> <span class="k">FOR</span> <span class="nv">I</span> <span class="o">=</span> <span class="m">0</span> <span class="k">TO</span> <span class="nv">NS</span> <span class="o">-</span> <span class="m">1</span>
<span class="m">750</span>   <span class="nv">K</span> <span class="o">=</span> <span class="nv">DS%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">2</span><span class="o">)</span>
<span class="m">760</span>   <span class="nv">CT%</span><span class="o">(</span><span class="nv">K</span><span class="o">)</span> <span class="o">=</span> <span class="nv">CT%</span><span class="o">(</span><span class="nv">K</span><span class="o">)</span> <span class="o">+</span> <span class="m">1</span>
<span class="m">770</span> <span class="k">NEXT</span>
<span class="m">780</span> <span class="k">PRINT</span> <span class="s">"TOTAL SAMPLES FOR EACH CLASS:"</span>
<span class="m">790</span> <span class="k">FOR</span> <span class="nv">I</span> <span class="o">=</span> <span class="m">0</span> <span class="k">TO</span> <span class="nv">KN</span> <span class="o">-</span> <span class="m">1</span>
<span class="m">800</span>   <span class="k">PRINT</span> <span class="nv">CT%</span><span class="o">(</span><span class="nv">I</span><span class="o">)</span><span class="p">;</span> <span class="nv">TAB</span><span class="o">(</span><span class="m">5</span><span class="o">)</span><span class="p">;</span> <span class="s">"SAMPLES IN CLASS "</span><span class="p">;</span> <span class="nv">I</span>
<span class="m">810</span> <span class="k">NEXT</span>
<span class="m">820</span> <span class="k">END</span>
</code></pre></div></div>

<h2 id="fixing-the-plotting-routines">Fixing the plotting routines</h2>

<p>The only remaining tweaks were for the plotting routines. The two routines below, which are called from the <code class="language-plaintext highlighter-rouge">ON..GOSUB</code> in line 700, are updated to use <code class="language-plaintext highlighter-rouge">XO%</code> and <code class="language-plaintext highlighter-rouge">X1%</code>. The rest is the same.</p>

<div class="language-bbcbasic highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="m">1200</span> <span class="c">REM == DRAW + AT X0, X1 ==</span>
<span class="m">1210</span> <span class="nv">X1%</span> <span class="o">=</span> <span class="m">159</span> <span class="o">-</span> <span class="nv">X1%</span>
<span class="m">1220</span> <span class="k">IF</span> <span class="nv">X0%</span> <span class="o">&lt;</span> <span class="m">1</span> <span class="o">OR</span> <span class="nv">X0%</span> <span class="o">&gt;</span> <span class="m">270</span> <span class="k">THEN</span> <span class="k">RETURN</span>
<span class="m">1230</span> <span class="k">IF</span> <span class="nv">X1%</span> <span class="o">&lt;</span> <span class="m">1</span> <span class="o">OR</span> <span class="nv">X1%</span> <span class="o">&gt;</span> <span class="m">150</span> <span class="k">THEN</span> <span class="k">RETURN</span>
<span class="m">1240</span> <span class="nv">HPLOT</span> <span class="nv">X0%</span> <span class="o">-</span> <span class="m">1</span><span class="p">,</span><span class="nv">X1%</span> <span class="k">TO</span> <span class="nv">X0%</span> <span class="o">+</span> <span class="m">1</span><span class="p">,</span><span class="nv">X1%</span>
<span class="m">1250</span> <span class="nv">HPLOT</span> <span class="nv">X0%</span><span class="p">,</span><span class="nv">X1%</span> <span class="o">-</span> <span class="m">1</span> <span class="k">TO</span> <span class="nv">X0%</span><span class="p">,</span><span class="nv">X1%</span> <span class="o">+</span> <span class="m">1</span>
<span class="m">1260</span> <span class="k">RETURN</span>

<span class="m">1300</span> <span class="c">REM == DRAW BOX AT X0, X1 ==</span>
<span class="m">1310</span> <span class="nv">X1%</span> <span class="o">=</span> <span class="m">159</span> <span class="o">-</span> <span class="nv">X1%</span>
<span class="m">1320</span> <span class="k">IF</span> <span class="nv">X0%</span> <span class="o">&lt;</span> <span class="m">1</span> <span class="o">OR</span> <span class="nv">X0%</span> <span class="o">&gt;</span> <span class="m">270</span> <span class="k">THEN</span> <span class="k">RETURN</span>
<span class="m">1330</span> <span class="k">IF</span> <span class="nv">X1%</span> <span class="o">&lt;</span> <span class="m">1</span> <span class="o">OR</span> <span class="nv">X1%</span> <span class="o">&gt;</span> <span class="m">150</span> <span class="k">THEN</span> <span class="k">RETURN</span>
<span class="m">1340</span> <span class="nv">HPLOT</span> <span class="nv">X0%</span> <span class="o">-</span> <span class="m">1</span><span class="p">,</span><span class="nv">X1%</span> <span class="o">-</span> <span class="m">1</span> <span class="k">TO</span> <span class="nv">X0%</span> <span class="o">+</span> <span class="m">1</span><span class="p">,</span><span class="nv">X1%</span> <span class="o">-</span> <span class="m">1</span>
<span class="m">1350</span> <span class="nv">HPLOT</span> <span class="nv">X0%</span> <span class="o">-</span> <span class="m">1</span><span class="p">,</span><span class="nv">X1%</span><span class="p">:</span> <span class="nv">HPLOT</span> <span class="nv">X0%</span> <span class="o">+</span> <span class="m">1</span><span class="p">,</span><span class="nv">X1%</span>
<span class="m">1360</span> <span class="nv">HPLOT</span> <span class="nv">X0%</span> <span class="o">-</span> <span class="m">1</span><span class="p">,</span><span class="nv">X1%</span> <span class="o">+</span> <span class="m">1</span> <span class="k">TO</span> <span class="nv">X0%</span> <span class="o">+</span> <span class="m">1</span><span class="p">,</span><span class="nv">X1%</span> <span class="o">+</span> <span class="m">1</span>
<span class="m">1370</span> <span class="k">RETURN</span>
</code></pre></div></div>

<h2 id="testing-irwin-hall-as-an-alternative-to-box-muller">Testing Irwin-Hall as an alternative to Box-Muller</h2>

<p>The final chunk of code is interesting. After sharing my <a href="/apple-2-blog/synthesizing-data#box-muller-to-the-rescue">Box-Muller</a> routine with my friend, <a href="https://www.linkedin.com/in/surdules/" target="_blank">Răzvan Surdulescu</a>, he suggested using the <a href="https://en.wikipedia.org/wiki/Irwin%E2%80%93Hall_distribution" target="_blank">Irwin-Hall Distribution</a>. <code class="language-plaintext highlighter-rouge">SQR</code> and <code class="language-plaintext highlighter-rouge">COS</code> are expensive operations in BASIC and Irwin-Hall <a href="https://en.wikipedia.org/wiki/Irwin%E2%80%93Hall_distribution#Approximating_a_Normal_distribution" target="_blank">approximates a normal distribution</a> by sampling 12 uniform random numbers.</p>

<div class="language-bbcbasic highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="m">1500</span> <span class="c">REM == IRWIN-HALL DISTRIBUTION ==</span>
<span class="m">1510</span> <span class="nv">Z0</span> <span class="o">=</span> <span class="o">-</span><span class="m">6</span><span class="p">:</span><span class="nv">Z1</span> <span class="o">=</span> <span class="o">-</span><span class="m">6</span>
<span class="m">1520</span> <span class="k">FOR</span> <span class="nv">Z</span> <span class="o">=</span> <span class="m">1</span> <span class="k">TO</span> <span class="m">12</span>
<span class="m">1530</span>   <span class="nv">Z0</span> <span class="o">=</span> <span class="nv">Z0</span> <span class="o">+</span> <span class="nb">RND</span><span class="o">(</span><span class="m">1</span><span class="o">)</span>
<span class="m">1540</span>   <span class="nv">Z1</span> <span class="o">=</span> <span class="nv">Z1</span> <span class="o">+</span> <span class="nb">RND</span><span class="o">(</span><span class="m">1</span><span class="o">)</span>
<span class="m">1550</span> <span class="k">NEXT</span>
<span class="m">1560</span> <span class="k">RETURN</span>
<span class="m">1600</span> <span class="c">REM == RANDOM GEN SPEED TEST ==</span>
<span class="m">1610</span> <span class="k">FOR</span> <span class="nv">I</span> <span class="o">=</span> <span class="m">1</span> <span class="k">TO</span> <span class="m">500</span>
<span class="m">1620</span>   <span class="k">GOSUB</span> <span class="m">1400</span>
<span class="m">1630</span> <span class="k">NEXT</span>
</code></pre></div></div>
<p>I gave it a go and ran a comparison by generating 500 pairs of random numbers with each. This took 63s with Box-Muller and 90s with Irwin-Hall, so I will stick with the former. That being said, this was a nifty idea.</p>

<h2 id="an-updated-final-plot">An updated final plot</h2>

<p>The final result looks pretty much the same as before but it’s flipped horizontally. The new data structure, however, will make implementing ML much easier.</p>

<p><img src="/assets/images/apple2/updated-final-plot.jpg" alt="Updated plot of synthesized data" title="Updated final plot of synthesized data" /></p>

<p>Now onto implementing the simplest and easiest to understand machine learning algorithm…</p>]]></content><author><name>Mark Cramer</name></author><category term="machine learning" /><category term="synthesizing data" /><category term="refactoring" /><summary type="html"><![CDATA[Changing the BASIC code... after only one week.]]></summary></entry><entry><title type="html">In the beginning, there was data</title><link href="https://mdcramer.github.io/apple-2-blog/synthesizing-data/" rel="alternate" type="text/html" title="In the beginning, there was data" /><published>2025-07-06T00:00:00-07:00</published><updated>2025-07-06T00:00:00-07:00</updated><id>https://mdcramer.github.io/apple-2-blog/synthesizing-data</id><content type="html" xml:base="https://mdcramer.github.io/apple-2-blog/synthesizing-data/"><![CDATA[<p>Crestfallen by the seeming impossibility of <a href="/apple-2-blog/recover/">recovering</a> my beloved <a href="https://mortalwayfare.com/remnant-from-the-past/" target="_blank">game</a> from high school, I turned my attention to the original <a href="apple-2-blog/motivation/">motivation</a>: developing machine learning on the Apple ][+. Grabbing my manuals, I got to work.</p>

<p><img src="/assets/images/apple2/manuals.jpg" alt="Apple 2+ manuals" title="Apple ][+ manuals" /></p>

<p>Every solid machine learning project begins with data. My old clunker, however, is cut off from the world. I never had a modem but I recall one of my best friends using the modem on his Apple ][ to hack long distance access codes. (He since went on to pursue a successful career in telecommunications… naturally.) Even if I had a modem, what would I connect it to? Do those services still even exists? (I’m sure they do but I’m not going to figure that out today.) In the meantime, the plan is to create synthetic data locally.</p>

<p>I’m sure everyone is familiar with the <a href="https://scikit-learn.org/stable/auto_examples/classification/plot_classifier_comparison.html" target="_blank">chart</a> comparing different classification algorithms. Since two-dimensional binary classification seems like a great place to start, I’ll need a simple way to graph the data.</p>

<p><a href="/assets/images/apple2/sphx_glr_plot_classifier_comparison_001.png"><img src="/assets/images/apple2/sphx_glr_plot_classifier_comparison_001.png" alt="Classifier comparison" title="Classifier comparison" /></a></p>

<p>Fortunately, the Apple ][+ comes with a high-resolution graphics screen (<code class="language-plaintext highlighter-rouge">HGR</code>) that is 280 pixels across by 160 pixels high. (There is another mode with 192 vertical pixels but I wanted leave the bottom 4 lines of the text window visible for running output.) There are <a href="https://en.wikipedia.org/wiki/Apple_II_graphics#High-Resolution_%28Hi-Res%29_graphics" target="_blank">8 color options</a> for each pixel, but I have a green screen monitor, so I set everything to <code class="language-plaintext highlighter-rouge">HCOLOR=7</code> (white2).</p>

<h2 id="drawing-the-axes">Drawing the axes</h2>

<p>Deciding to stick to the positive quadrant, I wrote a subroutine (Applesoft BASIC does not have methods) to simply display the axes. Interestingly, coordinate (0,0) is the upper left corner of the screen while (279,159) is the lower right, so <code class="language-plaintext highlighter-rouge">HPLOT 0,159 TO 279,159</code> draws the x-axis.</p>

<div class="language-bbcbasic highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="m">500</span> <span class="c">REM  == SET GRAPHICS AND DRAW Y AXIS WITH TICK MARKS ==</span>
<span class="m">510</span> <span class="nv">HGR</span> <span class="p">:</span> <span class="nv">HCOLOR</span><span class="o">=</span><span class="m">7</span>
<span class="m">520</span> <span class="nv">HPLOT</span> <span class="m">0</span><span class="p">,</span><span class="m">159</span> <span class="k">TO</span> <span class="m">279</span><span class="p">,</span><span class="m">159</span>
<span class="m">530</span> <span class="nv">HPLOT</span> <span class="m">0</span><span class="p">,</span><span class="m">159</span> <span class="k">TO</span> <span class="m">0</span><span class="p">,</span><span class="m">0</span>
<span class="m">540</span> <span class="nv">J</span> <span class="o">=</span> <span class="m">0</span>
<span class="m">550</span> <span class="k">FOR</span> <span class="nv">I</span> <span class="o">=</span> <span class="m">159</span> <span class="k">TO</span> <span class="m">0</span> <span class="k">STEP</span> <span class="o">-</span><span class="m">10</span>
<span class="m">560</span>   <span class="nv">HPLOT</span> <span class="m">0</span><span class="p">,</span><span class="nv">I</span> <span class="k">TO</span> <span class="m">1</span><span class="p">,</span><span class="nv">I</span>
<span class="m">570</span>   <span class="k">IF</span> <span class="nv">J</span> <span class="o">/</span> <span class="m">5</span> <span class="o">=</span> <span class="nb">INT</span><span class="o">(</span><span class="nv">J</span> <span class="o">/</span> <span class="m">5</span><span class="o">)</span> <span class="k">THEN</span> <span class="nv">HPLOT</span> <span class="m">0</span><span class="p">,</span><span class="nv">I</span> <span class="k">TO</span> <span class="m">2</span><span class="p">,</span><span class="nv">I</span>
<span class="m">580</span>   <span class="k">IF</span> <span class="nv">J</span> <span class="o">/</span> <span class="m">10</span> <span class="o">=</span> <span class="nb">INT</span><span class="o">(</span><span class="nv">J</span> <span class="o">/</span> <span class="m">10</span><span class="o">)</span> <span class="k">THEN</span> <span class="nv">HPLOT</span> <span class="m">0</span><span class="p">,</span><span class="nv">I</span> <span class="k">TO</span> <span class="m">3</span><span class="p">,</span><span class="nv">I</span>
<span class="m">590</span>   <span class="nv">J</span> <span class="o">=</span> <span class="nv">J</span> <span class="o">+</span> <span class="m">1</span>
<span class="m">600</span> <span class="k">NEXT</span> <span class="nv">I</span>
<span class="m">610</span> <span class="nv">J</span> <span class="o">=</span> <span class="m">0</span>
<span class="m">620</span> <span class="k">FOR</span> <span class="nv">I</span> <span class="o">=</span> <span class="m">0</span> <span class="k">TO</span> <span class="m">279</span> <span class="k">STEP</span> <span class="m">10</span>
<span class="m">630</span>   <span class="nv">HPLOT</span> <span class="nv">I</span><span class="p">,</span><span class="m">159</span> <span class="k">TO</span> <span class="nv">I</span><span class="p">,</span><span class="m">158</span>
<span class="m">640</span>   <span class="k">IF</span> <span class="nv">J</span> <span class="o">/</span> <span class="m">5</span> <span class="o">=</span> <span class="nb">INT</span><span class="o">(</span><span class="nv">J</span> <span class="o">/</span> <span class="m">5</span><span class="o">)</span> <span class="k">THEN</span> <span class="nv">HPLOT</span> <span class="nv">I</span><span class="p">,</span><span class="m">159</span> <span class="k">TO</span> <span class="nv">I</span><span class="p">,</span><span class="m">157</span>
<span class="m">650</span>   <span class="k">IF</span> <span class="nv">J</span> <span class="o">/</span> <span class="m">10</span> <span class="o">=</span> <span class="nb">INT</span><span class="o">(</span><span class="nv">J</span> <span class="o">/</span> <span class="m">10</span><span class="o">)</span> <span class="k">THEN</span> <span class="nv">HPLOT</span> <span class="nv">I</span><span class="p">,</span><span class="m">159</span> <span class="k">TO</span> <span class="nv">I</span><span class="p">,</span><span class="m">156</span>
<span class="m">660</span>   <span class="nv">J</span> <span class="o">=</span> <span class="nv">J</span> <span class="o">+</span> <span class="m">1</span>
<span class="m">670</span> <span class="k">NEXT</span> <span class="nv">I</span>
<span class="m">680</span> <span class="k">RETURN</span>
</code></pre></div></div>
<p>The first <code class="language-plaintext highlighter-rouge">FOR I</code> loop adds tick marks along the y-axis. I decided to get fancy and add elongated tick marks at every 5th unit and even longer ticks marks at every 10 unit. I use <code class="language-plaintext highlighter-rouge">J</code> to count the ticks. The second <code class="language-plaintext highlighter-rouge">FOR I</code> loop does the same for the x-axis. The final result looks like this:</p>

<p><img src="/assets/images/apple2/axis.jpg" alt="x- and y-axis of graph" title="The x- and y-axis of the graph" /></p>

<h2 id="synthesizing-the-data">Synthesizing the data</h2>

<p>With a particular fondness for all thing <a href="https://en.wikipedia.org/wiki/Carl_Friedrich_Gauss" target="_blank">Gaussian</a>, I decided to create two sets of data points with <a href="https://en.wikipedia.org/wiki/Normal_distribution" target="_blank">Gaussian distributions</a>. They look pretty and they’re fun.</p>

<div class="language-bbcbasic highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="m">10</span>  <span class="nv">HOME</span> <span class="p">:</span> <span class="nv">VTAB</span> <span class="m">21</span>
<span class="m">20</span>  <span class="nv">PI</span> <span class="o">=</span> <span class="m">3.14159265</span>
<span class="m">30</span>  <span class="k">GOSUB</span> <span class="m">500</span> <span class="p">:</span> <span class="c">REM  DRAW AXIS</span>
<span class="m">40</span>  <span class="k">DIM</span> <span class="nv">AM%</span><span class="o">(</span><span class="m">2</span><span class="o">)</span> <span class="p">:</span> <span class="c">REM  -- MEAN</span>
<span class="m">50</span>  <span class="k">DIM</span> <span class="nv">BM%</span><span class="o">(</span><span class="m">2</span><span class="o">)</span>
<span class="m">60</span>  <span class="k">DIM</span> <span class="nv">AS%</span><span class="o">(</span><span class="m">2</span><span class="o">)</span> <span class="p">:</span> <span class="c">REM  -- STD</span>
<span class="m">70</span>  <span class="k">DIM</span> <span class="nv">BS%</span><span class="o">(</span><span class="m">2</span><span class="o">)</span>
<span class="m">80</span>  <span class="c">REM  == HYPERPARAMETERS ==</span>
<span class="m">90</span>  <span class="nv">AN</span> <span class="o">=</span> <span class="m">100</span> <span class="p">:</span> <span class="c">REM  # OF POINTS</span>
<span class="m">100</span> <span class="nv">BN</span> <span class="o">=</span> <span class="m">50</span>
<span class="m">110</span> <span class="nv">AM%</span><span class="o">(</span><span class="m">1</span><span class="o">)</span> <span class="o">=</span> <span class="m">100</span> <span class="p">:</span> <span class="nv">AM%</span><span class="o">(</span><span class="m">2</span><span class="o">)</span> <span class="o">=</span> <span class="m">50</span>
<span class="m">120</span> <span class="nv">BM%</span><span class="o">(</span><span class="m">1</span><span class="o">)</span> <span class="o">=</span> <span class="m">200</span> <span class="p">:</span> <span class="nv">BM%</span><span class="o">(</span><span class="m">2</span><span class="o">)</span> <span class="o">=</span> <span class="m">100</span>
<span class="m">130</span> <span class="nv">AS%</span><span class="o">(</span><span class="m">1</span><span class="o">)</span> <span class="o">=</span> <span class="m">30</span> <span class="p">:</span> <span class="nv">AS%</span><span class="o">(</span><span class="m">2</span><span class="o">)</span> <span class="o">=</span> <span class="m">10</span>
<span class="m">140</span> <span class="nv">BS%</span><span class="o">(</span><span class="m">1</span><span class="o">)</span> <span class="o">=</span> <span class="m">50</span> <span class="p">:</span> <span class="nv">BS%</span><span class="o">(</span><span class="m">2</span><span class="o">)</span> <span class="o">=</span> <span class="m">25</span>
<span class="m">150</span> <span class="nv">AC</span> <span class="o">=</span> <span class="m">0.5</span>
<span class="m">160</span> <span class="nv">BC</span> <span class="o">=</span> <span class="o">-</span><span class="m">0.5</span>
<span class="m">170</span> <span class="c">REM  == END HYPERPARAMETERS ==</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">HOME</code> clears the screen and <code class="language-plaintext highlighter-rouge">VTAB 21</code> moves the cursor to the 21st text line on the screen, which will be the first line under the graphics after <code class="language-plaintext highlighter-rouge">HGR</code>. The Apple ][+ doesn’t come with a constant for π so I set that up using all the precision available.</p>

<p><code class="language-plaintext highlighter-rouge">AM%</code> is a two element array of integers to store, respectively, the x-mean and y-mean values for the A data set. (The % sign makes the variable an integer which save space but actually reduced performance because mathematical operations on the Apple ][+ convert integers to real numbers and then back again.) <code class="language-plaintext highlighter-rouge">AS%</code> does the same for the x- and y-standard deviations. <code class="language-plaintext highlighter-rouge">AC</code> is a real number correlation coefficient ∈ [-1, 1]. Finally, <code class="language-plaintext highlighter-rouge">AN</code> is the number of elements in the A dataset. The corresponding B data set hyperparameters are similar. (In Applesoft BASIC only the <a href="https://youtu.be/PHfKCxjsmos?si=lVgpeslJ8ZBiRaAl&amp;t=39" target="_blank">first two characters</a> of a variable name are ‘considered’ so you have to be careful of collisions.)</p>

<h3 id="box-muller-to-the-rescue">Box-Muller to the rescue</h3>
<p>The Apple ][+ can generate uniform random variables from 0 to 0.999999999 using <code class="language-plaintext highlighter-rouge">RND()</code>, however, to get standard normal random variables we’ll have to use the <a href="https://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform" target="_blank">Box-Muller</a> transform.</p>

<p>Given two independent samples, \(u_1\) and \(u_2\), chosen from a uniform distribution on the unit interval (0, 1), we can get two independent random variables, \(z_0\) and \(z_1\), with a standard normal distribution with the following:</p>

\[z_0 = R \cos(\theta) = \sqrt{-2 \ln(u_1)} \cos(2 \pi u_2) \\
z_1 = R \sin(\theta) = \sqrt{-2 \ln(u_1)} \sin(2 \pi u_2)\]

<p>Here is the code to do that.</p>

<div class="language-bbcbasic highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="m">900</span> <span class="c">REM  == BOX-MULLER TRANSFORM ==</span>
<span class="m">910</span> <span class="nv">U1</span> <span class="o">=</span> <span class="nb">RND</span><span class="o">(</span><span class="m">1</span><span class="o">)</span>
<span class="m">920</span> <span class="nv">U2</span> <span class="o">=</span> <span class="nb">RND</span><span class="o">(</span><span class="m">1</span><span class="o">)</span>
<span class="m">930</span> <span class="nv">R</span> <span class="o">=</span> <span class="nb">SQR</span><span class="o">(-</span><span class="m">2</span> <span class="o">*</span> <span class="nb">LOG</span><span class="o">(</span><span class="nv">U1</span><span class="o">))</span>
<span class="m">940</span> <span class="nv">TH</span> <span class="o">=</span> <span class="m">2</span> <span class="o">*</span> <span class="nv">PI</span> <span class="o">*</span> <span class="nv">U2</span>
<span class="m">950</span> <span class="nv">Z0</span> <span class="o">=</span> <span class="nv">R</span> <span class="o">*</span> <span class="nb">COS</span><span class="o">(</span><span class="nv">TH</span><span class="o">)</span>
<span class="m">960</span> <span class="nv">Z1</span> <span class="o">=</span> <span class="nv">R</span> <span class="o">*</span> <span class="nb">SIN</span><span class="o">(</span><span class="nv">TH</span><span class="o">)</span>
<span class="m">970</span> <span class="k">RETURN</span>
</code></pre></div></div>
<p>From there, to obtain a 2D Gaussian with mean \(\mu_x, \mu_y\) and covariance matrix \(\Sigma\), we’ll need to apply the following, where \(\sigma_x\) and \(\sigma_y\) are the standard deviations in the \(x\) and \(y\) directions, and \(\rho\) is the correlation coefficient:</p>

\[x = \mu_x + \sigma_x z_0 \\
y = \mu_y + \rho \sigma_y z_0 + \sqrt{1 - \rho^2} \sigma_y z_1\]

<p>Here is the code that generates all the samples using these transformations.</p>

<div class="language-bbcbasic highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="m">200</span> <span class="k">DIM</span> <span class="nv">AX%</span><span class="o">(</span><span class="nv">AN</span><span class="p">,</span><span class="m">2</span><span class="o">)</span>
<span class="m">210</span> <span class="k">DIM</span> <span class="nv">BX%</span><span class="o">(</span><span class="nv">BN</span><span class="p">,</span><span class="m">2</span><span class="o">)</span>
<span class="m">220</span> <span class="k">FOR</span> <span class="nv">I</span> <span class="o">=</span> <span class="m">1</span> <span class="k">TO</span> <span class="nv">AN</span>
<span class="m">230</span>   <span class="k">GOSUB</span> <span class="m">900</span> <span class="p">:</span> <span class="c">REM FETCH STANDARD NORMAL RANDOM VALUES</span>
<span class="m">240</span>   <span class="nv">X%</span> <span class="o">=</span> <span class="nv">AM%</span><span class="o">(</span><span class="m">1</span><span class="o">)</span> <span class="o">+</span> <span class="nv">AS%</span><span class="o">(</span><span class="m">1</span><span class="o">)</span> <span class="o">*</span> <span class="nv">Z0</span>
<span class="m">250</span>   <span class="nv">Y%</span> <span class="o">=</span> <span class="nv">AM%</span><span class="o">(</span><span class="m">2</span><span class="o">)</span> <span class="o">+</span> <span class="nv">AC</span> <span class="o">*</span> <span class="nv">AS%</span><span class="o">(</span><span class="m">2</span><span class="o">)</span> <span class="o">*</span> <span class="nv">Z0</span> <span class="o">+</span> <span class="nb">SQR</span><span class="o">(</span><span class="m">1</span> <span class="o">-</span> <span class="nv">AC</span> <span class="o">^</span> <span class="m">2</span><span class="o">)</span> <span class="o">*</span> <span class="nv">AS%</span><span class="o">(</span><span class="m">2</span><span class="o">)</span> <span class="o">*</span> <span class="nv">Z1</span>
<span class="m">260</span>   <span class="nv">AX%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">=</span> <span class="nv">X%</span>
<span class="m">270</span>   <span class="nv">AX%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">2</span><span class="o">)</span> <span class="o">=</span> <span class="nv">Y%</span>
<span class="m">280</span>   <span class="k">PRINT</span> <span class="s">"POINT IN SET A "</span><span class="p">;</span> <span class="nv">I</span><span class="p">;</span> <span class="s">" AT ("</span><span class="p">;</span> <span class="nv">X%</span><span class="p">;</span> <span class="s">","</span><span class="p">;</span> <span class="nv">Y%</span><span class="p">;</span> <span class="s">")"</span>
<span class="m">290</span>   <span class="k">GOSUB</span> <span class="m">700</span> <span class="p">:</span> <span class="c">REM  DRAW A +</span>
<span class="m">300</span> <span class="k">NEXT</span> <span class="nv">I</span>
<span class="m">310</span> <span class="k">FOR</span> <span class="nv">I</span> <span class="o">=</span> <span class="m">1</span> <span class="k">TO</span> <span class="nv">BN</span>
<span class="m">320</span>   <span class="k">GOSUB</span> <span class="m">900</span> <span class="p">:</span> <span class="c">REM FETCH STANDARD NORMAL RANDOM VALUES</span>
<span class="m">330</span>   <span class="nv">X%</span> <span class="o">=</span> <span class="nv">BM%</span><span class="o">(</span><span class="m">1</span><span class="o">)</span> <span class="o">+</span> <span class="nv">BS%</span><span class="o">(</span><span class="m">1</span><span class="o">)</span> <span class="o">*</span> <span class="nv">Z0</span>
<span class="m">340</span>   <span class="nv">Y%</span> <span class="o">=</span> <span class="nv">BM%</span><span class="o">(</span><span class="m">2</span><span class="o">)</span> <span class="o">+</span> <span class="nv">BC</span> <span class="o">*</span> <span class="nv">BS%</span><span class="o">(</span><span class="m">2</span><span class="o">)</span> <span class="o">*</span> <span class="nv">Z0</span> <span class="o">+</span> <span class="nb">SQR</span><span class="o">(</span><span class="m">1</span> <span class="o">-</span> <span class="nv">BC</span> <span class="o">^</span> <span class="m">2</span><span class="o">)</span> <span class="o">*</span> <span class="nv">BS%</span><span class="o">(</span><span class="m">2</span><span class="o">)</span> <span class="o">*</span> <span class="nv">Z1</span>
<span class="m">350</span>   <span class="nv">BX%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">1</span><span class="o">)</span> <span class="o">=</span> <span class="nv">X%</span>
<span class="m">360</span>   <span class="nv">BX%</span><span class="o">(</span><span class="nv">I</span><span class="p">,</span><span class="m">2</span><span class="o">)</span> <span class="o">=</span> <span class="nv">Y%</span>
<span class="m">370</span>   <span class="k">PRINT</span> <span class="s">"POINT IN SET B "</span><span class="p">;</span> <span class="nv">I</span><span class="p">;</span> <span class="s">" AT ("</span><span class="p">;</span> <span class="nv">X%</span><span class="p">;</span> <span class="s">","</span><span class="p">;</span> <span class="nv">Y%</span><span class="p">;</span> <span class="s">")"</span>
<span class="m">380</span>   <span class="k">GOSUB</span> <span class="m">800</span> <span class="p">:</span> <span class="c">REM  DRAW A BOX</span>
<span class="m">390</span> <span class="k">NEXT</span> <span class="nv">I</span>
<span class="m">400</span> <span class="k">END</span>
</code></pre></div></div>
<p>I decided to store the data samples in <code class="language-plaintext highlighter-rouge">AX%</code> and <code class="language-plaintext highlighter-rouge">BX%</code> as integers to save on memory. Since they are all randomly generated, the extra precision won’t make much of a difference anyway. I’m using 2D arrays where the first axis is the number of samples (e.g., <code class="language-plaintext highlighter-rouge">AN</code>) and the second axis is the feature dimension (e.g., 2 for \(x\) and \(y\)).</p>

<p>You can see calling <code class="language-plaintext highlighter-rouge">GOSUB 700</code> and <code class="language-plaintext highlighter-rouge">GOSUB 800</code> to plot the points. Here is the final code for that.</p>

<div class="language-bbcbasic highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="m">700</span> <span class="c">REM == DRAW + AT X,Y ==</span>
<span class="m">710</span> <span class="k">IF</span> <span class="nv">X%</span> <span class="o">&lt;</span> <span class="m">1</span> <span class="o">OR</span> <span class="nv">X%</span> <span class="o">&gt;</span> <span class="m">270</span> <span class="k">THEN</span> <span class="k">RETURN</span>
<span class="m">720</span> <span class="k">IF</span> <span class="nv">Y%</span> <span class="o">&lt;</span> <span class="m">1</span> <span class="o">OR</span> <span class="nv">Y%</span> <span class="o">&gt;</span> <span class="m">150</span> <span class="k">THEN</span> <span class="k">RETURN</span>
<span class="m">730</span> <span class="nv">HPLOT</span> <span class="nv">X%</span> <span class="o">-</span> <span class="m">1</span><span class="p">,</span><span class="nv">Y%</span> <span class="k">TO</span> <span class="nv">X%</span> <span class="o">+</span> <span class="m">1</span><span class="p">,</span><span class="nv">Y%</span>
<span class="m">740</span> <span class="nv">HPLOT</span> <span class="nv">X%</span><span class="p">,</span><span class="nv">Y%</span> <span class="o">-</span> <span class="m">1</span> <span class="k">TO</span> <span class="nv">X%</span><span class="p">,</span><span class="nv">Y%</span> <span class="o">+</span> <span class="m">1</span>
<span class="m">750</span> <span class="k">RETURN</span>
<span class="m">800</span> <span class="c">REM == DRAW BOX AT X,Y ==</span>
<span class="m">810</span> <span class="k">IF</span> <span class="nv">X%</span> <span class="o">&lt;</span> <span class="m">1</span> <span class="o">OR</span> <span class="nv">X%</span> <span class="o">&gt;</span> <span class="m">270</span> <span class="k">THEN</span> <span class="k">RETURN</span>
<span class="m">820</span> <span class="k">IF</span> <span class="nv">Y%</span> <span class="o">&lt;</span> <span class="m">1</span> <span class="o">OR</span> <span class="nv">Y%</span> <span class="o">&gt;</span> <span class="m">150</span> <span class="k">THEN</span> <span class="k">RETURN</span>
<span class="m">830</span> <span class="nv">HPLOT</span> <span class="nv">X%</span> <span class="o">-</span> <span class="m">1</span><span class="p">,</span><span class="nv">Y%</span> <span class="o">-</span> <span class="m">1</span> <span class="k">TO</span> <span class="nv">X%</span> <span class="o">+</span> <span class="m">1</span><span class="p">,</span><span class="nv">Y%</span> <span class="o">-</span> <span class="m">1</span>
<span class="m">840</span> <span class="nv">HPLOT</span> <span class="nv">X%</span> <span class="o">-</span> <span class="m">1</span><span class="p">,</span><span class="nv">Y%</span><span class="p">:</span> <span class="nv">HPLOT</span> <span class="nv">X%</span> <span class="o">+</span> <span class="m">1</span><span class="p">,</span><span class="nv">Y%</span>
<span class="m">850</span> <span class="nv">HPLOT</span> <span class="nv">X%</span> <span class="o">-</span> <span class="m">1</span><span class="p">,</span><span class="nv">Y%</span> <span class="o">+</span> <span class="m">1</span> <span class="k">TO</span> <span class="nv">X%</span> <span class="o">+</span> <span class="m">1</span><span class="p">,</span><span class="nv">Y%</span> <span class="o">+</span> <span class="m">1</span>
<span class="m">860</span> <span class="k">RETURN</span>
</code></pre></div></div>
<p>I first verify that the data point is on the visible region of the graph (plotting off the screen will throw an error) and then draw either a “+” or a “□”.</p>

<h2 id="running-the-code">Running the code</h2>
<p>Putting it all together, it takes the Apple ][+ about a minute to generate and plot 150 data points. The end result looks like this:</p>

<p><img src="/assets/images/apple2/final-plot.jpg" alt="Final plot of synthesized data" title="Final plot of synthesized data" /></p>

<p>Watch this video if you’d like to see it run, in all it’s glory.</p>

<p><a href="https://youtube.com/shorts/xi876Gqt4jk?autoplay=1" title="Video of Apple][+ synthesizing data" target="_blank"><img src="https://img.youtube.com/vi/xi876Gqt4jk/0.jpg" alt="Video of Apple2+ synthesizing data" /></a></p>

<p>Now that this is out of the way, we’ll start by implementing one of the simplest and easiest to understand machine learning algorithms.</p>]]></content><author><name>Mark Cramer</name></author><category term="machine learning" /><category term="synthesizing data" /><summary type="html"><![CDATA[Turning to machine learning, I begin by synthesizing some data.]]></summary></entry><entry><title type="html">Hope springs eternal</title><link href="https://mdcramer.github.io/apple-2-blog/hope/" rel="alternate" type="text/html" title="Hope springs eternal" /><published>2025-07-03T00:00:00-07:00</published><updated>2025-07-03T00:00:00-07:00</updated><id>https://mdcramer.github.io/apple-2-blog/hope</id><content type="html" xml:base="https://mdcramer.github.io/apple-2-blog/hope/"><![CDATA[<p>My <del>stupid</del> marvelous <a href="https://mortalwayfare.com/remnant-from-the-past/">game</a> from high school is almost assuredly lost to the sands of time. While the attempt to find the bits, let alone excavate them, buried in my old stash of 5¼” floppies was <a href="https://mdcramer.github.io/apple-2-blog/recover/">valiant</a>, we found naught. I don’t know what I did with the floppy but I most likely, for reasons I’ll never remember, took it to college (I didn’t take the Apple ][+ but I did have an <a href="https://en.wikipedia.org/wiki/Apple_IIc">Apple IIc</a>) where who knows what might have happened to it? It was probably used as a beer coaster and then eventually sent to the bitbucket at the bottom of the circular file.</p>

<p>Resigned, I looked at my box of archaic floppies and contemplated throwing them into the trash… when a thought occurred to me: what about the backsides?</p>

<p>Virtually all of my floppies are single sided, but as a broke high school student I didn’t care. Using a hole punch to open a write-protect notch on the other side of the floppy, I almost always flipped them over and wrote on the backside. Is it possible that, for some reason, I backed up the game on the “side B” of one of these floppies? Sure, I guess anything is possible.</p>

<p>So, I kept them. Because I am <del>stubborn</del> an optimist!</p>

<h2 id="another-helping-hand">Another helping hand</h2>

<p>A few months later, a good friend, <a href="https://www.linkedin.com/in/surdules/">Răzvan Surdulescu</a> (Principal Software Engineer, Robotics at Google DeepMind), came over to dinner with his family. Being a vintage computer enthusiast himself, who enjoys watching <a href="https://www.youtube.com/@adriansdigitalbasement">videos</a> about restoring computers (who doesn’t?), he was thrilled to see the Apple ][+ sitting on my office desk. When I gave him the whole blah blah about the high school computer game, while my wife rolled her eyes as she left the room, he suggested we try recovering it with <a href="https://github.com/keirf/greaseweazle/wiki/Yann-Serra-Tutorial">Greaseweazle</a>.</p>

<p>Greaseweazle is an open-source USB device and software that allows you to read and write data from various types of floppy disks, including those used by old Apple ][+s. It captures the raw flux transitions from the floppy, enabling it to handle a wide range of formats beyond what is typically supported by standard floppy disk controllers. To get it to work, however, we’d need some new hardware.</p>

<h2 id="new-hardware">New hardware</h2>

<p>While Răzvan had the latest v4.1 Greaseweazle <a href="https://github.com/keirf/greaseweazle/wiki/Greaseweazle-Models">hardware</a> at home, we’d need a standard <a href="https://en.wikipedia.org/wiki/Shugart_Associates">Shugart</a> floppy drive (not an Apple ][+ floppy drive) to read floppies. This is because Greaseweazle uses the Shugart interface to access the raw flux transitions on the disk, rather than relying on the drive’s built-in encoding and decoding. This allows it to read and write various disk formats, including those with custom encoding or copy protection.</p>

<p>No problem. A few eBay searches later, I purchased this Toshiba 5¼” 1.2Mb Internal Floppy Disk Drive ND-08DE for $76. There were different DOS formats for the Apple ][+, but this capacity is significantly greater than the whopping 140Kb with Apple DOS 3.3.</p>

<p><img src="/assets/images/apple2/toshiba-floppy-drive.jpg" alt="The Toshiba 5.25&quot; floppy drive" title="Toshiba 5.25&quot; floppy drive" /></p>

<p>The new drive would also require some new cables to hook it up. They were inexpensive. Răzvan already had a few of the necessary cables and very helpfully made sure that I bought all the right stuff.</p>

<p><img src="/assets/images/apple2/molex-4pin-ftof.jpeg" alt="Molex 4 pin female to female connector" title="Molex 4 pin female to female connector" /></p>

<p><img src="/assets/images/apple2/floppy-ribbon.jpeg" alt="36&quot; Universal 34-Pin Floppy Drive Ribbon Cable for 3.5&quot; and/or 5.25&quot; Drives" title="36&quot; Universal 34-Pin Floppy Drive Ribbon Cable" /></p>

<p>The opportunity to give it a go arrived when we were invited to a dinner party at the Surdulescu’s. We plugged everything into Răzvan’s beefy homemade Windows desktop, grabbed a couple flutes of Champagne and got to work.</p>

<p><img src="/assets/images/apple2/rig-with-greaseweazle.jpg" alt="The full rig with Greaseweazle" title="Full rig with Greaseweazle" /></p>

<h2 id="problems-with-the-backside">Problems with the backside</h2>

<p>The first thing I noticed was how quickly Greaseweazle was able to read data off the floppies. Unlike the <a href="https://applesaucefdc.com/">Applesauce</a> USB disk controller that needed to grind back and forth to find the data using an original Apple ][+ floppy drive, Greaseweazle was able to find the floppy catalog in a second. (We used <a href="https://cowlark.com/fluxengine/index.html">FluxEngine</a>, seen below, which is different from the command line utility that comes with the GreaseWeazle, to test the directory of files on the disk. If that didn’t work, it had an option to read the flux off the disk which involved grinding back and forth to try to locate data. We had to do that quite a few times.)</p>

<p><img src="/assets/images/apple2/greaseweazle-monitor.jpg" alt="FlexEngine on the monitor" title="FlexEngine" /></p>

<p>While we were able to find quite a bit of data, including a few titles from <a href="https://en.wikipedia.org/wiki/Beagle_Bros">Beagle Bros</a>, it seems the Apple ][+ may have used different kinds of data encodings to lay the sectors on the disk. Even if Greaseweazle can read the magnetic flux, the actual semantics of the flux (what sector goes where and in what order) can be opaque and the FluxEngine software might not interpret them in the right way. This might require more investigation.</p>

<p>After checking a few disks to verify that we were getting the same results as the last time I gave this a go, we decided to flip one over and try the backside. Immediately, we noticed that it wasn’t going to work. To calibrate the rotational timing, Greaseweazle uses the photoelectric sensor on the Shugart floppy drive which receives a ‘pulse’ every time the <a href="https://www.atarimagazines.com/compute/issue34/019_1_MASS_MEMORY_NOW_AND_IN_THE_FUTURE.php">index hole</a>, near the center of the floppy, passes over an LED. When you flip the floppy over, however, the index hole cutout is now no longer over the LED and so the pulse can’t be read.</p>

<p>One solution was to cut another hole in the floppy case. Given the proximity to the center of the floppy, however, this risked damaging the media. We explored some software solutions but then eventually decided it would be easiest to rip open the floppy and then manually flip the media. I had never opened a floppy before, but it was a relatively easy process. The old plastic was brittle and frequently crumbled in my fingers, but we were able to make it work.</p>

<p><img src="/assets/images/apple2/open-floppy-disk.jpg" alt="Opening a floppy disk" /></p>

<p><img src="/assets/images/apple2/floppy-media.jpg" alt="Looking at the floppy media" /></p>

<h2 id="disappointment-redux">Disappointment redux</h2>

<p>Unfortunately, the result was no different from my previous attempt: there was no game to be found. The process wasn’t relatively quick. We were able to rip open and flip the media on about half of the floppies. There are ~25 to go so perhaps we’ll get to those one day. And then I’ll just discard everything. In the meantime, hopefully I’ll have some time to get back to deep learning…</p>

<p><img src="/assets/images/apple2/deep-learning.jpg" alt="Deep learning on the Apple 2+" /></p>]]></content><author><name>Mark Cramer</name></author><category term="restoration" /><category term="Greaseweazle" /><category term="FluxEngine" /><summary type="html"><![CDATA[One more attempt, this time with Greaseweazle, to find the game.]]></summary></entry><entry><title type="html">Recovering the old game</title><link href="https://mdcramer.github.io/apple-2-blog/recover/" rel="alternate" type="text/html" title="Recovering the old game" /><published>2023-06-11T00:00:00-07:00</published><updated>2023-06-11T00:00:00-07:00</updated><id>https://mdcramer.github.io/apple-2-blog/recover</id><content type="html" xml:base="https://mdcramer.github.io/apple-2-blog/recover/"><![CDATA[<p>As mentioned in <a href="/apple-2-blog/revive/">Bringing the clunker back to life</a>, the original motivation was to resurrect a <a href="https://mortalwayfare.com/in-the-beginning-there-was-basic/">game</a> that I wrote in high school. I was obsessed with <a href="https://en.wikipedia.org/wiki/Ultima_(series)">Ultima</a>, beginning with <a href="https://en.wikipedia.org/wiki/Ultima_I:_The_First_Age_of_Darkness">The First Age of Darkness</a>, and so spent countless hours developing my own version.</p>

<p>The game logic was written in BASIC but this was much too slow for the graphics. Therefore, I convinced my parents to drive me to a remote bookstore to pick up a book on Apple ][ assembly (there were not a lot of resources in those early days) and then taught myself how to develop assembly subroutines that could be called from BASIC. I had a 2D array which I used as a map and then moved <a href="https://mortalwayfare.com/remnant-from-the-past/">hand-crafted images</a> directly to the graphics memory as the character moved around. This already difficult process was more complicated by the fact the computer’s graphics memory was interleaved and the Apple CPU didn’t have a multiply command. Fortunately, the book offered a routine that would accomplish this with bit shifting and addition.</p>

<blockquote>
  <p>Side note: After all these years, only today did I <a href="https://en.wikipedia.org/wiki/Ultima_I:_The_First_Age_of_Darkness#Development_and_release">learn</a> that <a href="https://en.wikipedia.org/wiki/Richard_Garriott">Richard Garriott</a> (aka Lord British) coded Ultima in BASIC while his friend Ken Arnold “wrote code in <a href="https://en.wikipedia.org/wiki/Assembly_language" title="Assembly language">assembly language</a> for the tile-based graphics system, the first game in the genre to do so.”</p>
</blockquote>

<p>To this day I’m pretty proud of this little piece of computer wizardry and would love to see it again.</p>

<p>Alas, this is not to be.</p>

<h2 id="box-of-floppies">Box of floppies</h2>

<p>Buried in my parent’s garage for decades, alongside the old Apple ][+, was a box of 5.25” floppy disks. Was my game still on one of them?</p>

<p><img src="/assets/images/apple2/box-of-floppies.jpg" alt="A very old box of 5.25&quot; floppy disks" /></p>

<p>For some unknown reason, my high school brain decided to just number the floppies, as opposed to just writing the contents on the stickers. I used to have a sheet that listed the contents of each disk, but that is long gone. Additionally, judging by the enumeration, it looks like some of the floppies are missing. I vaguely recall taking some to college. Was the game on one of them? Perhaps.</p>

<h2 id="a-glimmer-of-hope-on-twitter">A glimmer of hope on Twitter</h2>

<p>I don’t recall how I came across <a href="https://twitter.com/bzotto">@bzotto</a>’s Twitter thread about how he <a href="https://twitter.com/bzotto/status/1353797383201558531">resurrected his high school game</a> by reconstructing the data from an old 5.25” floppy, but it renewed my hope. He purchased some <a href="https://twitter.com/bzotto/status/1353800316693606402">specialized hardware</a> that repeatedly moved the drive head back and forth by fractions of a track to try to pull off enough magnetic signal to reconstruct the data.</p>

<p>Turns out <a href="https://twitter.com/bzotto">@bzotto</a> is a great guy and extremely generous with his time, so when I reached out with my story he offered to help.</p>

<h2 id="coffee-shop-excavation">Coffee shop excavation</h2>

<p>In February 2022 we met at a coffee shop near his home. I brought the box of floppies and he brought the hardware and his Mac laptop. We spent three hours chatting as we tried one floppy after another.</p>

<p>The floppies almost always started with a bunch of red squares. His software would make an initial pass to see what could be read, and typically that was not very much. The floppy had been sitting in a damp, cold garage for decades and most of the magnetic material had degenerated or fallen off. It did not help matters that I purchased inexpensive, single density floppies in high school, and then recorded on both the front and back. It worked fine back then, but I was clearly not thinking long term.</p>

<p><img src="/assets/images/apple2/a-lot-of-red-squares.jpg" alt="A lot of red squares" /></p>

<p>As we patiently allowed the hardware and software to do its magic, something incredible happened. The red squares became green! Not all of them, but enough for us to identify what, if anything, was on the floppy. We were primarily interested in the catalog which, if memory serves correctly, was on track 14. I had a few floppies that I used for personal development, so the catalog would enable us to find these.</p>

<p><img src="/assets/images/apple2/some-green-squares.jpg" alt="Some signs of life!" /></p>

<h2 id="disappointment">Disappointment</h2>

<p>Unfortunately, the old game floppy was not to be found. We were able to identify the contents of every floppy we tried, but the game wasn’t there. As they closed up the coffee shop there were still a handful of floppies remaining. <a href="https://twitter.com/bzotto">@bzotto</a> graciously offered to check them out at home, but no luck. In high school I had many printouts of my code, but we never saved them. It’s also disappointing that I never made a copy, but this is not how young people think. I’ll have to be satisfied with just the memory of what I put together.</p>

<p>What was the name of this game? In case anyone’s curious, I never got that far. If I remember correctly, the name of the main file was just “game”.</p>

<h2 id="what-next">What next?</h2>

<p>Nevertheless, I now have a <a href="/apple-2-blog/revive/">functional</a> Apple ][+ computer sitting on my desk. I don’t have much time to devote to tinkering with an ancient piece of hardware, but it would be cool to see if I could run some modern machine learning algorithms on it. I’ll be giving that some thought and will see what I can do…</p>]]></content><author><name>Mark Cramer</name></author><category term="restoration" /><category term="Applesauce" /><summary type="html"><![CDATA[The quest to recover my game from high school ended in vain, but the process was delightful.]]></summary></entry><entry><title type="html">Bringing the clunker back to life</title><link href="https://mdcramer.github.io/apple-2-blog/revive/" rel="alternate" type="text/html" title="Bringing the clunker back to life" /><published>2022-06-06T00:00:00-07:00</published><updated>2022-07-03T00:00:00-07:00</updated><id>https://mdcramer.github.io/apple-2-blog/revive</id><content type="html" xml:base="https://mdcramer.github.io/apple-2-blog/revive/"><![CDATA[<p>All journeys start with a little inspiration. This one begins with a <a href="https://twitter.com/bzotto/status/1353797383201558531">series of Tweets</a> from <a href="https://twitter.com/bzotto">@bzotto</a>. He tells a detailed story of how he restored, using custom hardware and software, and considerable gumption, a corrupted 5.25” floppy from elementary school. It contained a game that he wrote which he was able to salvage. I described <a href="https://mortalwayfare.com/in-the-beginning-there-was-basic/">a game I wrote</a> in high school, along with <a href="https://mortalwayfare.com/remnant-from-the-past/">giving up ever seeing it again</a>. <a href="https://twitter.com/bzotto">@bzotto</a>, motivated me to embark on a quest to see if I could get my game back.</p>

<h2 id="poof">Poof</h2>
<p>I chronicled my <a href="https://twitter.com/markdcramer/status/1460289742726217733">odyssey</a> on Twitter, but I’ll share the highlights here.</p>

<p>After recovering the machine from my parent’s house, where it spent decades in the closet and then the garage, I brought it back to San Francisco. I purchased a brand new set of <a href="https://www.ebluejay.com/ads/item/4249176">Apple DOS 3.3 floppies</a>, because I was skeptical that the old ones would still work, and was ready to see if it would boot. It turned on and did… something… but it did not look like it was working.</p>

<p><img src="/assets/images/apple2/first-boot-with-fire-extinguisher.jpg" alt="The first boot in many, many years..." /></p>

<p>After playing around with it a bit (swapping the floppy drives), I was able to get the BASIC prompt. I entered and ran a 1-line “HELLO WORLD”, which worked. Then I smelled something awful and saw smoke coming out of the PSU.</p>

<p><img src="/assets/images/apple2/psu-goes-up-in-smoke.jpg" alt="PSU fries and gives off smoke" /></p>

<p>Not having been powered on for decades, turns out a capacitor in the PSU dried out and fried.</p>

<h2 id="new-capacitors">New capacitors</h2>
<p>This is, apparently, a common occurrence with older power supplies. <a href="https://twitter.com/bzotto">@bzotto</a>, which whom I was chatting on Twitter, quickly identified the likely culprit (the 0.1 µf RIFA capacitor) and suggested a <a href="https://console5.com/store/apple-2-power-supply-cap-kit-p-n-605-5703-astec-aa11040b-aa11040-b.html">replacement kit</a>. Amazingly, there is an online store that sells a bag a replacement capacitors for the Apple ][+ PSU. According to folks on <a href="https://www.applefritter.com/comment/95703#comment-95703">Applefritter</a> (a forum for vintage Apple computer hobbyists), this sort of thing happens a lot.</p>

<p><img src="/assets/images/apple2/fried-rifa.jpg" alt="Fried RIFA capacitor" /></p>

<p>With a little advice from the friendly people at Console5.com, who sold me the replacement capacitors, and a ton of help from my best friend, who lent me the work desk in his garage, we rebuilt the PSU. My initial thought was to only replace the RIFA, but I figured, while we’re in there we might as well just change them all.</p>

<p><img src="/assets/images/apple2/old-vs-new-psu.png" alt="Rebuilt PSU" /></p>

<p>It was painstaking work that took many hours, and a steady hand, but in the end it was ready to go: the PSU, that is. The rest of the machine was still having a problem. When I booted the machine I got all kinds of strange things on the screen. Most frequently, however, were random green squares.</p>

<p><img src="/assets/images/apple2/green-squares.jpg" alt="Random green squares on the screen" /></p>

<h2 id="new-ram-chips">New RAM chips</h2>
<p>The new suspect was the RAM. Another helpful person on Applefritter gave me some <a href="https://www.applefritter.com/comment/95954#comment-95954">code to test the RAM</a>, but after carefully typing it in I wasn’t able to get it to work. Someone else suggested that I buy some contact cleaner and reseat all of the RAM chips, so I gave that a go. I bought the cleaner along with some chip removers and a multimeter. The good news is that every chip on the motherboard is in a socket, as opposed to being soldered to the PCB.</p>

<p><img src="/assets/images/apple2/cleaner-multimeter.jpg" alt="Contact cleaner, chip removers and multimeter" /></p>

<p>The process was slow and painstaking, but I eventually went through almost every chip on the motherboard. (There were a few under the keyboard that I wasn’t able to reach without removing the motherboard.) The result, however, was disappointing; green boxes were still there. I read on forum that all the rows of chips were not required for the machine to operate, so I took some advice and tried with just a single row of RAM. The machine looked go, so after adding back the floppy drive I was able to get it to boot!</p>

<p><img src="/assets/images/apple2/boot-with-one-row-ram.jpg" alt="Booting DOS with a single row of RAM" /></p>

<p>This exercise clearly indicated that the problem was somewhere with the RAM. Taking a closer look at the pins, many of them were obviously corroded. So, I proceed to remove them, again, and gently file away the corrosion with an emery board. This worked well…</p>

<p><img src="/assets/images/apple2/cleaning-pins.jpg" alt="Cleaning RAM pins" /></p>

<p>… right up until it didn’t.</p>

<p><img src="/assets/images/apple2/broken-pin.jpg" alt="Broken RAM pin" /></p>

<p>I tried to repair the broken pins with a soldering iron, but the work was too difficult. Eventually I decided that the hassle here was too great. Following <a href="https://www.applefritter.com/content/replacement-ram-chips-tms4116-20nl">more advice</a> from the forum, I went online and purchased a full set of 24 replacement <a href="https://www.questcomp.com/part/4/tms4116-20nl/408290989">TMS4116-20NL</a> RAM chips for $100, including shipping. They arrived in no time, with shiny pins, and worked like a charm. The machine booted immediately with no problems and I was able to run software off the new floppies I had purchased.</p>

<p><img src="/assets/images/apple2/boot-with-new-ram.jpg" alt="Booting with new RAM" /></p>

<p>We have liftoff!</p>

<p>In the meantime, with the help of my <a href="(https://twitter.com/bzotto)">new Twitter friend</a>, I tried to recover the old game from high school. That did not go as well.</p>]]></content><author><name>Mark Cramer</name></author><category term="restoration" /><category term="RAM" /><category term="memory" /><summary type="html"><![CDATA[The old Apple ][+ had not been turned on in decades. It needed a lot of help to work again.]]></summary></entry><entry><title type="html">Motivation</title><link href="https://mdcramer.github.io/apple-2-blog/motivation/" rel="alternate" type="text/html" title="Motivation" /><published>2022-05-27T00:00:00-07:00</published><updated>2022-06-12T00:00:00-07:00</updated><id>https://mdcramer.github.io/apple-2-blog/motivation</id><content type="html" xml:base="https://mdcramer.github.io/apple-2-blog/motivation/"><![CDATA[<h2 id="why-develop-machine-learning-on-an-apple-">Why develop machine learning on an Apple ][+?</h2>
<p>Everyone is aware of the impressive achievements of modern artificial intelligence and, more specifically, machine learning. <a href="https://achievements.ai/">Achievements.ai</a> chronicles a growing list of AI milestones, but today virtually every industry has benefited from AI in one way or another. From medicine to entertainment to information retrieval to scientific discovery to agriculture, machine learning algorithms, powered by massive amount of data and computational power, have delivered unprecedented gains in efficiency and user value.</p>

<p><a href="https://en.wikipedia.org/wiki/2020%E2%80%93present_global_chip_shortage">Chip shortages</a>, however, exacerbated by the pandemic, trade, weather, fires and wars, have severely crimped our ability to deploy the compute necessary to train increasingly complex models. In times of scarcity we need to make the most of whatever resources are at our disposal. This requires creativity and gumption. Wired even recently reported on how some companies are <a href="https://www.wired.com/story/chip-shortage-hacks">hacking their way around the shortages</a>:</p>

<blockquote>
  <p>Carmakers are using semiconductors taken from washing machines, rewriting code to use less silicon, and even shipping their products without some chips while promising to add them in later. With the shortage of semiconductors now a new normal, everyone is being forced to adapt.</p>
</blockquote>

<blockquote>
  <p>… one large industrial conglomerate had resorted to buying washing machines just to scavenge the chips inside them for its products.</p>
</blockquote>

<p>As such, I am going to see what sort of machine learning I can do on my Apple ][+ computer from high school.</p>

<p><img src="/assets/images/apple2/apple2-in-suitcase.jpg" alt="Apple ][+ computer in the suitcase" /></p>

<p>The <a href="https://en.wikipedia.org/wiki/Apple_II_series#Apple_II_Plus">specifications of the Apple ][+</a> are somewhat daunting:</p>
<ul>
  <li>Processor: 6502 microprocessor running at 1.023 MHz</li>
  <li>RAM: 48Kb</li>
  <li>Storage: dual external 5 1⁄4-inch floppy drives, each capable of storing 140Kb per side with DOS 3.3</li>
  <li>Programming languages: Applesoft BASIC and 6502 assembly</li>
</ul>

<p>Nevertheless, these machines, which launched the personal computing revolution, can add and multiply arrays of numbers. As such, they can do machine learning. It is our duty, for the betterment of humanity, to make our best effort to deploy these machines in the service of artificial intelligence.</p>

<p>Let’s see what I can do…</p>]]></content><author><name>Mark Cramer</name></author><category term="machine learning" /><category term="motivation" /><summary type="html"><![CDATA[Why develop machine learning on an Apple ][+?]]></summary></entry></feed>