<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: JoeStrout</title>
    <description>The latest articles on DEV Community by JoeStrout (@joestrout).</description>
    <link>https://dev.to/joestrout</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F955389%2Ffdd96374-bc61-43f2-bf73-c2d6d6968a72.png</url>
      <title>DEV Community: JoeStrout</title>
      <link>https://dev.to/joestrout</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/joestrout"/>
    <language>en</language>
    <item>
      <title>Where in the World Am I?</title>
      <dc:creator>JoeStrout</dc:creator>
      <pubDate>Wed, 20 May 2026 23:09:33 +0000</pubDate>
      <link>https://dev.to/joestrout/where-in-the-world-am-i-53bo</link>
      <guid>https://dev.to/joestrout/where-in-the-world-am-i-53bo</guid>
      <description>&lt;p&gt;There are so many fun things you can do with web services, and most of them are trivial to use in &lt;a href="https://miniscript.org/MiniMicro" rel="noopener noreferrer"&gt;Mini Micro&lt;/a&gt;.  Today, let's use a couple of these together to plot your location on a map of the world!&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding Your Place In the World
&lt;/h2&gt;

&lt;p&gt;...begins by finding your &lt;em&gt;external (or public) IP address&lt;/em&gt;.  This is the Internet Protocol address by which you are seen on the internet, outside your local network.  It's probably different from your &lt;em&gt;internal&lt;/em&gt; (or private) IP address, which is what probably appears when you look at the network settings on your computer.  We don't care how your computer is known to other computers in your house or neighborhood; we only care how packets are routed to it from the great wide world beyond.  Fire up Mini Micro (available free &lt;a href="https://miniscript.org/MiniMicro/#download" rel="noopener noreferrer"&gt;here&lt;/a&gt;, and type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http.get("https://api.ipify.org")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should return an IP address, something like &lt;code&gt;66.212.203.27&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, to get information about that IP address, including the city it's in and an approximate latitude/longitude, we'll use a different web service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http.get("http://ip-api.com/json/66.212.203.27")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can replace the IP address above with whatever you got from the first query.  The result will be a string of JSON code that contains your city, region, country, latitude, and longitude, along with the name of your internet service provider (ISP) and other details.&lt;/p&gt;

&lt;p&gt;Putting this all together into a little program.  Use &lt;code&gt;edit&lt;/code&gt; to enter the code editor, then paste in the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import "json"
import "stringUtil"
clear

// What's my external IP address?
myIP = http.get("https://api.ipify.org")

// And where am I?
locJson = http.get("http://ip-api.com/json/" + myIP)
locInfo = json.parse(locJson)

pprint locInfo
print "You are in {city}, {region} ({country})".fill(locInfo)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this, and it should tell you where you are (along with all those other details).  Neat, right?&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't Tell Me, Show Me!
&lt;/h2&gt;

&lt;p&gt;But we can make it more fun.  Let's pinpoint you on a map of the Earth!  &lt;code&gt;edit&lt;/code&gt; your program again, delete the last two lines (&lt;code&gt;pprint&lt;/code&gt; and &lt;code&gt;print&lt;/code&gt;), and add in this additional code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mapImage = file.loadImage("/sys/pics/earth_day.jpg")

// Find the pixel coordinates of our lat/long in this image
px = (locInfo.lon + 180) / 360 * mapImage.width
py = mapImage.height - (90 - locInfo.lat) / 180 * mapImage.height

// Draw a marker (using an offscreen PixelDisplay)
g = new PixelDisplay
g.clear color.black, mapImage.width, mapImage.height
g.drawImage mapImage
g.line 0, py, g.width, py, "#00FFFF88", 3
g.line px, 0, px, g.height, "#00FFFF88", 3
g.fillEllipse px-10, py-10, 20, 20, "#00FFFFFF"
g.drawEllipse px-15, py-15, 30, 30, "#00FFFFAA"
g.drawEllipse px-20, py-20, 40, 40, "#00FFFF88"
markedMap = g.getImage(0, 0, g.width, g.height)

// And draw the mapImage, fit to the screen!
scale = 960/mapImage.width
gfx.drawImage markedMap,
  480 - mapImage.width*scale/2, 320 - mapImage.height*scale/2,
  mapImage.width*scale, mapImage.height*scale

s = "{city}, {region}, ({countryCode})".fill(locInfo)
text.row = 2
text.column = 34 - s.len/2
print s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result, if you happen to be my neighbor right now, should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcfvyunv5oihji1vifsop.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcfvyunv5oihji1vifsop.png" alt="World map with Tucson pinpointed" width="800" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The trick here is knowing that the map image uses a standard &lt;strong&gt;equiprectangular projection&lt;/strong&gt;, where longitude and latitude both map linearly to x and y.  That lets us convert from lat/long to pixel coordinates with some reasonably simple math.  Most of the code above is then drawing the pinpoint lines and circles on top of the map.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Note: if you are reading this in the future and live somewhere other than Earth, this code will need adjustment.)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Now in Animated 3D!
&lt;/h2&gt;

&lt;p&gt;Let's take it one step further: combine what you've done so far with the &lt;code&gt;globe&lt;/code&gt; demo!  Follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Save your program, if you haven't already.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;edit "/sys/demo/globe"&lt;/code&gt; to load the globe demo code.&lt;/li&gt;
&lt;li&gt;Select All (&lt;code&gt;^A&lt;/code&gt;) and then Copy (&lt;code&gt;^C&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Exit the editor, then edit your program.&lt;/li&gt;
&lt;li&gt;Scroll to the bottom, and Paste (&lt;code&gt;^V&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Find the line (at the top of the pasted code) that says &lt;code&gt;bigMapImg = file.loadImage("/sys/pics/earth_day.jpg")&lt;/code&gt;, and change it to just say &lt;code&gt;bigMapImg = markedMap&lt;/code&gt; instead.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it.  Now run your program, and you should see your location on a beautiful rotating globe.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7rai0o20jhny387el2n1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7rai0o20jhny387el2n1.gif" alt="Globe animation with Tucson pinpointed" width="434" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Never Be Lost Again
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;http&lt;/code&gt; module is an often-overlooked gem in the Mini Micro APIs.  With one little call we can get our external IP address, and with a second little call, get all sorts of information about that, including the info we need to place ourselves on a map.  We also looked at how you are able — nay, &lt;em&gt;encouraged!&lt;/em&gt; — to steal from the demo code and make it your own.&lt;/p&gt;

&lt;p&gt;What do you think of this?  Let me know in the comments below!&lt;/p&gt;

</description>
      <category>programming</category>
      <category>miniscript</category>
      <category>minimicro</category>
      <category>geolocation</category>
    </item>
    <item>
      <title>MiniScript Weekly News — May 14, 2026</title>
      <dc:creator>JoeStrout</dc:creator>
      <pubDate>Thu, 14 May 2026 20:24:30 +0000</pubDate>
      <link>https://dev.to/joestrout/miniscript-weekly-news-may-14-2026-454h</link>
      <guid>https://dev.to/joestrout/miniscript-weekly-news-may-14-2026-454h</guid>
      <description>&lt;h2&gt;
  
  
  Development Updates
&lt;/h2&gt;

&lt;p&gt;MiniScript 2.0 saw a bunch of steady progress this week, especially around garbage collection and string handling. Joe landed a new &lt;code&gt;gc&lt;/code&gt; intrinsic with &lt;code&gt;gc.collect&lt;/code&gt; and &lt;code&gt;gc.stats&lt;/code&gt; methods, plus fixes for runtime error reporting and several VM/refactoring issues in the &lt;code&gt;miniscript2&lt;/code&gt; branch. The dev log and recent commits are worth a look if you enjoy following the architecture work: &lt;a href="https://github.com/JoeStrout/miniscript2/blob/main/notes/DEV_LOG.md" rel="noopener noreferrer"&gt;DEV_LOG&lt;/a&gt;, &lt;a href="https://github.com/JoeStrout/miniscript2/commits/main/" rel="noopener noreferrer"&gt;recent commits&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A notable performance decision also got locked in: separate &lt;code&gt;int&lt;/code&gt; storage was removed, and all numbers are now &lt;code&gt;double&lt;/code&gt;. Joe reported no meaningful benchmark difference, so the simpler design wins here — a great example of keeping the internals elegant without sacrificing speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Community Projects
&lt;/h2&gt;

&lt;p&gt;If you’ve been meaning to get started with Mini Micro, Joe’s been actively pointing newcomers toward the quickstart and the built-in demos. The best launch point is still the getting-started guide: &lt;a href="https://miniscript.org/wiki/How_to_get_started_with_Mini_Micro" rel="noopener noreferrer"&gt;How to get started with Mini Micro&lt;/a&gt;, and Joe specifically reminded folks to try running things like &lt;code&gt;run "asteroids"&lt;/code&gt; in &lt;code&gt;/sys/demo&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There was also a fun new wave of interest around MiniScript on Godot. Community members shared the extension repo &lt;a href="https://github.com/Mo-Tx/Godot-X-MiniScript" rel="noopener noreferrer"&gt;Godot-X-MiniScript&lt;/a&gt; and a broader GitHub search for related projects: &lt;a href="https://github.com/search?q=miniscript+godot&amp;amp;type=repositories" rel="noopener noreferrer"&gt;MiniScript + Godot repos&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Discussion Highlights
&lt;/h2&gt;

&lt;p&gt;Several great technical conversations happened this week around embedding and runtime design. People asked about using MiniScript as an embedded language in games, and Joe pointed to the Unity package while noting that the Unity plugin is a bit behind and may need the latest source folder from GitHub. He also shared the language quick reference: &lt;a href="https://miniscript.org/files/MiniScript-QuickRef.pdf" rel="noopener noreferrer"&gt;MiniScript QuickRef PDF&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On the Mini Micro side, there was a lively discussion about performance benchmarks, palette handling, and low-level 6502-inspired hardware ideas. Joe also highlighted a useful public API list providing online services usable from Mini Micro apps: &lt;a href="https://github.com/public-apis/public-apis" rel="noopener noreferrer"&gt;public-apis/public-apis&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  From the Blog
&lt;/h2&gt;

&lt;p&gt;Joe published a new Mini Micro tutorial, &lt;a href="https://dev.to/joestrout/hwydt-turning-the-page-24dm"&gt;HWYDT: Turning the Page&lt;/a&gt;, showing how to build a convincing page-flip animation using nothing but math, sprites, and texture mapping. It’s a fun, practical example of what Mini Micro can do — and a nice companion to &lt;a href="https://dev.to/joestrout/hwydt-swinging-from-vines-31ei"&gt;HWYDT: Swinging from Vines&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks for reading, and happy scripting!&lt;/p&gt;

&lt;h2&gt;
  
  
  Upcoming Game Jams
&lt;/h2&gt;

&lt;p&gt;These upcoming jams look like a great fit for Mini Micro:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/make-literally-anything-jam-2026" rel="noopener noreferrer"&gt;Make Literally Anything Jam '26&lt;/a&gt;&lt;/strong&gt; (starts 2026-05-15 04:00:00) — A wildly open-ended jam with a loose theme and total freedom to make almost anything you want, perfect for experimenting, getting weird, or shipping something small and fun in just a week.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/micro-jam-058" rel="noopener noreferrer"&gt;Micro Jam 058: Tides ($400+ Prizes)&lt;/a&gt;&lt;/strong&gt; (starts 2026-05-16 02:00:00) — A highly approachable theme with lots of room for clever interpretation, making it ideal for a compact 2D game built around atmosphere, movement, or shifting conditions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/utc-arcadia-jam-4" rel="noopener noreferrer"&gt;ARCADIA JAM #4&lt;/a&gt;&lt;/strong&gt; (starts 2026-05-15 17:00:00) — A short, open 48-hour jam with permissive rules and no engine restrictions, making it a great chance to build a polished web-friendly game and compete on art, gameplay, and originality.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/the-shorter-the-better" rel="noopener noreferrer"&gt;The Shorter, The Better&lt;/a&gt;&lt;/strong&gt; (starts 2026-05-15 17:30:00) — A very friendly fit for a compact, story-driven game: the 5-minute limit encourages tight pacing, a single ending keeps scope manageable, and web playability makes it easy to share and test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/metroidvania-month-super-32" rel="noopener noreferrer"&gt;MVM 32 - Super edition&lt;/a&gt;&lt;/strong&gt; (starts 2026-05-16 02:00:00) — A long-running metroidvania-focused jam with a relaxed three-month window, open engine rules, and room to iterate on exploration, abilities, and level design.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/campfire-creators-jam-2" rel="noopener noreferrer"&gt;Campfire Creators Jam #2&lt;/a&gt;&lt;/strong&gt; (starts 2026-05-21 21:00:00) — A highly flexible jam with a retro-friendly campfire theme, welcoming everything from cozy story games to survival, puzzle, and strategy concepts while only requiring a single animated campfire sprite.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>miniscript</category>
      <category>minimicro</category>
      <category>programming</category>
      <category>news</category>
    </item>
    <item>
      <title>HWYDT: Turning the Page</title>
      <dc:creator>JoeStrout</dc:creator>
      <pubDate>Wed, 13 May 2026 17:23:37 +0000</pubDate>
      <link>https://dev.to/joestrout/hwydt-turning-the-page-24dm</link>
      <guid>https://dev.to/joestrout/hwydt-turning-the-page-24dm</guid>
      <description>&lt;p&gt;On the MiniScript Discord this week, one of our users (Luckythespacecat) shared an animation he made for his Steam game &lt;em&gt;Wishlist Boreal&lt;/em&gt;.  It looks like an open book, with the page turning as if advancing through the book, including a nice paper-ish curl as it goes.  That led to another user (&lt;a class="mentioned-user" href="https://dev.to/dslower"&gt;@dslower&lt;/a&gt;) pointing out &lt;a href="https://www.instagram.com/reel/DUQ4KC2EnHe/?igsh=MWNibWZmZjRxb2Y5bA==" rel="noopener noreferrer"&gt;this Instagram reel&lt;/a&gt; in which somebody makes a texture-mapped page flip animation, also with a nice curvy page.  &lt;/p&gt;

&lt;p&gt;And then he had the audacity to opine that you &lt;em&gt;couldn't do such a thing in Mini Micro&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpu1bcjc0pm3bt4dqxarw.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpu1bcjc0pm3bt4dqxarw.gif" alt="Oh really?" width="350" height="306"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenge Accepted
&lt;/h2&gt;

&lt;p&gt;In fact we &lt;em&gt;can&lt;/em&gt; do such things in Mini Micro!  The trick is just to break the curving surface up into flat quadrilaterals, and then render each quad as a Sprite.&lt;/p&gt;

&lt;p&gt;Normally in these tutorials I walk through developing the code step by step — build a little, test a little.  But this program is short enough that I think I'll try the opposite: present the full code, and then just explain how it works.&lt;/p&gt;

&lt;p&gt;So, for maximum fun, fire up your copy of &lt;a href="https://miniscript.org/MiniMicro/#download" rel="noopener noreferrer"&gt;Mini Micro&lt;/a&gt;, &lt;code&gt;edit&lt;/code&gt; a new program, and paste in the following code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import "mathUtil"

clear
display(2).mode = displayMode.pixel
gfx = display(2)
gfx.clear color.clear
debug = false

pageTextures = [
  file.loadImage("page1.png"),
  file.loadImage("page2.png")]

pageWidth = 250
pageHeight = 400
xDivs = [0, 0.025, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.7, 1]

pageSprites = []
lastU = 0
for i in range(1, xDivs.len - 1)
    sp = new Sprite
    sp.image = pageTextures[0]
    sp.setUVs [[lastU, 0], [xDivs[i], 0], [xDivs[i],1], [lastU, 1]]
    pageSprites.push sp
    lastU = xDivs[i]
end for
display(4).sprites += pageSprites

getPolarPoint = function(pagePt, t, forward=true)
    radius = pageWidth * pagePt * (cos(t*2*pi)*0.2 + 0.9)
    if not forward then
        t2 = t^pagePt
        angle = mathUtil.lerp(0, 180, (t+t2)/2)
    else
        t = 1 - t
        t2 = t^pagePt
        angle = mathUtil.lerp(180, 0, (t+t2)/2)
    end if
    return [radius, angle]
end function

polarToXY = function(polarPt, baseX=480, baseY=320)
    radius = polarPt[0]
    ang = polarPt[1] * pi/180
    x = baseX + cos(ang) * radius
    y = baseY + sin(ang) * radius
    // apply a minimum representing the thickness of the pages
    // underneath, leaving a "dip" at the binding in the center
    thickness = 16
    q = abs(x - baseX) / pageWidth * 10
    if q &amp;lt; 1 then
        thickness *= sqrt(1 - (1-q)^2)  // (section of a circle)
    end if
    y = mathUtil.max(y, baseY + thickness)
    return [x,y]
end function

framePoly = function(corners)
    for i in range(-1, corners.len-2)
        p0 = corners[i]
        p1 = corners[i+1]
        gfx.line p0[0], p0[1], p1[0], p1[1], "#FF00FF", 2
    end for
end function

render = function(t, forward=true)
    if debug then gfx.clear color.clear
    topPts = []
    botPts = []
    for pagePt in xDivs
        p = getPolarPoint(pagePt, t, forward)
        topPts.push polarToXY(p, 480, 50 + pageHeight)
        botPts.push polarToXY(p, 480, 50)
    end for
    for i in pageSprites.indexes
        pageSprites[i].setCorners [
          [botPts[i][0], botPts[i][1]],
          [botPts[i+1][0], botPts[i+1][1]],
          [topPts[i+1][0], topPts[i+1][1]],
          [topPts[i][0], topPts[i][1]] ]
        if topPts[i+1][0] &amp;gt;= topPts[i][0] then
            // front surface
            pageSprites[i].image = pageTextures[0]
            u0 = xDivs[i]
            u1 = xDivs[i+1]
        else
            // back surface
            pageSprites[i].image = pageTextures[1]          
            u0 = 1 - xDivs[i]
            u1 = 1 - xDivs[i+1]
        end if
        sp.setUVs [[u0, 0], [u1, 0], [u1, 1], [u0, 1]]
        if debug then framePoly pageSprites[i].corners
    end for
end function

pageState = 0  // 0: page on the right; 1: flipped to the left
render pageState

turnPage = function(toState=1)
    delta = sign(toState - 0.5) * 0.02
    while pageState != toState and not key.available
        yield
        outer.pageState += delta
        if pageState &amp;lt; 0 or pageState &amp;gt; 1 then
            outer.pageState = mathUtil.clamp(pageState)
        end if
        render pageState, toState
    end while
end function

// Main loop
while true
    k = key.get
    if k == char(27) or k == "q" then break  // Esc or Q to quit
    if k == "d" then
        debug = not debug
        gfx.clear color.clear
        render pageState
    end if
    if k == char(17) then turnPage 1
    if k == char(18) then turnPage 0
    if k == char(10) or k == " " then turnPage not pageState
end while
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll also need two page images: one for the front, and one for the back of the turning page.  You can use whatever you like, but to match my demo, download these and save them as &lt;code&gt;page1.png&lt;/code&gt; and &lt;code&gt;page2.png&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz4mz3f4vnrui3ddytt6g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz4mz3f4vnrui3ddytt6g.png" alt="page1.png" width="553" height="746"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnypyhj6c42zn458rpxx0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnypyhj6c42zn458rpxx0.png" alt="page2.png" width="553" height="746"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now &lt;code&gt;run&lt;/code&gt; the program, and press the left/right arrow keys to flip the page.  It should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnbxl611vr3ta7pazs37d.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnbxl611vr3ta7pazs37d.gif" alt="Page-flip animation in Mini Micro" width="604" height="586"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Neat, right?&lt;/p&gt;

&lt;h2&gt;
  
  
  Carving up the Page
&lt;/h2&gt;

&lt;p&gt;The secret sauce here is to divide the page into vertical strips, each of which is flat.  In the demo, if you press the "d" (for "debug") key, it will actually draw the outlines of those quads, so you can see them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5kd7iwsbm8ddumun141a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5kd7iwsbm8ddumun141a.png" alt="Screen shot of curly page with quad outlines" width="310" height="511"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can divide the page however you like, but in my animation, I found that the curvature is much greater near the book binding (the left side of the original page) than on the outer (right) side of the page.  So I made the divisions smaller on the left, and larger on the right.  If we use &lt;code&gt;0&lt;/code&gt; to refer to the left side of the page image, and &lt;code&gt;1&lt;/code&gt; to refer to the right, then a sensible set of key points (dividing lines between the quads) might be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;xDivs = [0, 0.025, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.7, 1]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...which in fact you will find as line 15 in the code.&lt;/p&gt;

&lt;p&gt;To render any textured quad in Mini Micro, just make a Sprite, set its &lt;code&gt;image&lt;/code&gt; property to the texture image, and then use &lt;code&gt;setCorners&lt;/code&gt; and &lt;code&gt;setUVs&lt;/code&gt; to position the quad on the screen, and select what part of the texture it displays.  &lt;code&gt;setCorners&lt;/code&gt; takes a list of four &lt;code&gt;[x,y]&lt;/code&gt; pairs, in screen coordinates.  &lt;code&gt;setUVs&lt;/code&gt; takes a list of four &lt;code&gt;[u,v]&lt;/code&gt; pairs, where &lt;code&gt;u&lt;/code&gt; goes from 0-1 horizontally across the image (exactly like our &lt;code&gt;xDivs&lt;/code&gt; values above), and &lt;code&gt;v&lt;/code&gt; goes from 0-1 up the image vertically.&lt;/p&gt;

&lt;p&gt;If you need a review on &lt;code&gt;setCorners&lt;/code&gt; and &lt;code&gt;setUVs&lt;/code&gt;, play around with the &lt;strong&gt;spriteStretch&lt;/strong&gt; demo found in &lt;code&gt;/sys/demo/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb8e6wwxpmbd01w45aq9m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb8e6wwxpmbd01w45aq9m.png" alt="Screen shot of spriteStretch demo" width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In our page-flip demo, you'll find a loop at lines 17-26 which prepares our sprites based on the &lt;code&gt;xDivs&lt;/code&gt; we defined above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pageSprites = []
lastU = 0
for i in range(1, xDivs.len - 1)
    sp = new Sprite
    sp.image = pageTextures[0]
    sp.setUVs [[lastU, 0], [xDivs[i], 0], [xDivs[i],1], [lastU, 1]]
    pageSprites.push sp
    lastU = xDivs[i]
end for
display(4).sprites += pageSprites
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Later (starting at line 65), we have a &lt;code&gt;render&lt;/code&gt; function that updates those same sprites to reflect the current state of the animation.  It begins by building a set of &lt;code&gt;x,y&lt;/code&gt; coordinates for the points along the top and bottom of the page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    topPts = []
    botPts = []
    for pagePt in xDivs
        p = getPolarPoint(pagePt, t, forward)
        topPts.push polarToXY(p, 480, 50 + pageHeight)
        botPts.push polarToXY(p, 480, 50)
    end for
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The heavy lifting here is being done by &lt;code&gt;getPolarPoint&lt;/code&gt; and &lt;code&gt;polarToXY&lt;/code&gt;, which we'll get to in a moment.  For now, just understand that we're taking our &lt;code&gt;xDivs&lt;/code&gt; points along the page, and converting them into screen coordinates for the top (&lt;code&gt;topPts&lt;/code&gt;) and bottom (&lt;code&gt;botPts&lt;/code&gt;) of the page.  Then we just use those to update the corners of each corresponding sprite.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    for i in pageSprites.indexes
        pageSprites[i].setCorners [
          [botPts[i][0], botPts[i][1]],
          [botPts[i+1][0], botPts[i+1][1]],
          [topPts[i+1][0], topPts[i+1][1]],
          [topPts[i][0], topPts[i][1]] ]
    end for
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we wanted to show the same content (but reversed) on the back of the page, this would be all we need.  But really we want to show a different image on the back of the page, and because the sprites are flipped horizontally by that point, we need to invert the U coordinates as well.  So we need to insert some extra code into the above &lt;code&gt;for&lt;/code&gt; loop to update the image and UV coordinates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        if topPts[i+1][0] &amp;gt;= topPts[i][0] then
            // front surface
            pageSprites[i].image = pageTextures[0]
            u0 = xDivs[i]
            u1 = xDivs[i+1]
        else
            // back surface
            pageSprites[i].image = pageTextures[1]          
            u0 = 1 - xDivs[i]
            u1 = 1 - xDivs[i+1]
        end if
        sp.setUVs [[u0, 0], [u1, 0], [u1, 1], [u0, 1]]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now each section not only draws in the right position, but also shows the correct part of the right texture, depending on whether it's flipped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Computing the Page Shape
&lt;/h2&gt;

&lt;p&gt;For me, drawing the texture-mapped quads was the easy part.  The hard part was actually animating the shape of the page throughout the flip.&lt;/p&gt;

&lt;p&gt;You could just use keyframe animation: that is, make a tool (similar to that spriteStretch demo) that lets you drag the top and bottom points around by hand, and record the correct position for key frames throughout the flip.  Alternatively, you could use some external animation tool that writes to a file you can read in your Mini Micro program.  Then just play those positions back, interpolating as needed for in-between frames.&lt;/p&gt;

&lt;p&gt;But I didn't have the patience (nor, most likely, the art skills) for all that, so I went for a more mathematical approach.  Since the page is essentially rotating around its attachment point (the left side of the unflipped page), I found it easier to think about it in polar (&lt;strong&gt;radius&lt;/strong&gt; and &lt;strong&gt;angle&lt;/strong&gt;) rather than Cartesian (&lt;strong&gt;x&lt;/strong&gt; and &lt;strong&gt;y&lt;/strong&gt;) coordinates.  Some trial and error led me to this function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getPolarPoint = function(pagePt, t, forward=true)
    radius = pageWidth * pagePt * (cos(t*2*pi)*0.2 + 0.9)
    if not forward then
        t2 = t^pagePt
        angle = mathUtil.lerp(0, 180, (t+t2)/2)
    else
        t = 1 - t
        t2 = t^pagePt
        angle = mathUtil.lerp(180, 0, (t+t2)/2)
    end if
    return [radius, angle]
end function
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;getPolarPoint&lt;/code&gt; takes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a page point, i.e., one of those &lt;code&gt;xDivs&lt;/code&gt; points along the page from 0 (left) to (1) right&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;t&lt;/code&gt;, how far we are in the flip animation from 0 (starting position) to 1 (complete)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;forward&lt;/code&gt;, which is &lt;code&gt;true&lt;/code&gt; when flipping forward (right to left) and &lt;code&gt;false&lt;/code&gt; when flipping back the other way.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first version of this function had just &lt;code&gt;radius = pageWidth * pagePt; angle = mathUtil.lerp(0, 180, t)&lt;/code&gt; -- that is, a constant radius, and an angle that interpolates smoothly between 0 and 180 degrees.  This creates a stiff page that flips without bending.  The rest of the final code was just to fancy it up: make the radius a little smaller towards the center of the animation, and add a bit of curl by changing the angle based on how far along the page we are (and how far we are in the animation).&lt;/p&gt;

&lt;p&gt;But to actually use this, we have to convert from polar coordinates back to XY coordinates.  The basic function for that would be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;polarToXY = function(polarPt, baseX=480, baseY=320)
    radius = polarPt[0]
    ang = polarPt[1] * pi/180
    x = baseX + cos(ang) * radius
    y = baseY + sin(ang) * radius
    return [x,y]
end function
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But while I was at it, I decided to have this conversion function &lt;em&gt;also&lt;/em&gt; apply some "thickness" to the book, by calculating a minimum &lt;code&gt;Y&lt;/code&gt; that varies with our distance along the page.  This was to make the endpoints of the animation include a bit of a curl down where the page connects to the rest of the book, so it looks like an actual book with a binding, rather than a thin magazine or something.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzeb13m3wbho1kxyzyhb9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzeb13m3wbho1kxyzyhb9.png" alt="Close-up of bottom of book, showing binding curl" width="322" height="50"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The code for this (inserted into the function above) is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    // apply a minimum representing the thickness of the pages
    // underneath, leaving a "dip" at the binding in the center
    thickness = 16
    q = abs(x - baseX) / pageWidth * 10
    if q &amp;lt; 1 then
        thickness *= sqrt(1 - (1-q)^2)  // (section of a circle)
    end if
    y = mathUtil.max(y, baseY + thickness)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Incorporating Into Your Game
&lt;/h2&gt;

&lt;p&gt;That's all our demo does.  To incorporate this into a full game, you would need to draw the "rest" of the book (the open cover and pages underneath the turning page, and ensure that your page sprites are topmost.&lt;/p&gt;

&lt;p&gt;Also, you'll probably have more than just one (front and back page).  You might have &lt;em&gt;lots&lt;/em&gt; of pages in your book.  I would implement that with just three sets of page sprites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one that shows the resting right-hand page&lt;/li&gt;
&lt;li&gt;one for the resting left-hand page&lt;/li&gt;
&lt;li&gt;one for the actively turning page&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At rest, you don't actually need the third one.  But as soon as it's time to turn the page, you would add it to the view.  If advancing through the book, the right-hand page is going to flip left.  So you would set your third page with the content on the right, plus the subsequent page as its back; and update the resting right-hand page to show the page after that.  Then animate.  At the end of the animation, update the left-hand page to show the same thing as the animation page, and hide the animation page.  If going backwards, you'd do something similar, but animating from left to right.&lt;/p&gt;

&lt;p&gt;I toyed with the idea of making this into its own little library on GitHub that manages multiple pages, provides different ways of specifying the page animation, etc.  But maybe it's not worth it; the core ideas are in this demo, and you can take it from here.  Let me know what you think in the comments below, or join us on Discord to discuss more.  I can't wait to see what you do with it!&lt;/p&gt;

</description>
      <category>miniscript</category>
      <category>minimicro</category>
      <category>programming</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>HWYDT: Swinging from Vines</title>
      <dc:creator>JoeStrout</dc:creator>
      <pubDate>Wed, 06 May 2026 16:23:32 +0000</pubDate>
      <link>https://dev.to/joestrout/hwydt-swinging-from-vines-31ei</link>
      <guid>https://dev.to/joestrout/hwydt-swinging-from-vines-31ei</guid>
      <description>&lt;h2&gt;
  
  
  The Venerable History of Vine-Swinging
&lt;/h2&gt;

&lt;p&gt;Swinging on vines has been a favorite game mechanic since 1982, when both &lt;em&gt;Jungle Hunt&lt;/em&gt; (a thinly disguised Tarzan arcade game) and &lt;em&gt;Pitfall!&lt;/em&gt; (on of the Atari 2600's best-selling action games) hit the market.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjya8jyz69i2yei11u3m.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjya8jyz69i2yei11u3m.gif" alt="Animated GIF of Pitfall!"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since then, swinging from vines, ropes, webs, grappling hooks, extensible bionic arms, etc. has been a recurring staple (including in such modern hits as &lt;a href="https://joestrout.itch.io/spider-pig" rel="noopener noreferrer"&gt;&lt;em&gt;Spider-Pig!&lt;/em&gt;&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;But how can you actually do this in your own games?  Let's dig in and find out!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Big Picture
&lt;/h2&gt;

&lt;p&gt;We're going to implement a vine-swinging demo in Mini Micro.  Our vines will be composed of five or so straight segments, connected like links in a chain.  These will oscillate back and forth, using some simple trigonometry (don't worry, I said &lt;em&gt;simple&lt;/em&gt; and I meant it!) to update their positions and rotations.  We need a sprite image for each vine segment; this should be a thick line with rounded ends (so they join neatly together), and for decoration, I've added some leaves:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fghxp4tr3pctp8xhzrs4a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fghxp4tr3pctp8xhzrs4a.png" alt="VineSegment.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go ahead and save this image as &lt;code&gt;VineSegment.png&lt;/code&gt;, and fire up Mini Micro (download it &lt;a href="https://miniscript.org/MiniMicro/" rel="noopener noreferrer"&gt;here&lt;/a&gt; if you don't have it already).  Notice that the image is mostly empty on the left; the "pivot point" (around which the sprite rotates) in Mini Micro is always in the center of the image, so we've simply offset our drawing so that the center of the image is at one end of the visible segment.&lt;/p&gt;

&lt;p&gt;Our hero (Kip) will be, at any given moment, either attached to some vine segment, or flying ballistically through the air.  When attached, we just need to update his position along with the vine segment he's clinging to.  When flying, then Isaac Newton is in charge, and fortunately &lt;em&gt;his&lt;/em&gt; equations are even simpler.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preliminaries
&lt;/h2&gt;

&lt;p&gt;In Mini Micro, enter &lt;code&gt;edit&lt;/code&gt; to start a new program, and then paste in these preliminary steps to clear the screen, get a handy reference to the sprite display, and load some sounds we'll need later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import "mathUtil"

clear
spriteDisp = display(4)
jumpSound = file.loadSound("/sys/sounds/pickup.wav")
catchSound = file.loadSound("/sys/sounds/swoosh.wav")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we're going to need a bit of math to convert between local and world coordinates.  By "local" coordinates, I mean coordinates relative to a sprite — for example, the pixel coordinates of some point in its image; those are &lt;em&gt;local&lt;/em&gt; to the sprite.  "World" coordinates are basically positions on the screen, except that we're going to be scrolling the view our hero progresses, so it's more accurate to say they are positions relative to the sprite display.  We need to convert back and forth: converting from local to world in order to position each vine segment relative to the previous one, or Kip relative to the segment he's holding; and converting from world to local to see if Kip is close enough to grab a vine when flying through the air.&lt;/p&gt;

&lt;p&gt;This is where the trigonometry comes in.  Honestly, I don't memorize or derive these equations; I just google 'em when I need them.  You can just copy and paste this into your program:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Set the x and y values of the given map to
// the world position corresponding to the
// given XY local to this sprite.  In other words,
// assume targetMap is attached to this sprite
// at local position localX, localY; update its
// x and y accordingly.
Sprite.setXYtoLocal = function(targetMap, localX, localY)
    radians = self.rotation * pi/180
    cosAng = cos(radians)
    sinAng = sin(radians)
    targetMap.x = self.x + localX * cosAng - localY * sinAng
    targetMap.y = self.y + localX * sinAng + localY * cosAng    
end function

// Get the local [x,y] position of some world object
// relative to this sprite.
Sprite.getLocal = function(worldXY)
    radians = self.rotation * pi/180
    dx = worldXY.x - self.x
    dy = worldXY.y - self.y
    cosAng = cos(radians)
    sinAng = sin(radians)
    return [dx * cosAng + dy * sinAng, -dx * sinAng + dy * cosAng]  
end function
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save your program as &lt;code&gt;swing.ms&lt;/code&gt; (or whatever you like), and run it just to be sure you haven't made a syntax error.  If it seems to do nothing, you're doing great so far!&lt;/p&gt;

&lt;h2&gt;
  
  
  Swingy vines
&lt;/h2&gt;

&lt;p&gt;Now let's add the code that actually makes a vine.  We'll have a Sprite subclass called Segment, just to give them all a common image and length; and then we'll have another class called Vine, which encapsulates a list of segments and knows how to update them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Segment = new Sprite
Segment.image = file.loadImage("VineSegment.png")
Segment.length = 64

Vine = {}
Vine.angRange = 55  // how far to swing from vertical (degrees)
Vine.period = 2  // how long a full back-and-forth takes (seconds)
Vine.Instances = []

Vine.Make = function(x=480, y=640, qtySegments=5)
    vine = new self
    vine.segments = []
    for i in range(0, qtySegments-1)
        seg = new Segment
        seg.x = x
        seg.y = y - seg.length * i
        seg.rotation = -90
        spriteDisp.sprites.push seg
        vine.segments.push seg
    end for
    Vine.Instances.push vine
    return vine
end function
Vine.update = function(time)
    t = 2*pi * time/self.period
    for i in self.segments.indexes
        seg = self.segments[i]
        ang = -90 + self.angRange * sin(t - i*0.2)
        seg.rotation = ang
        if i &amp;lt; self.segments.len-1 then
            seg.setXYtoLocal self.segments[i+1], seg.length, 0
        end if
    end for
end function
Vine.UpdateAll = function(time)
    for vine in Vine.Instances
        vine.update time
    end for
end function
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key bit is of course the &lt;code&gt;Vine.update&lt;/code&gt; function, which iterates over its segments, setting the angle of each, and then the position of the &lt;em&gt;next&lt;/em&gt; using that &lt;code&gt;setXYtoLocal&lt;/code&gt; function we prepared before.  The angle is set according to the time (divided by &lt;code&gt;self.period&lt;/code&gt;, which is how we make vines swing faster or slower), and subtracts a little factor of the segment number, &lt;code&gt;i * 0.2&lt;/code&gt;.  This makes the end of the vine lag a bit relative to the top of the vine, making it look like a rope/chain rather than a rigid bar.&lt;/p&gt;

&lt;p&gt;To test this code, let's add a simple main program that creates one vine, and updates it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Vine.Make
while true
    yield
    Vine.UpdateAll time
end while
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now run, and it should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7y5t111qzl4rh7h1sww6.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7y5t111qzl4rh7h1sww6.gif" alt="Animated GIF of swinging vine"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; Add a second vine, at a different position.  Double challenge: give it a different period, so they're not swinging in perfect sync!&lt;/p&gt;

&lt;h2&gt;
  
  
  Our Hero, Kip
&lt;/h2&gt;

&lt;p&gt;Now we need a player character to jump from vine to vine.  We'll use our hero Kip (fresh from the &lt;a href="https://joestrout.itch.io/kip-in-the-caves-of-lava" rel="noopener noreferrer"&gt;Caves of Lava&lt;/a&gt;), since his sprites are included with Mini Micro.  Delete the mini-main-program you added above, and paste in the code below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kip = new Sprite
kip.image = file.loadImage("/sys/pics/KP/KP-jump.png")
kip.grabbed = null  // Segment he's hanging onto
kip.grabPos = [0,0] // grab position local to that segment
kip.vx = 0; kip.vy = 0  // velocity, in pixels/sec

kip.update = function(dt)
    if self.grabbed then
        // Stick to the vine, updating our velocity as we go
        lastPos = [self.x, self.y]
        self.grabbed.setXYtoLocal self, self.grabPos[0], self.grabPos[1]
        self.vx = (self.x - lastPos[0]) / dt
        self.vy = (self.y - lastPos[1]) / dt
    else
        // free flying!
        self.vy -= 1000*dt  // gravity
        self.x += self.vx * dt
        self.y += self.vy * dt
        // Try to catch any vine except our last one.
        for vine in Vine.Instances
            if vine == self.lastVine then continue
            if self.tryCatch(vine) then break
        end for
    end if
end function

kip.jump = function(extraVx=0, extraVy=100)
    if not self.grabbed then return
    jumpSound.play
    self.grabbed = null
    self.vy += 100  
end function

kip.tryCatch = function(vine)
    for seg in vine.segments
        localPos = seg.getLocal(self)
        if (0 &amp;lt;= localPos[0] &amp;lt; seg.length) and
          (-12 &amp;lt;= localPos[1] &amp;lt; 12) then
            // Valid catch!
            self.grabbed = seg
            self.grabPos = localPos
            self.lastVine = vine
            catchSound.play
            return true
        end if
    end for
    return false
end function
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, Kip's &lt;code&gt;update&lt;/code&gt; method has two modes: it does one thing when he has grabbed a vine, and something else when he is flying through space.  Both cases are pretty simple.  An important trick in the first case is updating kip's velocity (vx and vy) based on how the vine is causing him to move.  That matters because when you jump, you want to continue with (more or less) that same velocity.&lt;/p&gt;

&lt;p&gt;In the free-flying case, we just apply some gravity to our vertical velocity (&lt;code&gt;vy&lt;/code&gt;), and then update our position according to current velocity.  The only tricky bit here is checking to see when we're close enough to another vine to grab it — so I extracted that into its own method, &lt;code&gt;kip.tryCatch&lt;/code&gt;.  This iterates over the segments of the given vine, and sees what our position would be local to that segment.  If it's close enough, then we do the catch.  &lt;em&gt;(Who says MiniScript doesn't have try/catch?!)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Camera, Final Setup, and Main Program
&lt;/h2&gt;

&lt;p&gt;Our demo is almost done, so let's press on with the final bit: a function to scroll the display so that the "camera" stays centered on the current vine; some code to create a bunch of vines, increasingly far apart; and the main program.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
updateCamera = function(dt)
    // try to center the active vine on the screen
    targetX = kip.lastVine.segments[0].x - 480
    spriteDisp.scrollX = mathUtil.moveTowards(
      spriteDisp.scrollX, targetX, 200 * dt)  
end function

// create the vines
x = 300; dx = 400
while x &amp;lt; 4000
    Vine.Make(x).period = 1.8 + rnd*0.4
    dx += 35
    x += dx
end while

// place Kip on the first vine
kip.lastVine = Vine.Instances[0]
kip.grabbed = kip.lastVine.segments[-2]
kip.grabPos = [40, 0]
kip.update
spriteDisp.sprites.push kip

// main loop
lastTime = time
jumpWasDown = false
while kip.y &amp;gt; 0
    yield
    now = time
    dt = now - lastTime
    lastTime = now
    Vine.UpdateAll now
    kip.update dt
    jumpDown = key.pressed("space")
    if jumpDown and not jumpWasDown then kip.jump
    jumpWasDown = jumpDown
    updateCamera dt
end while
key.clear
text.row = 1
print "Game over!"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it!  Run now and you should have a simple but playable game.  How far can you make it before you fall?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2gicrva4riabni706z6w.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2gicrva4riabni706z6w.gif" alt="Animated screencap of swing demo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Full Program
&lt;/h2&gt;

&lt;p&gt;
  Here's the whole program in one big listing.
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import "mathUtil"

clear
spriteDisp = display(4)
jumpSound = file.loadSound("/sys/sounds/pickup.wav")
catchSound = file.loadSound("/sys/sounds/swoosh.wav")

// Set the x and y values of the given map to
// the world position corresponding to the
// given XY local to this sprite.  In other words,
// assume targetMap is attached to this sprite
// at local position localX, localY; update its
// x and y accordingly.
Sprite.setXYtoLocal = function(targetMap, localX, localY)
    radians = self.rotation * pi/180
    cosAng = cos(radians)
    sinAng = sin(radians)
    targetMap.x = self.x + localX * cosAng - localY * sinAng
    targetMap.y = self.y + localX * sinAng + localY * cosAng    
end function

// Get the local [x,y] position of some world object
// relative to this sprite.
Sprite.getLocal = function(worldXY)
    radians = self.rotation * pi/180
    dx = worldXY.x - self.x
    dy = worldXY.y - self.y
    cosAng = cos(radians)
    sinAng = sin(radians)
    return [dx * cosAng + dy * sinAng, -dx * sinAng + dy * cosAng]  
end function

Segment = new Sprite
Segment.image = file.loadImage("VineSegment.png")
Segment.length = 64

Vine = {}
Vine.angRange = 55  // how far to swing from vertical (degrees)
Vine.period = 2  // how long a full back-and-forth takes (seconds)
Vine.Instances = []

Vine.Make = function(x=480, y=640, qtySegments=5)
    vine = new self
    vine.segments = []
    for i in range(0, qtySegments-1)
        seg = new Segment
        seg.x = x
        seg.y = y - seg.length * i
        seg.rotation = -90
        spriteDisp.sprites.push seg
        vine.segments.push seg
    end for
    Vine.Instances.push vine
    return vine
end function
Vine.update = function(time)
    t = 2*pi * time/self.period
    for i in self.segments.indexes
        seg = self.segments[i]
        ang = -90 + self.angRange * sin(t - i*0.2)
        seg.rotation = ang
        if i &amp;lt; self.segments.len-1 then
            seg.setXYtoLocal self.segments[i+1], seg.length, 0
        end if
    end for
end function
Vine.UpdateAll = function(time)
    for vine in Vine.Instances
        vine.update time
    end for
end function

kip = new Sprite
kip.image = file.loadImage("/sys/pics/KP/KP-jump.png")
kip.grabbed = null  // Segment he's hanging onto
kip.grabPos = [0,0] // grab position local to that segment
kip.vx = 0; kip.vy = 0  // velocity, in pixels/sec

kip.update = function(dt)
    if self.grabbed then
        // Stick to the vine, updating our velocity as we go
        lastPos = [self.x, self.y]
        self.grabbed.setXYtoLocal self, self.grabPos[0], self.grabPos[1]
        self.vx = (self.x - lastPos[0]) / dt
        self.vy = (self.y - lastPos[1]) / dt
    else
        // free flying!
        self.vy -= 1000*dt  // gravity
        self.x += self.vx * dt
        self.y += self.vy * dt
        // Try to catch any vine except our last one.
        for vine in Vine.Instances
            if vine == self.lastVine then continue
            if self.tryCatch(vine) then break
        end for
    end if
end function

kip.jump = function(extraVx=0, extraVy=100)
    if not self.grabbed then return
    jumpSound.play
    self.grabbed = null
    self.vy += 100  
end function

kip.tryCatch = function(vine)
    for seg in vine.segments
        localPos = seg.getLocal(self)
        if (0 &amp;lt;= localPos[0] &amp;lt; seg.length) and
          (-12 &amp;lt;= localPos[1] &amp;lt; 12) then
            // Valid catch!
            self.grabbed = seg
            self.grabPos = localPos
            self.lastVine = vine
            catchSound.play
            return true
        end if
    end for
    return false
end function

updateCamera = function(dt)
    // try to center the active vine on the screen
    targetX = kip.lastVine.segments[0].x - 480
    spriteDisp.scrollX = mathUtil.moveTowards(
      spriteDisp.scrollX, targetX, 200 * dt)  
end function

// create the vines
x = 300; dx = 400
while x &amp;lt; 4000
    Vine.Make(x).period = 1.8 + rnd*0.4
    dx += 35
    x += dx
end while

// place Kip on the first vine
kip.lastVine = Vine.Instances[0]
kip.grabbed = kip.lastVine.segments[-2]
kip.grabPos = [40, 0]
kip.update
spriteDisp.sprites.push kip

// main loop
lastTime = time
jumpWasDown = false
while kip.y &amp;gt; 0
    yield
    now = time
    dt = now - lastTime
    lastTime = now
    Vine.UpdateAll now
    kip.update dt
    jumpDown = key.pressed("space")
    if jumpDown and not jumpWasDown then kip.jump
    jumpWasDown = jumpDown
    updateCamera dt
end while
key.clear
text.row = 1
print "Game over!"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;h2&gt;
  
  
  Taking it Further
&lt;/h2&gt;

&lt;p&gt;This simple demo could be quickly expanded into a more engaging game:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep a score, adding points for every successful jump, and/or for reaching new vines.&lt;/li&gt;
&lt;li&gt;Add hazards (water, alligators, whatever) below to heighten the tension.&lt;/li&gt;
&lt;li&gt;Add platforms/ground with traditional platformer mechanics (e.g. running and climbing).&lt;/li&gt;
&lt;li&gt;Enable Kip to climb up and down the vine he's on (this makes it a &lt;em&gt;lot&lt;/em&gt; easier!).&lt;/li&gt;
&lt;li&gt;Improve the vine physics, allowing stretching, slinging, and other fun effects, much like &lt;a href="https://joestrout.itch.io/spider-pig" rel="noopener noreferrer"&gt;&lt;em&gt;Spider-Pig&lt;/em&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Can you think of other good uses for this sort of mechanic?  How might you apply this in your own games?  Share your thoughts in the comments below!&lt;/p&gt;

</description>
      <category>miniscript</category>
      <category>minimicro</category>
      <category>programming</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>MiniScript Weekly News — April 30, 2026</title>
      <dc:creator>JoeStrout</dc:creator>
      <pubDate>Thu, 30 Apr 2026 23:08:43 +0000</pubDate>
      <link>https://dev.to/joestrout/miniscript-weekly-news-april-30-2026-6dd</link>
      <guid>https://dev.to/joestrout/miniscript-weekly-news-april-30-2026-6dd</guid>
      <description>&lt;h2&gt;
  
  
  Development Updates
&lt;/h2&gt;

&lt;p&gt;MiniScript 2 got a solid round of command-line quality-of-life improvements this week. Joe added editable input history, searchable REPL history (&lt;code&gt;!? foo&lt;/code&gt; style queries), and a new &lt;code&gt;_in&lt;/code&gt; / &lt;code&gt;_out&lt;/code&gt; history model; he also fixed some extra blank lines in the REPL.&lt;/p&gt;

&lt;p&gt;On the raylib side, &lt;strong&gt;raylib-miniscript&lt;/strong&gt; moved forward nicely with raylib now pinned to &lt;strong&gt;6.0&lt;/strong&gt; as a git submodule, plus a bump to version &lt;strong&gt;0.3&lt;/strong&gt;. Even better, every API now has a code example in the &lt;a href="https://github.com/JoeStrout/raylib-miniscript/wiki" rel="noopener noreferrer"&gt;wiki&lt;/a&gt;, along with a new &lt;a href="https://github.com/JoeStrout/raylib-miniscript/wiki/Raylib_Types" rel="noopener noreferrer"&gt;Raylib Types&lt;/a&gt; page to help map Raylib structures into MiniScript.&lt;/p&gt;

&lt;p&gt;In the Mini Micro channel,  Joe explained display layering and mode switching in detail, and users kicked around some "worst sorting algorithm" ideas just for fun.&lt;/p&gt;

&lt;h2&gt;
  
  
  Community Projects
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Zaxabock&lt;/strong&gt; shared a playful MiniScript experiment called &lt;strong&gt;PseudoLambda&lt;/strong&gt;: &lt;a href="https://github.com/Marutzo/Mini-Micro/blob/main/PseudoLambda" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;. It defines lambda-like expressions using Joe’s eval support, and it looks like a fun little throwback while waiting on more language features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;𝔇𝔢𝔞𝔱𝔥&lt;/strong&gt; has been building an ambitious project, &lt;strong&gt;Ultimate Space Odyssey&lt;/strong&gt;, and shared a deep dive into Mini Micro display management while working through layered UI, sprite displays, and planet generation. The repo is here: &lt;a href="https://github.com/death-dev96/Ultimate_Space_Odyssey/tree/main/Game" rel="noopener noreferrer"&gt;Ultimate_Space_Odyssey/Game&lt;/a&gt; — great to see someone pushing the display system and documenting what they learn along the way.&lt;/p&gt;

&lt;p&gt;Joe also highlighted a great visual idea for the community to try: a &lt;strong&gt;Mini Micro kaleidoscope&lt;/strong&gt; built from sprites and animated UVs. He posted the write-up here: &lt;a href="https://dev.to/joestrout/make-a-mini-micro-kaleidoscope-1kbc"&gt;Make a Mini Micro Kaleidoscope&lt;/a&gt;, and it sounds like a fun little program to play with for anyone looking for a colorful weekend project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Discussion Highlights
&lt;/h2&gt;

&lt;p&gt;There was a lively thread about how MiniScript fits into game architecture, especially around threading and when to use &lt;code&gt;install&lt;/code&gt; versus simply switching display modes. Joe’s advice was consistent and reassuring: MiniScript is already thread-safe, and in most games you’ll be happier treating &lt;code&gt;install&lt;/code&gt; as a special-case tool rather than a default workflow.&lt;/p&gt;

&lt;p&gt;Another useful discussion centered on REPL smart quotes and paste behavior in Mini Micro 2. The conversation surfaced a real tension between convenience and control, with several community members offering thoughtful use cases; it was a nice example of the group thinking carefully about developer experience.&lt;/p&gt;

&lt;p&gt;There was also some cheerful design brainstorming in &lt;code&gt;#game-ideas&lt;/code&gt;, where &lt;strong&gt;Midsubspace&lt;/strong&gt; floated the idea of making a MiniScript Mancala game. Joe gave it an encouraging “go for it,” which feels like the perfect nudge for a good community project.&lt;/p&gt;

&lt;p&gt;Thanks for reading — happy scripting, and keep sharing what you’re building!&lt;/p&gt;

&lt;h2&gt;
  
  
  Upcoming Game Jams
&lt;/h2&gt;

&lt;p&gt;These upcoming jams look like a great fit for Mini Micro:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/mini-choco-jam-1" rel="noopener noreferrer"&gt;Mini Choco Jam&lt;/a&gt;&lt;/strong&gt; (starts 2026-02-28 23:00:00) — A romance-focused, story-first jam with 2D-friendly requirements and no need for 3D or networking is an excellent fit for Mini Micro, especially for visual-novel or text-based games.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/16bits-2nd-caravan-jam" rel="noopener noreferrer"&gt;16bits 2nd Caravan Jam&lt;/a&gt;&lt;/strong&gt; (starts 2026-05-05 13:30:17) — A great fit for a fast, score-chasing arcade game: the jam is built around short, easy-to-pick-up runs with high skill ceilings, especially in the spirit of classic Caravan-style shoot ’em ups.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/three-button-jam-2026" rel="noopener noreferrer"&gt;Three Button Jam 2026 (8 Bits to Infinity)&lt;/a&gt;&lt;/strong&gt; (starts 2026-05-08 17:00:00) — A highly accessible restriction jam with a fun twist: build a complete game using only three buttons, making it perfect for inventive controls, tight design, and clever feedback loops.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/ak-gaming-game-jam-6" rel="noopener noreferrer"&gt;Ak Gaming Game Jam #6&lt;/a&gt;&lt;/strong&gt; — A very accessible jam with free engine choice, no 3D or networking demands, and a strong emphasis on playable, theme-driven entries—great for retro 2D or text-based game ideas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/aeiougamejam" rel="noopener noreferrer"&gt;AEIOU GameJam 2026&lt;/a&gt;&lt;/strong&gt; (starts 2026-05-01 03:45:00) — A creativity-first jam with a clever twist theme, encouraging illusions, hidden mechanics, and unexpected gameplay that fits retro 2D experimentation perfectly.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>miniscript</category>
      <category>minimicro</category>
      <category>programming</category>
      <category>news</category>
    </item>
    <item>
      <title>Make a Mini Micro Kaleidoscope</title>
      <dc:creator>JoeStrout</dc:creator>
      <pubDate>Wed, 29 Apr 2026 20:11:14 +0000</pubDate>
      <link>https://dev.to/joestrout/make-a-mini-micro-kaleidoscope-1kbc</link>
      <guid>https://dev.to/joestrout/make-a-mini-micro-kaleidoscope-1kbc</guid>
      <description>&lt;p&gt;You have &lt;em&gt;got&lt;/em&gt; to try this.&lt;/p&gt;

&lt;p&gt;Inspired by a &lt;a href="https://www.facebook.com/share/v/18j8zCBMcK/" rel="noopener noreferrer"&gt;Facebook post&lt;/a&gt;, I decided this morning to make a kaleidoscope demo for Mini Micro.  And man, this thing is trippy!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa6690hy7m23s8i7gds8f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa6690hy7m23s8i7gds8f.png" alt="Screen shot of kaleidoscope program" width="800" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are a lot of knobs to tweak and settings to explore, so fire up Mini Micro (download it free &lt;a href="https://miniscript.org/MiniMicro/#download" rel="noopener noreferrer"&gt;here&lt;/a&gt; if you don't have it already), and follow along!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Big Idea
&lt;/h2&gt;

&lt;p&gt;A kaleidoscope is a set of mirrors that reflect onto some interesting "stuff" (traditionally, colorful little bits of plastic at the end of the tube) to provide a multiple views of the same content.  In Mini Micro, we're going to provide those views by using Sprites, each using the same image, and stretched out to a diamond shape.  Those are arranged in a circle, providing a star shape overall, like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2u7lwfs2xgqfzuwsa58d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2u7lwfs2xgqfzuwsa58d.png" alt="Screen shot of Mini Micro showing six diamonds arranged into a 6-pointed star." width="800" height="536"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've made the diamonds smaller, and shifted one of the diamonds out a bit so you can see it in the image above.  In the real program, we'll scale these up enough that they fill the screen without gaps.&lt;/p&gt;

&lt;p&gt;Then, we set the texture coordinates or "UVs" on each diamond to the same portion of a colorful texture, which represents the "interesting stuff" at the end of the tube.  Importantly, the UV coordinates are &lt;em&gt;flipped&lt;/em&gt; on alternating diamonds.  That causes the texture at the edge of one diamond to match up perfectly with the texture on its neighbor, and results in the trippy symmetrical patterns.&lt;/p&gt;

&lt;p&gt;Finally, we just move those UV coordinates around on the texture, causing the pattern to whirl and change.  It's mesmerizing!&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Start by downloading one or both of these images: &lt;/p&gt;


&lt;div class="ltag-slides ltag-slides--carousel"&gt;
  &lt;div class="ltag-slides__track"&gt;
    &lt;div class="ltag-slide"&gt;
      &lt;img class="ltag-slide__image" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvexyvzi2fwmf7jmdktkb.png" alt="kaltex1.png" width="800" height="800"&gt;
&lt;/div&gt;


&lt;div class="ltag-slide"&gt;
      &lt;img class="ltag-slide__image" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7f1yw4aytfjsawuzi2od.png" alt="kaltex2.png" width="800" height="800"&gt;
&lt;/div&gt;



  &lt;/div&gt;
    ‹
    ›
    &lt;div class="ltag-slides__dots"&gt;&lt;/div&gt;
    
      (function() {
        var container = document.currentScript.closest('.ltag-slides--carousel');
        var track = container.querySelector('.ltag-slides__track');
        var slides = track.querySelectorAll('.ltag-slide');
        var prevBtn = container.querySelector('.ltag-slides__nav--prev');
        var nextBtn = container.querySelector('.ltag-slides__nav--next');
        var dotsContainer = container.querySelector('.ltag-slides__dots');
        var current = 0;
        var total = slides.length;

        for (var i = 0; i &amp;lt; total; i++) {
          var dot = document.createElement('button');
          dot.className = 'ltag-slides__dot' + (i === 0 ? ' ltag-slides__dot--active' : '');
          dot.setAttribute('aria-label', 'Go to slide ' + (i + 1));
          dot.dataset.index = i;
          dot.addEventListener('click', function() { goTo(parseInt(this.dataset.index)); });
          dotsContainer.appendChild(dot);
        }

        function goTo(index) {
          current = ((index % total) + total) % total;
          track.style.transform = 'translateX(-' + (current * 100) + '%)';
          var dots = dotsContainer.querySelectorAll('.ltag-slides__dot');
          for (var i = 0; i &amp;lt; dots.length; i++) {
            dots[i].classList.toggle('ltag-slides__dot--active', i === current);
          }
        }

        prevBtn.addEventListener('click', function() { goTo(current - 1); });
        nextBtn.addEventListener('click', function() { goTo(current + 1); });
      })();
    
&lt;/div&gt;


&lt;p&gt;I called them "kaltex1.png" and "kaltex2.png".  Save them to whatever folder you want to do this project in.  Then launch Mini Micro, and mount that same folder (using the disk-slot icon below the screen; or on Mac/Windows, you can just drop the folder onto the Mini Micro window to mount it).&lt;/p&gt;

&lt;p&gt;Type &lt;code&gt;view "kaltex1.png"&lt;/code&gt; and press Return in Mini Micro to verify that you're set up with everything in the right place.  Then use the &lt;code&gt;edit&lt;/code&gt; command, and paste in this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;clear
texture = file.loadImage("kaltex1.png")
cx = 480; cy = 320; r = 600  // screen center and radius
n = 6  // number of wedges

for i in range(n-1)
    sp = new Sprite
    sp.image = texture
    a = i * 2*pi/n
    a1 = a + pi/n
    a2 = a1 + pi/n
    sp.setCorners [[cx, cy],
      [cx+cos(a)*r, cy+sin(a)*r],
      [cx+cos(a1)*r*2, cy+sin(a1)*r*2],  
      [cx+cos(a2)*r, cy+sin(a2)*r]]
    display(4).sprites.push sp
end for
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save that as "kaleidoscope" (or whatever you like), then run it.  It should fill your screen with repeating copies of the full texture.&lt;/p&gt;

&lt;p&gt;This code mostly just figures out the angle (in radians) to the corners of each diamond, and then calculates the positions of those using &lt;code&gt;cos&lt;/code&gt; and &lt;code&gt;sin&lt;/code&gt;.  If you need a review of how angles, &lt;code&gt;sin&lt;/code&gt;, and &lt;code&gt;cos&lt;/code&gt; work, run &lt;code&gt;/sys/demo/angles&lt;/code&gt;.  (And you thought you'd never need trigonometry in real life!)&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting UVs
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;edit&lt;/code&gt; your program again, and append this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uvs = function(t, flip=false)
    cx = 0.5 + 0.25 * sin(t * 0.13)
    cy = 0.5 + 0.25 * cos(t * 0.17)
    a = t * 0.21
    r = 0.25 + 0.08 * sin(t * 0.31)
    result = []
    for i in range(0,3)
        result.push [cx + cos(a)*r, cy + sin(a)*r]
        if flip then a -= pi/2 else a += pi/2
    end for
    return result
end function
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're just defining a function here, not actually calling it, so if you run now it shouldn't behave any differently.  But let's take a moment to understand what's going on.&lt;/p&gt;

&lt;p&gt;This code is somewhat similar to the diamond-positioning code we had before.  It's basically defining a four points arranged in a square that moves around, scales, and rotates, sampling different areas of the sprite texture.&lt;/p&gt;

&lt;p&gt;First we calculate a center (&lt;code&gt;cx&lt;/code&gt;, &lt;code&gt;cy&lt;/code&gt;) position that depends on the value &lt;code&gt;t&lt;/code&gt; (representing "time").  When you read &lt;code&gt;cx = 0.5 + 0.25 * sin(t * 0.13)&lt;/code&gt;, think "cx is going to vary around 0.5, by up to 0.25 in either direction".  The factor &lt;code&gt;0.13&lt;/code&gt; controls how &lt;em&gt;quickly&lt;/em&gt; &lt;code&gt;cx&lt;/code&gt; varies with time.  &lt;code&gt;cy&lt;/code&gt; is similar but varies at a different rate.&lt;/p&gt;

&lt;p&gt;Then we calculate an angle &lt;code&gt;a&lt;/code&gt;, as a straight-up multiple of &lt;code&gt;t&lt;/code&gt;.  This makes our square rotate steadily with time.  (What would happen if you threw a factor of &lt;code&gt;sin&lt;/code&gt; in this one too?)  Finally, we calculate a radius &lt;code&gt;r&lt;/code&gt; that uses the same &lt;code&gt;sin&lt;/code&gt; trick to vary by +/- 0.08 around 0.25.&lt;/p&gt;

&lt;p&gt;With our center, angle, and radius ready to go, the &lt;code&gt;for&lt;/code&gt; loop just calculates each point and stuffs them into &lt;code&gt;result&lt;/code&gt;.  But note the &lt;code&gt;if&lt;/code&gt; statement that adjusts our angle after each point.  When &lt;code&gt;flip&lt;/code&gt; is true, we &lt;em&gt;subtract&lt;/em&gt; &lt;code&gt;pi/2&lt;/code&gt; (90°, or a quarter circle) as we go; otherwise we &lt;em&gt;add&lt;/em&gt; it.  This is what flips the texture on every other diamond, so that they mesh properly at the edges.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy2ulcea4yexg4cz4plsy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy2ulcea4yexg4cz4plsy.png" alt="Screenshot of kaleidoscope program" width="800" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Animation!
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;edit&lt;/code&gt; your code again, and paste in this final piece:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;while not key.pressed("escape")
    yield
    t = time
    //t = mouse.x/100 + mouse.y/71
    for i in range(n-1)
        display(4).sprites[i].setUVs uvs(t, i%2)
    end for
end while
key.clear
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the main loop.  We start with a &lt;code&gt;yield&lt;/code&gt;, as all good main loops do, fixing the animation rate to 60 frames/sec.  Then we choose a &lt;code&gt;t&lt;/code&gt; value equal to the &lt;code&gt;time&lt;/code&gt; (or, if you uncomment the line after that, a &lt;code&gt;t&lt;/code&gt; value that depends on the mouse!).  Then we just call &lt;code&gt;setUVs&lt;/code&gt; for each of our diamond sprites, passing in &lt;code&gt;t&lt;/code&gt;, and with &lt;code&gt;flip&lt;/code&gt; set to true (1) for the odd-numbered diamonds.&lt;/p&gt;

&lt;p&gt;That's it.  Run the program now, and you'll be in trippy kaleidoscope land!  But you're not done.  Now you get to do the fun part:&lt;/p&gt;

&lt;h2&gt;
  
  
  Tinkering
&lt;/h2&gt;

&lt;p&gt;This is code that just begs to be played with!  Here are some places to start:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Change the texture loaded at the top of the program!  This makes a dramatic difference in the overall feel.  Try both of the ones above, or try making your own.  (Or ask an AI to make one for you.)&lt;/li&gt;
&lt;li&gt;Change the value of &lt;code&gt;n&lt;/code&gt; (number of diamonds) at the top.  4, 6, 8, and 10 all work well.  But push it till it breaks!  What happens if you use 5 or 7 or 30?&lt;/li&gt;
&lt;li&gt;Uncomment that &lt;code&gt;t = mouse&lt;/code&gt; line, and instead of animating with time, you can actually control the thing by moving your mouse around.  Play with the constants on that line.  Or, can you think of useful ways to combine time &lt;em&gt;and&lt;/em&gt; mouse position?&lt;/li&gt;
&lt;li&gt;Change the constants in the &lt;code&gt;uvs&lt;/code&gt; method.  Can you find values you like better?&lt;/li&gt;
&lt;li&gt;Throw a .mp3 or .ogg file of background music into your folder, and have your program load and play it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's such a small program, but so fun to mess with.  There's a good chance this will end up being one of the built-in demos in Mini Micro 2, but for now, only those sharp enough to follow this blog post will get to have that fun.  Enjoy!&lt;/p&gt;

</description>
      <category>miniscript</category>
      <category>minimicro</category>
      <category>programming</category>
      <category>graphics</category>
    </item>
    <item>
      <title>MiniScript Weekly News — April 22, 2026</title>
      <dc:creator>JoeStrout</dc:creator>
      <pubDate>Wed, 22 Apr 2026 23:31:54 +0000</pubDate>
      <link>https://dev.to/joestrout/miniscript-weekly-news-april-22-2026-3oep</link>
      <guid>https://dev.to/joestrout/miniscript-weekly-news-april-22-2026-3oep</guid>
      <description>&lt;h2&gt;
  
  
  Development Updates
&lt;/h2&gt;

&lt;p&gt;MiniScript 2 made a big step forward this week with the new &lt;code&gt;error&lt;/code&gt; type landing in the language. Errors can now be created with &lt;code&gt;err(...)&lt;/code&gt;, checked with &lt;code&gt;isa error&lt;/code&gt;, and even subtyped for more specific handling — a major improvement to one of MiniScript’s longtime weak spots.&lt;br&gt;&lt;br&gt;
Read more in the dev log and discussion thread: &lt;a href="https://github.com/JoeStrout/miniscript2/blob/main/notes/DEV_LOG.md" rel="noopener noreferrer"&gt;miniscript2 DEV_LOG&lt;/a&gt; and &lt;a href="https://forums.miniscript.org/d/617-an-error-handling-proposal-for-miniscript-20/6" rel="noopener noreferrer"&gt;error handling proposal&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There was also active design discussion around Mini Micro 2, especially whether display mode should keep using the current “magic assignment” style or move to a clearer function call like &lt;code&gt;setMode&lt;/code&gt;. Joe is leaning toward preserving compatibility while introducing the cleaner API, which seems like a thoughtful path for existing projects.&lt;br&gt;&lt;br&gt;
Related discussion: &lt;a href="https://discord.com/channels/" rel="noopener noreferrer"&gt;#minimicro-2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On the platform side, raylib-miniscript got more polished documentation and continues to evolve quickly. The API docs now include autogenerated sections for custom intrinsics, plus &lt;code&gt;http.post&lt;/code&gt;, &lt;code&gt;file.loadRaw&lt;/code&gt;, and &lt;code&gt;file.saveRaw&lt;/code&gt; support for practical app and game integration.&lt;br&gt;&lt;br&gt;
Repo: &lt;a href="https://github.com/JoeStrout/raylib-miniscript" rel="noopener noreferrer"&gt;raylib-miniscript&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Community Projects
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;maho_citrus&lt;/strong&gt; published a new utility library, &lt;strong&gt;utf-8-for-miniscript&lt;/strong&gt;, for environments (like GreyHack) without native UTF8 encode/decode support.&lt;br&gt;
Repo: &lt;a href="https://github.com/mahocitrus/utf-8-for-miniscript" rel="noopener noreferrer"&gt;utf-8-for-miniscript&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dat_One_Dev&lt;/strong&gt; updated a personal site with a growing collection of Mini Micro articles and courses, including multiple tutorial series. They also mentioned training a model on their site’s content, which is a neat sign that Mini Micro resources are becoming substantial enough to support discovery and reuse.&lt;br&gt;&lt;br&gt;
Links: &lt;a href="https://cbsemastery.in/tutorial/game-development/mini-micro" rel="noopener noreferrer"&gt;Mini Micro tutorial hub&lt;/a&gt;, &lt;a href="https://cbsemastery.in/course/tech/zero-to-game-dev" rel="noopener noreferrer"&gt;Zero to Game Dev&lt;/a&gt;, &lt;a href="https://cbsemastery.in/course/tech/learn-by-code" rel="noopener noreferrer"&gt;Learn by Code&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zaxabock&lt;/strong&gt; continued building out logic and automata projects in Mini Micro, including a logic engine template, finite-state automata work, and a Mini-Micro-based logic engine repo. These are the kind of deep, playful technical projects that show how flexible the language can be.&lt;br&gt;&lt;br&gt;
Repo: &lt;a href="https://github.com/Marutzo/Mini-Micro/blob/main/LogicEngineV4" rel="noopener noreferrer"&gt;LogicEngineV4&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Joe also shared progress on &lt;strong&gt;MiniClaw&lt;/strong&gt;, a tiny LLM agent built in Mini Micro for working with text files. There’s already a write-up on dev.to, and it’s a fun example of MiniScript being used for something modern while still keeping that compact, readable style.&lt;br&gt;&lt;br&gt;
Article: &lt;a href="https://dev.to/joestrout/miniclaw-a-tiny-llm-agent-for-mini-micro-4akf"&gt;MiniClaw: A Tiny LLM Agent for Mini Micro&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Code: &lt;a href="https://github.com/JoeStrout/miniclaw" rel="noopener noreferrer"&gt;miniclaw on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Discussion Highlights
&lt;/h2&gt;

&lt;p&gt;There were lots of helpful embedding questions this week, especially from &lt;strong&gt;Frib&lt;/strong&gt;, who is integrating MiniScript into a Unity-based project. Joe confirmed that &lt;code&gt;_foo&lt;/code&gt;-style globals are fine for host code, but suggested using intrinsics when possible for safer exposure to user scripts.  &lt;/p&gt;

&lt;p&gt;MiniScript 2 also sparked some classic language-design brainstorming, from better error propagation to compiler/source-location tracking and stack traces. It’s great to see the community stress-testing the new features and helping shape them before release.&lt;/p&gt;

&lt;p&gt;In Mini Micro circles, there’s ongoing interest in tutorials, docs, and even a community book built around expanding existing demos. That kind of “learn by remixing” idea feels very on-brand for MiniScript, and it’s wonderful to see people building on the demos as teaching material.&lt;/p&gt;

&lt;p&gt;Thanks for reading — and as always, keep sharing your experiments, tools, and little sparks of weirdness. See you next week!&lt;/p&gt;

&lt;h2&gt;
  
  
  Upcoming Game Jams
&lt;/h2&gt;

&lt;p&gt;These upcoming jams look like a great fit for Mini Micro:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/mini-choco-jam-1" rel="noopener noreferrer"&gt;Mini Choco Jam&lt;/a&gt;&lt;/strong&gt; (starts 2026-02-28 23:00:00) — A romance-focused, story-first jam with 2D-friendly requirements and no need for 3D or networking is an excellent fit for Mini Micro, especially for visual-novel or text-based games.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/trigamedev-monthly-game-jam-6" rel="noopener noreferrer"&gt;TriGameDev Monthly Game Jam #6&lt;/a&gt;&lt;/strong&gt; (starts 2026-04-24 21:00:00) — A 48-hour theme-driven jam about imprisonment and escape, with flexible interpretation and optional challenges like a Panopticon concept or dual perspectives, making it a strong fit for concise 2D retro-style games.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/portfolio-builders-jam-week-70" rel="noopener noreferrer"&gt;Portfolio Builders Jam - Week #70&lt;/a&gt;&lt;/strong&gt; (starts 2026-04-27 11:00:00) — A very flexible weekly jam focused on portfolio pieces, with room for small playable demos or even standalone art, audio, or code samples—great for polished 2D, retro-style projects and skill-building experiments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/deltarune-undertale-jam" rel="noopener noreferrer"&gt;Deltarune + Undertale Jam&lt;/a&gt;&lt;/strong&gt; — A strong fit for a retro 2D project: this jam centers on turn-based battles, dialogue-driven storytelling, and the emotional, quirky atmosphere that pairs perfectly with pixel art and simple RPG mechanics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/aeiougamejam" rel="noopener noreferrer"&gt;AEIOU GameJam 2026&lt;/a&gt;&lt;/strong&gt; (starts 2026-05-01 03:45:00) — A creativity-first jam with a clever twist theme, encouraging illusions, hidden mechanics, and unexpected gameplay that fits retro 2D experimentation perfectly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/oink-jam-2" rel="noopener noreferrer"&gt;Oink Jam 2 ($3000+ in Prizes)&lt;/a&gt;&lt;/strong&gt; (starts 2026-04-30 13:00:00) — A short, beginner-friendly four-day jam with flexible themes, light rules, and a welcoming community—great for making a polished retro-style 2D game fast.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>miniscript</category>
      <category>minimicro</category>
      <category>news</category>
      <category>programming</category>
    </item>
    <item>
      <title>MiniClaw: A Tiny LLM Agent for Mini Micro</title>
      <dc:creator>JoeStrout</dc:creator>
      <pubDate>Thu, 16 Apr 2026 18:13:14 +0000</pubDate>
      <link>https://dev.to/joestrout/miniclaw-a-tiny-llm-agent-for-mini-micro-4akf</link>
      <guid>https://dev.to/joestrout/miniclaw-a-tiny-llm-agent-for-mini-micro-4akf</guid>
      <description>&lt;p&gt;Agents are all the rage these days.  &lt;a href="https://code.claude.com/docs/en/overview" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt; was one of the first, and perhaps still the most heavily used, specialized for coding.  Then OpenClaw burst onto the scene, able to do all sorts of general computer-use things, and caused a &lt;a href="https://www.tomshardware.com/tech-industry/artificial-intelligence/openclaw-fueled-ordering-frenzy-creates-apple-mac-shortage-delivery-for-high-unified-memory-units-now-ranges-from-6-days-to-6-weeks" rel="noopener noreferrer"&gt;shortage of Mac Minis&lt;/a&gt;.  More recently, &lt;a href="https://github.com/nousresearch/hermes-agent" rel="noopener noreferrer"&gt;Hermes Agent&lt;/a&gt; is a common favorite, with over 93 thousand stars on GitHub.&lt;/p&gt;

&lt;p&gt;All of these agents work in fundamentally the same way.  A "harness" acts as both the main program for an LLM, controlling its context so that it always knows what it needs to know; and provides tools the LLM can use so that it can always do what it needs to do.&lt;/p&gt;

&lt;p&gt;I covered &lt;a href="https://dev.to/joestrout/use-gpt-3-in-mini-micro-1h63"&gt;accessing LLMs from Mini Micro&lt;/a&gt; back in 2022, and &lt;a href="https://dev.to/joestrout/combining-gpt-and-wolfram-alpha-ma2"&gt;again in 2023&lt;/a&gt;, so why don't we take it to the logical next step, and &lt;strong&gt;create an agent in Mini Micro&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwfsemryrv3042zq40a1v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwfsemryrv3042zq40a1v.png" alt="MiniClaw logo" width="512" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing MiniClaw
&lt;/h2&gt;

&lt;p&gt;Yesterday I sat down and created &lt;a href="https://github.com/JoeStrout/miniclaw" rel="noopener noreferrer"&gt;MiniClaw&lt;/a&gt;.  It consists mainly of three files:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://github.com/JoeStrout/miniclaw/blob/main/instructions.txt" rel="noopener noreferrer"&gt;instructions.txt&lt;/a&gt;: these are the instructions to the LLM&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/JoeStrout/miniclaw/blob/main/agent.ms" rel="noopener noreferrer"&gt;agent.ms&lt;/a&gt;: the main program, which invokes the LLM and manages its context&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/JoeStrout/miniclaw/blob/main/tools.ms" rel="noopener noreferrer"&gt;tools.ms&lt;/a&gt;: code for the tools the agent can use to read, write, and manipulate files&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So what can it do?  Well, MiniClaw can read any file accessible within Mini Micro, which means the &lt;code&gt;/sys&lt;/code&gt; disk, plus whatever minidisk or folder you have mounted as &lt;code&gt;/usr&lt;/code&gt; and &lt;code&gt;/usr2&lt;/code&gt;.  It can also write files (only) under &lt;code&gt;/usr/workspace&lt;/code&gt;.  So, similar to Claude Code or most other agents, you can use it to create and modify pretty much any kind of text file.  Or you can just ask it to explain and summarize things for you.  For example, I asked it "tell me about the pictures on the sys disk", and it wrote out a nice summary:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fie14mbok8oxwe5pv97qc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fie14mbok8oxwe5pv97qc.png" alt="Screen shot of /sys/pics summary" width="800" height="639"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On another occasion, I asked it to create a &lt;code&gt;.md&lt;/code&gt; (Markdown) file describing all the demos found in /sys/demo.  But then, in a later session, I decided that the document it created was too wordy, so I asked it to shorten it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9ycpkf1fyi66le5aftv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9ycpkf1fyi66le5aftv.png" alt="Screen shot of agent shortening DEMO_GUIDE.md" width="800" height="639"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The gray text gives us some hints as to what the code is doing: it shows when we call the LLM, how much data we get back as a response, and what tool the LLM is using (and why).&lt;/p&gt;

&lt;p&gt;Some tasks, like this one, take only a couple of tool calls.  Others take more.  The LLM will keep invoking tools, occasionally printing some messages for us about its work, until it figures the task is complete (or that it's unable to complete it).&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;The complete &lt;a href="https://github.com/JoeStrout/miniclaw/blob/main/agent.ms" rel="noopener noreferrer"&gt;agent.ms&lt;/a&gt; file is only 263 lines long, divided into 14 functions.  That's a bit too long to go over line by line here, but we'll hit the highlights, and I encourage you to check the source file for details.&lt;/p&gt;

&lt;p&gt;The big picture is this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each time we call the LLM, we give it our instructions (always the same), and the prompt (varies each turn).&lt;/li&gt;
&lt;li&gt;The prompt includes messages from the user, previous tool calls made by the agent, and the results of those calls -- all this stuff is called the "history".  It also includes the current task, so the LLM is clear on what it's supposed to be doing.&lt;/li&gt;
&lt;li&gt;The LLM gives us a response in JSON format: either a tool call, a question for the user, an intermediate message, or a final message (indicating it's done).&lt;/li&gt;
&lt;li&gt;We run any tool calls the LLM has asked for, and append the call and results to the history.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And that's pretty much it.  The main loop looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;currentUserInput&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gray&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;"==&amp;gt; "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"#00AA00"&lt;/span&gt;
            &lt;span class="n"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currentUserInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gray&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getResponse&lt;/span&gt;
        &lt;span class="n"&gt;respData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;respData&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
            &lt;span class="n"&gt;addToHistory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"**IMPORTANT:** You must format your response as a JSON object!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
            &lt;span class="n"&gt;handleResponse&lt;/span&gt; &lt;span class="n"&gt;respData&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;currentUserInput&lt;/code&gt; is the instruction the agent is working on; it's empty at the start of the run, or when the agent says it's finished.  So then we get more input from the user.  (Half the code above is just fiddling with the text color to be fancy.)&lt;/p&gt;

&lt;p&gt;Then we call &lt;code&gt;getResponse&lt;/code&gt; to get the LLM's response to the current context (instructions plus prompt as described above), and try to parse it as JSON.  Occasionally the LLM will forget to format its response as JSON; if that happens, we just add a stern reminder to the history (so the LLM will see it) and try again.  Otherwise, we call &lt;code&gt;handleResponse&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="n"&gt;handleResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;addToHistory&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"message"&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;printNicely&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
        &lt;span class="n"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lastMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"question"&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;printNicely&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
        &lt;span class="n"&gt;addToHistory&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"--- User response ---"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"--- End user response ---"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EOL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"finish"&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;printNicely&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
        &lt;span class="n"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currentUserInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"tool_call"&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;handleToolCall&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="n"&gt;addToHistory&lt;/span&gt; &lt;span class="s2"&gt;"ERROR: invalid response type """&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
          &lt;span class="s2"&gt;"""; must be ""message"", ""question"", ""finish"", or ""tool_call""."&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt;  
&lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've removed some of the error handling and text-coloring above for clarity, but this is the gist of it.  We just switch based on the &lt;code&gt;type&lt;/code&gt; of response we got from the LLM; it should be one of the four types we put in the instructions.  Again, note that when we want to give the LLM more information -- like the user's response to a question -- we just add it to the history.  &lt;/p&gt;

&lt;p&gt;Let's talk about that &lt;code&gt;addHistory&lt;/code&gt; method a moment.  Its job is mainly just to append the given string(s) to a list of strings, so they can be included in the context.  But for any agent, context management is very important!  Too much context burns through tokens, and degrades LLM performance.  So, our &lt;code&gt;addHistory&lt;/code&gt; method limits how much history it remembers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="n"&gt;addToHistory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;
    &lt;span class="n"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;historyLen&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;historyLen&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
        &lt;span class="n"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;historyLen&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;
        &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pull&lt;/span&gt;  &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;discard&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Probably the next most important function is &lt;code&gt;promptInput&lt;/code&gt;, which calculates the "prompt" part of the context -- the part that varies from turn to turn.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="n"&gt;promptInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;
    &lt;span class="nf"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt; &lt;span class="s2"&gt;"# Task/User Input"&lt;/span&gt;
    &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt; &lt;span class="n"&gt;currentUserInput&lt;/span&gt;
    &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
    &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt; &lt;span class="s2"&gt;"# Current State"&lt;/span&gt;
    &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt; &lt;span class="s2"&gt;"Date/time: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
        &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt; &lt;span class="s2"&gt;"# Recent history"&lt;/span&gt;
        &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EOL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple, right?  It's just composing a bit of Markdown calling out the current user input, the current state (which for this version of MiniClaw, is only the date/time), and the history.  This stuff is appended to the static &lt;a href="https://github.com/JoeStrout/miniclaw/blob/main/instructions.txt" rel="noopener noreferrer"&gt;instructions&lt;/a&gt;, and sent to the LLM.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tools
&lt;/h2&gt;

&lt;p&gt;The functions above are going to be pretty standard for any agent.  What determines what the agent actually &lt;em&gt;does&lt;/em&gt; are the tools and instructions provided to it.  In MiniClaw, the tools are separated out into their own file, &lt;a href="https://github.com/JoeStrout/miniclaw/blob/main/tools.ms" rel="noopener noreferrer"&gt;tools.ms&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This file begins with some little helper functions: &lt;code&gt;err&lt;/code&gt;, &lt;code&gt;errMissingArg&lt;/code&gt;, and &lt;code&gt;okResult&lt;/code&gt;, which all generate little result maps to be returned to the LLM; plus &lt;code&gt;resolvePath&lt;/code&gt; and &lt;code&gt;isWriteable&lt;/code&gt;, which help the tool code deal with files properly.  Then, it has a function for each tool:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;list_files&lt;/li&gt;
&lt;li&gt;read_file&lt;/li&gt;
&lt;li&gt;head_file&lt;/li&gt;
&lt;li&gt;tail_file&lt;/li&gt;
&lt;li&gt;write_file&lt;/li&gt;
&lt;li&gt;delete_file&lt;/li&gt;
&lt;li&gt;move_file&lt;/li&gt;
&lt;li&gt;make_dir&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these takes a map containing arguments (which we've gotten by parsing the JSON from the LLM), does its thing if it can, and then returns a map of results -- usually from one of the &lt;code&gt;err&lt;/code&gt; functions, or from &lt;code&gt;okResult&lt;/code&gt;, with details (like the path of the affected file) added in.  This lets the LLM know whether its attempt to use a tool was successful.&lt;/p&gt;

&lt;p&gt;As an example, let's look at &lt;code&gt;head_file&lt;/code&gt;, whose job it is to return the first so-many lines of a text file.  This tool is described in the instructions file as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"head_file"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"desc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Return the first n lines of a UTF-8 file.  Use this to examine large or unknown text files."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"lines"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"int"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We give the agent the name of the tool, a description including advice on when to use it, and info on the expected arguments.  So, the actual MiniScript function is expecting its &lt;code&gt;args&lt;/code&gt; map to contain &lt;code&gt;"path"&lt;/code&gt; and &lt;code&gt;"lines"&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="n"&gt;head_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;errMissingArg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Invalid path `"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"`"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"lines"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readLines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;okResult&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EOL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It pulls those arguments out of the map, does some simple validation on them, reads the file, and returns the requested data as the "content" string of the result map.&lt;/p&gt;

&lt;p&gt;The other tools all work in a similar fashion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying it out
&lt;/h2&gt;

&lt;p&gt;You can download the MiniClaw source files from &lt;a href="https://github.com/JoeStrout/miniclaw" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, but in order to access the LLM (gpt-5.4-nano) it uses, you'll need to set up an API key:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Log in to &lt;a href="https://platform.openai.com/" rel="noopener noreferrer"&gt;platform.openai.com&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;API Keys&lt;/strong&gt; on the left.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create new secret key&lt;/strong&gt;, give it a name like "MiniClaw", and copy the key it shows you.&lt;/li&gt;
&lt;li&gt;Paste that into a file called &lt;code&gt;api_key.secret&lt;/code&gt; next to &lt;code&gt;agent.ms&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then you can mount that directory in Mini Micro, and &lt;code&gt;run "agent"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As for cost, I wouldn't worry about it too much... I used this thing a &lt;em&gt;lot&lt;/em&gt; yesterday and today while developing it, and it cost under 40 cents.  &lt;code&gt;gpt-5.4-nano&lt;/code&gt; is pretty cheap, and seems smart enough for everything I've tried so far.&lt;/p&gt;

&lt;h2&gt;
  
  
  Taking it further
&lt;/h2&gt;

&lt;p&gt;This is where it gets fun: &lt;strong&gt;add your own tools!&lt;/strong&gt;  This version of MiniClaw only does basic file creation/manipulation, as you can see from the tool list above.  But you could make your own MiniClaw do anything Mini Micro is capable of.  Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Display a picture&lt;/li&gt;
&lt;li&gt;Play a sound (or series of sounds — making music?)&lt;/li&gt;
&lt;li&gt;Launch a program&lt;/li&gt;
&lt;li&gt;Access the web or web services via &lt;code&gt;http&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Do math&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/joestrout/combining-gpt-and-wolfram-alpha-ma2"&gt;Call Wolfram Alpha&lt;/a&gt; for help&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And adding more tools is pretty easy: just create a function for it in &lt;code&gt;tools.ms&lt;/code&gt;, and add a description of the tool to &lt;code&gt;instructions.txt&lt;/code&gt;.  That's it; the LLM should be smart enough to invoke it when the time is right.&lt;/p&gt;

&lt;p&gt;If you want to switch to a different LLM provider (here's a handy guide to &lt;a href="https://get-hermes.ai/models/" rel="noopener noreferrer"&gt;AI models for Hermes&lt;/a&gt;), you might have to adjust or rewrite the &lt;code&gt;getResponse&lt;/code&gt; function, which formats the input for the LLM and then digs the actual response text out of the JSON package it's buried in.  But this is totally doable.  You could even run a local LLM, if that's your thing, and connect to it at &lt;code&gt;localhost&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There's also a lot that could be done to improve MiniClaw's user interface.  Right now it just prints (albeit with pretty colors, supporting basic markdown commonly used by LLMs) stuff to the text display as it comes in.  You could instead make a structured display, keeping the current task up top, showing some info about the context on the side, and neatly formatted responses (perhaps drawn with proportional fonts into a PixelDisplay) below.&lt;/p&gt;

&lt;p&gt;My main goal with MiniClaw was to create an agent that is simple and small enough (and MiniScript enough!) to be easily understood and modified.  And the great thing is, Mini Micro is a safe sandbox environment, assuming you only mount minidisks or folders you aren't worried about.  So go nuts and have fun!&lt;/p&gt;

</description>
      <category>miniscript</category>
      <category>minimicro</category>
      <category>agents</category>
      <category>programming</category>
    </item>
    <item>
      <title>MiniScript Weekly News — Apr 9, 2026</title>
      <dc:creator>JoeStrout</dc:creator>
      <pubDate>Thu, 09 Apr 2026 23:01:27 +0000</pubDate>
      <link>https://dev.to/joestrout/miniscript-weekly-news-apr-9-2027-456o</link>
      <guid>https://dev.to/joestrout/miniscript-weekly-news-apr-9-2027-456o</guid>
      <description>&lt;p&gt;&lt;em&gt;Correction: the headline originally said 2027... now corrected, before the temporal police could find me.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Development Updates
&lt;/h2&gt;

&lt;p&gt;MiniScript 2 saw a nice round of progress this week, with function metadata support being the big new feature. The new &lt;code&gt;info(@func)&lt;/code&gt; intrinsic can now expose a function’s name, note, and params: &lt;a href="https://github.com/JoeStrout/miniscript2" rel="noopener noreferrer"&gt;JoeStrout/miniscript2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On the raylib side, Joe added &lt;code&gt;http.post&lt;/code&gt; support, plus &lt;code&gt;file.loadRaw&lt;/code&gt; and &lt;code&gt;file.saveRaw&lt;/code&gt; for binary data. That should make it easier to talk to REST services and handle non-text assets in &lt;a href="https://github.com/JoeStrout/raylib-miniscript" rel="noopener noreferrer"&gt;raylib-miniscript&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The alchemy UI toolkit keeps taking shape too. Joe shared the first functional UI demo, and the dev log notes working progress on buttons, static text, spinner controls, and a coordinate-system-agnostic &lt;code&gt;Rect&lt;/code&gt; class: &lt;a href="https://github.com/JoeStrout/alchemy-ui" rel="noopener noreferrer"&gt;alchemy-ui&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Community Projects
&lt;/h2&gt;

&lt;p&gt;A big update for the Mini Micro showcase: Joe built a tool to scan itch.io pages and auto-fill missing entries, then expanded the catalog to 80 programs. If you’ve got a MiniScript or Mini Micro game or demo to share, he’s still asking for links to add to the catalog: &lt;a href="https://miniscript.org/MiniMicro/#programs" rel="noopener noreferrer"&gt;Mini Micro programs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Several community-made itch.io projects were highlighted this week. If you want to browse or share them, check out &lt;a href="https://dslower.itch.io/march-of-the-zomblez" rel="noopener noreferrer"&gt;March of the Zomblez&lt;/a&gt;, &lt;a href="https://dslower.itch.io/two-souls" rel="noopener noreferrer"&gt;Two Souls&lt;/a&gt;, and &lt;a href="https://dslower.itch.io/going-viral" rel="noopener noreferrer"&gt;Going Viral&lt;/a&gt;, all by &lt;a class="mentioned-user" href="https://dev.to/dslower"&gt;@dslower&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Other community submissions that came up include &lt;a href="https://dat-one-dev.itch.io/chaturanga" rel="noopener noreferrer"&gt;Chaturanga&lt;/a&gt;, and &lt;a href="https://dat-one-dev.itch.io/color-crash" rel="noopener noreferrer"&gt;Color Crash&lt;/a&gt; by @dat-one-dev, and &lt;a href="https://bibleclinger.itch.io/color-smash-tank" rel="noopener noreferrer"&gt;Color Smash Tank&lt;/a&gt;, and &lt;a href="https://bibleclinger.itch.io/space-fighter-85" rel="noopener noreferrer"&gt;Space Fighter 85&lt;/a&gt; by &lt;a class="mentioned-user" href="https://dev.to/bibleclinger"&gt;@bibleclinger&lt;/a&gt;. It’s great to see so many jam entries and experiments getting some love.&lt;/p&gt;

&lt;h2&gt;
  
  
  Discussion Highlights
&lt;/h2&gt;

&lt;p&gt;There was some fun brainstorming around a possible physical Mini Micro device. Ideas ranged from a compact keyboard (with arrow keys a must-have), to SD card storage, to even a rounded case with arcade buttons or bundled gamepads.&lt;/p&gt;

&lt;p&gt;In the MiniScript 2 forum discussion, Joe floated the new &lt;code&gt;info&lt;/code&gt; intrinsic before implementing it, and got a warm response. It’s a promising direction for making introspection and tooling much more pleasant in MS2: &lt;a href="https://forums.miniscript.org/d/618-accessing-function-metadata" rel="noopener noreferrer"&gt;forum thread&lt;/a&gt;.  There was also more discussion of a new &lt;a href="https://forums.miniscript.org/d/617-an-error-handling-proposal-for-miniscript-20/5" rel="noopener noreferrer"&gt;error handling system&lt;/a&gt;, which may see implementation next week — stay tuned!&lt;/p&gt;

&lt;h2&gt;
  
  
  Game Jam Notes
&lt;/h2&gt;

&lt;p&gt;This week’s jam theme was “one light source,” and the community quickly pointed folks toward the &lt;code&gt;/sys/demo/2dVis&lt;/code&gt; demo and the &lt;a href="https://itch.io/jam/micro-jam-056" rel="noopener noreferrer"&gt;micro-jam-056&lt;/a&gt; page. Joe also pointed out Florian’s earlier gem, &lt;a href="https://florian-castel.itch.io/in-my-bubble" rel="noopener noreferrer"&gt;In My Bubble&lt;/a&gt;, as another brilliant example of what can be done with a focused idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  From the Community
&lt;/h2&gt;

&lt;p&gt;BibleClinger also kicked around an interesting idea for a community-authored MiniScript book, where experienced users could each contribute a chapter. That sounds like a wonderful way to share knowledge and celebrate the depth of the community.&lt;/p&gt;

&lt;p&gt;Thanks for reading, and keep those MiniScript projects coming!&lt;/p&gt;

&lt;h2&gt;
  
  
  Upcoming Game Jams
&lt;/h2&gt;

&lt;p&gt;These upcoming jams look like a great fit for Mini Micro:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/impressions-composing-jam-season-4-melody-jam" rel="noopener noreferrer"&gt;Impressions Composing Jam Season 4: Melody Jam&lt;/a&gt;&lt;/strong&gt; (starts 2026-04-10 22:00:00) — A focused music jam centered on crafting a memorable melody from a provided theme, with clear, simple requirements and plenty of room for creative interpretation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/kbgames-game-boys-chillin-on-a-grid-jam" rel="noopener noreferrer"&gt;KBGames "Game Boys Chillin' On A Grid" Jam&lt;/a&gt;&lt;/strong&gt; (starts 2026-04-13 22:18:30) — A super flexible jam centered on Game Boy vibes, chill/cozy energy, and grid-based ideas—an excellent match for retro pixel art, tile grids, and simple 2D gameplay.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/smashthefash" rel="noopener noreferrer"&gt;Games Transformed 2026 - 'Smash The Fash' Game Jam&lt;/a&gt;&lt;/strong&gt; — A politically charged jam inviting all kinds of antifascist games, from activist tactics and mutual aid to surreal puzzles and hopeful visions of solidarity, with room for serious, playful, or experimental takes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/dtj36-25" rel="noopener noreferrer"&gt;Devs That Jam 36-hour Challenge #25: Anniversary Edition&lt;/a&gt;&lt;/strong&gt; (starts 2026-04-25 10:00:00) — A fast, beginner-friendly 36-hour jam with a community-chosen theme, flexible use of pre-made assets, and judging that rewards fun, clarity, visuals, audio, and polish.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/panafrican2026" rel="noopener noreferrer"&gt;Pan-African Jam with Lagos Games Week 2026&lt;/a&gt;&lt;/strong&gt; (starts 2026-04-23 22:00:00) — A highly welcoming, fully virtual jam centered on creativity, cross-border collaboration, and game discovery—open to any genre and especially appealing for developers who want a community-driven event with real visibility and prizes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/portfolio-builders-jam-week-69" rel="noopener noreferrer"&gt;Portfolio Builders Jam - Week #69&lt;/a&gt;&lt;/strong&gt; (starts 2026-04-20 11:00:00) — A very flexible weekly jam focused on building portfolio pieces, with room for playable demos, polished art, audio, or code showcases—ideal if you want to make something small, skill-focused, and retro-friendly.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>miniscript</category>
      <category>minimicro</category>
      <category>news</category>
      <category>programming</category>
    </item>
    <item>
      <title>The Master Algorithm</title>
      <dc:creator>JoeStrout</dc:creator>
      <pubDate>Tue, 07 Apr 2026 17:03:14 +0000</pubDate>
      <link>https://dev.to/joestrout/the-master-algorithm-2oie</link>
      <guid>https://dev.to/joestrout/the-master-algorithm-2oie</guid>
      <description>&lt;p&gt;In 2015, a book by AI researcher Pedro Domingos came out called &lt;a href="https://www.hachettebookgroup.com/titles/pedro-domingos/the-master-algorithm/9780465061921" rel="noopener noreferrer"&gt;&lt;em&gt;The Master Algorithm: How the Quest for the Ultimate Learning Machine Will Remake Our World&lt;/em&gt;&lt;/a&gt;.  The author explored the "five tribes" of artificial intelligence (AI), and how each one might develop into the "Master Algorithm" of intelligence itself — the algorithm that could learn to do virtually anything humans and other animals can learn to do.  The five tribes are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;inductive reasoning&lt;/li&gt;
&lt;li&gt;connectionism (aka neural networks)&lt;/li&gt;
&lt;li&gt;evolutionary computation&lt;/li&gt;
&lt;li&gt;Bayesian networks&lt;/li&gt;
&lt;li&gt;analogical modelling&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This was ten years ago.  It was very much not obvious, at that time, which of these — or what combination of them — or even whether something entirely different — might turn out to be the Master Algorithm.&lt;/p&gt;

&lt;p&gt;But it's clear now.  I'm calling it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Master Algorithm is neural networks.
&lt;/h2&gt;

&lt;p&gt;It turns out, the master algorithm is connectionism.  A neural network, when it's big enough, structured appropriately, and trained on enough data, can do it all: language, reasoning, translation, programming, answering questions, following instructions, understanding pictures and videos, &lt;em&gt;generating&lt;/em&gt; pictures and videos, solving complex math problems, and on and on.&lt;/p&gt;

&lt;p&gt;The goalpost-movers like to pick at each of those and say "yes, but, it doesn't do &lt;em&gt;this thing&lt;/em&gt; very well and &lt;em&gt;that thing&lt;/em&gt; doesn't really count because of the following hand-wavy reasons."  But if you read the book, it's obvious that everything we all take for granted nowadays was far-off science fiction in 2015.  AI researchers literally &lt;em&gt;dreamed&lt;/em&gt; of machines that could do half of what ChatGPT or Claude does before breakfast, or what &lt;a href="https://deepmind.google/models/gemma/" rel="noopener noreferrer"&gt;Gemma&lt;/a&gt; does running locally &lt;em&gt;on my cell phone&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The other four approaches?  They still have their uses in specialized cases, and they're still doing more or less what they were doing in 2015.  None of them turned out to be able to do pretty much everything, nor did any of them turn out to get a lot smarter and more capable when you simply scale them up.  (Indeed, most of them explode when you try to do that.)&lt;/p&gt;

&lt;h2&gt;
  
  
  This was not obvious.
&lt;/h2&gt;

&lt;p&gt;I've considered myself a "connectionist" since middle school, when I did my first neural networks science fair project.  (Fun fact: my junior year of high school, I went to the International Science &amp;amp; Engineering Fair with my novel algorithm for recursive neural networks, and won the U.S. Army's top prize and a 2-week trip to Japan... and I still practice Japanese every day today.)&lt;/p&gt;

&lt;p&gt;But I did not expect neural networks, on their own, to be the master algorithm.  Hardly anyone expected that.  Neural networks were very good at perception (identifying/classifying patterns in raw data), and at prediction (which is really the same thing).  But it was not at all clear how a neural network would ever be able to carry on a real conversation, reason through problems, etc.  I thought you'd probably need some combination of a neural network and symbolic (e.g. inductive) reasoning.&lt;/p&gt;

&lt;p&gt;I mean, of course our own brains are made entirely of neurons.  So as a raw proof of concept, yes, it was obvious that neural networks &lt;em&gt;could&lt;/em&gt; do it all.  But I assumed that evolution had equipped our brains with very specialized circuits for language, logic, &amp;amp; reasoning, and that you would not get these facilities out of a general-purpose neural network running some general-purpose learning algorithm.&lt;/p&gt;

&lt;p&gt;But nope.  It turns out that you do get exactly that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The moment this happened.
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://mitpress.mit.edu/9780262049955/what-is-intelligence/" rel="noopener noreferrer"&gt;What Is Intelligence?&lt;/a&gt; (2025), Google AI researcher Blaise Agüera y Arcas writes about the moment that I would consider the pivotal turning point.  They were training a neural network to predict the next word after a bunch of previous words, so that they could make a better autocomplete function.  This is a natural and obvious application of neural networks' strengths, and because people are terrible (and terribly slow) at typing messages on cell phones, better autocomplete was worth investing in.&lt;/p&gt;

&lt;p&gt;But they had trained a very &lt;em&gt;large&lt;/em&gt; model.  On a very large amount of text.  And the model started talking to them.  They discovered that they could ask it questions, and it would answer.  The could even ask it things that had certainly never been in its training data — like, how to translate a specific made-up sentence from one language to another — and, with a bit of urging, it would do it, and do it correctly.&lt;/p&gt;

&lt;p&gt;I can only imagine the complex emotions they must have felt at that moment.  They realized what some deniers still haven't understood today: a sufficiently deep language model isn't just parroting its training data; it is &lt;em&gt;understanding&lt;/em&gt; and responding with real intelligence.&lt;/p&gt;

&lt;p&gt;Now, a raw language model, without additional reinforcement learning to shape its behavior, is rather schizophrenic; it slips in and out of different personalities, carries on both halves of a conversation all by itself, etc.  Few people get to interact with a network in this state, because (as Agüera y Arcas explains) it is quite disturbing.  But the behavioral training fixes that, improves reasoning skills, and gives us the AI coworkers we use daily today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prediction, all the way down
&lt;/h2&gt;

&lt;p&gt;So how is this possible?  How &lt;em&gt;does&lt;/em&gt; a neural network act as the master algorithm?&lt;/p&gt;

&lt;p&gt;It turns out, intelligence is fundamentally just prediction.  It's prediction at all levels: cortical columns predict what their next inputs are going to be; our visual system learns to predict what the world is going to look like in the next moment; our language system learns to predict the next word we are going to hear or say.  In a literal sense, LLMs like Claude do nothing but predict the next token, based on all the tokens in the session so far.&lt;/p&gt;

&lt;p&gt;But prediction is all you need.  When Claude generates a string of tokens working through a problem, it is not fundamentally different from the string of thoughts (mostly in the form of words) that you and I would generate working through a similar problem.  Reinforcement learning biases the network so that these strings of predictions tend to go in &lt;em&gt;useful&lt;/em&gt; directions, ones that were successful during training.  In humans (unlike current LLMs), training is an ongoing process, so we are constantly tweaking our own neural networks to make these trains of predictions more successful.  But fundamentally, moment to moment, it's still just predicting the next thing, at every level of the network.&lt;/p&gt;

&lt;p&gt;We now understand that some problems are what Agüera y Arcas calls &lt;strong&gt;AI complete&lt;/strong&gt;.  Solving an AI complete problem requires actually understanding the world that the problem represents; no shallower, surface-features sort of representation can do it.  Examples of AI complete problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Next word prediction: it's impossible to predict the next word in a string of text with any accuracy unless you understand what those words actually mean.&lt;/li&gt;
&lt;li&gt;Language translation: ditto; you can't just look up individual words in a dictionary.&lt;/li&gt;
&lt;li&gt;Video next-frame prediction: requires understanding what the objects in the video are, how they behave, what gravity does, etc.&lt;/li&gt;
&lt;li&gt;Picture or video captioning: requires understanding both what the objects are, and how they are referred to in language.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A sufficiently big neural network, given sufficient training data, will eventually solve almost any problem you give it.  So if you give it an AI complete problem, which can only be solved by understanding the world... it will understand the world.&lt;/p&gt;

&lt;p&gt;Is its understanding perfect?  Of course not.  But then, that's &lt;a href="https://spyscape.com/article/illuminati-qanon-lizard-people-and-other-conspiracy-theories" rel="noopener noreferrer"&gt;true of humans&lt;/a&gt;, too.&lt;/p&gt;

&lt;h2&gt;
  
  
  So that's it for AI research
&lt;/h2&gt;

&lt;p&gt;Haha, just kidding!  LLMs are &lt;a href="https://www.noemamag.com/artificial-general-intelligence-is-already-here/" rel="noopener noreferrer"&gt;general intelligence&lt;/a&gt;, and already &lt;a href="https://scitechdaily.com/for-the-first-time-chatgpt-has-solved-an-unproven-math-problem-in-geometry/" rel="noopener noreferrer"&gt;smarter than humans&lt;/a&gt; in some ways.  But they have glaring limitations: in particular, "training" and "inference" are completely separated processes.  The LLMs we use every day do not learn from experience, except insofar as that experience can be crammed into the context window.  &lt;/p&gt;

&lt;p&gt;The addition of &lt;a href="https://cameronrwolfe.substack.com/p/rl-continual-learning" rel="noopener noreferrer"&gt;continual learning&lt;/a&gt; will allow them to get better at tasks with practice, just like we do.  But there are a lot of tricky open questions: how do you avoid forgetting too much old knowledge while acquiring new knowledge?  How do you keep the AI's personality stable over time?  And how do you even learn this new stuff quickly enough to matter?&lt;/p&gt;

&lt;p&gt;Moreover, there really &lt;em&gt;are&lt;/em&gt; things that neural networks are terrible at.  And ironically, those are exactly the things that most AI research has been doing for the last 80 years: game playing, deep search, complex logical reasoning, optimization, etc.  We (and other neural networks) can &lt;em&gt;approximately&lt;/em&gt; do those things, with a lot of training and practice, but there are classical (GOFAI or "Good Old-Fashioned AI") algorithms that do them much better and faster.  We'll continue to need progress on these, and on ways to integrate them with neural networks to get the best of both worlds.&lt;/p&gt;

&lt;p&gt;This is just a random sampling; in fact there are many, many open research directions.  We've cracked the code of intelligence; we know now what intelligence &lt;em&gt;is&lt;/em&gt; and how to produce it in a machine.  But that's only the tip of the iceberg.  It is the beginning of wisdom, not the end.&lt;/p&gt;

&lt;p&gt;The next 5 or 10 years are going to be very exciting.  Hold on tight!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>discuss</category>
      <category>science</category>
    </item>
    <item>
      <title>MiniScript Weekly News — Apr 1, 2026</title>
      <dc:creator>JoeStrout</dc:creator>
      <pubDate>Thu, 02 Apr 2026 00:36:09 +0000</pubDate>
      <link>https://dev.to/joestrout/miniscript-weekly-news-apr-1-2026-7b1</link>
      <guid>https://dev.to/joestrout/miniscript-weekly-news-apr-1-2026-7b1</guid>
      <description>&lt;h2&gt;
  
  
  Development Updates
&lt;/h2&gt;

&lt;p&gt;Work on &lt;strong&gt;MiniScript 2&lt;/strong&gt; continues to pick up speed, and the team shared that a working &lt;strong&gt;REPL&lt;/strong&gt; is now in place in both C# and C++. The latest dev log also mentions a refactor to better preserve globals across REPL entries, plus a fix for multi-function REPL handling and Ctrl-D to exit.&lt;br&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/JoeStrout/miniscript2" rel="noopener noreferrer"&gt;miniscript2&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the &lt;strong&gt;raylib-miniscript&lt;/strong&gt; side, there were a few useful updates landed this week: &lt;code&gt;resourceCounts&lt;/code&gt; now reports loaded resources, &lt;code&gt;FileHandle&lt;/code&gt; was added, and the text mutation intrinsics were refreshed with new &lt;code&gt;...Alloc&lt;/code&gt; variants. These changes should help with debugging leaks and keeping the bindings in step with newer raylib APIs.  &lt;/p&gt;

&lt;p&gt;An important change this week: raylib-miniscript now requires scripts to call &lt;code&gt;rl.InitWindow&lt;/code&gt; themselves instead of the engine doing it behind the scenes. Joe also added a direct MiniScript translation of the raylib high-DPI demo, which should make window setup clearer for developers who want to support high-res displays like the Apple Retina or MacBook display.&lt;br&gt;
GitHub: &lt;a href="https://github.com/JoeStrout/raylib-miniscript" rel="noopener noreferrer"&gt;raylib-miniscript&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Community Projects
&lt;/h2&gt;

&lt;p&gt;Joe shared a fresh tutorial on Dev.to: &lt;strong&gt;&lt;a href="https://dev.to/joestrout/create-a-maze-game-in-mini-micro-4j32"&gt;Create a Maze Game in Mini Micro&lt;/a&gt;&lt;/strong&gt;. It walks through turning the built-in maze demo into a custom game, and it’s a great example of how approachable Mini Micro development can be for new creators.&lt;/p&gt;

&lt;p&gt;Also in the Mini Micro ecosystem, &lt;strong&gt;Dat_One_Dev&lt;/strong&gt; highlighted the possibility of listing Mini Micro, Soda, and the upcoming Raylib project on a game-distribution site so community-made games can be more discoverable. That came up in the context of showcasing user projects like &lt;strong&gt;SPACE EVADERS&lt;/strong&gt; by community member DSlower — always exciting to see MiniScript games getting attention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Discussion Highlights
&lt;/h2&gt;

&lt;p&gt;There was a lively MiniScript 2 design discussion about language minimalism, type hints, and the role of &lt;code&gt;@&lt;/code&gt; in function references. Joe reaffirmed the project’s “mini” philosophy while also noting that assertions can already help catch errors today, and might even inform future optimizations.  But he also outlined a proposal for a more extensive error-handling system based on a new "error" data type, with strict rules about how it interacts with other types.&lt;/p&gt;

&lt;p&gt;The community also brainstormed about broader tooling and extensibility, including ideas for terminal color customization, configurable prompts, and even an eventual FFI-style integration for things like Ollama or other external libraries. Joe’s response was encouraging: it’s a neat direction for “eventually,” though beyond the language core for now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mini Micro &amp;amp; Raylib Chatter
&lt;/h2&gt;

&lt;p&gt;A few Raylib-related debugging threads were especially productive this week. The team tracked down an HDPI issue to window initialization order, and Joe wrapped up the fix by moving the config flag setup earlier and clarifying that scripts should initialize the window themselves.&lt;/p&gt;

&lt;p&gt;There was also some good old-fashioned collaborative troubleshooting on the breakout demo and audio init behavior, with fixes landing for Windows build issues and asset handling along the way. It’s great to see community members jumping in with bug reports, PRs, and real-world testing.&lt;/p&gt;

&lt;p&gt;Until next week, happy scripting!&lt;/p&gt;

&lt;h2&gt;
  
  
  Upcoming Game Jams
&lt;/h2&gt;

&lt;p&gt;These upcoming jams look like a great fit for Mini Micro:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/pixelforge" rel="noopener noreferrer"&gt;Pixel Forge JAM 2026 [PRIZES]&lt;/a&gt;&lt;/strong&gt; (starts 2026-04-18 07:30:00) — A strong fit for retro, pixel-art, or tile-based ideas, with a 7-day format that encourages fast, creative experimentation and accessible gameplay.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/micro-jam-057" rel="noopener noreferrer"&gt;Micro Jam 057: Symmetry ($400 Prizes)&lt;/a&gt;&lt;/strong&gt; (starts 2026-04-18 01:00:00) — A strong fit for 2D pixel, tile, or text-driven ideas, with the symmetry theme inviting clever puzzle, action, or visual-design twists and a forgiving jam format that’s easy to jump into.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/smashthefash" rel="noopener noreferrer"&gt;Games Transformed 2026 - 'Smash The Fash' Game Jam&lt;/a&gt;&lt;/strong&gt; — A politically charged jam inviting all kinds of antifascist games, from activist tactics and mutual aid to surreal puzzles and hopeful visions of solidarity, with room for serious, playful, or experimental takes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/reagent-game-jam-2026" rel="noopener noreferrer"&gt;Reagent Game Jam 2026&lt;/a&gt;&lt;/strong&gt; (starts 2026-04-10 07:00:00) — An accessible beginner-friendly jam with lots of freedom in tools and style, making it a great chance to build a creative 2D game in a low-pressure setting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/gaminglejam" rel="noopener noreferrer"&gt;GamingleJam&lt;/a&gt;&lt;/strong&gt; (starts 2026-04-10 17:14:37) — A low-pressure, any-engine jam that encourages collaboration and welcomes solo creators too, making it a great excuse to build a stylish 2D game and maybe team up with artists, musicians, or writers along the way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://itch.io/jam/avantbeetles-protest-games-jam" rel="noopener noreferrer"&gt;AvantBeetle's Protest Games Jam&lt;/a&gt;&lt;/strong&gt; (starts 2026-04-04 04:00:00) — An open-ended protest-themed jam that welcomes experimental, symbolic, and small-scale games, making it a great chance to turn a strong message into a creative playable statement.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>miniscript</category>
      <category>minimicro</category>
      <category>news</category>
    </item>
    <item>
      <title>Create a Maze Game in Mini Micro</title>
      <dc:creator>JoeStrout</dc:creator>
      <pubDate>Wed, 01 Apr 2026 18:17:19 +0000</pubDate>
      <link>https://dev.to/joestrout/create-a-maze-game-in-mini-micro-4j32</link>
      <guid>https://dev.to/joestrout/create-a-maze-game-in-mini-micro-4j32</guid>
      <description>&lt;p&gt;In the classic arcade days, there were a lot of maze games.  That's because mazes are &lt;em&gt;fun!&lt;/em&gt;  They give the player interesting choices at every point, and force you to look and plan globally while also paying attention to local hazards and opportunities.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9z79nqu3difu85jnh1q0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9z79nqu3difu85jnh1q0.png" alt="Maze game screen shots" width="800" height="781"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Clockwise from top left: Pac-Man (1980), Wizard of Wor (1980), Nibbler (1982), Mouse Trap (1981).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You don't see this as much in modern games.  A 3D shooter or adventure game might be in a maze, but with a first-person view, you miss out on that global planning because you can only see what's directly in front of you.&lt;/p&gt;

&lt;p&gt;All of which is to say, &lt;strong&gt;people should make more maze games.&lt;/strong&gt;  And &lt;a href="https://miniscript.org/MiniMicro" rel="noopener noreferrer"&gt;Mini Micro&lt;/a&gt; is a great environment for making them.  Let's make one right now!&lt;/p&gt;
&lt;h2&gt;
  
  
  Start with the maze demo
&lt;/h2&gt;

&lt;p&gt;Mini Micro has a handy demo of displaying and editing a maze at &lt;code&gt;/sys/demo/maze&lt;/code&gt;.  Start by running that.  You can run it online &lt;a href="https://miniscript.org/MiniMicro/index.html?cmd=run%20%22%2Fsys%2Fdemo%2Fmaze%22" rel="noopener noreferrer"&gt;here&lt;/a&gt;, but better to &lt;a href="https://miniscript.org/MiniMicro/index.html#download" rel="noopener noreferrer"&gt;download&lt;/a&gt; Mini Micro if you don't have it already, and then just type &lt;code&gt;run "/sys/demo/maze"&lt;/code&gt; and press Enter.  The demo looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs7kxovlmfxg83oxjvrgx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs7kxovlmfxg83oxjvrgx.png" alt="Screen shot of /sys/demo/maze in Mini Micro" width="800" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can click and drag with the mouse to edit the maze.  It's important to understand that you're adding and deleting &lt;em&gt;wall corners&lt;/em&gt;, not walls or rooms.  These are the intersections of two (potential or actual) walls &lt;em&gt;between&lt;/em&gt; rooms.&lt;/p&gt;

&lt;p&gt;An important thing to understand is how what you see relates to the actual &lt;a href="https://miniscript.org/wiki/TileDisplay" rel="noopener noreferrer"&gt;TileDisplay&lt;/a&gt; creating it.  The best way to get that understanding is to press G in the demo, which toggles a visible grid (by shrinking the cells and spacing them out slightly).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj1tlypp099h92yu5n15q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj1tlypp099h92yu5n15q.png" alt="Screen shot of image with grid" width="514" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These are Wang 2-corner tiles, which are explained in &lt;a href="https://dev.to/joestrout/wang-2-corner-tiles-544k"&gt;this post&lt;/a&gt;, so I won't go into detail here.  But they're very neat and easy once you get the hang of them.&lt;/p&gt;
&lt;h2&gt;
  
  
  Start your program!
&lt;/h2&gt;

&lt;p&gt;Now let's leverage the code in this demo to make our own maze game.  If you were running the demo on the web, now you really should &lt;a href="https://miniscript.org/MiniMicro/index.html#download" rel="noopener noreferrer"&gt;download&lt;/a&gt; it so that you can save your work.&lt;/p&gt;

&lt;p&gt;We're going to use the &lt;em&gt;build a little, test a little&lt;/em&gt; approach here, building up the program bit by bit.  This is a great habit to have in all your coding; it's much easier and more successful than trying to write out an entire program at once.&lt;/p&gt;

&lt;p&gt;So if you're still in the demo, exit out of it by pressing &lt;code&gt;Esc&lt;/code&gt;, then type &lt;code&gt;clear&lt;/code&gt; and &lt;code&gt;reset&lt;/code&gt; to clear the screen and program.  Then &lt;code&gt;edit&lt;/code&gt;, and type or paste in this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;clear

if env.importPaths.indexOf("/sys/demo") == null then
    env.importPaths.push "/sys/demo"
end if
import "maze"

maze.generate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This clears the screen, and then checks whether our import paths already include "/sys/demo".  That's not normally a directory that &lt;code&gt;import&lt;/code&gt; searches, but it's where &lt;code&gt;maze.ms&lt;/code&gt; lives, so we need it to check there.  Then we &lt;code&gt;import "maze"&lt;/code&gt;, which loads the code from maze.ms into a map called &lt;code&gt;maze&lt;/code&gt; in our program.  Finally, we call &lt;code&gt;maze.generate&lt;/code&gt; to make and display a random maze, just like the demo.&lt;/p&gt;

&lt;p&gt;Save this program (I called mine "mouseMaze", for reasons that will become apparent), and then run it.  You should see a maze appear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Customize the maze
&lt;/h2&gt;

&lt;p&gt;The maze generated so far is a "pure" maze, i.e., one with exactly one path between any two points.  That's not actually much fun to play in; we want the player to have choices.  You could manually edit your maze layout, save it to disk in some way, and load it into your game; this would be most similar to what the classic games above did.  But for now, let's just knock out a random selection of walls.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tiles = maze.tiles
cellSize = tiles.cellSize
extent = tiles.extent
maxCol = (extent[0] / 2) - 1
maxRow = (extent[1] / 2) - 1
tiles.setCellTint range(extent[0]-1), range(extent[1]-1), "#44FF44"

// Delete some extra walls so it's not a "pure" maze
for i in range(20)
    room = maze.rooms[rnd*maze.Room.cols][rnd*maze.Room.rows]
    if rnd &amp;lt; 0.5 then wall = "north" else wall = "east"
    room[wall] = false
end for
maze.setCornersFromRooms
maze.updateTiles
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;edit&lt;/code&gt; your program and add the above code, below what you wrote before.  This code begins by copying &lt;code&gt;maze.tiles&lt;/code&gt; into a &lt;code&gt;tiles&lt;/code&gt; variable, just to make it easier to refer to.  Then we also copy &lt;code&gt;cellSize&lt;/code&gt; and &lt;code&gt;extent&lt;/code&gt; out of that.  We also compute the &lt;code&gt;maxRow&lt;/code&gt; and &lt;code&gt;maxCol&lt;/code&gt; of rooms (rooms are the little spaces between the walls — there are only half as many rooms as walls in any direction).  Then we use &lt;code&gt;tiles.setCellTint&lt;/code&gt; to color the maze green.&lt;/p&gt;

&lt;p&gt;Then, the &lt;code&gt;for&lt;/code&gt; loop repeatedly selects a random room, and sets either the north or east wall to &lt;code&gt;false&lt;/code&gt;, removing it.  The code in maze.ms defines rooms this way, each room only responsible for the walls to its north and east side.  How would you be expected to know this?  Why, by reading the maze.ms code, of course!  (Or by reading this blog post.)&lt;/p&gt;

&lt;p&gt;Once the rooms have been edited, we call &lt;code&gt;maze.setCornersFromRooms&lt;/code&gt; and &lt;code&gt;maze.updateTiles&lt;/code&gt; to update the actual display on screen.&lt;/p&gt;

&lt;p&gt;Run the program again, and you should see a green maze with extra missing walls (sometimes even producing a dot, where a corner isn't attached to any walls at all).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foanf6ykye8ud7orm0igh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foanf6ykye8ud7orm0igh.png" alt="Screen shot of program result at this point" width="800" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a MazeSprite class
&lt;/h2&gt;

&lt;p&gt;Now to make this maze into a game, we need some sprites to move around in it.  And we want those things to move only in valid ways, i.e. not through walls.  We'll use the same Room data structure that the maze demo already defined for creating the maze, to determine where sprites can move.&lt;/p&gt;

&lt;p&gt;So, &lt;code&gt;edit&lt;/code&gt; your program again, and append this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MazeSprite = new Sprite
MazeSprite.col = 0
MazeSprite.row = 0
MazeSprite.goToRoom = function(roomCol, roomRow)
    self.col = roomCol; self.row = roomRow
    self.x = cellSize * (roomCol * 2 + 1)
    self.y = cellSize * (roomRow * 2 + 1)
end function

MazeSprite.canMove = function(dx, dy)
    col = self.col; row = self.row
    if dx and dy then return false
    if not 0 &amp;lt;= round(col + dx) &amp;lt;= maxCol then return false
    if not 0 &amp;lt;= round(row + dy) &amp;lt;= maxRow then return false
    if dx &amp;lt; 0 then return not maze.rooms[self.col-1][self.row].east
    if dx &amp;gt; 0 then return not maze.rooms[self.col][self.row].east
    if dy &amp;lt; 0 then return not maze.rooms[self.col][self.row-1].north
    if dy &amp;gt; 0 then return not maze.rooms[self.col][self.row].north
end function

MazeSprite.move = function(dx, dy)
    if not self.canMove(dx, dy) then return
    col = self.col; row = self.row
    for t in range(0, 1, 0.1)
        self.goToRoom col + dx*t, row + dy*t
        yield
    end for
    self.goToRoom round(self.col), round(self.row)
end function
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a Sprite subclass called MazeSprite.  A MazeSprite knows what room column and row it's in, and in the &lt;code&gt;goToRoom&lt;/code&gt; method, it also converts those to screen coordinates.  Then there's the &lt;code&gt;canMove&lt;/code&gt; method, which determines whether the sprite can move in a given direction.  This includes bounds checks, as well as checking the appropriate wall.&lt;/p&gt;

&lt;p&gt;Finally, we have a &lt;code&gt;move&lt;/code&gt; method that, if the sprite is able to move in the given direction at all, does so smoothly over 10 frames.  (In a full game you might need a fancier approach that does not block the rest of the game while moving -- but for today's game, there is only one sprite moving at a time, so this works fine.)&lt;/p&gt;

&lt;p&gt;You can run your program again at this point, just to ensure you haven't made any typos.  But if everything is good, it won't look any different from before.  We've defined the MazeSprite class, but we're not actually doing anything with it yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add the sprites
&lt;/h2&gt;

&lt;p&gt;It's time to add our characters to the maze!  I chose to make my game about a mouse questing for cheese, but feel free to explore the pictures available in /sys/pics and make it a bunny chasing carrots, or whatever you like.  (The &lt;code&gt;findFile&lt;/code&gt; command is a great way to explore!)&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;edit&lt;/code&gt; your program again, and add this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;player = new MazeSprite
player.image = file.loadImage("/sys/pics/animals/mouse.png")
player.scale = 1/3
player.goToRoom 0,0
display(4).sprites.push player

cheese = new MazeSprite
cheese.image = file.loadImage("/sys/pics/food/Cheese.png")
cheese.scale = 1/2
cheese.goToRoom round(rnd*maxCol), round(rnd*maxRow)
display(4).sprites.push cheese
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is pretty straightforward: we create a player sprite in room 0,0, and a cheese sprite in some random room.  Both sprites are scaled down in order to fit into the maze.  Run your program now, and you should see these new additions to the game.  But the program still exits immediately to the blinking cursor.&lt;/p&gt;

&lt;h2&gt;
  
  
  The main loop
&lt;/h2&gt;

&lt;p&gt;We're almost done!  Let's just load a sound to play when the mouse gets the cheese, and then add the main loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gotSound = file.loadSound("/sys/sounds/munch.wav")

while not key.pressed("escape")
    yield
    dx = sign(round(key.axis("Horizontal", false)))
    dy = sign(round(key.axis("Vertical", false)))
    if not dx and not dy then continue
    player.move dx, dy
    if player.row == cheese.row and player.col == cheese.col then
        cheese.goToRoom round(rnd*maxCol), round(rnd*maxRow)
        gotSound.play
    end if
end while
key.clear
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our main loop here is pretty simple.  We start by getting the horizontal (&lt;code&gt;dx&lt;/code&gt;) and vertical (&lt;code&gt;dy&lt;/code&gt;) axis inputs.  Using &lt;code&gt;key.axis&lt;/code&gt; allows the player to use arrow keys, WASD, or even a game pad.  We &lt;code&gt;round&lt;/code&gt; that and take the &lt;code&gt;sign&lt;/code&gt; so that we get clean input values of -1, 0, or 1 for each axis.  And if we don't have any input, we just &lt;code&gt;continue&lt;/code&gt; at the top of the loop (where we included a &lt;code&gt;yield&lt;/code&gt;, as all good main loops do).&lt;/p&gt;

&lt;p&gt;When we &lt;em&gt;do&lt;/em&gt; have an input, then we call &lt;code&gt;player.move&lt;/code&gt;, and then check whether the player is now in the same location as the cheese.  When that's the case, we play the &lt;em&gt;munch&lt;/em&gt; sound and teleport the cheese to a different room.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6fjezubvf5of2qi85pv4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6fjezubvf5of2qi85pv4.png" alt="Screen shot of the final game" width="800" height="536"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it!&lt;/strong&gt;  It's such a simple game, and yet it is immediately engaging.  You will see how you could build on this in all sorts of ways: make the goal move around, add enemies, add some sort of shooting, provide doors that can open or close or pivot, add sub-goals like visiting every part of the maze, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comments?  Questions?
&lt;/h2&gt;

&lt;p&gt;What was your favorite maze game?  What sort of maze game would you like to see in Mini Micro?  And what's stopping you from creating that game right now?  Share your thoughts in the comments below!&lt;/p&gt;

</description>
      <category>miniscript</category>
      <category>minimicro</category>
      <category>programming</category>
      <category>gamedev</category>
    </item>
  </channel>
</rss>
