pancelor [Lexaloffle Blog Feed]https://www.lexaloffle.com/bbs/?uid=27691 Bitplanes! An overview <p><a href="https://en.wikipedia.org/wiki/Bit_plane">Bitplanes</a> are powerful, but they can be difficult to understand. How do you use them in PICO-8?</p> <p>The short version: bitplanes let you draw colors to the screen without completely overwriting the existing colors, making it possible to do effects like shadows, transparency, etc. But be warned: they come with a lot of unintuitive restrictions, like monopolizing half your screen palette or requiring it to be set up in a particular way.</p> <p>Longer version: PICO-8 colors are 4-bit numbers (0-15). The screen is a 128x128 4-bit image, but you can imagine it as 4 separate 12x128 1-bit images overlaid on top of each other. By poking a particular location in memory, we can tell PICO-8 to draw to these &quot;bit planes&quot; separately. Normally, drawing overwrites any existing colors, but if we selectively disable some of the bitplanes, some bits of the old color will remain onscreen.</p> <p>Technical version: see &quot;Technical details&quot; below.</p> <p>This post lists some specific examples and tricks that you can do with bitplanes. I won't attempt to fully explain how bitplanes work, but I'll leave some resources at the end.</p> <h2>Examples</h2> <h3>Shadows</h3> <ol> <li>set up your screen palette:<br /> i. 0-7: shadow palette; 0 is the shadow color for 8, 1 is the shadow color for 9, etc<br /> ii. 8-15: any colors</li> <li>draw anything</li> <li>enable bitplane mode 0x08 (clear the 0/8 bitplane)</li> <li>draw anything -- any pixels &quot;drawn&quot; will instead overwrite colors 8-15 with their shadow colors</li> <li>disable bitplane mode</li> </ol> <p> <table><tr><td> <a href="/bbs/?pid=134693#p"> <img src="/bbs/thumbs/pico8_bitplane_shadows-2.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=134693#p"> bitplane_shadows</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=134693#p"> [Click to Play]</a> </td></tr></table> </p> <p>I used this method in my <a href="https://www.lexaloffle.com/bbs/?pid=freecell1k#p">freecell clone</a>.</p> <img style="margin-bottom:16px" border=0 src="/media/27691/80_spr6.png" alt="" /> <h3>Spotlight</h3> <ol> <li>set up your screen palette:<br /> i. 0-7: all identical; e.g. all 0 (black)<br /> ii. 8-15: any colors</li> <li>draw your scene using colors 0-7. they will all appear black</li> <li>enable bitplane mode 0xf8 (only draw the 0/8 bit)</li> <li>draw a <code>circfill</code> in color 8 -- instead of drawing red, it will add 8 to each color it hits, revealing the image &quot;underneath&quot; the darkness</li> <li>disable bitplane mode</li> </ol> <p><a href="https://www.lexaloffle.com/bbs/?pid=120402#p">This cart</a> uses a very similar technique to create an &quot;xray&quot; effect. (They set up their palette differently and use a different bitplane mode, swapping adjacent colors instead of shifting the palette by 8)</p> <img style="margin-bottom:16px" border=0 src="/media/27691/81_spr1.png" alt="" /> <h3>1-bit sprite packing</h3> <p>If you have 1-bit sprites, you can store them merged in the 4 bitplanes in the spritesheet, and extract them at runtime using bitplanes. It's essentially another way to do <a href="https://8bit-caaz.tumblr.com/post/171458093376/layering-sprite-data">this sprite-layering technique</a>. Here's a <a href="https://www.lexaloffle.com/bbs/?tid=53207">tool</a> that does something similar. Neither of these actually use pico8's bitplane feature, but they could be implemented that way to save some tokens and a tiny bit of cpu.</p> <img style="margin-bottom:16px" border=0 src="/media/27691/spr3.png" alt="" /> <h3>Chromatic aberration</h3> <p>You can create some cool effects (like <code>https://mastodon.social/<a href="https://www.lexaloffle.com/bbs/?uid=1"> @zep</a>/109315783011955478</code>) by drawing slightly different images on different bitplanes.</p> <img style="margin-bottom:16px" border=0 src="/media/27691/spr2.png" alt="" /> <h3>Trippy motion blur</h3> <p>Flashing lights warning, this one can be pretty rough on the eyes. It's a neat <a href="https://twitter.com/lexaloffle/status/1318792339351990272">trippy effect</a> where each bitplane gets updated every 4 frames, leaving a slightly out-of-date impression onscreen for those other 3 frames, which creates a motion blur of sorts. I find it hard to look at (especially the jelpi example in the replies!) but it looks fascinating and it's very neat how bitplanes make it easy to create this effect in any game. I wonder how this could look with palette specifically designed for it... (the palette from the following &quot;Anti-aliasing&quot; example works decently well!)</p> <img style="margin-bottom:16px" border=0 src="/media/27691/84_spr8.png" alt="" /> <p>(thanks freds72 for finding the link to this! I couldn't remember where I'd seen it)</p> <h3>Anti-aliasing</h3> <p>You can add anti-aliasing by drawing the same thing at slight subpixel offsets, like in <a href="https://www.lexaloffle.com/bbs/?pid=104555#p">this example</a>. (Note that this restricts your palette to 5 colors)</p> <img style="margin-bottom:16px" border=0 src="/media/27691/spr5.png" alt="" /> <h2>Technical details</h2> <p><a href="https://en.wikipedia.org/wiki/Bit_plane">Wikipedia</a> has some general info, and the <a href="http://pico8wiki.com/index.php?title=Memory">PICO-8 wiki</a> (search &quot;bitplane&quot;) has specifics about how they work in PICO-8:</p> <p>&gt; 0x5f5e / 24414<br /> &gt; Allows PICO-8 to mask out certain bits of the input source color of drawing operations, and to write to specific bitplanes in the screen (there's 4 of them since PICO-8 uses a 4BPP display). Bits 0..3 indicate which bitplanes should be set to the new color value, while bits 4..7 indicate which input color bits to keep.<br /> &gt; For example, <code>poke(0x5f5e, 0b00110111)</code> will cause drawing operations to write to bitplanes 0, 1, and 2 only, with 0 and 1 receiving the color value bits, 2 being cleared, and 3 being unaltered.<br /> &gt; This formula is applied for every pixel written:<br /> &gt; <code>dst_color = (dst_color &amp; ~write_mask) | (src_color &amp; write_mask &amp; read_mask)</code></p> <p>If you're not sure what to try, setting the write_mask and read_mask to the same value is often useful.</p> <h2>Other resources</h2> <p>-<a href="https://www.lexaloffle.com/bbs/?tid=38338">Bitwise math tutorial</a><br /> -<a href="https://www.lexaloffle.com/bbs/?tid=39726">A simple bitplane example</a> -- three circles rotating in 3D<br /> -<a href="https://www.lexaloffle.com/bbs/?tid=54214">My bitplane explorer</a> -- it helps visualize how colors will interact</p> <img style="margin-bottom:16px" border=0 src="/media/27691/spr4.png" alt="" /> <p>-<a href="https://www.lexaloffle.com/bbs/?pid=76044#p">The original discovery</a><br /> -<a href="https://twitter.com/lexaloffle/status/1258464454485803009">Bitplanes confirmed by zep</a><br /> -<a href="https://www.lexaloffle.com/bbs/?tid=46286">Circular Clipping Masks</a> -- discusses other non-bitplane ways to get shadow and spotlight effects</p> https://www.lexaloffle.com/bbs/?tid=54215 https://www.lexaloffle.com/bbs/?tid=54215 Thu, 21 Sep 2023 11:11:20 UTC bitplane explorer <p>PICO-8 supports bitplane drawing; the <a href="http://pico8wiki.com/index.php?title=Memory">wiki</a> (search &quot;bitplane&quot;) has a description of how they work:</p> <p>&gt; 0x5f5e / 24414<br /> &gt; Allows PICO-8 to mask out certain bits of the input source color of drawing operations, and to write to specific bitplanes in the screen (there's 4 of them since PICO-8 uses a 4BPP display). Bits 0..3 indicate which bitplanes should be set to the new color value, while bits 4..7 indicate which input color bits to keep.<br /> &gt; For example, <code>poke(0x5f5e, 0b00110111)</code> will cause drawing operations to write to bitplanes 0, 1, and 2 only, with 0 and 1 receiving the color value bits, 2 being cleared, and 3 being unaltered.<br /> &gt; This formula is applied for every pixel written:<br /> &gt; <code>dst_color = (dst_color &amp; ~write_mask) | (src_color &amp; write_mask &amp; read_mask)</code></p> <p>This is precise and correct, but I find it a bit hard to understand. So I made this cart to give myself an interactive sandbox where I can play around with bitplanes, to see how they affect drawing.</p> <p> <table><tr><td> <a href="/bbs/?pid=134692#p"> <img src="/bbs/thumbs/pico8_bitplane_explorer-1.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=134692#p"> bitplane_explorer</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=134692#p"> [Click to Play]</a> </td></tr></table> </p> <p>The code is straightforward:</p> <ol> <li>draw the full 16-color palette</li> <li>enable bitplanes, using the <code>poke</code> shown onscreen</li> <li>draw a circle, using the <code>circfill</code> shown onscreen</li> </ol> <p>You can change the bitplane settings and the circle's &quot;color&quot; -- the controls are shown onscreen. This interactively shows how drawing any color will interact with any other color under the current bitplane settings.</p> <p>You'll still need to study the description from the wiki to understand how to use bitplanes, but this cart is a helpful supplement for me when working with bitplanes. I hope you find it helpful too!</p> https://www.lexaloffle.com/bbs/?tid=54214 https://www.lexaloffle.com/bbs/?tid=54214 Thu, 21 Sep 2023 02:13:53 UTC Responsive menuitem() UX <p>PICO-8 has <a href="https://www.lexaloffle.com/bbs/?tid=41544">fancy menuitems</a> but there are some <a href="https://www.lexaloffle.com/bbs/?tid=52196">gotchas</a> and <a href="https://www.lexaloffle.com/bbs/?tid=42392">bugs</a> to be aware of.</p> <p>Here's an example of what I do by default; the rest of this post will explain how the code works and why I do things this way:</p> <p> <table><tr><td> <a href="/bbs/?pid=133332#p"> <img src="/bbs/thumbs/pico8_menuitems-1.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=133332#p"> menuitems</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=133332#p"> [Click to Play]</a> </td></tr></table> </p> <h2>L/R pitfall</h2> <p>Imagine you want to add a &quot;mute&quot; button to your game's menu. Can you spot the issue with this 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>function _init() menuitem(1,&quot;mute&quot;,function() ismuted=not ismuted -- ... play/pause the music end) -- ... other game init 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>The issue: left/right can be used to change the menuitem, and left/right always leaves the menu open afterward (this may be a bug; it's unclear) so there's no indication to the player that they accidentally muted the game! It's a minor issue... but it's easy to accidentally hit left/right when navigating the menu on a gamepad, which makes this worse.</p> <p>An easy fix: don't respond to L/R (<code>menuitem(1,&quot;mute&quot;,function(bb) if bb&gt;=16 then ... end end</code>). This is what I'd recommend for games that are short on tokens but willing to spare a few to fix this.</p> <h2>Responsive menu</h2> <p>If you'd like a more responsive menu, you can do this:</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 _init() menuitem(1,ismuted and &quot;music: off&quot; or &quot;music: on&quot;,function() ismuted=not ismuted -- ... play/pause the music -- update the label: menuitem(nil,ismuted and &quot;music: off&quot; or &quot;music: on&quot;) -- feels like we could save some tokens... return true end) -- ... other game init 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>Now the option shows its status, and updates its label immediately when you change it!</p> <p>Note the <code>return true</code> -- this keeps the menu open. Without this, if the player presses left (toggling the setting) and then presses enter (trying to close the menu but instead toggling the setting again), they'll wonder why their change isn't sticking. By leaving the menu open, we show them that they actually re-toggled the setting, and they need to choose &quot;continue&quot; instead to resume the game.</p> <h2>Code organization idiom</h2> <p>The repeated <code>ismuted and &quot;music: off&quot; or &quot;music: on&quot;</code> is suspicious -- can we save some tokens? Yes, by <strong>setting up each menuitem() again at the end of the callback</strong>, just to refresh the labels:</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 _init() menuitems() -- setup all menuitem()s -- ... other game init end function menuitems() menuitem(1,ismuted and &quot;music: off&quot; or &quot;music: on&quot;,function() ismuted=not ismuted -- ... play/pause the music menuitems() -- refresh the label return true end) -- menuitem(2,... -- menuitem(3,... 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>I do this for code-organizing reasons -- it's nice to have all the menuitem code in one place -- but it can also save a few tokens, depending on your setup.</p> <h2>Context-dependent menuitems</h2> <p>Some menuitems should only be show in certain situations -- e.g. don't show &quot;back to title&quot; if we're already on the title screen. You could break each menuitem into its own little setup function (like zep's original <code>LOAD #CUSTOM_MENU</code> cart does -- check it out, it's nice!) or do what I do: <code>menuitem(1,condition and &quot;label&quot;,...)</code>. This will only show the menuitem when <code>condition</code> is truthy. I call <code>menuitems()</code> anytime the menu needs changing (e.g. at the end of <code>init_title_screen()</code>) and it all works out.</p> <h2>Recommendations</h2> <p>I put all my <code>menuitem()</code> calls inside a custom <code>menuitems()</code> function. Every callback runs <code>menuitems()</code> at the end, to refresh the labels.</p> <p>There are three types of menu options I commonly use: <strong>commands, toggles, and selectors</strong>.</p> <ul> <li>For <strong>commands</strong> (run some code, e.g. return to the title screen), I wrap the callback code in a <code>if bb&gt;=16 then ... end</code> block, to prevent L/R from triggering the command</li> <li>For <strong>toggles</strong> (e.g. mute/unmute the game), my callback always has <code>return true</code>, leaving the menu open afterward and avoiding the UX issue described above</li> <li>For <strong>selectors</strong> (choose a number from a range, e.g. change levels), my callback has <code>return bb&lt;16</code>, leaving the menu open only if L/R were used to change the selection. (this is technically unnecessary due to a longstanding <a href="https://www.lexaloffle.com/bbs/?tid=42392">bug(?)</a> in PICO-8's menu handling)</li> </ul> <p>Read the code of the example cart at the top of this post for more info!</p> https://www.lexaloffle.com/bbs/?tid=53822 https://www.lexaloffle.com/bbs/?tid=53822 Mon, 21 Aug 2023 01:02:04 UTC Feature request: commandline exit code on failure <p>I often use shell scripts to export and then upload my games to itch.io, and there's a small inconvenience that trips me up sometimes: If my game is too large, the export fails, but I have no way of detecting that from my shell script.</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; pico8 game.p8 -export &quot;-f froggypaint.html&quot; EXPORT: -f froggypaint.html failed: code block too large &gt; echo $? 0</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>I would expect the status code (<code>echo $?</code>) to be 1 or some other failure code</p> <p>(Hm, now that I've written this up I suppose I could work around this by reading <del>stderr</del> stdout and checking for the string &quot;failed&quot;...)</p> https://www.lexaloffle.com/bbs/?tid=52848 https://www.lexaloffle.com/bbs/?tid=52848 Wed, 24 May 2023 08:14:05 UTC low-token sort <p>A slow but token-efficient sort:</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>-- 35-token bubblesort, by pancelor function sort(arr) for i=1,#arr do for j=i,#arr do if arr[j] &lt; arr[i] then add(arr,deli(arr,j),i) --slow swap end end 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>I did a brief speed analysis and this function seems reasonable to use for <strong>arrays up to around 50 or 100 elements</strong>, depending on how much CPU you have available to burn. </p> <p>speed analysis:<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 did some minimal testing, using this 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>cls() function _draw() arr={ --20,5,8,3,7,4,1,9,2,-30, --20,5,8,3,7,4,1,9,2,-30, --20,5,8,3,7,4,1,9,2,-30, --20,5,8,3,7,4,1,9,2,-30, 20,5,8,3,7,4,1,9,2,-30, } sort(arr) 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>By commenting out lines of the array, I changed it's length. Here's how much CPU the function spent, as a percentage of a single 30fps frame (measured with the ctrl-p monitor, on pico8 0.2.5g)</p> <p>The &quot;best case cpu&quot; was calculated by defining <code>arr</code> outside of <code>_draw</code>, so that the array was already sorted after the first frame</p> <table> <thead> <tr> <th>Array length</th> <th>Typical cpu (30fps)</th> <th>Best case cpu (30fps)</th> </tr> </thead> <tbody> <tr> <td>10</td> <td>0%</td> <td>0%</td> </tr> <tr> <td>20</td> <td>1%</td> <td>1%</td> </tr> <tr> <td>50</td> <td>5%</td> <td>4%</td> </tr> <tr> <td>100</td> <td>21%</td> <td>15%</td> </tr> <tr> <td>200</td> <td>81%</td> <td>58%</td> </tr> <tr> <td>300</td> <td>181%</td> <td>130%</td> </tr> <tr> <td>400</td> <td>321%</td> <td>231%</td> </tr> </tbody> </table> <p>I believe this algorithm is O(n^3): O(n^2) for the two loops, and an additional O(n) b/c the swap step uses <code>add</code> and <code>deli</code>, which shift around the array elements. But the chart seems to indicate that doubling the length will quadruple the cpu cost (instead of octupling it) so maybe it's only O(n^2) somehow? It doesn't much matter, since you don't want to use this for large arrays anyway, but maybe add/deli are cheaper than I would expect.<br /> </div></div></div></p> https://www.lexaloffle.com/bbs/?tid=52478 https://www.lexaloffle.com/bbs/?tid=52478 Fri, 21 Apr 2023 23:04:42 UTC orthogonal line drawing perf <p>mildly interesting: when drawing a perfectly vertical line (<code>line(1,1,1,h)</code>) or perfectly horizontal line (<code>line(1,1,w,1)</code>), it used to be cheaper (in cpu cycles) to use <code>rect</code> or <code>rectfill</code> instead. but not anymore! I'm testing this on 0.2.5g; I'm not sure when this changed.</p> <p><strong>tl;dr: line and rectfill cost the same for orthogonal lines</strong> (rect is more expensive for vertical lines only)</p> <p>simple test:<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 /> <code>load #prof</code>, then add these lines:</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>-- horizontal prof(function() line(0,0,90,0,2) end,function() rect(0,0,90,0,2) end,function() rectfill(0,0,90,0,2) end) -- 9+10=19 (lua+sys) -- 9+10=19 (lua+sys) -- 9+10=19 (lua+sys) -- vertical prof(function() line(0,0,0,90,2) end,function() rect(0,0,0,90,2) end,function() rectfill(0,0,0,90,2) end) -- 9+10=19 (lua+sys) -- 9+22=31 (lua+sys) -- 9+10=19 (lua+sys)</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>full test:<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>function one(fn,opts) local fmt=function(n) -- leftpad a number to 2 columns return n&lt;9.9 and &quot; &quot;..n or n end local dat=prof_one(fn,opts) printh(fmt(dat.lua) ..&quot;+&quot; ..fmt(dat.sys) ..&quot;=&quot; ..fmt(dat.total) ..&quot; (lua+sys)&quot;) end function hline(x) line(0,0,x,0,2) end function hrect(x) rect(0,0,x,0,2) end function hrfil(x) rectfill(0,0,x,0,2) end function vline(x) line(0,0,0,x,2) end function vrect(x) rect(0,0,0,x,2) end function vrfil(x) rectfill(0,0,0,x,2) end printh&quot;--&quot; for i=0,127 do printh(i..&quot;: &quot;) one(hline,{locals={i}}) one(hrect,{locals={i}}) one(hrfil,{locals={i}}) one(vline,{locals={i}}) one(vrect,{locals={i}}) one(vrfil,{locals={i}}) end --[[ ... 14: 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 15: 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 16: 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 9+ 4=13 (lua+sys) 9+ 2=11 (lua+sys) 17: 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 9+ 4=13 (lua+sys) 9+ 2=11 (lua+sys) ... ]]</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>summary:</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>rect: when h is 1, sys cost is: max(1,w\16)*2 (agrees with CPU wiki page) when w is 1, sys cost is: max(1,(h-1)\8)*2 (disagrees? unsure) line and rectfill a,b = max(w,h),min(w,h) when b is 1, sys cost is: max(1,a\16)*2 (agrees with CPU page for rectfill, but not line(?))</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> https://www.lexaloffle.com/bbs/?tid=52166 https://www.lexaloffle.com/bbs/?tid=52166 Sat, 25 Mar 2023 03:47:18 UTC the tower <p> <table><tr><td> <a href="/bbs/?pid=125012#p"> <img src="/bbs/thumbs/pico8_thetower-2.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=125012#p"> thetower</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=125012#p"> [Click to Play]</a> </td></tr></table> </p> <p><em>welcome to the tower</em><br /> <em>welcome to the tower</em><br /> <em>welcome &zwj; to the tower</em><br /> <em>welcome to the tower</em></p> <h2>controls</h2> <ul> <li>arrow keys: walk, interact</li> <li>enter: pause menu (volume, toggle effects, etc)</li> </ul> <p>the game will autosave your progress each floor</p> <h2>credits</h2> <p>made by <a href="https://tallywinkle.itch.io/">tally</a> (art, room design, dialog) and <a href="http://pancelor.com/website2022-12/">pancelor</a> (code, game design, music, dialog)</p> <p>pixel art created with tiles from <a href="https://teaceratops.itch.io/1-bit-tileset">teaceratops</a> and <a href="https://1bityelta.itch.io/pokelike-tileset">yelta</a></p> <p>built with pico-8, <a href="https://www.aseprite.org/api/">aseprite</a>, <a href="https://www.lexaloffle.com/bbs/?pid=px9#p">PX9</a>, and <a href="https://github.com/thisismypassport/shrinko8">shrinko8</a></p> <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> <ul> <li>thetower-1: added title screen</li> <li>thetower-0: initial release<br /> </div></div></div></li> </ul> https://www.lexaloffle.com/bbs/?tid=51413 https://www.lexaloffle.com/bbs/?tid=51413 Sat, 18 Feb 2023 13:09:04 UTC custom font character height newline bug <p>switching between custom and default fonts (<code>?&quot;\015&quot;</code>) behaves a bit strangely; the first line of text is spaced differently from the rest, depending on whether:</p> <ol> <li>custom fonts are enabled by default (<code>poke(0x5f58,0x81)</code>), and</li> <li>the line height (<code>peek(0x5602)</code>) is more or less than 6 (the size of the default font)</li> </ol> <p>run this cart to see what I mean:</p> <p> <table><tr><td> <a href="/bbs/?pid=125013#p"> <img src="/bbs/thumbs/pico8_kahedapibi-1.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=125013#p"> kahedapibi</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=125013#p"> [Click to Play]</a> </td></tr></table> </p> <p>specifically:</p> <ol> <li>when the custom font is enabled by default and the custom font's height is more than 6, the default-font text in this cart has a large gap between the first and second lines:</li> </ol> <img style="margin-bottom:16px" border=0 src="/media/27691/temp_3.png" alt="" /> <ol start="2"> <li>when the custom font is not enabled by default and the custom font's height is less than 6, the custom-font text in this cart has a large gap between the first and second lines:</li> </ol> <img style="margin-bottom:16px" border=0 src="/media/27691/temp_2.png" alt="" /> <p>I didn't test how <code>?&quot;\^y&quot;</code> affects things.</p> <p>my system info: 64-bit linux, pico8 0.2.5e</p> https://www.lexaloffle.com/bbs/?tid=51414 https://www.lexaloffle.com/bbs/?tid=51414 Mon, 30 Jan 2023 07:21:14 UTC minor weirdness with .. token cost <p>how many tokens should this cart cost?</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>s=&quot;x&quot;..&quot;=&quot; a=1+2</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>in 0.2.1b, it costs 10 tokens (5 for each line). this seems correct to me. however, in 0.2.5e, the first line only costs 4 tokens for some reason.</p> <p>edit: even weirder: <code>s=&quot;x&quot;..&quot;=&quot;</code> costs 4 tokens but <code>s=&quot;x&quot;..&quot;y&quot;</code> costs 5 tokens. it seems like concatenating any string that starts with the equals symbol is 1 token cheaper than it should be; how odd! maybe this is somehow due to the recent parser updates for <code>+=</code> etc?</p> https://www.lexaloffle.com/bbs/?tid=50966 https://www.lexaloffle.com/bbs/?tid=50966 Tue, 03 Jan 2023 07:52:05 UTC twiddler: knobs for livecoding <p>When I'm making games or tweetcarts, I often adjust numbers just a tiny bit, then rerun the whole game. e.g. I change the player speed by 0.1, then change it back, then try 0.05...</p> <p>This is a bit slow, so here's a library I made to help me do it faster. I also find it useful for analyzing other people's tweetcarts -- if I can easily adjust the values they use, I can quickly figure out what they mean</p> <h2>setup</h2> <ul> <li><code>load #twiddler</code></li> <li>copy the <code>knobs.lua</code> + <code>helpers</code> tabs into your game</li> <li>use <code>kn[1]</code>, <code>kn[2]</code>, ... <code>kn[8]</code> in place of any number</li> <li>add <code>twiddler()</code> to the end of your <code>_draw</code> function</li> </ul> <p>Now, run your code: </p> <ul> <li>press tab and adjust the values (see &quot;controls&quot; below)</li> <li>press tab again -- the values will be copied to your clipboard</li> <li>paste the values into the start of your code to save them</li> </ul> <h2>example 1</h2> <p>Start with a tweetcart you want to study. For example, this one by 2DArray: <a href="https://twitter.com/2DArray/status/1492566780451205120">https://twitter.com/2DArray/status/1492566780451205120</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>?&quot;\^!5f10◆█🐱░4웃9&hearts;&quot; ::_::?&quot;⁶1⁶c&quot; w=t()/8 c=cos(w) s=sin(w) n=1800 for i=0,n do a=i*.618 j=1-i/n*i/n y=1+j*j-2*j*j*j r=1-(1-j)^3+sin(a*10)/9 p=(6+sin(a*10)*(.5+r/2))*(.7+y*y/3) circfill(64+cos(a+w)*r*20,80-y*30+sin(a+w)*r*12,2,p) end 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>Add the knobs, and replace some interesting-looking numbers with knobs:</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>kn={.5,.7,.618,2,0,0,0,0} --changed ?&quot;\^!5f10◆█🐱░4웃9&hearts;&quot; ::_::?&quot;⁶1⁶c&quot; w=t()/8 c=cos(w) s=sin(w) n=1200 --changed -- twiddler needs a little bit of cpu to run for i=0,n do a=i*kn[3] --changed j=1-i/n*i/n y=1+j*j-2*j*j*j r=1-(1-j)^3+sin(a*10)/9 p=(6+sin(a*10)*(kn[1]+r/2))*(kn[2]+y*y/3) --changed circfill(64+cos(a+w)*r*20,80-y*30+sin(a+w)*r*12,kn[4],p) --changed end twiddler() --changed 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>Now run the code and play around! looks like knobs 1 and 2 control the color gradient, knob 3 controls the spiralness of the circles. cool! Add new knobs, remove old knobs, keep going until satisfied.</p> <img style="margin-bottom:16px" border=0 src="/media/27691/temp_0.gif" alt="" /> <h2>example 2</h2> <p>This process works on your own code that you're in the middle of writing, too. Write <code>kn[1]</code> etc instead of a number, open the knobs panel, and edit the value until it looks right:</p> <img style="margin-bottom:16px" border=0 src="/media/27691/tweet-dev.gif" alt="" /> <p>This is from the dev process of a recent tweetcart of mine (<a href="https://twitter.com/pancelor/status/1583765792004640769?s=20&amp;amp;t=Yh6RYSIz43DsQoc3UB3__g">twitter</a>, <a href="https://cohost.org/pancelor/post/146796-adrift">cohost</a>)</p> <h3>controls:</h3> <p>Press Tab or W to open/close the knob panel</p> <ul> <li>LMB - change slider value</li> <li>MMB - pan slider range</li> <li>RMB - cancel current interaction</li> <li>wheel - zoom slider range</li> </ul> <p>When you close the panel (Tab/W), knob values will be copied to your clipboard -- paste them into your code to save them.</p> <p> <table><tr><td> <a href="/bbs/?pid=119729#p"> <img src="/bbs/thumbs/pico8_twiddler-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=119729#p"> twiddler</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=119729#p"> [Click to Play]</a> </td></tr></table> </p> <p>When you're done with the knobs, you can replace <code>kn[1]</code> etc with the literal value of the knobs, and delete the twiddler code. This is a tool meant for development, not meant to be used during a finished game. (although, a finished game that used something like this would be pretty nifty!)</p> https://www.lexaloffle.com/bbs/?tid=49949 https://www.lexaloffle.com/bbs/?tid=49949 Fri, 28 Oct 2022 22:11:27 UTC mine1k <p> <table><tr><td> <a href="/bbs/?pid=118854#p"> <img src="/bbs/thumbs/pico8_mine1k-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=118854#p"> mine1k</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=118854#p"> [Click to Play]</a> </td></tr></table> </p> <p>A demake of the classic minesweeper.</p> <p>The game cartridge is just 1024 bytes -- see <a href="https://gist.github.com/pancelor/a3aadc5e8cdf809cf0a4972ac9598433">https://gist.github.com/pancelor/a3aadc5e8cdf809cf0a4972ac9598433</a> for some lightly commented source code</p> <h2>RULES / CONTROLS:</h2> <ul> <li>left click to reveal a tile <ul> <li>if you hit a mine, you lose</li> <li>revealed tiles will show a number, telling how many of their 8 neighbors are mines</li> </ul></li> <li>right click to flag a tile</li> <li>reveal all non-mine tiles to win!</li> <li>click the smiley face to restart</li> </ul> <h2>TIPS</h2> <ul> <li>left click + right click (simultaneous) to auto-reveal neighbors, if the number of nearby flags matches the number on the tile you clicked</li> <li>mines left and a timer are displayed in the top corners</li> </ul> https://www.lexaloffle.com/bbs/?tid=49736 https://www.lexaloffle.com/bbs/?tid=49736 Sun, 09 Oct 2022 21:57:28 UTC feature request: export -e folders <p>When exporting a game to a binary format (.exe, etc), the <a href="https://www.lexaloffle.com/dl/docs/pico-8_manual.html#Binary_Applications_">manual</a> says:</p> <p>&gt; To include an extra file in the output folders and archives, use the -E switch:</p> <p>&gt; &gt; EXPORT -E README.TXT MYGAME.BIN</p> <p>I tried this (<code>pico8 game.p8 -export &quot;-f game.bin -e examples/ -e samples/&quot;</code>) but it doesn't include those subfolders. If I <code>-e examples/kick.pcm</code>, then that file is included, but it's included at the top level, and not in an &quot;examples&quot; subfolder</p> <p>Am I doing this wrong somehow? I assume this just isn't supported (yet? fingers crossed)</p> https://www.lexaloffle.com/bbs/?tid=47834 https://www.lexaloffle.com/bbs/?tid=47834 Mon, 16 May 2022 22:38:59 UTC im hungry <p> <table><tr><td> <a href="/bbs/?pid=110904#p"> <img src="/bbs/thumbs/pico8_imhungry-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=110904#p"> imhungry</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=110904#p"> [Click to Play]</a> </td></tr></table> </p> <p>&gt; Vous contr&ocirc;lez un petit rennes qui doit attraper le plus de nourriture possible, il s'agit de pain et de m&ucirc;res. Plus vous attrapez de, plus la nourriture va vite et plus il est compliqu&eacute; de l'attraper. En haut &agrave; droite, vous verrez qu'il y a votre tableau de bord, chaque aliment p&ecirc;ch&eacute; vaut un point. Et en haut &agrave; gauche, il y a un panneau qui vous montre combien d'aliments vous n'avez pas attrap&eacute;s. Attention ! au bout d'une dizaine d'aliments non attrap&eacute;s vous perdez et le jeu affiche alors &laquo; GAME OVER ! &raquo;, alors il faut appuyer sur enter pour recommencer.</p> <p>&gt; Les commandes sont : la fl&egrave;che droite pour se d&eacute;placer vers la droite, la fl&egrave;che gauche pour se d&eacute;placer vers la gauche et la fl&egrave;che vers le haut pour sauter. C'est si simple !</p> <p>(for more info, see <a href="https://itch.io/jam/im-hungry">https://itch.io/jam/im-hungry</a> / <a href="https://pancelor.itch.io/im-hungry">https://pancelor.itch.io/im-hungry</a>)</p> https://www.lexaloffle.com/bbs/?tid=47552 https://www.lexaloffle.com/bbs/?tid=47552 Tue, 26 Apr 2022 21:12:05 UTC P8SCII repeat (\*) doesn't work with newline <p>I hit this bug while working on a tweetcart:</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>?&quot;\*6a&quot; -- prints 6 'a's (expected) ?&quot;\*6\&quot;&quot; -- prints 6 quotes (expected) ?&quot;\*6\n&quot; -- prints 1 newline (unexpected!) ?&quot;\n&quot; -- prints 1 newline (expected)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <img style="margin-bottom:16px" border=0 src="/media/27691/untitled_1.png" alt="" /> https://www.lexaloffle.com/bbs/?tid=47223 https://www.lexaloffle.com/bbs/?tid=47223 Sun, 03 Apr 2022 02:06:44 UTC input buffering in grid-based games <p>here's a demo cart showing off some different ways to handle input in grid-based games:</p> <p> <table><tr><td> <a href="/bbs/?pid=107587#p"> <img src="/bbs/thumbs/pico8_hojohiyomu-1.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=107587#p"> hojohiyomu</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=107587#p"> [Click to Play]</a> </td></tr></table> </p> <p>controls:</p> <ul> <li>move around with the arrow keys</li> <li>change &quot;chapters&quot; in the pause menu (enter + arrow keys)</li> <li>slow down the game speed (in the later chapters) in the pause menu</li> </ul> <p>I made this cart as a companion to a <a href="https://pancelor.tumblr.com/post/677053912115707904/input-buffering-in-grid-based-games">blog post about input buffering</a></p> https://www.lexaloffle.com/bbs/?tid=46735 https://www.lexaloffle.com/bbs/?tid=46735 Thu, 24 Feb 2022 09:19:19 UTC &quot;unsaved changes&quot; when there are no changes <p><a href="https://www.lexaloffle.com/bbs/?uid=1"> @zep</a> o/</p> <p>A weird bug has been messing with me recently: pico-8 keeps telling me I have &quot;unsaved changes&quot; when I'm <em>pretty</em> sure I don't. I caught the bug on camera this time:</p> <p><object width="640" height="400"><param name="movie" value="https://www.youtube.com/v/AEf0MN_tCUU&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/AEf0MN_tCUU&hl=en&fs=1&rel=0" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="640" height="400"></embed></object></p> <p>I don't know how to reproduce it; I tried adding a new tab and messing with the text cursor position (since I wondered if this had something to do with the fix for <a href="https://www.lexaloffle.com/bbs/?tid=39379">https://www.lexaloffle.com/bbs/?tid=39379</a> ) and soon after I was able to trigger the bug. But I was able to trigger the bug without even opening up the code editor -- all I did was <code>load cart1</code> <code>load cart2</code> <code>load cart1</code> over and over again until it said &quot;unsaved changes&quot;. strange</p> <p>Jump to 1:12 and 1:24 in the video to see me trigger the bug two separate times. (the video description has a few other timestamps too)</p> <hr /> <p>I'm worried I'll stop trusting that message and accidentally lose real changes!</p> <p>I think this bug is new as of 0.2.4b; I don't remember it happening beforehand.</p> https://www.lexaloffle.com/bbs/?tid=46601 https://www.lexaloffle.com/bbs/?tid=46601 Wed, 16 Feb 2022 05:04:43 UTC La Sal (puzzle-y celeste map mod) <p> <table><tr><td> <a href="/bbs/?pid=105290#p"> <img src="/bbs/thumbs/pico8_lasal-1.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=105290#p"> lasal</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=105290#p"> [Click to Play]</a> </td></tr></table> </p> <p>A short celeste map mod. It's built as a bit of a puzzle, meant to teach you one specific thing about the game's mechanics.</p> <p>This doesn't require any advanced speedrunning tech (spike clips, corner jumps, etc) -- executing the intended solutions should be possible for anyone who's beaten <a href="https://www.lexaloffle.com/bbs/?tid=2145">celeste classic</a> once or twice.</p> <p>If you're trying something that seems too hard or only barely possible, try looking for alternatives!</p> <h2>controls</h2> <ul> <li>arrow keys / Z / X: move / jump / dash</li> <li>E: toggle screenshake</li> </ul> <h2>credits</h2> <p><a href="https://www.lexaloffle.com/bbs/?tid=2145">celeste classic</a>: maddy thorson + noel berry</p> <p><a href="https://github.com/CelesteClassic/smalleste">smalleste</a>: a token-optimized version of classic celeste that I used as a starting point</p> <p>playtesting: cryss, sharkwithlasers, James, meep</p> <p>berry follow code: meep's <a href="https://www.lexaloffle.com/bbs/?tid=37117">Terra Australis</a></p> <h2>map editor</h2> <p>I made this map to see how my <a href="https://www.lexaloffle.com/bbs/?tid=46225">custom map editor</a> felt to use. I think it's pretty neat -- check it out! It lets you build much larger maps than the built-in pico-8 map editor.</p> <img style="margin-bottom:16px" border=0 src="/media/27691/resize.gif" alt="" /> https://www.lexaloffle.com/bbs/?tid=46224 https://www.lexaloffle.com/bbs/?tid=46224 Wed, 19 Jan 2022 00:23:33 UTC bigmap editor <h2>demo cart</h2> <p> <table><tr><td> <a href="/bbs/?pid=105301#p"> <img src="/bbs/thumbs/pico8_bigmap_demo-1.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=105301#p"> bigmap_demo</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=105301#p"> [Click to Play]</a> </td></tr></table> </p> <h2>motivation</h2> <p>The recent <a href="https://www.lexaloffle.com/bbs/?tid=45538">0.2.4 release</a> added support for larger maps:</p> <p>&gt; Similar to gfx memory mapping, the map can now be placed at address 0x8000 and above (in increments of 0x100). This gives 4 times as much runtime space as the default map, and an additional POKE is provided to allow customisable map sizes.</p> <p>Larger maps are now possible, but it's difficult to get them into memory -- the built-in map editor only works with vanilla-sized maps.</p> <p>This cart is a full map editor that makes it easy to make these larger maps! </p> <h2>features</h2> <ul> <li>tight iteration loop - play a level in your game, jump into the map editor to make a small tweak, and return to the game with minimal friction <ul> <li> <img style="margin-bottom:16px" border=0 src="/media/27691/iterloop.gif" alt="" /> </li> </ul></li> <li>change map size at any time <ul> <li> <img style="margin-bottom:16px" border=0 src="/media/27691/resize.gif" alt="" /> </li> <li>max width: 256 tiles</li> <li>max height: none</li> <li>max total size: 32K tiles (e.g. 128*256, or 32*1024)</li> </ul></li> <li>easy copy-paste (right mouse + drag to copy, left mouse to paste)</li> <li>zooming in/out</li> <li>large brushes - place multiple tiles at a time</li> <li>show 16x16 &quot;room&quot; outlines (useful for carts like celeste that are made of many 16x16 rooms) <ul> <li> <img style="margin-bottom:16px" border=0 src="/media/27691/outlines.gif" alt="" /> </li> </ul></li> <li>&quot;transparency&quot; - optionally treat sprite 0 in large brushes as &quot;transparent&quot; <ul> <li> <img style="margin-bottom:16px" border=0 src="/media/27691/transparent.gif" alt="" /> </li> </ul></li> <li>compressed maps using <a href="https://www.lexaloffle.com/bbs/?tid=34058">PX9</a></li> <li>autosaving</li> <li>the map editor uses 0 of your tokens -- it's a completely separate cart that you only use during development<br /> (well, it costs ~300 tokens to load the map string and run the decompressor)</li> <li>your game will still be splore-compatible</li> <li>your game can call <code>map()</code>, <code>mget()</code>, <code>tline()</code> etc without any extra work</li> </ul> <h2>undo/redo?</h2> <p>The editor currently has no undo/redo functionality. That's not ideal! I'm hoping to get it working soon.</p> <p>To undo all changes since your last save (probably the end of your last session using bigmap), use the &quot;discard changes&quot; button in the top-right.</p> <p>If you make a large mistake, replace map.p8l with your an autosaved version (inside <code>mygame/autosave/</code>) and reload bigmap.</p> <h2>setup</h2> <p><object width="640" height="400"><param name="movie" value="https://www.youtube.com/v/llBF08wDT_U&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/llBF08wDT_U&hl=en&fs=1&rel=0" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="640" height="400"></embed></object></p> <p>Setting the bigmap editor up takes a bit of work. This is mainly necessary to enable a tight map iteration loop, and also to work around the restrictions of pico-8 (for example, it's impossible to read a file from disk without user interaction, and asking the user to drag-and-drop their map file every time they wanted to edit is way too much friction for my tastes)</p> <p>To start, make sure you have a folder (e.g. <code>mygame/</code>) with your game cart inside (e.g. <code>mygame/mygame.p8</code>)</p> <ol> <li><code>cd mygame</code> (navigate into the folder containing your game)</li> <li><code>mkdir autosave</code> (create a folder to store backups/autosaves)</li> <li><code>printh(&quot;&quot;,&quot;map.p8l&quot;)</code> (this creates an empty map.p8l file)</li> <li>Save this file (<a href="https://gist.github.com/pancelor/f933286f244c6b85b7720dbe6f809143">https://gist.github.com/pancelor/f933286f244c6b85b7720dbe6f809143</a>) as <code>px9_decomp.lua</code> (inside the <code>mygame/</code> directory)</li> <li><code>load #bigmap</code> (note: this is different from the bigmap_demo cart)</li> <li>Uncomment the two <code>#include</code> lines in the second tab (tab 1)</li> <li><code>save bigmap.p8</code></li> <li> <p>Paste this snippet into <code>mygame.p8</code>: (inside <code>_init()</code>, or at top-level; either works)</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>menuitem(1,&quot;▒ edit map&quot;,function() -- pass spritesheet through upper memory, -- avoiding an extra second of load time local focusx,focusy=0,0 memcpy(0x8000,0x0000,0x2000) poke(0x5500,1,focusx,focusy) --signal load(&quot;bigmap.p8&quot;,&quot;discard changes&quot;,&quot;mygame.p8&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>(make sure you change &quot;mygame.p8&quot; to the actual filename of your game)</p> <p>This snippet adds the menu option to enter bigmap while playing your game. If you set <code>focusx</code> and <code>focusy</code>, bigmap will start focused on that map coordinate.</p> </li> <li>Paste this snippet into <code>mygame.p8</code> at top-level: <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>#include map.p8l #include px9_decomp.lua if map_import then map_import() 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><strong>Save <code>mygame.p8</code></strong></li> </ol> <p>You should now be good to go!</p> <h2>test+edit iteration loop</h2> <ol> <li>Save <code>mygame.p8</code>. <strong>any unsaved changes will be lost</strong> every time you launch bigmap. (I wish this was avoidable but I couldn't find a way around it that preserved the quick test+edit loop I wanted)</li> <li>Run <code>mygame.p8</code></li> <li>Pause the game (with P or Enter)</li> <li>Choose &quot;edit map&quot;</li> <li>Edit your map!<br /> <strong>If you accidentally press escape and exit the map editor</strong>, type <code>r</code> or <code>resume</code> into the console to resume the map editor.</li> <li>Return to your game with P, Enter, or the clickable &quot;Play&quot; button in the top-right</li> </ol> <img style="margin-bottom:16px" border=0 src="/media/27691/iterloop.gif" alt="" /> <p>I advise setting up <code>mygame.p8</code> to jump you to the room you were editing when it starts - this can be done by reading the <code>focusx</code> and <code>focusy</code> global variables that are set inside the <code>map_import()</code> function (inside <code>map.p8l</code>)</p> <p>See <a href="https://www.lexaloffle.com/bbs/?tid=46224">my celeste mod</a> or the getting started video for an example of how to do this.</p> <h2>technical details</h2> <p>When you press the save or play button, bigmap uses <code>printh</code> to write a text file called <code>map.p8l</code> into the current directory. This text file happens to be valid lua code that defines a function called <code>map_import()</code>, so when <code>mygame.p8</code> executes <code>#include map.p8l</code> and <code>map_import()</code>, it runs the code generated by bigmap.</p> <p>Here's an example of what map.p8l looks like:</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>-- this file was auto-generated by bigmap.p8 function map_import() focusx=11 focusy=12 mapw=128 maph=64 poke(0x5f56,0x80,mapw) local function vget(x,y) return @(0x8000+x+y*mapw) end local function vset(x,y,v) return poke(0x8000+x+y*mapw,v) end px9_sdecomp(0,0,vget,vset,&quot;◝◝◝ユ◝7な◝◝✽,ゃf\0★)&amp;るちP;](&hearts;KねF ... many many more chars here ... X▤ミヘラ⬇️⬇️Bれん&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>This has 3 main parts:</p> <ol> <li>Set up some helpful global vars - <code>mapw</code> and <code>maph</code> are the width and height of the map, in tiles. <code>focusx</code> and <code>focusy</code> are the tile coordinate of the tile that was in the center of the screen when <code>map.p8l</code> was saved -- you can use this info to jump directly to that room to let you make small tweaks and test them very quickly</li> <li><code>poke(0x5f56,0x80,mapw)</code> -- this tells pico-8 to use mapdata stored at 0x8000, with map width <code>mapw</code></li> <li>The rest of the function uses PX9 to decompress the binary data stored in that long string. The decompressed data gets stored starting at 0x8000 and takes up <code>mapw*maph</code> bytes.</li> </ol> <p>That compressed data string is created using <a href="https://www.lexaloffle.com/bbs/?tid=34058">PX9</a> and <a href="https://www.lexaloffle.com/bbs/?tid=38692">this snippet</a> for encoding binary data in strings.</p> <h2>links</h2> <p>stuff I used:</p> <ul> <li>PX9, used for compression: <a href="https://www.lexaloffle.com/bbs/?tid=34058">https://www.lexaloffle.com/bbs/?tid=34058</a></li> <li>zep's string-packing snippet: <a href="https://www.lexaloffle.com/bbs/?tid=38692">https://www.lexaloffle.com/bbs/?tid=38692</a></li> <li>sprites used in bigmap_example.p8 (shown in the setup video): FROGBLOCK by Polyducks: <a href="https://polyducks.itch.io/frogblock">https://polyducks.itch.io/frogblock</a></li> </ul> <p>alternative map editors you might consider using instead:</p> <ul> <li><a href="https://www.lexaloffle.com/bbs/?tid=42848">https://www.lexaloffle.com/bbs/?tid=42848</a> -- make larger worlds out of metatiles</li> <li><a href="https://github.com/ExOK/Celeste2/">https://github.com/ExOK/Celeste2/</a> -- you can see in their source code how they make maps in individual carts and then combine+compress them all together into a single cart before release</li> <li><a href="https://github.com/samhocevar/tiled-pico8">https://github.com/samhocevar/tiled-pico8</a> -- use <a href="https://www.mapeditor.org/">Tiled</a> (an external program) to edit vanilla pico-8 maps. consider combining this with the Celeste2 method</li> </ul> <p>other:</p> <ul> <li>a celeste mod I made to make sure that it felt good to use this editor: <a href="https://www.lexaloffle.com/bbs/?tid=46224">https://www.lexaloffle.com/bbs/?tid=46224</a></li> <li>;) my webpage: <a href="https://pancelor.com">https://pancelor.com</a></li> </ul> <h2>happy map editing!</h2> <p>I'd like to see what you make -- let me know if you use this in your projects! And if you want to credit me I'd appreciate it :)</p> <p>Is bigmap helpful? Is it too confusing to set up? Find any bugs? Let me know what you think!</p> https://www.lexaloffle.com/bbs/?tid=46225 https://www.lexaloffle.com/bbs/?tid=46225 Wed, 19 Jan 2022 00:12:53 UTC CPU cycle counter / profiler <h2>tl;dr</h2> <p>In the pico8 console, do <code>load #prof</code>, then edit the third tab with some 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>prof(function(x) local _=sqrt(x) -- code to measure end,function(x) local _=x^0.5 -- some other code to measure end,{ locals={9} }) -- &quot;locals&quot; (optional) are passed in as args</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Run the cart: it will tell you exactly how many cycles it takes to run each code snippet.</p> <hr /> <h2>what is this?</h2> <p>The <a href="https://pico-8.fandom.com/wiki/CPU">wiki</a> is helpful to look up CPU costs for various bits of code, but I often prefer to directly compare two larger snippets of code against each other. (plus, the wiki can get out of date sometimes)</p> <p>For the curious, here's how I'm able to calculate exact cycle counts<br /> (essentially, I run the code many times and compare it against running nothing many times, using stat(1) and stat(2) for timing)<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>-- slightly simplified from the version in the cart function profile_one(func) local n = 0x1000 -- we want to type -- local m = 0x80_0000/n -- but 8𝘮𝘩z is too large a number to handle in pico-8, -- so we do (0x80_0000&gt;&gt;16)/(n&gt;&gt;16) instead -- (n is always an integer, so n&gt;&gt;16 won't lose any bits) local m = 0x80/(n&gt;&gt;16) -- given three timestamps (pre-calibration, middle, post-measurement), -- calculate how many more 𝘤𝘱𝘶 cycles func() took compared to noop() -- derivation: -- 𝘵 := ((t2-t1)-(t1-t0))/n (frames) -- this is the extra time for each func call, compared to noop -- this is measured in #-of-frames (at 30fps) -- it will be a small fraction for most ops -- 𝘧 := 1/30 (seconds/frame) -- this is just the framerate that the tests run at, not the framerate of your game -- can get this programmatically with stat(8) if you really wanted to -- 𝘮 := 256*256*128 = 8𝘮𝘩z (cycles/second) -- (𝘱𝘪𝘤𝘰-8 runs at 8𝘮𝘩z; see https://www.lexaloffle.com/bbs/?tid=37695) -- cycles := 𝘵 frames * 𝘧 seconds/frame * 𝘮 cycles/second -- optimization / working around pico-8's fixed point numbers: -- 𝘵2 := 𝘵*n = (t2-t1)-(t1-t0) -- 𝘮2 := 𝘮/n := m (e.g. when n is 0x1000, m is 0x800) -- cycles := 𝘵2*𝘮2*𝘧 local function cycles(t0,t1,t2) return ((t2-t1)-(t1-t0))*m/30 end local noop=function() end -- this must be local, because func is local flip() local atot,asys=stat(1),stat(2) for i=1,n do noop() end -- calibrate local btot,bsys=stat(1),stat(2) for i=1,n do func() end -- measure local ctot,csys=stat(1),stat(2) -- gather results local tot=cycles(atot,btot,ctot) local sys=cycles(asys,bsys,csys) return { lua=tot-sys, sys=sys, total=tot, } 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> <h2>how do I use it?</h2> <p>Here's an older demo to wow you:</p> <p> <table><tr><td> <a href="/bbs/?pid=104795#p"> <img src="/bbs/thumbs/pico8_cyclecounter-2.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=104795#p"> cyclecounter</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=104795#p"> [Click to Play]</a> </td></tr></table> </p> <p>This is neat but impractical; for everyday usage, you'll want to <code>load #prof</code> and edit the last tab.</p> <p>The cart comes with detailed instructions, reproduced here for your convenience:</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>================= ★ usage guide ★ ================= 웃: i have two code snippets; which one is faster? 🐱: edit tab 2 with your snippets, then run. it will tell you precisely how much cpu it takes to run each snippet. the results are also copied to your clipboard. (for ease of use, consider integrating this cart into your own cart during dev) 웃: what do the numbers mean? 🐱: the cpu cost is reported as lua and system cycle counts. look up stat(1) and stat(2) for more info. if you're not sure, just look at the sum -- lower is faster (better) 웃: why &quot;{locals={3,5}}&quot; do in the example? 🐱: accessing local variables is faster than global vars. /!\ /!\ /!\ /!\ &quot;local&quot; values outside the current scope are also slower to access! /!\ /!\ /!\ /!\ so if the scenario you're trying to test involves local variables, simulate this by passing them in: prof(function(a) local _=sqrt(a) end,{ locals={9} }) note: you can profile many functions at once, or just one. also, passing options at the end isn't required: prof(function() memcpy(0,0x200,64) end,function() poke4(0,peek4(0x200,16)) end) 웃: can i do &quot;prof(myfunc)&quot;? 🐱: no, this will give wrong results! always use inline functions: prof(function() -- code for myfunc here end) as an example, &quot;prof(sin)&quot; reports &quot;-2&quot; -- wrong! but &quot;prof(function()sin()end)&quot; correctly reports &quot;4&quot; (see the notes at the start of the next tab for a brief technical explanation) ====================== ★ alternate method ★ ====================== this cart is based on code by samhocevar: https://www.lexaloffle.com/bbs/?pid=60198#p if you do this method, be very careful with local/global vars. it's very easy to accidentally measure the wrong thing. here's an example of how to measure cycles (ignoring this cart and using the old method) local a=11.2 -- locals local n=1024 flip() local tot1,sys1=stat(1),stat(2) for i=1,n do end -- calibrate local tot2,sys2=stat(1),stat(2) for i=1,n do local _=sqrt(a) end -- measure local tot3,sys3=stat(1),stat(2) function cyc(t0,t1,t2) return ((t2-t1)-(t1-t0))*128/n*256/stat(8)*256 end local lua = cyc(tot1-sys1,tot2-sys2,tot3-sys3) local sys = cyc(sys1,sys2,sys3) print(lua..&quot;+&quot;..sys..&quot;=&quot;..(lua+sys)..&quot; (lua+sys)&quot;) run this once, see the results, then change the &quot;measure&quot; line to some other code you want to measure.</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> <h2>misc results</h2> <p>(these may be out of date now, but they were interesting)<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> <h3>poke4 v. memcopy</h3> <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> profile(&quot;memcpy &quot;, function() memcpy(0,0x200,64) end) profile(&quot;poke4/poke4&quot;, function() poke4(0,peek4(0x200,16)) 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>&gt; memcpy : 7 +64 = 71 (lua+sys)<br /> &gt; poke4/poke4 : 7 +60 = 67 (lua+sys)</p> <p>Copying 64 bytes of memory is very slightly faster if you use poke4 instead of memcpy -- interesting!<br /> (iirc this is true for other data sizes... find out for yourself for sure by downloading and running the cart!)</p> <p><em>edit: this has changed in <a href="https://www.lexaloffle.com/bbs/?pid=105760">0.2.4b</a>! the memcpy in this example now takes 7 +32 cycles</em></p> <h3>constant folding</h3> <p>I thought lua code was not optimized by the lua compiler/JIT at all, but it turns out there are some very specific optimizations it will 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> profile(&quot; +&quot;, function() return 2+2 end) profile(&quot; +++&quot;, function() return 2+2+2+2+2+2+2+2 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>These functions both take a single cycle! That long addition gets optimized by lua, apparently. <a href="https://www.lexaloffle.com/bbs/?uid=24137"> <a href="https://www.lexaloffle.com/bbs/?uid=24137"> @luchak</a></a> found these explanations:</p> <p><a href="https://stackoverflow.com/questions/33991369/does-the-lua-compiler-optimize-local-vars/33995520">https://stackoverflow.com/questions/33991369/does-the-lua-compiler-optimize-local-vars/33995520</a><br /> &gt; Since Lua often compiles source code into byte code on the fly, it is designed to be a fast single-pass compiler. It does do some constant folding</p> <p><strong>A No Frills Introduction to Lua 5.1 VM Instructions</strong> (book)<br /> &gt; As of Lua 5.1, the parser and code generator can perform limited constant expression folding or evaluation. Constant folding only works for binary arithmetic operators and the unary minus operator (UNM, which will be covered next.) There is no equivalent optimization for relational, boolean or string operators.</p> <h3>constant folding...?</h3> <p>One further test case:</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> profile(&quot;tail add x3&quot;, function() local a=2 return 2+2+2+2+2+2+2+a end) profile(&quot;head add x3&quot;, function() local a=2 return a+2+2+2+2+2+2+2 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>&gt; tail add x3 : 2 + 0 = 2 (lua+sys)<br /> &gt; head add x3 : 8 + 0 = 8 (lua+sys)</p> <p>These cost different amounts! Constant-folding only seems to work at the start of expressions. (This is all highly impractical code anyway, but it's fun to dig in and figure out this sort of thing)<br /> </div></div></div></p> <h2>credits</h2> <p>Cart by <a href="https://pancelor.com">pancelor</a>.</p> <p>Thanks to <a href="https://www.lexaloffle.com/bbs/?uid=14958"> @samhocevar</a> for the <a href="https://www.lexaloffle.com/bbs/?pid=60198#p">initial snippet</a> that I used as a basis for this profiler!</p> <p>Thanks to <a href="https://www.lexaloffle.com/bbs/?uid=25532"> @freds72</a> and <a href="https://www.lexaloffle.com/bbs/?uid=24137"> <a href="https://www.lexaloffle.com/bbs/?uid=24137"> @luchak</a></a> for discussing an earlier version of this with me!</p> <p>Thanks to thisismypassword for updating the wiki's CPU page!</p> <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>v1.3</h3> <ul> <li>simpler BBS post, friendlier cart instructions</li> </ul> <h3>v1.2</h3> <ul> <li>rewrite; recommend using <code>load #prof</code> instead now</li> </ul> <h3>v1.1</h3> <ul> <li>added: press X to copy to clipboard</li> <li>added: can pass args; e.g. <code>profile(&quot;lerp&quot;, lerp, {args={1,4,0.3}})</code></li> </ul> <h3>v1.0</h3> <ul> <li>intial release<br /> </div></div></div></li> </ul> https://www.lexaloffle.com/bbs/?tid=46117 https://www.lexaloffle.com/bbs/?tid=46117 Tue, 11 Jan 2022 03:31:01 UTC linecook v2.1 - multiplayer update <p> <table><tr><td> <a href="/bbs/?pid=103294#p"> <img src="/bbs/thumbs/pico8_linecook-2.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=103294#p"> linecook</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=103294#p"> [Click to Play]</a> </td></tr></table> </p> <p>these busy birds will eat almost anything that falls into their gullet -- what will you feed them? they have their preferences, but people food beats bird food any day of the week!</p> <p>a difficult, chaotic arcade game. now with local multiplayer support! also available on <a href="https://pancelor.itch.io/linecook">itch</a>.</p> <h2>controls:</h2> <ul> <li>left / right: move</li> <li>x / up: grab</li> <li>ESDF: movement keys for player 2</li> </ul> <h2>features</h2> <ul> <li>4 difficulty modes: &quot;easy&quot;, medium, hard, and practice</li> <li>3 different control schemes: <ul> <li>solo</li> <li>local multiplayer</li> <li>two-handed singleplayer </li> </ul></li> <li>4 challenging maps for 4 different flavors of gameplay</li> </ul> <img style="margin-bottom:16px" border=0 src="/media/27691/maps.png" alt="" /> <h2>#ChainLetterJam</h2> <p>this was made for the #ChainLetterJam! <a href="https://patricktraynor.itch.io/">Patrick</a> nominated me; I had fun playing <a href="https://patrickgh3.itch.io/arithmetic-bounce">Arithmetic Bounce</a> (my high score on hard mode is 24), so I decided to try making a game that would feel similarly chaotic.</p> <p>I took inspiration from the fact that none of the target numbers in Arithmetic Bounce were inherently good or bad; their value changed depending on the current goal. This became the ingredient/recipe idea in linecook: specific ingredients are sometimes good and sometimes bad, depending on the current recipe. I also liked the chaos and time pressure caused by gravity in Arithmetic Bounce; I've gone for a slightly different but still chaotic spin by giving the player two grabber-claws that are tricky to aim.</p> <p>the continuation of this chain is: <a href="https://tallywinkle.itch.io/the-witchs-almanac">https://tallywinkle.itch.io/the-witchs-almanac</a></p> <img style="margin-bottom:16px" border=0 src="/media/27691/thesave.gif" alt="" /> <h2>blog</h2> <p>I wrote about this game's design <a href="https://pancelor.com/posts/linecook">here</a>. tl;dr: if you allow your game systems to play out as physical processes in the game world, there's a lot more opportunities for the player to interrupt and cause surprising interactions</p> <img style="margin-bottom:16px" border=0 src="/media/27691/banner.png" alt="" /> <p>hope you enjoy it!</p> https://www.lexaloffle.com/bbs/?tid=45799 https://www.lexaloffle.com/bbs/?tid=45799 Wed, 22 Dec 2021 02:03:23 UTC