pancelor [Lexaloffle Blog Feed]https://www.lexaloffle.com/bbs/?uid=27691 Open URL in browser (export plate) <p>Here's a snippet I made to launch webpages in the player's browser from within a pico8 cartridge. It only works in html exports, because it uses the GPIO pins to send the url to a <a href="https://www.lexaloffle.com/dl/docs/pico-8_manual.html#Web_Applications_">custom html template</a>.</p> <h2>Example (player perspective)</h2> <p>For an example of this in action, play <a href="https://pancelor.itch.io/make-ten-deluxe">https://pancelor.itch.io/make-ten-deluxe</a> and click &quot;info&quot; on the title screen. A paper will pop up with some clickable URLs on them, which will load new tabs when clicked.</p> <h2>Setup</h2> <ol> <li><code>folder config</code>, open the &quot;plates&quot; subfolder, copy the default template (TODO: is there a default template, or did I make it myself by exporting a cart and then undoing pico8's <code>##js_file##</code> / <code>##label_file##</code> replacement?)</li> <li>Find <code>var pico8_gpio = new Array(128);</code> (it's near the top, around line 30)</li> <li>Replace it with this:</li> </ol> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>var pico8_gpio = new Proxy(new Array(128),{ // pancelor's gpio-url, https://www.lexaloffle.com/bbs/?tid=149864 set(arr, pin, value) { // console.log(typeof pin, typeof value); // string number 0.o pin = parseInt(pin,10); arr[pin] = value; // write url chars to pins 1,2... // then set pin 0 to ord(&quot;u&quot;) to open the url if (pin==0) { var code = String.fromCharCode(value); if (code==&quot;u&quot;) { // read URL (and reset pins to undefined) var url = &quot;&quot;; for (var ii = 1; ii &lt; 128; ii++) { var state = arr[ii]; if (state==undefined) break; url += String.fromCharCode(state); arr[ii] = undefined; } arr[0] = undefined // open if (!url.startsWith(&quot;http&quot;)) url = &quot;https://&quot;+url; console.log(&quot;opening gpio url:&quot;,url); window.open(url,'_blank'); } else { console.log(&quot;unknown gpio command:&quot;,code,arr[0]); } } } });</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <ol start="4"> <li>Save your new template as <code>plates/gpio_url.html</code></li> <li>Inside your cart, use this function to open a URL in the player's browser:</li> </ol> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>function open_external(url) assert(#url&lt;=127) poke(0x5f81,ord(url,1,#url)) ?&quot;\^!5f80u&quot; --must be last end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <ol start="6"> <li>Export your cart with <code>export -f mygame.html -p gpio_url</code></li> </ol> <h2>Example cart</h2> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>-- export -f test.html -p gpio_url function _update() if btnp(4) and not launched then launched=true open_external(&quot;pancelor.com&quot;) end end function _draw() cls(launched and 2 or 1) print(launched and &quot;done&quot; or &quot;press z&quot;,54,64,7) end function open_external(url) poke(0x5f81,ord(url,1,#url)) ?&quot;\^!5f80u&quot; --must be last end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <h2>License</h2> <p><a href="https://creativecommons.org/licenses/by/4.0/">CC BY 4.0</a>. Just leave the &quot;// pancelor's gpio-url &lt;link&gt;&quot; comment in the code itself and you can use this for anything. (I think CC BY normally means you need to give more credit than just leaving a code comment, e.g. a shoutout in the game's description or something, but in this case I'm not bothered about it.)</p> https://www.lexaloffle.com/bbs/?tid=149864 https://www.lexaloffle.com/bbs/?tid=149864 Sat, 28 Jun 2025 06:31:30 UTC Make Ten Deluxe <p> <table><tr><td> <a href="/bbs/?pid=167095#p"> <img src="/bbs/thumbs/pico8_make_ten_deluxe_demo-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=167095#p"> Make Ten Deluxe (demo v1.3)</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=167095#p"> [Click to Play]</a> </td></tr></table> </p> <p><em>tl;dr: use left-mouse to play, this is a free demo, the full game is on <a href="https://pancelor.itch.io/make-ten-deluxe">itch</a>. have fun!</em></p> <p><img src="https://img.itch.zone/aW1nLzIxMTQwNjAxLnBuZw==/original/gg%2FNaQ.png" alt="Features" /></p> <p>Make Ten Deluxe is an expansion pack that builds on the original <a href="https://pancelor.itch.io/make-ten">Make Ten</a>, featuring 35+ variant modes. Some modes modify the rules slightly in ways that significantly change the flavor of the game, some modes are particularly puzzley situations for you to solve, and some modes are too strange and specific to describe.</p> <p>There's something for everybody to love: each of the game's dozens of modes were carefully chosen to be interesting, surprising, or otherwise delightful.</p> <p><img src="https://img.itch.zone/aW1nLzIxMTMyNjgxLmdpZg==/original/FsY0qH.gif" alt="MakeWar" /></p> <p>Make Ten Deluxe! Featuring...</p> <ul> <li><strong>Drop10</strong>: Make10 + gravity: new numbers fall from the sky</li> <li><strong>MakeHeaven</strong>: Make10 with wildly different RNG</li> <li><strong>Make42</strong>: exactly what it sounds like</li> <li><strong>MakeTen</strong>: not at all what it sounds like</li> <li><strong>MakeWar</strong>: explosions! made of numbers! (does that count?)</li> <li><strong>BlueGoo</strong>: Make10 with negative numbers</li> <li><strong>MakeChä̶̝̣́̈́o̴̯͊s̶͉̟͒</strong>: ???</li> <li>and many more!</li> </ul> <p>The demo (playable in-browser, above) features a handful of these modes, including a daily mode, a mode with negative numbers, &quot;MakeX&quot;, and a mode with no timer and infinite undo. If you're left wanting more, you can <a href="https://pancelor.itch.io/make-ten-deluxe">purchase the full game</a> for Linux, Mac, Windows, and PICO-8.</p> <p><img src="https://img.itch.zone/aW1nLzIxMTMyNjg1LmdpZg==/original/xYjt%2Fu.gif" alt="Drop10" /></p> <p>The <a href="https://pancelor.itch.io/make-ten-deluxe">full game</a> is downloadable, but I haven't figured out how to port it <strong>mobile devices</strong> yet. I'm hoping to add an Android APK file in the future, and I'll see if there's anything I can do about iPhone support (though that seems unlikely). I hear there are PICO-8 or windows emulators that run on phones -- I'll update this section later when I've done more research.</p> <p><img src="https://img.itch.zone/aW1nLzIxMTMzMTYzLnBuZw==/original/t5la4O.png" alt="Summary screen for Daily" /></p> <p><img src="https://img.itch.zone/aW1nLzIxMTQwNjA4LnBuZw==/original/1i4HoQ.png" alt="Controls" /></p> <p>The whole game can be played with the Left Mouse Button: click and drag to select groups of numbers. If they add up to 10, they'll be removed from the board, increasing your score.</p> <p><img src="https://img.itch.zone/aW1nLzIxMTM1MDc5LmdpZg==/original/oHxso0.gif" alt="Basic gameplay" /></p> <p>In some modes, you can press Z to undo. You can pause the game with Enter or P. There are volume controls in the pause menu, and ctrl-plus, ctrl-minus, and ctrl-m also control the volume.</p> <p><img src="https://img.itch.zone/aW1nLzIxMTQwNjExLnBuZw==/original/EUOHqM.png" alt="Credits" /></p> <p>Game by <a href="https://pancelor.com">pancelor</a>, itch page by <a href="https://tallywinkle.itch.io/">tally</a> and pancelor.</p> <p>An homage to <a href="https://www.gamesaien.com/game/fruit_box_a/">Fruit Box</a> by GameSaien, circa 2010.</p> <p>Made using PICO-8, <a href="https://github.com/thisismypassport/shrinko8">shrinko8</a>, Sublime Text, Aseprite, Krita, Obsidian</p> <p>Thanks to all my playtesters, and especially to: biggiemac42, Dad, droqen, Gabe, incnone, polychotomous, q1, Roland, tally, Waporwave</p> <p><img src="https://img.itch.zone/aW1nLzIxMTMyNzI1LmdpZg==/original/FEXs%2Bd.gif" alt="an empty board" /></p> https://www.lexaloffle.com/bbs/?tid=149014 https://www.lexaloffle.com/bbs/?tid=149014 Wed, 21 May 2025 14:02:02 UTC patches <p> <table><tr><td> <a href="/bbs/?pid=167184#p"> <img src="/bbs/thumbs/pico8_patches-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=167184#p"> patches</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=167184#p"> [Click to Play]</a> </td></tr></table> </p> <p>An interactive quilt for <a href="https://itch.io/jam/tweettweetjam-10">TTJ10</a>, made in &lt;500 chars of code.</p> <p>More toy than game; think of it as a jigsaw puzzle, if you like.</p> <h2>controls</h2> <p>Left click to pick up a patch, left click again to drop it.</p> <hr /> <p>Also <a href="https://pancelor.itch.io/patches">hosted on itch.io</a></p> https://www.lexaloffle.com/bbs/?tid=149058 https://www.lexaloffle.com/bbs/?tid=149058 Mon, 19 May 2025 09:50:23 UTC Fast loops <p>There are many ways to loop over an array-style table. For example:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>local tab = {10,20,30,40} -- method 1 local calc1 = 0 for i,elem in ipairs(tab) do calc1 += i*elem end -- method 2 local calc2 = 0 for i=1,#tab do local elem = tab[i] calc2 += i*elem end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Which way is fastest? Well it often doesn't matter, since the work inside the loop usually far outweighs the cost of the loop itself. Or tokens might matter more to you than speed. But in some situations you want your code to be as fast as possible, and that means minimizing the overhead from the loop itself.</p> <h2>Setup</h2> <p>So, which way of looping is fastest? Here are the methods we'll compare:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>function for_i(tab) for i=1,#tab do local x=tab[i] -- do some work end end function for_all(tab) for x in all(tab) do -- do some work end end function for_ipairs(tab) for i,x in ipairs(tab) do -- do some work end end function for_inext(tab) for i,x in inext,tab do -- do some work end end function for_each(tab) foreach(tab, function(x) -- do some work end) end function while_i(tab) local i = 1 while i&lt;=#tab do local x = tab[i] -- do some work i += 1 end end function while_isize(tab) local i,size = 1,#tab while i&lt;=size do local x = tab[i] -- do some work i += 1 end end -- note: issues if tab is empty function repeat_i(tab) local i = 1 repeat local x = tab[i] -- do some work i += 1 until i&gt;#tab end -- note: issues if tab is empty function repeat_isize(tab) local i,size = 1,#tab repeat local x = tab[i] -- do some work i += 1 until i&gt;size end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p><em>edit: In my analysis below I'll refer to some extra methods (mainly based on reader comments). I haven't updated the graph/chart for these methods:</em></p> <p><div><div><input type="button" value=" Show " onClick="if (this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display != '') { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = ''; this.innerText = ''; this.value = ' Hide '; } else { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = 'none'; this.innerText = ''; this.value = ' Show '; }"></div><div><div style="display: none;"></p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>function for_isize(tab) local size=#tab for i=1,size do local x=tab[i] -- do some work end end function for_pairs(tab) for k,v in pairs(tab) do -- do some work end end function for_next(tab) for k,v in next,tab do -- do some work end end function goto_isize(tab) local i,size = 1,#tab ::loop:: local x = tab[i] -- do some work i += 1 if i&lt;=size then goto loop end end -- by Siapran function tail_inext(tab) local function loop(i, v) if i then -- do work return loop(inext(tab, i)) end end return loop(inext(tab)) end -- by Siapran function tail_i(tab) local n = #tab local function loop(i) if i &lt;= n then local x = tab[i] -- do work return loop(i+1) end end return loop(1) end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p></div></div></div></p> <p>I used my <a href="https://www.lexaloffle.com/bbs/?tid=46117">cycle counter</a> to measure how long each function takes to run on arrays of various lengths -- every length from 0 to 80. The results are uninteresting after length 25, so I've truncated the table and chart below.</p> <p>Here's my measurement code. It prints out a csv of cycle data to the <a href="https://www.lexaloffle.com/bbs/?tid=42367">host console</a>. It's not too interesting to run the cart, but here it is if you want to reproduce or check my results:</p> <p> <table><tr><td> <a href="/bbs/?pid=164729#p"> <img src="/bbs/thumbs/pico8_loop_overhead-1.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=164729#p"> loop_overhead</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=164729#p"> [Click to Play]</a> </td></tr></table> </p> <h2>Results</h2> <p>I ran my measurement cart (#loop_overhead, above) on PICO-8 0.2.6b for Linux. Here's the data; higher numbers are slower/worse.</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>len,while_i,for_each,while_isize,repeat_i,repeat_isize,for_i,for_ipairs,for_inext,for_all 0,5,30,5,8,8,8,18,9,21 1,14,51,12,8,8,12,20,11,36 2,23,57,19,15,13,16,22,13,36 3,32,63,26,22,18,20,24,15,36 4,41,69,33,29,23,24,26,17,36 5,50,75,40,36,28,28,28,19,36 6,59,81,47,43,33,32,30,21,36 7,68,87,54,50,38,36,32,23,36 8,77,93,61,57,43,40,34,25,36 9,86,99,68,64,48,44,36,27,36 10,95,105,75,71,53,48,38,29,36 11,104,111,82,78,58,52,40,31,36 12,113,117,89,85,63,56,42,33,36 13,122,123,96,92,68,60,44,35,36 14,131,129,103,99,73,64,46,37,36 15,140,135,110,106,78,68,48,39,36 16,149,141,117,113,83,72,50,41,36 17,158,149,124,120,88,76,52,43,38 18,167,157,131,127,93,80,54,45,40 19,176,165,138,134,98,84,56,47,42 20,185,173,145,141,103,88,58,49,44 21,194,181,152,148,108,92,60,51,46 22,203,189,159,155,113,96,62,53,48 23,212,197,166,162,118,100,64,55,50 24,221,205,173,169,123,104,66,57,52 25,230,213,180,176,128,108,68,59,54</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>And here's a chart:</p> <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/27691/chart.png" alt="" /> <p>(For colorblind folks, the labels are stacked in order of their performance at length=25)</p> <h2>Commentary</h2> <h3>for_inext()</h3> <p>The clear winner here is for_inext() -- it's the fastest in most situations, you get access to both i and x inside the body of the loop, and its token count is nearly the best as well (just 1 token more than for_each()).</p> <p>Downsides: it's an unusual bit of code that might trip up other people reading it. inext() is <a href="https://www.lexaloffle.com/bbs/?tid=47314">defined by PICO-8</a>, not Lua, if that matters to you. Compare against Lua's <a href="https://www.lua.org/manual/5.2/manual.html#pdf-next">next()</a>. But these downsides seem unimportant if all you want is to make one particular PICO-8 cart run as fast as it can.</p> <h3>for_all()</h3> <p>However, when looping over medium/large arrays (length&gt;=14), for_all() is a bit faster than for_inext() -- exactly 5 cycles faster when length&gt;=16. But for_all() is much slower than for_inext() up until that breakpoint. When length=2, for_all() takes 36 cycles versus for_inext()'s 13 cycles -- nearly 3 times as slow!</p> <p>Other possible downsides: index i is not accessible inside the loop.</p> <p>all() has some special tricks that let you delete while iterating (within limits), which may be considered an upside or a downside. See the conversation starting <a href="https://www.lexaloffle.com/bbs/?pid=98002#p">here</a> for details.</p> <p>for_all() has the strangest timing out of all the options tested. At low array lengths it performs second-worst, but at medium/high array lengths it performs best.</p> <h3>for_i()</h3> <p>Despite feeling like one of the more natural ways to write a loop, for_i() is noticeably slower than for_ipairs()/for_inext()/for_all(). Specifically, for_i() costs 4 extra cycles for each extra iteration, compared to 2 cycles for the best methods. For large arrays, the choice between for_ipairs()/for_inext()/for_all() mostly doesn't matter, but for_i() will be noticeably worse.</p> <h3>for_each()</h3> <p>for_each() has the lowest token-count but is one of the slowest options here. Its only advantage is its token-count (it beats for_inext() by a single token), but I would still generally prefer for_inext().</p> <p>edit: foreach() is actually faster (!) than all() in one very specific case: when the body of the loop is a single function call. Details:<br /> <div><div><input type="button" value=" Show " onClick="if (this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display != '') { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = ''; this.innerText = ''; this.value = ' Hide '; } else { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = 'none'; this.innerText = ''; this.value = ' Show '; }"></div><div><div style="display: none;"><br /> I ran this code through my <a href="https://www.lexaloffle.com/bbs/?pid=168652#p">profiler</a>:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>list={} function do_some_work(x) add(list,x) end prof{ locals={&quot;0123456789abcdef&quot;}, function(chars) --faster: 257 cycles list={} foreach(chars,do_some_work) end, function(chars) --slower: 264 cycles list={} for c in all(chars) do do_some_work(c) end end, function(chars) --(actually fastest, if inlining the function call is possible: 152 cycles) list={} for c in all(chars) do add(list,c) end end, }</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>For arrays of length 10+, foreach() is faster than all()! Of course, this only applies if your code requires the loop body to be a single function call. (In this particular case, you should inline do_some_work() and use all(), but sometimes that isn't possible)<br /> </div></div></div></p> <h3>slope change in for_each() / for_all()</h3> <p>At length 16, the slope of the for_each() and for_all() lines change slightly. This isn't a mistake in the measurement; all() artificially adjusts its cost starting at length 16, and foreach() is a thin wrapper around all() and so it inherits this cost weirdness.</p> <h3>while_isize() versus while_i()</h3> <p>while_isize() is faster than while_i(), but for_isize() is 1 cycle slower than for_i(). This is because while-loops re-evaluate their condition every iteration, but numeric for-loops only evaluate their bounds once.</p> <h3>while_i() versus repeat_i()</h3> <p>I was surprised to see that repeat_i() is quite a bit faster than while_i(). Why are they so different? It helps to look at the generated Lua bytecode using a tool like <a href="https://www.luac.nl/">https://www.luac.nl/</a> -- you can see that while_i() uses an extra JMP instruction that can be avoided with repeat_i().</p> <p>As you might expect, goto-based loops perform exactly the same as while_i()/repeat_isize()/etc, since they compile to the same bytecode or similar.</p> <h3>for_next() / for_pairs()</h3> <p>for_next() and for_pairs() perform exactly the same as for_inext() and for_ipairs(), respectively. It would be a bit odd to loop over an array-style table using next()/pairs() instead of inext()/ipairs() with their guaranteed iteration order, but it's good to know there's no performance difference. (and nice to know that looping over key-value tables is quite fast)</p> <h2>Conclusion</h2> <p>I see four viable options for looping:</p> <ol> <li>for_inext() is generally the fastest method: <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>for i,x in inext,tab do -- do some work end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>With arrays of length 2 to 13, it's the fastest option, but it performs excellently at any array length. At higher lengths, it's 5 cycles slower than for_all() -- 5 cycles for the entire loop, not 5 cycles per iteration. Most other methods have additional cost per-iteration, as shown by their higher slopes in the chart. At high array-lengths the differences between for_inext(), for_ipairs(), and for_all() become less and less significant -- they each only cost 2 extra cycles for each extra iteration.<br /> As a nice bonus, for_inext() is only 1 token worse than the most token-efficient method, for_each().</p></li> <li>With arrays of length 14+, for_all() is slightly faster than for_inext(): <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>for x in all(tab) do -- do some work end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>However, be aware that all() is much slower on arrays of length 0 to 13. (This may not matter, since the arrays are so small.)</p></li> <li>For carts with extreme token-limitations and no problem with much larger loop overhead (3-4x), <code>foreach()</code> is slightly better than the alternatives. Separately, <code>foreach()</code> is slightly faster (than all other options!) when the body of the loop is a single function call that you can't inline for some reason.</li> </ol> <p>And finally, option 4: If you don't have any particular hard limits you're up against, loop however you like! Whatever lets you get your ideas on the page without getting distracted by unimportant timing details. You're trying to make a game here, not a pretty piece of code. This is most likely the scenario you are in!</p> <p>Even if you know you want fast code, the first step is to measure your code and find out which parts are slow, and the answer probably won't be the loop overhead. Loop overhead is easy to measure, but that doesn't make it important. Don't link people to this post saying &quot;pancelor says you have to loop THIS way instead of THAT way&quot;, it usually just doesn't matter. But if it ever does matter, or if you're curious (like I was) about how much it matters, then hey, this is the post for you. Hope it was interesting!</p> https://www.lexaloffle.com/bbs/?tid=148143 https://www.lexaloffle.com/bbs/?tid=148143 Mon, 31 Mar 2025 09:07:04 UTC Web clipboard broken (0.2.6c dev8) <p>repro steps:</p> <ul> <li>run cart bitplane_grid-3 in the web player</li> <li>press ctrl-c</li> <li>paste into an external text editor</li> </ul> <p>expected behavior: clipboard should have <code>poke(0x5f5e,0xff)</code> in it<br /> actual behavior: clipboard is unchanged (my specs: linux, firefox, 0.2.6c dev8)</p> <p>(cart from <a href="https://www.lexaloffle.com/bbs/?tid=54215">here</a>)<br /> <table><tr><td> <a href="/bbs/?pid=134709#p"> <img src="/bbs/thumbs/pico8_bitplane_grid-3.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=134709#p"> bitplane_grid</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=134709#p"> [Click to Play]</a> </td></tr></table> </p> <p>Maybe this is a known bug, which is part of why it's &quot;0.2.6cdev8&quot; instead of &quot;0.2.6c&quot;? But here's a ping just in case you don't know about it <a href="https://www.lexaloffle.com/bbs/?uid=1"> @zep</a>. (web clipboard stuff seems like a huge pain, I don't envy you trying to fix it...)</p> <hr /> <p>The cart has some debug print statements -- open the browser dev tools and you can see it print out &quot;copying str:&quot; right before calling <code>printh(str,'@clip')</code>. I noticed there's no red &quot;press c to copy&quot; prompt that the web player used to show, maybe that's a clue.</p> <p>I also noticed that a lone ctrl press is now caught by <code>stat(31)</code>; my debug logging prints <code>ch code ソ 218</code> on web, but completely misses this key press in the desktop app (linux, 0.2.6b). I think that may be a separate bug? Shift and alt are not caught in the same way, and I don't think I'd care about a bare ctrl press while reading text input</p> https://www.lexaloffle.com/bbs/?tid=147118 https://www.lexaloffle.com/bbs/?tid=147118 Mon, 10 Feb 2025 02:28:58 UTC Toggle Music mod for any cart <p>Sometimes it'd be nice to mute the music in a game you downloaded from the BBS, but not mute the sound effects. Maybe their music isn't to your taste, maybe you've played it way too much and now you want to listen to your own music instead; there are plenty of reasons.</p> <p>One easy trick is just typing <code>music=min</code> to kill the music completely. If you'd rather be able to toggle the music on and off, here's a quick snippet:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>-- pancelor music toggle v4 -- https://www.lexaloffle.com/bbs/?tid=146963 _music_fn,_music_last=music,-1 function music(...) if _music_muted then _music_last=... --save 1st arg else _music_fn(...) end end menuitem(5,&quot;♪music&quot;,function() _music_muted=not _music_muted if _music_muted then _music_fn(-1,500) menuitem(nil,&quot;&hellip;music&quot;) else _music_fn(_music_last) menuitem(nil,&quot;♪music&quot;) end return true end)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/27691/music-toggle.gif" alt="" /> <p>It should work for most carts, just paste it onto the end of the cart's code. It might be convenient to use this as a dev as well (not just as a player) -- it remembers the last call to music() and re-calls it when the player unmutes.</p> https://www.lexaloffle.com/bbs/?tid=146963 https://www.lexaloffle.com/bbs/?tid=146963 Mon, 03 Feb 2025 08:36:48 UTC pancelor's postcarts <p> <table><tr><td> <a href="/bbs/?pid=156720#p"> <img src="/bbs/thumbs/pico8_pancelors_postcarts-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=156720#p"> pancelors_postcarts</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=156720#p"> [Click to Play]</a> </td></tr></table> </p> <p>This is a collection of my various tweetcarts and other code art, circa 2019-2023. Most of my non-interactable postcarts etc are in here, including a few that are only included as a part of this collection. 25+ fun little animations!</p> <p>(I posted this on <a href="https://pancelor.itch.io/tweetcart-gallery">itch</a> a while back but forgot to post it here until just now, as I was reading zep's <a href="https://www.lexaloffle.com/bbs/?tid=145008">release notes</a> for the BBS's new postcart feature. So I'm uploading my postcart gallery now; better late than never!)</p> <h2>Controls</h2> <p>These carts are non-interactive animations for you to look at. The only interaction is opening the menu (<strong>Enter/P</strong>), where you can change the active cart.</p> <ul> <li>Press <strong>left/right</strong> on the cart name to manually swap out the active cart</li> <li>The carts will auto-advance to a random cart every 20~60 seconds. You can disable this by setting &quot;Cycle: no&quot; in the menu</li> </ul> <p>An example of navigating the menu:<br /> <div><div><input type="button" value=" Show " onClick="if (this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display != '') { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = ''; this.innerText = ''; this.value = ' Hide '; } else { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = 'none'; this.innerText = ''; this.value = ' Show '; }"></div><div><div style="display: none;"></p> <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/27691/gallery-818f.gif" alt="" /> <p></div></div></div></p> <hr /> <p>There are 25+ carts in this thing, be sure to check out at least one of these before you leave: system restored, adrift, wormys, weird bug, firefly spring</p> <p>Hope you enjoy!</p> <h2>Technical details:</h2> <p><div><div><input type="button" value=" Show " onClick="if (this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display != '') { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = ''; this.innerText = ''; this.value = ' Hide '; } else { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = 'none'; this.innerText = ''; this.value = ' Show '; }"></div><div><div style="display: none;"><br /> Search for <code>carts={</code> to find the source code for each cart (inside the &quot;script&quot; function of each table entry). The code isn't very readable, but that's where you can find it. There are links to the canonical versions elsewhere, which you can try to pick apart using my <a href="https://www.lexaloffle.com/bbs/?tid=49949">twiddler tool</a>, if you'd like.</p> <p>For this gallery cartridge, I had to set up this whole harness that runs each cart in a sort of sandbox, letting them act as intended without messing with each other. There were lots of finicky parts to this:</p> <ul> <li>resetting the draw state after each cart</li> <li>using coroutines to allow carts to use <code>goto</code> and other unusual control flow</li> <li>saving a screenshot of the initial pico-8 state, for the carts that depend on it</li> <li>redefining the <code>time()</code> function so that carts would think they started at t=0</li> <li>saving a copy of the values of special characters like 🐱, in case carts redefine those values</li> <li>etc, etc</li> </ul> <p>I had to make some minor changes to some carts (mainly changing <code>flip()</code> calls to <code>yield()</code> calls) but mostly I was able to wholesale copy-paste them directly into this gallery cartridge, which felt pretty neat.<br /> </div></div></div></p> https://www.lexaloffle.com/bbs/?tid=145063 https://www.lexaloffle.com/bbs/?tid=145063 Sat, 02 Nov 2024 12:52:43 UTC music desync (0.1.1b) <p><a href="https://www.lexaloffle.com/bbs/?uid=1"> @zep</a> o/ thanks for fixing the sfx arpeggio effects! here's another audio bug for you:</p> <p>The music for embedded BBS carts has a noticeable music desync in some cases (the individual tracks sound out-of-sync from each other). I noticed it on linux+firefox with my <a href="https://www.lexaloffle.com/bbs/?tid=143443">music visualizer demo cart</a>, reproduced here for convenience:</p> <p> <table><tr><td> <a href="/bbs/?pid=152071#p"> <img src="/bbs/thumbs/pico64_datufbuyi-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=152071#p"> datufbuyi</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=152071#p"> [Click to Play]</a> </td></tr></table> </p> <p>Individual tracks desync from each other; it consistently happens after the song switches from pattern 3 to pattern 4. The second screenshot here shows me catching it right when it happens:</p> <p>&gt; <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/27691/Screenshot_20240822_155741.png" alt="" /> <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/27691/Screenshot_20240822_160359.png" alt="" /></p> <p>That last number (the one that desyncs by 11 ticks for me) is <code>stat(400+i,11)</code>. I believe this represents the number of ticks played on channel i for the current pattern.</p> <p><em>This also happens locally</em> in the linux executable, but the tracks only desync by a single tick. I can't hear it, but you can see it by pausing after pattern 4.</p> <p>I think this is new as of 0.1.1, but it's possible I just didn't notice in 0.1.0h when I uploaded this cart, or the desync might be less consistent than it currently seems</p> https://www.lexaloffle.com/bbs/?tid=143812 https://www.lexaloffle.com/bbs/?tid=143812 Thu, 22 Aug 2024 23:26:15 UTC sfx/music data layout <p>Inspired by <a href="https://www.lexaloffle.com/bbs/?tid=2341">https://www.lexaloffle.com/bbs/?tid=2341</a>, here's a description of how sfx and music are stored in Picotron. (Well, there's not much description here yet, just some helpful code. I'll add to this over time)</p> <p>The data can be in unusual custom formats. The header data is supposed to help in these cases and is accounted for in this code (with some asserts to crash if the data format is unknown). But for most people using this code, the data will be in the standard format.</p> <h2>Library</h2> <p>Code:<br /> <div><div><input type="button" value=" Show " onClick="if (this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display != '') { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = ''; this.innerText = ''; this.value = ' Hide '; } else { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = 'none'; this.innerText = ''; this.value = ' Show '; }"></div><div><div style="display: none;"></p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>-- sfx/music data wrangler -- by pancelor -- The docs call this the &quot;index&quot;, I call it the &quot;header&quot; -- https://www.lexaloffle.com/dl/docs/picotron_synth.html#Index function sfxheader_read() local num_instruments, num_tracks, num_patterns, flags = peek2(0x30000, 4) -- ...8 unused bytes here... local insts_addr, tracks_addr, patterns_addr, unused1 = peek4(0x30010, 4) assert(unused1==0,&quot;bad sfx header 1&quot;) local tick_len, def_len, def_spd = peek2(0x30020, 3) local def_spd2, unused2, unused3, unused4 = peek(0x30026, 4) -- TODO def_spd2 v. def_spd? assert(unused2==0,&quot;bad sfx header 2&quot;) assert(unused3==0,&quot;bad sfx header 3&quot;) assert(unused4==0,&quot;bad sfx header 4&quot;) return { num_instruments = num_instruments, num_tracks = num_tracks, num_patterns = num_patterns, flags = flags, --0x1 use default track indexing (base+0x20000, increments of 328 bytes) insts_addr = insts_addr, --relative address of instruments tracks_addr = tracks_addr, --relative address of track index patterns_addr = patterns_addr, --relative address of pattern data tick_len = tick_len, --in 1/16ths of a sample at 44100Hz [0 means 5880 -- 120 ticks / second] def_len = def_len, --used by patterns that do not have a default length specified def_spd = def_spd, --used by patterns that do not have a default speed specified def_spd2 = def_spd2, -- ?? } end function pattern_read(i) local base = 0x30100 + sfxheader_read().patterns_addr -- normally 0x30100 local addr = base + i*20 local tracks = { peek(addr, 8) } local flow_flags, channel_mask = peek(addr+8, 2) local len = peek2(addr+10) -- ...8 unused bytes here... return { tracks = tracks, -- array with 8 track ids flow_flags = flow_flags, -- 0x1 loop forward, 0x2 loop backward, 0x4 stop channel_mask = channel_mask, -- 0x1 is tracks[1] unmuted? 0x2 is tracks[2] unmuted? 0x4 =&gt; tracks[3], 0x8 =&gt; tracks[4], ... 0x80 =&gt; tracks[8] len = len, --TODO: how does this interact with track length and header def_len/def_len2? } end function track_read(i) local header = sfxheader_read() assert(header.flags&amp;1==1,&quot;unknown track format&quot;) local base = 0x30000 + header.tracks_addr -- normally 0x50000 local addr = base + i*328 local len = peek2(addr) local spd, loop0, loop1, delay, flags, unused = peek(addr+2, 6) assert(unused==0,&quot;bad sfx track&quot;) -- note: if len&lt;64 then some of this data is irrelevant: local pitches = { peek(addr+8,64) } local instruments = { peek(addr+72,64) } local volumes = { peek(addr+136,64) } local effects = { peek(addr+200,64) } local effect_params = { peek(addr+264,64) } return { len = len, spd = spd, loop0 = loop0, loop1 = loop1, delay = delay, flags = flags, --0x1 mute -- 64-length arrays, 1 entry per note: pitches = pitches, instruments = instruments, volumes = volumes, -- a 0xFF entry means muted effects = effects, -- stored as their ascii code -- retrieve with ord() effect_params = effect_params, } end -- an alternate version of track_read that extracts a single row -- ti: track index -- ri: row index function track_row_read(ti,ri) local header = sfxheader_read() assert(header.flags&amp;1==1,&quot;unknown track format&quot;) local base = 0x30000 + header.tracks_addr -- normally 0x50000 local addr = base + ti*328 + ri return { pitch = peek(addr+8), instrument = peek(addr+72), volume = peek(addr+136), effect = peek(addr+200), effect_params = peek(addr+264), } end function instrument_read(i) local base = 0x30000 + sfxheader_read().tracks_addr -- normally 0x40000 local addr = base + i*0x200 assert(false,&quot;not implemented&quot;) -- see https://www.lexaloffle.com/dl/docs/picotron_synth.html#Index -- or read /system/apps/sfx.p64/data.lua:clear_instrument() for info end -- i: channel index 0..7 function channel_current_track(i) return stat(464,0)&gt;&gt;i&amp;1~=0 and stat(400+i,12) or -1 end -- i: channel index 0..7 function channel_current_row(i) return stat(464,0)&gt;&gt;i&amp;1~=0 and stat(400+i,9) or -1 end -- number of ticks played on current pattern function channel_ticks_played(i) return stat(400+i,11) end --currently playing pattern, or -1 function current_pattern() return stat(466) end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p></div></div></div></p> <p>Sources:</p> <ul> <li>/system/apps/sfx.p64/data.lua</li> <li><a href="https://www.lexaloffle.com/dl/docs/picotron_synth.html#Index">https://www.lexaloffle.com/dl/docs/picotron_synth.html#Index</a></li> </ul> <h2>Demo cart</h2> <p>Here's a tiny music &quot;visualizer&quot; that uses this library:<br /> <table><tr><td> <a href="/bbs/?pid=152071#p"> <img src="/bbs/thumbs/pico64_datufbuyi-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=152071#p"> datufbuyi</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=152071#p"> [Click to Play]</a> </td></tr></table> </p> <h2>License</h2> <p>This library is licensed as <a href="https://creativecommons.org/licenses/by/4.0/">CC BY 4.0</a> (basically, credit me and link back to this post, and you can use this code for anything)</p> <p>(This does not include the music playing in the demo cart. The music is by <a href="https://www.lexaloffle.com/bbs/?uid=12744"> @iaoth</a> and is licensed as <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a> -- see <a href="https://www.lexaloffle.com/bbs/?pid=51460">https://www.lexaloffle.com/bbs/?pid=51460</a>)</p> https://www.lexaloffle.com/bbs/?tid=143443 https://www.lexaloffle.com/bbs/?tid=143443 Fri, 02 Aug 2024 22:56:07 UTC spritesheet remapping 0x5f54 crashes after reboot <p><a href="https://www.lexaloffle.com/bbs/?uid=1"> @zep</a> o/</p> <p>0.2.6b / linux:</p> <ol> <li>launch pico8 executable</li> <li>run <code>poke(0x5f54,0x80)</code> (in the console)</li> <li>run <code>reboot</code></li> <li>run <code>poke(0x5f54,0x80)</code> (copied from <a href="https://www.lexaloffle.com/bbs/?tid=140421">the 0.2.6 release notes</a>)</li> <li>the executable crashes</li> </ol> <p>Step 2 is optional -- it still crashes in step 5 if you skip step 2<br /> In Step 4, running <code>poke(0x5f54,0x60)</code> instead does <em>not</em> crash.</p> <p>I'm not sure how to get a stacktrace on my machine, or if it's even possible. this is the best I know how to do:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>&gt; coredumpctl debug PID: 12604 (pico8) UID: 1000 (pancelor) GID: 1000 (pancelor) Signal: 11 (SEGV) Timestamp: Tue 2024-06-18 18:51:34 PDT (3min 17s ago) Command Line: /home/pancelor/.config/itch/apps/pico-8/pico-8/pico8 -root_path /run/media/pancelor/data/Users/pancelor/Documents/pancelor/src/pico8/ Executable: /home/pancelor/.config/itch/apps/pico-8/pico-8/pico8 Control Group: /user.slice/user-1000.slice/[email protected]/app.slice/app-pico8-b4acdb07c9cf4629b15a6b78f024fa7f.scope Unit: [email protected] User Unit: app-pico8-b4acdb07c9cf4629b15a6b78f024fa7f.scope Slice: user-1000.slice Owner UID: 1000 (pancelor) Boot ID: bc016245c64e47f1b3755470beec5d96 Machine ID: 4eec57f899074b4eb49773a72b91be1c Hostname: pancelor-manjaro Storage: /var/lib/systemd/coredump/core.pico8.1000.bc016245c64e47f1b3755470beec5d96.12604.1718761894000000.zst (present) Size on Disk: 10.3M Message: Process 12604 (pico8) of user 1000 dumped core. Stack trace of thread 12604: #0 0x00000000004734e6 n/a (/home/pancelor/.config/itch/apps/pico-8/pico-8/pico8 + 0x734e6) ELF object binary architecture: AMD x86-64 Failed to invoke gdb: No such file or directory</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>tl;dr: SEGV at 0x734e6</p> https://www.lexaloffle.com/bbs/?tid=142755 https://www.lexaloffle.com/bbs/?tid=142755 Wed, 19 Jun 2024 01:38:37 UTC coroutine stack pollution <p> <table><tr><td> <a href="/bbs/?pid=149328#p"> <img src="/bbs/thumbs/pico8_wutewuwiho-1.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=149328#p"> wutewuwiho</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=149328#p"> [Click to Play]</a> </td></tr></table> </p> <p>hi <a href="https://www.lexaloffle.com/bbs/?uid=1"> @zep</a>! This cart should not error, but it does (pico8 0.2.6b / linux)</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>-- wrap everything in func _init to fix it --function _init() --uncomment this to fix it --poke(0,0) --make this local to fix it coro = cocreate(function() -- uncomment this to fix it -- local x=1 for i=0,32000 do -- flr() is arbitary here; it's -- just a func that should -- return only one value local rets = pack(flr(i)) if #rets~=1 then local str=&quot;flr rvals:&quot; for i,v in ipairs(rets) do str ..= &quot;\n\t&quot;..i..&quot;: &quot;..tostr(v) end str ..=&quot;\nstat(1): &quot;..stat(1) print(str..&quot;\n&quot;) assert(false) end -- uncomment this to fix it -- if flr==flr then end end end) cls() --remove second param to fix it assert(coresume(coro, &quot;uh-oh&quot;)) print&quot;no errors&quot;</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <hr /> <p>It seems like the string &quot;uh oh&quot; is being popped from the stack in a completely wrong place.</p> <p>See also <a href="https://www.lexaloffle.com/bbs/?tid=141267">https://www.lexaloffle.com/bbs/?tid=141267</a> , a picotron coroutine bug. This cart is a modified version of <a href="https://www.lexaloffle.com/bbs/?uid=46556"> @jesstelford</a>'s code there. I'm unsure if the root cause is related, but the result is the same.</p> <p>Notably, this cart consistently crashes every single time (unlike the picotron version), and seems directly related to stat(1) going over 1.0</p> <p>I marked a bunch of things you can uncomment to make this test case pass. (there are some very weird things you can do to make it suddenly work)</p> <hr /> <p>I'm fuzzy on the details, but it seems like something like this is happening:</p> <ol> <li><code>coresume(coro, &quot;uh-oh&quot;)</code> pushes &quot;uh-oh&quot; to the internal lua/C/lua_State stack, where it stays for a while (because there isn't a <code>yield()</code> that pops that value)</li> <li>the coroutine takes a long time (<code>stat(1)&gt;1</code>), and pico8 pauses my cart to do its own thing (flip, etc)</li> <li>pico8 prepares to hand control back to my cart, by restoring the state my cart was in when it got forcibly paused. it restores the stack with &quot;uh-oh&quot; on it, but it does it wrong somehow</li> <li>my code resumes running, maybe right at the point where <code>flr()</code> returns? (that would be quite a coincidence, but it might explain why irrelevant changes like <code>local x=1</code> would squash the bug)</li> <li><code>flr</code> returns the correct value, but also the rest of the stack, which is &quot;uh oh&quot;.</li> </ol> https://www.lexaloffle.com/bbs/?tid=142550 https://www.lexaloffle.com/bbs/?tid=142550 Sun, 02 Jun 2024 04:59:16 UTC 0x5f36 automatic character wrap + camera <p>I'm pretty sure this is a bug; I'm on pico8 0.2.6b / linux</p> <p>You can turn on character wrapping with 0x5f36 / 24374, but it acts unexpectedly when you move the camera:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>cls(1) poke(0x5f36,0x80) --turn on character wrapping msg='0123456789abcdefghijklmnopqrstuvwxyz' ?msg,0,0,12 camera(16,0) ?msg,16,12,13</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Expected behavior: both prints should wrap at the edge of the screen.<br /> Actual behavior:</p> <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/27691/game_0.png" alt="" /> <hr /> <p>There's a similar issue with p8scii &quot;\^r&quot; wrapping:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>cls(1) msg='\^rf0123456789abcdefghijklmnopqrstuvwxyz' --note p8scii wrap at start ?msg,0,0,13 camera(16,0) ?msg,16,18,14</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/27691/untitled_2_1.png" alt="" /> https://www.lexaloffle.com/bbs/?tid=142400 https://www.lexaloffle.com/bbs/?tid=142400 Thu, 23 May 2024 21:45:57 UTC menuitem never runs with P8SCII flip() <p>cart A: (broken)</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>msg=&quot;havent pressed test yet&quot; menuitem(1,&quot;★test&quot;,function() msg=&quot;pressed test!&quot; end) ::_:: ?&quot;\^1\^c&quot; --https://www.lexaloffle.com/dl/docs/pico-8_manual.html#Control_Codes print(time(),0,0,7) print(msg) goto _</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>cart B: (working)</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>msg=&quot;havent pressed test yet&quot; menuitem(1,&quot;★test&quot;,function() msg=&quot;pressed test!&quot; end) ::_:: flip()?&quot;\^c&quot; --this line is the only difference print(time(),0,0,7) print(msg) goto _</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>These two carts should behave the same, but the menuitem function in cart A never runs -- after opening the menu and selecting the menuitem, it still says &quot;havent pressed test yet&quot;</p> <p>cart B works as expected -- it says &quot;pressed test!&quot;</p> <p>My system is linux / pico8 0.2.6b</p> https://www.lexaloffle.com/bbs/?tid=142313 https://www.lexaloffle.com/bbs/?tid=142313 Fri, 17 May 2024 11:49:05 UTC Make Ten <p>OUT NOW: <a href="https://www.lexaloffle.com/bbs/?tid=149014">Make Ten Deluxe</a>. I'll leave this page up but you should go play the updated version instead!</p> <p> <table><tr><td> <a href="/bbs/?pid=148268#p"> <img src="/bbs/thumbs/pico8_make_ten-3.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=148268#p"> make_ten</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=148268#p"> [Click to Play]</a> </td></tr></table> </p> <p>Select numbers that add to ten, to remove them from the board. How high can you score in just 2 minutes?</p> <p>An homage to Fruit Box. A FruitBox-like? There are some big differences in this version, the most obvious being the lack of music. (the music in the original is incredible)</p> <h2>How to play</h2> <ul> <li>Drag your mouse to select groups of numbers that add up to 10.</li> <li>I recommend playing in fullscreen; it's easier to click on larger numbers.</li> <li>In the original game, you're awarded one point per number removed,</li> <li>Get the highest score you can within 2 minutes. Good luck!</li> </ul> <p>This was my submission for TweetTweetJam 9, written in 490 characters of code:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>z={}c=-8s=0g='⁶!5f58⁵d8&quot;'::_::?&quot;⁶1⁶c0⁶!5f2d3&quot; e=1x=stat(32)y=stat(33)if btn(5)do f=mid(16,96,y)if(p==a)p=x q=f?&quot;⁷i6v1c1&quot; 😐=c&amp;min(x,p)⌂=c&amp;min(f,q)♪=c&amp;max(x,p)&hearts;=c&amp;max(f,q)else if(p)p=a if(n==c)e=0?&quot;⁷i7f1a&quot; end?g,d n=2for i=32,207do w=z[i]or rnd(9)\1+1u=i%16*8v=c&amp;i\2if(u-😐|♪-u|v-⌂|&hearts;-v&gt;0)w*=e n-=w ⬅️+=➡️&amp;~e z[i]=w?w,u,v,w end?&quot;⁶jc1score &quot;..s,-🅾️ if(btnp(6))🅾️=⬇️ if(p)rect(😐,⌂,♪+9,&hearts;+8) d=-t()?&quot;⁶x2&sup1;b█&quot;,d-5,106 if(d==-128)➡️=0g=&quot;⁶j8rtime up!&quot;⬇️=🅾️?&quot;⁷v3fg&quot; if(s&lt;⬅️)s+=1?&quot;⁷x5v3c0&quot; ?&quot;⁶.&sup1;&sup3;⁷ᶠ&sup3;⁴\0\0&quot;,x-2,y-2,7 goto _</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>There's a <a href="https://gist.github.com/pancelor/7e43d62bcf4bb757f454eecebbbd897b">commented version here</a>.</p> <p>See also: <a href="https://pancelor.itch.io/make-ten">itch</a> | <a href="https://mastodon.social/@pancelor/112421461290262396">mastodon</a> | <a href="https://cohost.org/pancelor/post/5925753-make-ten">cohost</a></p> <h2>Deluxe edition</h2> <p><a href="https://www.lexaloffle.com/bbs/?tid=149014">Out now!</a></p> https://www.lexaloffle.com/bbs/?tid=142209 https://www.lexaloffle.com/bbs/?tid=142209 Sat, 11 May 2024 11:27:15 UTC if-shorthand parser weirdness <p><a href="https://www.lexaloffle.com/bbs/?uid=1"> @zep</a> This maybe isn't a bug, but it feels very weird to me:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>cls() if (1)&lt;2 print&quot;a&quot; print&quot;b&quot;</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>this prints <code>a b</code>, but I would expect a syntax error instead -- the condition is &quot;1&lt;2&quot; but the if-shorthand parens are only surrounding the 1, instead of the whole condition.</p> <p>(if you change it to <code>if (1)&lt;0</code> then it only prints &quot;b&quot;, which verifies that the condition isn't being interpreted as just <code>if (1)</code> or something like that)</p> <p>oh, and my system is pico8 0.2.6b / linux</p> https://www.lexaloffle.com/bbs/?tid=142179 https://www.lexaloffle.com/bbs/?tid=142179 Thu, 09 May 2024 01:02:15 UTC parser bug: shorthand if+print+comment <p>Hi <a href="https://www.lexaloffle.com/bbs/?uid=1"> @zep</a>!</p> <p>pico8 0.2.6b / linux:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>cls() if (false)?1 --hi print(2) print(3)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>expected output: <code>2 3</code><br /> actual output: <code>3</code></p> <p>Here's an altered version that works as expected:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>cls() if (false)?1 print(2) print(3)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>(this prints <code>2 3</code>, as expected)</p> <hr /> <p>3 things seem required to trigger this bug:</p> <ol> <li>shorthand if</li> <li>shorthand print on the same line</li> <li>!! further text after the shorthand print (&quot; --hi&quot;, in this example. but just a single trailing space triggers the bug too)</li> </ol> <p>When these are all true, the next line seems to get scooped up into the shorthand line. This can include attaching an <code>else</code> to the wrong <code>if</code>, like in this more complicated example:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>cls() if false then if (false)?1 --hi else print(2) print(3) end print(4)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>expected output: <code>2 3 4</code><br /> actual output: <code>4</code></p> https://www.lexaloffle.com/bbs/?tid=142148 https://www.lexaloffle.com/bbs/?tid=142148 Tue, 07 May 2024 11:54:35 UTC photo_carousel <p>This is a silent tutorial video; skip to 2:10 for the juicy bit:<br /> <object width="640" height="400"><param name="movie" value="https://www.youtube.com/v/bkB_kN-3L8U&hl=en&fs=1&rel=0"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="https://www.youtube.com/v/bkB_kN-3L8U&hl=en&fs=1&rel=0" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="640" height="400"></embed></object></p> <p>This is an animated wallpaper that shows your custom PNG files -- just place them in a particular folder! It also works as a screensaver. It's cpu-friendly, only drawing during transitions.</p> <h2>Installing</h2> <ul> <li><code>load #photo_carousel</code></li> <li><code>mkdir /appdata/system/wallpapers</code></li> <li><code>save /appdata/system/wallpapers/photo_carousel.p64</code></li> <li>run the cart once, to generate the appdata folder</li> <li><code>cd /appdata/photo_carousel</code></li> <li><code>folder</code></li> <li>using your host OS, copy any PNG files into this folder (don't put them in subfolders)</li> <li>set your wallpaper to <code>photo_carousel</code> in System Settings</li> </ul> <h2>Settings</h2> <p>Run <code>podtree /appdata/photo_carousel/settings.pod</code> to edit the settings. Be sure to not press enter while editing (this crashes Picotron 0.1.0e) and you must save with the mouse, not ctrl-s (another Picotron bug(?) -- ctrl-s saves the current cart, instead of the settings file)</p> <p>Set the transition time to 0 to disable transitions</p> <h2>Details</h2> <h3>Converting PNGs</h3> <p>This cart uses <a href="https://www.lexaloffle.com/bbs/?pid=importpng#p">importpng</a> to convert PNG files into Picotron graphics. It does this once, in the background, and caches the results.</p> <p>If you edit one of your PNGs, photo_carousel won't notice (afaik it's impossible to get last-edited time of PNGs in Picotron) Rename your file to generate a new cache entry, or delete <code>/appdata/photo_carousel/cache.pod</code> to regenerate the entire cache.</p> <p><a href="https://www.lexaloffle.com/bbs/?pid=importpng#p">importpng</a> is fast, but it's sometimes lacking in its ability to convert images into Picotron's palette. If you want better colors, you should use an external tool (like Aseprite) to convert your PNG into the base Picotron palette (you'll want the base palette, because the global palette changes to the base palette whenever you focus on some other window)</p> <p>In the future I may use <a href="https://www.lexaloffle.com/bbs/?pid=pngframe#p">PNG Frame</a> to get better colors by default, but I haven't done that work yet.</p> <h3>Transitions</h3> <p>You can add your own transitions -- you can skim through src/subdraw.lua without needing to understand anything else in the cart, and add new entries to the list of &quot;subdraws&quot; (transitions). Its very shadery/demosceney code</p> <p>Many of the subdraws use some helper functions in src/tools.lua</p> <p>Post your transition code here!</p> <h3>CPU usage</h3> <p>The cart uses about 0.002 cpu (0.2%) most of the time. During transitions, it can spike up to 25% cpu or so (depending on the transition). If you care, you can disable the transitions as a whole (by editing the settings) or individually (by editing the multi-line comments in src/subdraw.lua)</p> <h3>Cart</h3> <p>Here's the cart file. (It won't work here on the BBS, you need to download it)</p> <p> <table><tr><td> <a href="/bbs/?pid=145966#p"> <img src="/bbs/thumbs/pico64_photo_carousel-1.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=145966#p"> photo_carousel</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=145966#p"> [Click to Play]</a> </td></tr></table> </p> https://www.lexaloffle.com/bbs/?tid=141520 https://www.lexaloffle.com/bbs/?tid=141520 Sun, 07 Apr 2024 11:59:48 UTC coroutine bug <p>hey <a href="https://www.lexaloffle.com/bbs/?uid=1"> @zep</a>, I've found a nasty coroutine(?)/multival bug. I'm on Linux + picotron 0.1.0d.</p> <p>tl;dr: sometimes <code>select(&quot;#&quot;,tostr(i))</code> is 2, possibly triggered by calling coresume() with extra args.</p> <hr /> <p>I ran into this initially because <code>add({},3,nil)</code> is a runtime error now (it used to work in PICO-8, but now it throws <code>bad argument #2 to 'add' (position out of bounds)</code>). I had some code: <code>add(list,quote(arg))</code> that was crashing as if quote() was returning a second value for some reason, even though the code for quote() definitely returned just one value. (surrounding it in parens (to only keep the first return value) fixed my bug: <code>add(list,(quote(arg)))</code>)</p> <p>Version A of the code is very short, and trips the assert inside <code>spin</code> maybe 50% of the time? sometimes many runs in a row don't trigger the assert, but sometimes many runs in a row <em>all</em> trigger the assert. (maybe that's just statistics tho). Version B is a bit more complex but always trips the assert instantly for me.</p> <hr /> <p>Version A: (short, inconsistent) <div><div><input type="button" value=" Show " onClick="if (this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display != '') { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = ''; this.innerText = ''; this.value = ' Hide '; } else { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = 'none'; this.innerText = ''; this.value = ' Show '; }"></div><div><div style="display: none;"></p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>printh&quot;---&quot; function _init() local bad,good = cocreate_spin() fn = bad end function _draw() cls() local _,prog = fn(0.70) ?stat(1) ?prog or &quot;done&quot; end function cocreate_spin() local coro = cocreate(spin) return function( cpu_limit) if costatus(coro)==&quot;suspended&quot; then --this one breaks sometimes return assert(coresume(coro,cpu_limit or 0.90)) end end, function() if costatus(coro)==&quot;suspended&quot; then --this one always works return assert(coresume(coro)) end end end local total = 1000000 function spin() for i=1,total do if i%10000==0 --[[and stat(1)&gt;cpu_limit]] then yield(i/total) end local n = select(&quot;#&quot;,quote(i)) if n!=1 then assert(false,tostr(n)..&quot; retvals?! &quot;..i) end end end function quote(t) return tostr(t) end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p></div></div></div></p> <p>Version B: (longer, very consistent) <div><div><input type="button" value=" Show " onClick="if (this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display != '') { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = ''; this.innerText = ''; this.value = ' Hide '; } else { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = 'none'; this.innerText = ''; this.value = ' Show '; }"></div><div><div style="display: none;"></p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>printh&quot;---&quot; function _init() poke(0x5f36,0x80) -- wrap text window{ title=&quot;import png&quot;, width=160, height=64, } job = job_importpng() end function _update() job:work(0.70) end function _draw() cls() print(stat(1)) print(costatus(job.coro)) end function job_importpng() local coro = cocreate(pq_many) assert(coresume(coro)) local job = { coro = coro, progress = 0.00, } -- returns true iff job has more work to do function job:work( cpu_limit) if costatus(self.coro)==&quot;suspended&quot; then local _,dat = assert(coresume(self.coro,cpu_limit or 0.90)) if dat then self.progress = dat return true -- more work to do end end end return job end function pq_many() for i=1,1000000 do if i&amp;2047==0 then yield() end pq(i) end end -- quotes all args and prints to host console -- usage: -- pq(&quot;handles nils&quot;, many_vars, {tables=1, work=11, too=111}) function pq(arg) local s= {} local n = select(&quot;#&quot;,quote(arg)) if n!=1 then local second = select(2,quote(arg)) assert(false,tostr(n)..&quot; retvals?: &quot;..quote(arg)..&quot; &quot;..tostr(second)) end add(s,(quote(arg))) -- add(s,quote(arg)) printh(table.concat(s,&quot; &quot;)) end -- quote a single thing -- like tostr() but for tables -- don't call this directly; call pq or qq instead function quote(t) if type(t)~=&quot;table&quot; then return tostr(t) end local s={} for k,v in pairs(t) do add(s,tostr(k)..&quot;=&quot;..quote(v)) end return &quot;{&quot;..table.concat(s,&quot;,&quot;)..&quot;}&quot; end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p></div></div></div></p> <hr /> <p>As noted in Version A, the two coresume-wrapping-functions inside cocreate_spin() act differently -- I've never been able to trip the assert with the &quot;good&quot; version. I've tried versions of this code with no coroutines and haven't been able to trip the assert.</p> <p>idk what else to say, this bug seems baffling -- sometimes <code>select(&quot;#&quot;,quote(i))</code> is 2, despite quote() being a wrapper for tostr()</p> https://www.lexaloffle.com/bbs/?tid=141267 https://www.lexaloffle.com/bbs/?tid=141267 Sat, 30 Mar 2024 08:35:58 UTC import png <p> <table><tr><td> <a href="/bbs/?pid=144661#p"> <img src="/bbs/thumbs/pico64_importpng-9.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=144661#p"> importpng</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=144661#p"> [Click to Play]</a> </td></tr></table> </p> <h2>Guide</h2> <p>Currently (in Picotron 0.1.0e) it's hard to use a PNG image as a sprite. You can <code>fetch(&quot;myimage.png&quot;)</code>, but the result isn't in the right image format. So, here's a small tool to convert PNG files into picotron sprites.</p> <p>Drag any .png or .qoi image into the tool to convert it into a sprite pod on your clipboard. You can paste this into the graphics editor, or into code.</p> <p>Drag in a .hex file (e.g. from lospec.com) or a .pal file (e.g. from <a href="https://www.lexaloffle.com/bbs/?pid=okpal#p">OkPal</a>) before importing your png to change the import palette.</p> <h2>Details</h2> <h3>Import speed</h3> <p>This tool prioritizes import speed. Images with only a few unique colors (e.g. an image already in the picotron palette) will import much faster than images with many colors.</p> <h3>Color quantization</h3> <p>You can use any colors, and the tool will try it's best to fit them to the palette. It isn't very smart about converting colors (it uses nearest euclidean distance in RGB space, and doesn't do any dithering). If you want better color conversion, use some external tool and make sure the PNG is in the correct palette before converting it with this tool.</p> <p>See also PNG Frame, in &quot;Related tools&quot; below.</p> <h3>Wallpapers</h3> <p>If you're making a desktop wallpaper, I think you should stick to the default palette. If you use a custom palette, then your colors will change whenever you focus some window besides the desktop, I'm pretty sure.</p> <p>To make a wallpaper:</p> <ul> <li>make sure your wallpaper folder exists (<code>mkdir /appdata/system/wallpapers</code>)</li> <li>save a new cart into that folder with this code: <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>function _draw() spr(1) _draw = function() end end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div></li> <li>convert your image using this tool, and copy it into your clipboard</li> <li>paste your image into sprite 1 of the default sprite file</li> </ul> <h3>Library usage</h3> <p>This code is organized to make it easy to use as a library in your code. The library is self-contained in <code>lib/importpng.lua</code>, and you interact with it by calling <code>myjob = job_importpng(myimg)</code>. The returned &quot;job&quot; is essentially a coroutine, but one that will let you limit its processing time. Call <code>myjob:work(limit)</code> during _update() until the job is done, or call <code>myjob:join()</code> a single time to finish it all at once (likely lagging the game). See the code for more details.</p> <p>License: <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC4-BY-NC-SA</a></p> <h2>Related tools</h2> <ul> <li>Cutievirus' <a href="https://www.lexaloffle.com/bbs/?pid=pngframe#p">PNG Frame</a> -- a similar tool with much better color conversion. If your png is already in the right palette, importpng is faster than PNG Frame. But if you want better color quantization, use PNG Frame.</li> <li>My <a href="https://gist.github.com/pancelor/43512028087bf34e5d9773a61a03f93a">Aseprite PICO-8/Picotron export extension</a> lets you export Picotron pods directly to your clipboard from Aseprite.</li> <li>My <a href="https://www.lexaloffle.com/bbs/?tid=140800">sprite importer</a> lets you import PICO-8 spritesheets into Picotron.</li> <li>My <a href="https://www.lexaloffle.com/bbs/?pid=photo_carousel#p">photo carousel</a> uses this importer to show your custom PNGs as an animated wallpaper.</li> <li>drakmaniso's <a href="https://www.lexaloffle.com/bbs/?pid=okpal#p">OkPal</a> palette editor.</li> </ul> <h2>changelog</h2> <p><div><div><input type="button" value=" Show " onClick="if (this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display != '') { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = ''; this.innerText = ''; this.value = ' Hide '; } else { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = 'none'; this.innerText = ''; this.value = ' Show '; }"></div><div><div style="display: none;"></p> <h3>v0.4 (#importpng-9)</h3> <ul> <li>add <a href="https://qoiformat.org/">QOI</a> image support - thanks to vebrun for prompting me to add it!</li> </ul> <h3>v0.3 (#importpng-7)</h3> <ul> <li>support for custom palettes</li> <li>make it easy to use importpng as a library</li> </ul> <h3>v0.2 (#importpng-6)</h3> <ul> <li>show image as it imports</li> <li>draw black in preview (not transparent anymore)</li> <li>export to compressed pod format</li> </ul> <h3>v0.1 (#importpng-4)</h3> <ul> <li>initial release<br /> </div></div></div></li> </ul> https://www.lexaloffle.com/bbs/?tid=141149 https://www.lexaloffle.com/bbs/?tid=141149 Wed, 27 Mar 2024 11:24:42 UTC p8x8: convert PICO-8 carts to Picotron <p> <table><tr><td> <a href="/bbs/?pid=144532#p"> <img src="/bbs/thumbs/pico64_p8x8-7.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=144532#p"> p8x8</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=144532#p"> [Click to Play]</a> </td></tr></table> </p> <h3>p8x8: convert PICO-8 carts into Picotron carts (some assembly required)</h3> <p>I'm declaring p8x8 good enough for public release! It's a tool to convert pico8 carts to picotron -- it's not perfect and it requires some manual intervention in most cases, but it's magical being able to play a bunch of games on the new system without much effort.</p> <p>Lots more info (instructions, compatibility notes, CC license, etc) here: <a href="https://github.com/pancelor/p8x8/">https://github.com/pancelor/p8x8/</a></p> <p>Teaser video here: <a href="https://mastodon.social/@pancelor/112162470395945383">https://mastodon.social/@pancelor/112162470395945383</a></p> <h2>changelog</h2> <h3>v1.8 (#p8x8-8, unreleased)</h3> <ul> <li>music/sfx conversion!! just waiting on picotron 010h to add instrument effects</li> </ul> <h3>v1.7 (#p8x8-7)</h3> <ul> <li>fix secret palette</li> <li>add more <a href="https://github.com/pancelor/p8x8/tree/main/doc">docs</a></li> <li>add <code>p8x8_symbol_visual()</code> and <code>p8x8_datastring()</code> alongside <code>p8x8_symbol()</code> to help convert symbols. <a href="https://github.com/pancelor/p8x8/blob/main/doc/symbols.md">details</a></li> </ul> <p><div><div><input type="button" value=" Show " onClick="if (this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display != '') { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = ''; this.innerText = ''; this.value = ' Hide '; } else { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = 'none'; this.innerText = ''; this.value = ' Show '; }"></div><div><div style="display: none;"></p> <h3>v1.6 (#p8x8-6)</h3> <ul> <li>speed up dget/dset (batch disk writes)</li> <li>speed up map() (use picotron's builtin map() which does much better camera culling)</li> <li>add support for pico8 &quot;secret palette&quot;</li> </ul> <h3>v1.5 (#p8x8-5)</h3> <ul> <li>custom fonts work now <ul> <li>docs + snippet for making custom fonts work even if you set them up with p8scii</li> </ul></li> <li>fix player 2 btn/btnp</li> <li>some progress on porting sfx</li> <li>minor bugfix: prevent crash when input cart has no <strong>gfx</strong> section</li> </ul> <h3>v1.4 (#p8x8-4)</h3> <ul> <li>bugfixes for add(), rnd()</li> <li>organize github docs</li> <li>fix sfx()/music() (used to be noop; now passthrough to p64)</li> </ul> <h3>v1.3 (#p8x8-3)</h3> <ul> <li>better default fullscreen border</li> <li>better warnings</li> </ul> <h3>v1.2 (#p8x8-2)</h3> <ul> <li>support fullscreen border images</li> <li>easier main.lua tweaking (fullscreen, pause_when_unfocused)</li> <li>more docs / warnings / ux</li> </ul> <h3>v1.1 (#p8x8-1)</h3> <ul> <li>nicer error/warning handling (e.g. when there's no code section)</li> <li>handle windows CRLF carts properly</li> <li>generate warnings much faster (smarter sorting algorithm)</li> </ul> <h3>v1.0 (#p8x8-0)</h3> <ul> <li>public release<br /> </div></div></div></li> </ul> https://www.lexaloffle.com/bbs/?tid=141107 https://www.lexaloffle.com/bbs/?tid=141107 Tue, 26 Mar 2024 14:35:54 UTC