jasondelaat [Lexaloffle Blog Feed]https://www.lexaloffle.com/bbs/?uid=54967 Picotron web player keypress bug. <p>In my vector library demo cart (<a href="https://www.lexaloffle.com/bbs/?tid=141574">https://www.lexaloffle.com/bbs/?tid=141574</a>) I have a main menu of demos each linked to a key press. From within each demo you should be able to return to the menu. On the web player I can get from the main menu to any of the demos but, once I'm in one of the demos, I can't get back to the menu.</p> <p>I've uploaded 4 versions of the cart with the key to return to the menu as: <code>tab</code>, <code>space</code>, <code>0</code> and now <code>m</code>. All of these work for me within my copy of Picotron (0.1.0e) but none of them seem to work in the web player.</p> <p>No idea what could be going on here unless all those keys have been reserved for some future web player only functionality and I'm just really good at picking the wrong key to bind my menu to.</p> https://www.lexaloffle.com/bbs/?tid=141724 https://www.lexaloffle.com/bbs/?tid=141724 Sun, 14 Apr 2024 18:04:16 UTC GAVEL: Vector Library. 2D, 3D and beyond. (Up to 9D) <p>This is a general purpose geometry/vector library.</p> <p>It's based on Geometric Algebra (GA) but you don't need to know what that is to use it as most of the standard vector operations you're probably familiar with are here.</p> <p>I'd be happy to add additional demos (with credit obviously) so if you make something interesting that you want to include let me know.</p> <h3>Updates</h3> <p><em>2024-04-21</em></p> <ul> <li>Added 3D triple pendulum demo (based on the 2D version here: <a href="https://matthias-research.github.io/pages/tenMinutePhysics/index.html">https://matthias-research.github.io/pages/tenMinutePhysics/index.html</a>)<br /> Hat tip to <a href="https://www.lexaloffle.com/bbs/?uid=47628"> @shanecelis</a> for this post (<a href="https://www.lexaloffle.com/bbs/?tid=54905">https://www.lexaloffle.com/bbs/?tid=54905</a>) which made me aware of PBD and Matthias M&uuml;ller.</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;"><br /> <em>2024-04-14</em></p> <ul> <li>Added 3D forward kinematics demo</li> <li>Added 4D hypercube rotation demo</li> <li>Switched to [m] to return to menu. *Edit* It works now! You can navigate back to the menu on the web player and switch back and forth among the demos.</li> </ul> <p><em>2024-04-10</em></p> <ul> <li>Added a 2D forward kinematics demo</li> <li>Changed to 'space' instead of 'tab' to return to the menu (Hopefully it works better with the web player.) *Edit* It does not.</li> <li>Fixed a minor error in <code>rotor_from_to</code> which resulted in things rotating the wrong way. There may be other errors like this lurking so if anyone notices things transforming in ways they don't expect, please let me know!<br /> </div></div></div></li> </ul> <p> <table><tr><td> <a href="/bbs/?pid=146152#p"> <img src="/bbs/thumbs/pico64_gavel_demos-5.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=146152#p"> gavel_demos</a><br><br> by <a href="/bbs/?uid=54967"> jasondelaat</a> <br><br><br> <a href="/bbs/?pid=146152#p"> [Click to Play]</a> </td></tr></table> </p> <p>To get the library code itself, <code>load #gavel_demos</code> and then copy the <code>algebra.lua</code> file from <code>/ram/cart/src</code> to a convenient location.</p> <h1>Cheat sheet</h1> <p>I don't want to dive too deeply into the mathematics of GA here so this doesn't include everything but should include most of what people are probably already familiar with. Feel free to poke around the code and ask about anything that's not clear or that you just want to know more about.</p> <img style="margin-bottom:16px" border=0 src="/media/54967/62_cheatsheet.png" alt="" /> <p>Notes</p> <ul> <li><strong>1</strong> : GA is &quot;component free&quot; meaning you always work with the whole vector and not its individual components. That has a bunch of advantages but the most relevant one here is that you can pick how many dimensions you want---two, three, four, nine...eight hundred---and everything just works.</li> <li><strong>2</strong> : Multivectors are a GA thing which you can think of as sort a more expansive vector. Technically vectors are also multivectors but with the &quot;extra&quot; components all set to 0. For simple usage you don't really need to know too much about them but it can be useful to know they exist.</li> <li><strong>3</strong> : All tranformations are automatically reversible without having to build a whole new transformation. So if <code>v.transform(t)</code> rotates <code>v</code> clockwise then <code>v.transform(t, true)</code> will rotate it the same amount but counter-clockwise.</li> <li><strong>4</strong> : For vectors---as opposed to general multivectors---the dot product and scalar product return the same <em>value</em> but as different types. For vectors <code>u</code> and <code>v</code>, <code>(u..v).scalar == u%v</code></li> <li><strong>5</strong> : string representations will look a bit weird. Since you can set any number of dimensions you want vector components are named by number. So <code>print(vector(10, 20))</code> will display <code>10e1+20e2</code>. You can think of <code>e1</code> and <code>e2</code> as <code>x</code> and <code>y</code>. As a small quality of life feature, you can access those components on a vector using either notation: <code>v.e1 == v.x</code></li> </ul> <p>There is one obvious thing missing that I should probably address.</p> <h3>Dude, where's my cross product?</h3> <p>GA's component free nature is what allows it to be general enough that you can just plug in a number of dimensions and have everything work. As you're probably aware, the cross product does <em>not</em> generalize to dimensions other than 3 so it's not here. What we have instead is the &quot;wedge product&quot; (<code>__pow</code> or <code>^</code> operator.) The wedge product <em>does</em> generalize.</p> <p>In 3-dimensions the wedge product of two vectors and the cross product of two vectors contain the same information just presented slightly differently.</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>algebra(3):make_global() u = vector(1, 2, 3) v = vector(7, 5, 2) print(u ^ v) -- -9e12-19e13-11e23</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Don't worry, that's not a vector in 23 dimensions, it's what's called a bivector. Compare this result with the cross product of these two vectors:</p> <p>u &times; v = (-11, 19, -9)</p> <p>The order is reversed and the middle term is multiplied by -1. That seems like a tiny difference but it's the difference between a product which only exists in 3-dimensions and one that exists in any number of dimensions.(Although I'm being very hand-wavy here and there's more to it than that.)</p> <p>You can write yourself a cross product function if you want to but learning to use the wedge product will usually make your code simpler and more general.</p> <h3>Will this actually work with 800 dimensions?</h3> <p>No. But that's just because it would be computationally infeasible not because there's anything wrong with the math. <em>In principle</em> everything would work in 800 dimensions. In practice you'd probably melt your processor. The way I've written it, GAVEL should work for anything up to 9 dimensions. It'll eat up a lot of memory and probably be <em>slow</em> but it should work. It could be expanded beyond 9 dimensions without too much trouble but I'm going to assume that if you're doing something requiring more than 9 dimensions you're (a) probably not doing it in Picotron, and (b) don't need my help.</p> <h1>Resources</h1> <p>Hopefully I'll make a proper geometric algebra tutorial at some point but until then here are some resources if you want to dive deeper into the math.</p> <p>Geometric Algebra Playlist<br /> &lt;<a href="https://www.youtube.com/playlist?list=PLpzmRsG7u_gqaTo_vEseQ7U8KFvtiJY4K&gt">https://www.youtube.com/playlist?list=PLpzmRsG7u_gqaTo_vEseQ7U8KFvtiJY4K&gt</a>;</p> <p>Siggraph2019 Projective Geometric Algebra<br /> &lt;<a href="https://www.youtube.com/watch?v=tX4H_ctggYo&amp;list=WL&amp;index=3&amp;t=634s&amp;pp=gAQBiAQB&gt">https://www.youtube.com/watch?v=tX4H_ctggYo&amp;list=WL&amp;index=3&amp;t=634s&amp;pp=gAQBiAQB&gt</a>;</p> https://www.lexaloffle.com/bbs/?tid=141574 https://www.lexaloffle.com/bbs/?tid=141574 Tue, 09 Apr 2024 22:47:54 UTC db: print debugging based on the IceCream print debugging tool for python <p>A tool to help with print debugging based on the Python package <a href="https://github.com/gruns/icecream?tab=readme-ov-file">IceCream</a>. There's already a <a href="https://github.com/wlingze/icecream-lua">Lua version of IceCream</a> but it won't work with Pico-8/Picotron as far as I know. I've called it <code>db</code> for debug rather than <code>ic</code> but it's very similar in concept.</p> <p>You can download the code <a href="https://www.lexaloffle.com/bbs/files/54967/db.txt">here</a> or copy/paste below:</p> <p><strong>db.lua</strong> <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>---------------------------------------------------------------------------- -- print debugging based on IceCream for python -- pico-8 lua version by jason delaat do local ignore = {} local lookup = _ENV for k,_ in pairs(_ENV) do ignore[k] = true end local function format_arg(value, env) for k,v in pairs(lookup) do if v == value and not ignore[k] then return 'db: '..k..'='..tostr(v) end end return 'db: '..tostr(value) end local db_meta = { __call=function(self, value, log) if db.display and log then print(log) elseif db.display then print(format_arg(value)) end return value end } db = { display = true, local_env = function(t) lookup = setmetatable(t or {}, {__index=_ENV}) return lookup end, reset_env = function() lookup = _ENV end, wrap = function(f) local fn = sub(split(format_arg(f), '=')[1], 5) _ENV[fn] = function(...) local log = 'db: '..fn..'(' local result = f(...) for a in all({...}) do log ..= tostr(a)..',' end log = sub(log, 1, -2) log ..= ') --&gt; '..tostr(result) return result, log end end } setmetatable(db, db_meta) 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>Summary</h2> <p><img src="https://www.lexaloffle.com/bbs/files/54967/summary-table.png" alt="summary-table.png" /></p> <h2>Displaying variables</h2> <p>The most basic thing you can do with <code>db</code> is just display values and variables. When displaying variables with <code>db</code> it displays both the variable name and its value.</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>x = 1 db(2) db(x)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p><strong>Output</strong></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>db: 2 db: x=1</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <ul> <li> <p><strong>Note: Duplicate values</strong><br /> <code>db</code> creates its output by searching the global environment by key/value pairs looking for a matching <em>value</em> and then displaying it with the corresponding key. This <em>mostly</em> works. But if more than one variable has the same value <code>db</code> may log the wrong one. 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>x = 1 y = 1 db(x)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Because the ordering of keys in a table isn't guaranteed this will sometimes (correctly) display <code>db: x=1</code> but will also sometimes (incorrectly) display <code>db: y=1</code>. <code>db</code> automatically ignores variable/function names defined in the global environment before the <code>debug.lua</code> code&mdash;which includes all built-ins&mdash;to try to minimize these collisions.</p> </li> </ul> <h2>Inline debug output</h2> <p><code>db</code> returns its input so you can debug variables where they're actually used.</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>x = 1 y = 2 z = db(x) + db(y) db(z)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p><strong>Output</strong></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>db: x=1 db: y=2 db: z=3</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <h2>User defined types</h2> <p>Since <code>db</code> is essentially just a fancy print statement it should work properly with any object as long as it has a <code>__tostring</code> meta-method defined.</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>do local vector_meta = { __tostring=function(_ENV) return 'vec('..x..','..y..')' end } function vector(x, y) return setmetatable({x=x, y=y}, vector_meta) end end v = vector(1, 2) db(v)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p><strong>Output</strong></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>db: v=vec(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> <h2>Function calls</h2> <p>Since functions return values, by default <code>db</code> displays a function call as a bare value:</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 plus(a, b) return a + b end db(plus(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><strong>Output</strong></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>db: 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>You can use <code>db.wrap</code> to enable functions to output their name, input parameters and output value, like so:</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 plus(a, b) return a + b end db.wrap(plus) db(plus(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><strong>Output</strong></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>db: plus(1,2) --&gt; 3</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <h2>Selective debug output</h2> <p>The <code>db.display</code> property can be used to turn debug output on and off wherever you want throughout your code. This lets you leave debug statements in place if you're not sure you're done with them yet but only display those parts you're interested in at the moment.</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>x = 1 y = 2 function plus(a, b) return db(x) + db(y) end function minus(a, b) return db(x) - db(y) end db.wrap(plus) db.wrap(minus) db.display = false p = db(plus(x, y)) db.display = true m = db(minus(x, y)) db(p) db(m)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p><strong>Output</strong></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>db: x=1 db: y=1 db: minus(1,2) --&gt; -1 db: p=3 db: m=-1</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 the above example the debug output for <code>plus</code> is not displayed. However, <code>db.display</code> doesn't prevent the <code>plus</code> function from being called or returning a value: You can see via <code>db(p)</code> that <code>p</code> has the correct value.</p> <h2>Debugging functions</h2> <h3>The Problem</h3> <p>Consider the following 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>x = 1 y = 2 function plus(a, b) local c = 'who am i?' db(c) return db(a) + db(b) end db(plus(x, y))</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p><strong>Output</strong></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>db: who am i? db: x=1 db: y=2 db: 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>First notice that the output of the function call is logged simply as <code>db: 3</code> because the function hasn't been wrapped with <code>db.wrap</code>. But more importantly, <code>db(c)</code> is displaying the string but not the variable name while <code>db(a)</code> and <code>db(b)</code> are displaying as variables <code>x</code> and <code>y</code> respectively. That's becuase <code>a</code>, <code>b</code> and <code>c</code> are all local variables and Lua doesn't give us direct access to those in the same way as globals. Standard Lua has debug tools which can get a hold of those values but we don't have access to those here in Pico-land.</p> <h3>The Solution</h3> <p>If you want/need to debug values within a function use <code>db.local_env</code> and <code>db.reset_env</code> like so:</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>x = 1 y = 2 function plus(a, b) --local _ENV = db.local_env() -- can be called with no arguments local _ENV = db.local_env({a=a, b=b}) -- note the 'local' keyword! c = 'who am i?' -- note the *lack* of 'local' db(c) return db(a) + db(b), db.reset_env() end db.wrap(plus) db(plus(x, y)) db(c) db(x)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p><strong>Output</strong></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>db: c=who am i? db: a=1 db: b=2 db: plus(1,2) --&gt; 3 db: [nil] db: x=1</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p><code>db.local_env</code> creates a temporary enviroment and tells <code>db</code> to use <em>that environment</em> for building its output. <code>db.reset_env</code> just tells <code>db</code> to go back to using the global environment. Including the call <code>db.reset_env()</code> as a second return value let's us reset the environment &quot;after&quot; returning from the function.</p> <ul> <li><strong>Note: The <code>local</code> keyword</strong><br /> Importantly <code>_ENV</code> <em>must</em> be defined as local here or you'll overwrite the global environment. Equally importantly, local variables should <em>not</em> be defined with the <code>local</code> keyword or they'll go back to outputting just values without names. These variables <em>are</em> still local though. As you can see in the output <code>db(c)</code>, when called outside the function, displays <code>db: [nil]</code>. When you're done debugging you'll probably want to remove that <code>_ENV</code>: be sure to add <code>local</code> where necessary or all your function local variables will suddenly be globals and you'll have whole new errors to hunt down!</li> </ul> <h2></h2> <p>Providing a table to <code>db.local_env</code> is optional. It's mainly included to ensure that input parameters display correctly: Since input parameters are actually defined before our temporary environment, they don't exist in it unless we specifically put them there. You can still use those values but they won't display the variable name when logged.</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 no_param(a) local _ENV = db.local_env() return db(a), db.reset_env() end no_param(1)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p><strong>Output</strong></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>db: 1</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> https://www.lexaloffle.com/bbs/?tid=141016 https://www.lexaloffle.com/bbs/?tid=141016 Sat, 23 Mar 2024 12:55:41 UTC Animation Path Editor <p><em>This cart has devkit mode enabled and requires a mouse and keyboard.</em><br /> <em>It's intended to be run locally; not all feature work correctly in the web player.</em></p> <p>A simple path editor for creating animation paths for game objects. The paths created are simple interpolated paths not bezier or similar curves. The path is guaranteed to pass through all of the control points. You don't have as much control over the exact shape of the path but you can still get some pretty nice results.</p> <p> <table><tr><td> <a href="/bbs/?pid=138411#p"> <img src="/bbs/thumbs/pico8_animation_path_editor-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=138411#p"> animation_path_editor</a><br><br> by <a href="/bbs/?uid=54967"> jasondelaat</a> <br><br><br> <a href="/bbs/?pid=138411#p"> [Click to Play]</a> </td></tr></table> </p> <p><img style="margin-bottom:16px" border=0 src="/media/54967/path-editor p8_1.gif" alt="" /> <img style="margin-bottom:16px" border=0 src="/media/54967/path-editor p8_2.gif" alt="" /></p> <h2>Features</h2> <ul> <li>Save and Load path files in a binary format to .p8 files so they can be easily changed later.</li> <li>Export paths and related functions to a .txt file which can be <code>#include</code>d in your project.</li> <li>When exported, paths are translated so the first control point is at (0, 0) so you can easily add any offset allowing you to use the same path in multiple locations without having to change the path itself.</li> <li>When you save a path file the filename is saved in persistent cart data and that file will be automatically loaded the next time you start the editor. (Assumes the editor and path file are in the same directory.)</li> <li>Path files contain multiple paths so you can keep all paths for a single project in a single file.</li> <li>Path names are editable so you can give them meaningful names.</li> <li>Create open or closed paths.</li> <li>A timeline allows you to adjust the animation timing of individual sections of the path as well as the total duration (in seconds) of the animation.</li> <li>Import sprite and map data from another cart. (Assumes cart is in the same directory as the editor.)</li> <li>Select sprites to preview path animation. (Currently a single, static, 8x8 sprite can be selected per path. This is just for preview purposes: selected sprites are saved in path files but are not exported.)</li> <li>Adjust map position either by dragging it or via text input. (As with sprites this is just for preview purposes. Each path can have the map displayed at a different position and those postiions are saved in the path file but not exported.)</li> <li>If you don't like my colour choices, the first tab of the .p8.png contains configuration data which you can edit to better suit your needs/tastes. (You can also suppress the help message which pops up when you run the cart from here if you want to.) I'm not sure I made everything configurable so, if I've missed something you'd like to change the colour of, feel free to let me know and I'll add it in.</li> </ul> <h2>Command Summary</h2> <h3>(also available in the editor by pressing 'h')</h3> <img style="margin-bottom:16px" border=0 src="/media/54967/Screenshot 2023-12-07 at 16-41-35 Lexaloffle BBS Edit Post.png" alt="" /> <h2>Example</h2> <p>A simple example using a single path to animate two squares at different offsets and with different timing.</p> <img style="margin-bottom:16px" border=0 src="/media/54967/etest_0.gif" alt="" /> <h3>Code:</h3> <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>-- exported file ------------------------------------------------------------ do local function parse(p_str) -- parses the exported path string into a table. -- paths are accessed by name with dashes (-) and spaces -- converted to underscores. for example: 'path-1' in the editor -- becomes 'paths.path_1' when exported. -- each path is a three element array. the first element of each -- path is the animation duration in seconds. the other two -- elements are arrays used by p_lerp to interpolate the -- position and timing between points. local paths = {} local pstrs = split(p_str, '|') for p in all(pstrs) do local vals = split(p, ':') paths[ vals[1] ] = {vals [2], split(vals[3]), split(vals[4])} end return paths end local function choose(n, k) -- combinatorial choose function for real number 'n' and -- positive integer 'k'. this function is used by the -- interpolation functions. local num, den = 1, 1 for i=1,k do num *= n - (i - 1) den *= i end return k == 0 and 1 or num/den end function p_lerp(path, t) -- calculates the position of 'path' at time 't' -- where 0 &lt;= t &lt;= duration in seconds. The duration of a path -- is stored at index 1 of the path array. local _, coefs, times = unpack(path) local ti = 0 for i=1,#times do ti = t &gt;= times[i] and i or ti end local t1 = times[ti] or -times[ti+2] local t2 = times[ti + 1] or 2*times[ti] - times[ti-1] local x, y = 0, 0 for i=1,#coefs / 2 do local c = choose(ti - 1 + (t - t1) / (t2 - t1), i) x += coefs[2*i-1]*c y += coefs[2*i]*c end return x, y end function p_lerp_norm(path, t) -- a wrapper around 'p_lerp'. -- 'p_lerp_norm' expects 't' to be normalized -- such that 0 &lt;= t &lt;= 1. This is more in line with how -- interpolation functions usually work and is the better choice -- if you're planning on applying easing functions to the -- animation or if you want to use the same path but vary the -- durations. return p_lerp(path, t*path[1]) end paths=parse&quot;line:1:46,34:0,1|loop:1:-19,31,51,-13,-61,-32,14,82:0,0.3,0.5,0.8,1|curve:1.8:-6,35,66,-49,-134,91:0,0.6,1.2,1.8&quot; end -- end exported file -------------------------------------------------------- -- example ------------------------------------------------------------------ ts1 = time() ts2 = time() --p = paths.curve --p = paths.line p = paths.loop function _update() t1 = time() - ts1 if t1 &gt;= p[1] then -- p[1] is the animation duration as set in the editor ts1 = time() end x1,y1 = p_lerp(p, t1) x1 += 30 y1 += 30 t2 = time() - ts2 if t2 &gt;= 3 then -- make the animation take 3 seconds instead ts2 = time() end x2,y2 = p_lerp_norm(p, t2/3) -- normalize the time so it's between 0 and 1 x2 += 60 y2 += 60 end function _draw() cls(13) rectfill(x1, y1, x1+7, y1+7, 7) rectfill(x2, y2, x2+7, y2+7, 7) print('timer 1:'..t1) print('timer 2:'..t2) end -- end example --------------------------------------------------------------</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=55246 https://www.lexaloffle.com/bbs/?tid=55246 Fri, 08 Dec 2023 13:08:02 UTC Animation Operators <p>Code for combining simple animations together to create more complex animations using + and * operators. </p> <p> <table><tr><td> <a href="/bbs/?pid=134087#p"> <img src="/bbs/thumbs/pico8_animation_operators-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=134087#p"> animation_operators</a><br><br> by <a href="/bbs/?uid=54967"> jasondelaat</a> <br><br><br> <a href="/bbs/?pid=134087#p"> [Click to Play]</a> </td></tr></table> </p> <p>The example uses four simple animations&mdash;each one just a different coloured circle starting in a different corner and moving to the diagonally opposite corner&mdash;and combines them in different ways to create the final animation. </p> <p>For simple animations A and B:</p> <ul> <li>The + operators first runs A and, once it's finished, runs B.</li> <li>The * operator runs both A and B at the same time.</li> </ul> <p>As with normal addition and multiplication you can string together as many animations as you want and use parentheses to indicate a particular ordering.</p> <p>To create animations use <code>new_animation()</code> and then give the animation object an <code>init</code> method. The <code>init</code> method should return a table containing a <code>draw</code> and an <code>update</code> function; <code>update</code> should return true when the animation has finished.</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> a = new_animation() a.init = function() local offset = 0 return { update=function() offset += 5 if offset &gt; 128 then return true end end, draw=function() circfill(offset, offset, 2, 7) 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>The animation object then needs to be instantiated to use it.</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> anim = a() -- or a.init(), same thing function _update() anim.update() end function _draw() cls() anim.draw() 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>Having the <code>init</code> function means you can use the same animation multiple times since any internal values, like <code>offset</code>, will be re-initialized each time. You don't need to initialize the animation each time when combining animations, that's handled internally, you only need to initialize the combined animation as a whole.</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> -- creates a compound animation which will play the simple animation -- three times in a row. anim = (a + a + a)() -- or (a + a + a).init(). Again, same thing.</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Lua Code (indented 3 spaces)<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>do local ani_meta = { __add=function(self, other) local an = new_animation() an.init = function() local children = {self(), other()} local i = 1 return { update=function() local a = children[i] if a.update() then i += 1 if i &gt; 2 then return true end end end, draw=function() local a = children[i] a.draw() end } end return an end, __mul=function(self, other) local an = new_animation() an.init = function() local children = {self(), other()} return { update=function() local done = true for a in all(children) do local d = a.update() done = done and d end return done end, draw=function() for a in all(children) do a.draw() end end } end return an end, __call=function(self) return self.init() end, } function new_animation() return setmetatable({}, ani_meta) 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></div></div></div></p> <p>Lua Code (indented 1 space)<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>do local ani_meta = { __add=function(self, other) local an = new_animation() an.init = function() local children = {self(), other()} local i = 1 return { update=function() local a = children[i] if a.update() then i += 1 if i &gt; 2 then return true end end end, draw=function() local a = children[i] a.draw() end } end return an end, __mul=function(self, other) local an = new_animation() an.init = function() local children = {self(), other()} return { update=function() local done = true for a in all(children) do local d = a.update() done = done and d end return done end, draw=function() for a in all(children) do a.draw() end end } end return an end, __call=function(self) return self.init() end, } function new_animation() return setmetatable({}, ani_meta) 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></div></div></div></p> https://www.lexaloffle.com/bbs/?tid=54044 https://www.lexaloffle.com/bbs/?tid=54044 Sat, 09 Sep 2023 11:10:38 UTC Two Minor BBS UI annoyances <p>If I view a post on the BBS and scroll down to the bottom to post a comment, there are two buttons: Submit, on the left and Preview, on the right.</p> <p>If I click Preview I'm taken to a new screen. The first issue is that I've just clicked a Preview button but I'm not shown a preview just a larger text box. To actually see the preview I have to scroll down and click the Preview button again.</p> <p>Which brings me to the second issue: The Preview button is now on the left and the submit button, now called Publish Changes, is on the right. I would expect the buttons to still be in the same place and the number of times I've almost posted half a comment because I almost clicked Publish when what I wanted was Preview are many.</p> <p>Minor issues. Not the end of the world if it never gets fixed&mdash;at this point fixing it might cause as many or even more mistakes than not fixing it so maybe it's just not worth doing&mdash;but thought I'd point it out anyway.</p> https://www.lexaloffle.com/bbs/?tid=53854 https://www.lexaloffle.com/bbs/?tid=53854 Wed, 23 Aug 2023 12:18:19 UTC A very long walk in the woods <p>This is just something I've been playing with in between working on other things.</p> <p> <table><tr><td> <a href="/bbs/?pid=130640#p"> <img src="/bbs/thumbs/pico8_walkinthewoods-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=130640#p"> walkinthewoods</a><br><br> by <a href="/bbs/?uid=54967"> jasondelaat</a> <br><br><br> <a href="/bbs/?pid=130640#p"> [Click to Play]</a> </td></tr></table> </p> <p>I didn't know about sprite stacking until <a href="https://www.lexaloffle.com/bbs/?tid=39014">this old post</a> popped up a couple of months ago. Although that example rotates the sprites themselves it turns out you can get some interesting results even without it. I haven't bothered with collision detection so you can walk right through the trees and the rocks. Waking through the water was intentional though. I wanted it to look like actually walking in water.</p> <p>The space is procedurally generated but persistent so it's the same every time the cart is run. And it's effectively infinite in all directions. Not <em>actually</em> infinite obviously but pretty darn big for no particularly good reason.</p> https://www.lexaloffle.com/bbs/?tid=53003 https://www.lexaloffle.com/bbs/?tid=53003 Wed, 07 Jun 2023 12:01:51 UTC Drafting Table <p>This cart uses devkit mode so you'll need a mouse and keyboard to use it.</p> <p> <table><tr><td> <a href="/bbs/?pid=129079#p"> <img src="/bbs/thumbs/pico8_drafting_table-4.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=129079#p"> drafting_table</a><br><br> by <a href="/bbs/?uid=54967"> jasondelaat</a> <br><br><br> <a href="/bbs/?pid=129079#p"> [Click to Play]</a> </td></tr></table> <br /> <img style="margin-bottom:16px" border=0 src="/media/54967/47_main_0.png" alt="" /><img style="margin-bottom:16px" border=0 src="/media/54967/main_1.png" alt="" /></p> <p>Update:<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 /> New version updates the s-expression parser and the importer code. The old parser was incredibly slow; the new one is much faster. And the importer now properly handles larger images that don't compress as much which it handled incorrectly before.<br /> </div></div></div></p> <p>This started off as a level editor for the <a href="https://www.lexaloffle.com/bbs/?tid=52384">Space Taxi Remake</a> I'm working on. At some point I realized I was going a bit overboard and instead of doing the sensible thing and saying, &quot;well that's good enough for my purposes I guess I'll stop now,&quot; I just kinda leaned into it.</p> <p>Obviously any images you make with this are yours to do with as you please. If you use it and like it credit is, of course, appreciated but not required.</p> <p>I've posted a new comment in the <a href="https://www.lexaloffle.com/bbs/?tid=52384">Space Taxi</a> thread with a few very rough screens made with this if anyone wants to check that out.</p> <h3>Saving and Loading</h3> <p>Saving doesn't work from the BBS embedded player or on Education edition so you'll need to run the cart locally to do that. Files are saved as .p8l files. They're just plain text with a lisp-like syntax describing the points and shapes, etc. of the drawing. </p> <p>By default files save to the desktop and are overwritten if they have the same name. You can change this behaviour by modifying the variables <code>overwrite</code> and <code>save_to_desktop</code> at the very top of the code (Tab 0.)</p> <p>Loading is done via dragging the file into the program and that does work on the BBS player! <a href="https://www.lexaloffle.com/bbs/files/54967/drafting.txt">This sample save file</a> loads the drawing of the words &quot;Drafting Table&quot; as seen on the cart image. Just save it locally as a text file then drag it in to load.</p> <p>You can load more than one file and it'll just keep adding in the objects from each. This is so you can have, for instance, a file with a bunch of different platform types and one with different obstacles, etc. and combine them without having to redraw them every time.</p> <h3>Exporting and Importing</h3> <p>To compress and export (to the clipboard) the image as a string of binary data press the button at the bottom right of the group in the center or press '8'. <table><tr><td width=32> <img src="https://www.lexaloffle.com/bbs/gfxc/54967_28.png" width=32 height=32> </td> <td valign=bottom> <a style="cursor:pointer;font-size:8pt" onclick=' var el = document.getElementById("gfxcode_54967_28"); if (el.style.display == "none") el.style.display = ""; else el.style.display = "none"; microAjax("https://www.lexaloffle.com/bbs/gfxc/54967_28.txt", function (retdata){ var el = document.getElementById("gfxcode_54967_28"); el.innerHTML = retdata; el.focus(); el.select(); } ); '> [8x8]</a> </td></tr> <tr><td colspan=2> <textarea rows=3 class=lexinput id="gfxcode_54967_28" style="width:640px;background-color:#fed;display:none;overflow:hidden; font-size:6pt;"></textarea> </td> </tr> </table> </p> <p>To import the image into your own cart, paste the binary string into the cart and copy and uncomment the code from tab 0 of this cart. There are only three functions you need to know about:</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>store_image(dest, str) converts the binary data string 'str' to bytes and stores them in memory beginning at memory address 'dest'. This data is still compressed and not the actual image. Returns the memory address immediately after the data which you can use to store additional images or other data. next = store_image(0x8000, img1) -- where my img1 is a string exported by Drafting Table store_image(next, img2) -- as is img2. load_image_from_memory(dest, src) used in conjunction with 'store_image'. Reads the compressed data from memory address 'src' and writes the decompressed images data to memory address 'dest'. This can then be drawn to the screen with 'memcpy'. load_image_from_memory(0x1000, 0x8000) memcpy(0x6000, 0x1000, 0x2000) -- draw the image to the screen load_image_from_string(dest, str) you can instead decompress the image directly from 'str' to memory address 'dest' without storing the compressed data to memory. load_image_from_string(0x1000, img1) memcpy(0x6000, 0x1000, 0x2000)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p><img style="margin-bottom:16px" border=0 src="/media/54967/drafting p8_0.png" alt="" /><img style="margin-bottom:16px" border=0 src="/media/54967/main_1.gif" alt="" /><br /> The rotation tool in action and &quot;Drafting Table&quot; exported as collision demo &quot;level geometry&quot; for my Space Taxi remake.</p> <h3>Some caveats</h3> <h4>Available drawing area</h4> <p>The UI at the bottom of Drafting Table and the UI at the bottom of Space Taxi are the same size leaving me exactly enough drawing space for a single level and that's all that gets exported. If anyone thinks that they'd find this useful it shouldn't be too hard to add a shortcut to hide the UI and allow editing and exporting the full screen. Unless/until someone asks though it will stay as it is because it suits my purpose.</p> <h4>Positive and negative rotations</h4> <p>When using numerical input for rotations I've gone with the convention in mathematics: positive angles rotate <em>counterclockwise</em> and negative angles rotate <em>clockwise.</em> Additionally, angles are given as a number between 0 and 1 as in Pico-8 itself. So 0.25 is a 90 degree rotation counterclockwise while -0.125 is a 45 degree rotation clockwise.</p> <p>All objects rotate around their own center point even if multiple objects are selected at once unless grouped together with 'g' in which case all grouped objects rotate as a unit around their common center point.</p> <h4>Moving objects ignores snap</h4> <p>This is entirely because my sleep deprived brain was having trouble getting it to work. It'll be fixed eventually. For now the workaround is to just get it in approximately the right spot then use the arrow keys to place it precisely.</p> <h4>Polygons are a bit weird</h4> <p>Due to the way I'm drawing/filling/detecting collisions with the various shapes, the free polygon tool sometimes gives unexpected results like not filling the whole thing or, sometimes, making it completely un-selectable except via the 'select all' shortcut. This only happens when the polygon in <em>not</em> a <a href="https://en.wikipedia.org/wiki/Convex_polygon">convex polygon</a>.</p> <p><img style="margin-bottom:16px" border=0 src="/media/54967/main_2.png" alt="" /><img style="margin-bottom:16px" border=0 src="/media/54967/main_3.png" alt="" /><br /> The &quot;same&quot; non-convex polygon in outline and fill mode.</p> <p>So to make sure you're getting what you expect, stick to convex polygons (all interior angles less than 180 degrees) and for complex shapes, draw separate parts and group them together. </p> <p><img style="margin-bottom:16px" border=0 src="/media/54967/main_4.png" alt="" /><img style="margin-bottom:16px" border=0 src="/media/54967/main_5.png" alt="" /><br /> Same shape as above but constructed from two shapes grouped together.</p> https://www.lexaloffle.com/bbs/?tid=52529 https://www.lexaloffle.com/bbs/?tid=52529 Sun, 30 Apr 2023 13:24:41 UTC Space-Taxi: Morning Shift Playtest version <p>The original Space Taxi divided up its 24 levels into three &quot;shifts&quot; of eight levels each corresponding to the easy, medium and hard levels. And it turns out eight levels is about what I'm able to fit on a single cart so I've decided to release the game by shift and then merge them all together into a single multi-cart game when I'm done. It's actually already multi-cart with one for displaying the menu and loading the level data into upper memory and the other for actually running the game.</p> <p>The first one, Morning Shift, is not quite finished but it <em>is</em> fully playable. The first level is a copy of the first level from the original game as sort of an homage to it but the rest of the levels are original. And they could use some play testing. If anyone is willing I'd be happy to hear any feedback.</p> <p> <table><tr><td> <a href="/bbs/?pid=128419#p"> <img src="/bbs/thumbs/pico8_space_taxi-2.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=128419#p"> space_taxi</a><br><br> by <a href="/bbs/?uid=54967"> jasondelaat</a> <br><br><br> <a href="/bbs/?pid=128419#p"> [Click to Play]</a> </td></tr></table> </p> <p>In particular I'm interested in hearing:</p> <ol> <li>Are the blinking platform markers helpful?</li> <li>Would they be more helpful if they blinked a different colour?</li> <li>Keeping in mind that these are the <em>easy</em> levels&mdash;with 16 more levels to come at some point in the future&mdash;how does the progression feel?</li> <li>Would you change the order of any of the levels?</li> <li>Was there anything that you particularly liked or disliked about the game?</li> </ol> <p>Don't feel like you have to address all (or any) of those and it's by no means an exhaustive list so any other comments are also appreciated.</p> <h2>Things to do</h2> <ul> <li>Level art :: You'll see that I've spent more time on some of the levels than others. The layouts are, I think, pretty much set but I'm still working on making some of the levels more visually interesting.</li> <li>SFX and Music :: The original had voice synthesis which I don't but I'd like to add some sound effects when passengers appear, get knocked over, etc. as well as other sound effects generally. And while the original didn't have music I'll probably add some.</li> <li>Fix how money (aka: score) tips, etc. work</li> <li>Add saved high scores</li> <li>Add an actual win screen</li> <li>Various tweaks (refuel faster, etc.)</li> </ul> <h4>Original Post Below</h4> <p>I don't know if anyone else remembers Space Taxi from the C64 era but I used to love playing it as a kid. I'm not sure why it popped into my mind a while back but I thought I'd take a shot at implementing a Pico-8 version.</p> <p>This is a work in progress so it only has a few very boring levels for testing out the mechanics. I'm a bit too close to it and have gotten used to the controls so if anybody who isn't me felt like giving it a shot and letting me know how the controls feel I'd be grateful.</p> <p> <table><tr><td> <a href="/bbs/?pid=128419#p"> <img src="/bbs/thumbs/pico8_space_taxi-1.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=128419#p"> space_taxi</a><br><br> by <a href="/bbs/?uid=54967"> jasondelaat</a> <br><br><br> <a href="/bbs/?pid=128419#p"> [Click to Play]</a> </td></tr></table> </p> <p>There will likely be a second screen under the &quot;Controls&quot; menu option which explains the UI but I haven't implemented it yet. So briefly:</p> <ul> <li>The money indicator on the left is your &quot;Earned Money&quot; aka, your score.</li> <li>Above that is your lives</li> <li>The money indicator on the right is your &quot;Tip.&quot; Deliver passengers faster, get a bigger tip.</li> <li>The &quot;clock&quot; on the right is the level indicator. (1:00 is level 1, 12:00 is level 12, etc.)</li> <li>The black box at bottom center is where passenger instructions are displayed (where to take them)</li> <li>Above that is the fuel gauge. Fuel usage is tied to how long you're in the air <em>not</em> how much you use the thrusters and fuel resets between levels. The third (and last) sample level has a fuel platform to test out the refuelling mechanic but not all levels have fuel platforms.</li> <li>The gray checked bar above the other UI elements is the landing indicator and flashes either green, yellow or red. Green for a soft landing, yellow for a harder landing and red when you crash. Your tip is reduced (well not yet but it will be) for &quot;yellow&quot; landings.</li> </ul> <p>Once I've got the mechanics dialed in I'll probably throw together a quick level editor and start making some actual levels. Hopefully I've got room for a full 24. I've got a little under 3000 tokens left and about half my character space and I haven't tried to optimize it yet but I suspect I may end up using a second cart for the level data.</p> <p>Thanks to anybody who tries it out!</p> https://www.lexaloffle.com/bbs/?tid=52384 https://www.lexaloffle.com/bbs/?tid=52384 Mon, 10 Apr 2023 19:50:53 UTC Simple 'Types' and Type Constructors <p>This function creates simple extensible 'types' with a few additional (potentially) handy properties. If you find it useful, help yourself to it. Credit appreciated but not required.</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> -- 52 tokens function new_type(names) local names = split(names, ' ') local meta = {} return setmetatable( meta, { __call=function(self, ...) local obj = {...} for i=1,#names do obj[names[i]] = obj[i] end return setmetatable(obj, {__index = meta}) 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></div></div></div></p> <p>You use it like 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> -- a 2d point object point = new_type('x y') -- argument names separated by spaces p = point(1, 2) print(p.x) -- prints 1 print(p.y) -- prints 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>As for the handy properties, the first is that the object's data is stored both by key and as an array. Which means you can access the properties using the dot notation, numerical indexes, or even unpacking the object, all of which can come in handy in different situations.</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> p = point(1, 2) x, y = unpack(p) -- extract all the data in order -- all of these print 1 print(p.x) print(p[1]) print(x) -- and all of these print 2 print(p.y) print(p[2]) print(y)</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 downside of this is that each object is storing the data twice and, therefore, requires twice as much space. So if you have a lot of active objects in your program at once you'll run out of memory about twice as fast. But you can always modify <code>new_type()</code> to only use one or the other of these methods instead of both if you prefer.</p> <p>The second handy property is that functions created with <code>new_type()</code> aren't actually functions, they're objects which you can add methods to. Those methods are then available to every object of that 'type'. For instance:</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> point = new_type('x y') point.add = function(a, b) return point(a.x + b.x, a.y + b.y) end p = point(1, 2) q = point(2, 3) r = p:add(q) print(r.x) -- prints 3 print(r.y) -- prints 5</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>It even works if the methods are defined <em>after</em> you've already created the objects:</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> point.dot_product = function(a, b) return a.x * b.x + a.y * b.y end print(p:dot_product(r)) -- prints 13</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> https://www.lexaloffle.com/bbs/?tid=51945 https://www.lexaloffle.com/bbs/?tid=51945 Fri, 10 Mar 2023 01:20:55 UTC Bug: metatables and __tostring (v0.2.5g) <p>Not sure if this is actually a bug or expected behaviour. Seems like a bug to me. That said, don't know if it's a problem with PICO-8 or just with Lua in general.</p> <p>Here's the problem. If I define a metatable using the <code>__metatable</code> property then the <code>__tostring</code> method doesn't get called automatically (by print for example) when 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>m = {} x = setmetatable( {}, { __index=m, __metatable=m, __tostring=function(self) return 'blah blah blah' end } ) print(x) print(tostring(x))</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Output:</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>[table] blah blah blah</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>So, I can call the tostring() function explicitly but it's not called automatically by print as I would expect. If I leave out the <code>__metatable</code> property though it 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>m = {} x = setmetatable( {}, { __index=m, __tostring=function(self) return 'blah blah blah' end } ) print(x) print(tostring(x))</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Output:</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>blah blah blah blah blah blah</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Putting the <code>__tostring</code> function in m also doesn't work:</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>m = { __tostring=function(self) return 'blah blah blah' end } x = setmetatable( {}, { __index=m, __metatable=m } ) print(x) print(tostring(x))</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Output:</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>table: 0x30bb9cc table: 0x30bb9cc</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Nor does giving m its own metatable with a <code>__tostring</code> method:</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>m = setmetatable({}, { __tostring=function(self) return 'blah blah blah' end }) x = setmetatable( {}, { __index=m, __metatable=m } ) print(x) print(tostring(x))</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Output:</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>[table] table: 0x30bb9cc</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 <code>__metatable</code> property doesn't seem to interfere with other metamethods. Haven't checked them all though. 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>m = {} x = setmetatable( {}, { __index=m, __metatable=m, __add=function(a, b) return 1 end, __tostring=function(self) return 'blah blah blah' end } ) print(x) print(x + x)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Output:</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>[table] 1</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 isn't really a major problem, I'm mostly just messing around but it seems weird so might be worth taking a look at at some point.</p> https://www.lexaloffle.com/bbs/?tid=51807 https://www.lexaloffle.com/bbs/?tid=51807 Wed, 01 Mar 2023 12:55:40 UTC Song: Home Stretch <p> <iframe src="sfxp2.php?id=54967_28" width="402" height="300" style="border:none; overflow:hidden" scrolling="no" allow="autoplay"></iframe><a style="cursor:pointer; font-size:8pt" onclick=' var el = document.getElementById("sfxcode_54967_28"); if (el.style.display == "none") el.style.display = ""; else el.style.display = "none"; microAjax("/bbs/sfxc/54967_28.txt", function (retdata){ var el = document.getElementById("sfxcode_54967_28"); el.innerHTML = retdata; el.focus(); el.select(); } ); '> [sfx] </a> <textarea rows=3 class=lexinput id="sfxcode_54967_28" style="width:480px;background-color:#fed;display:none;overflow:hidden; font-size:4pt;"></textarea> </p> <p>I don't know if people are still having problems with the inline music player but here's a cart with the song just in case.</p> <p> <table><tr><td> <a href="/bbs/?pid=125018#p"> <img src="/bbs/thumbs/pico8_mibamawojo-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=125018#p"> mibamawojo</a><br><br> by <a href="/bbs/?uid=54967"> jasondelaat</a> <br><br><br> <a href="/bbs/?pid=125018#p"> [Click to Play]</a> </td></tr></table> </p> <p>I've made no attempt to optimize space and used pretty much all the sfx slots. Though some are duplicated just so if you happen to listen on the editor music/sfx tab it's kind of satisfying to watch. To me at least.</p> <img style="margin-bottom:16px" border=0 src="/media/54967/home-stretch_0.gif" alt="" /> https://www.lexaloffle.com/bbs/?tid=51416 https://www.lexaloffle.com/bbs/?tid=51416 Mon, 30 Jan 2023 11:33:00 UTC Noisy marching squares <p>Just a little weekend &quot;I need a break from my projects&quot; project.</p> <p> <table><tr><td> <a href="/bbs/?pid=121419#p"> <img src="/bbs/thumbs/pico8_noise_march-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=121419#p"> noise_march</a><br><br> by <a href="/bbs/?uid=54967"> jasondelaat</a> <br><br><br> <a href="/bbs/?pid=121419#p"> [Click to Play]</a> </td></tr></table> </p> <p>Uses the marching squares algorithm to determine tile placement at each step and 3 dimensional value noise to animate it.</p> https://www.lexaloffle.com/bbs/?tid=50368 https://www.lexaloffle.com/bbs/?tid=50368 Sat, 26 Nov 2022 17:50:14 UTC Overloading the sub() function <p>This isn't really anything special, just a little quality of life improvement I've found handy and keep in a file of utility functions I use frequently.</p> <p>Overloads <code>sub()</code> so it works on arrays as well as strings. I find it useful to be able to slice and dice arrays in the same way as strings and especially now that we can index strings with square bracket notation the difference between them isn't always important. I've got something with a length and I want a sub-section of it. Makes sense to use the same function for both, in my mind at least.</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>_sub = sub function sub(lst, i, j) if type(lst) == 'string' then return _sub(lst, i, j) else local r = {} for n=i,j or #lst do add(r, lst[n]) end return r 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>Creates a new array without modifying the original. Doesn't handle negative indices because I've not really needed them so far but it would be easy enough to add.</p> <p>Potentially a feature suggestion <a href="https://www.lexaloffle.com/bbs/?uid=1"> @zep</a>? I don't mind writing the function myself but I'd also be happy not to. But maybe I'm the only one who finds it useful.</p> https://www.lexaloffle.com/bbs/?tid=50186 https://www.lexaloffle.com/bbs/?tid=50186 Sat, 12 Nov 2022 13:15:28 UTC Procedural Generation: Graph Rewriting <p>I'm working on a game using procedural generation and although the game is nowhere near done I thought the generation technique itself was interesting enough on its own to post about.</p> <p>Rather than try to explain it myself I'll just send you to someone who already has, better than I likely could. <a href="https://www.boristhebrave.com/2021/04/02/graph-rewriting/">BorisTheBrave: Graph Rewriting for Procedural Level Generation</a></p> <p>Instead, I'll just share some of my initial results. The general idea is to generate the &quot;big picture&quot; details first and then successively refine them to build a fleshed out so the levels generated here are shown at 1/8 scale. Eventually every pixel in these maps will expand to be one tile at full scale with additional generation filling in the fine details along the way.</p> <p> <table><tr><td> <a href="/bbs/?pid=120219#p"> <img src="/bbs/thumbs/pico8_nisofunajo-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=120219#p"> nisofunajo</a><br><br> by <a href="/bbs/?uid=54967"> jasondelaat</a> <br><br><br> <a href="/bbs/?pid=120219#p"> [Click to Play]</a> </td></tr></table> </p> <p>The rules I'm using a pretty simple for now; they were mostly intended to help me shake out any bugs in the graph rewriting system itself. The actual rules I end up using will probably be somewhat more complex but even so I think I'm already getting some decent results.</p> <h2>The Cycle</h2> <p>The first thing I do is generate a cycle with a start (green square) and an end (red square.) The idea for each level is you go in, get whatever is waiting for you at the end&mdash;treasure, a boss fight, whatever&mdash;and then have to get back out. Having a cycle gives you multiple potential routes to accomplish that. </p> <p>At the moment the cycles go one-way&mdash;although it's not shown&mdash;with one path leading toward the end and the other path leading from the end back to the start. But you could have two paths heading towards the start where one might be shorter but full of enemies and the other is longer but relatively safe, and all sorts of other variations.</p> <h2>Terrain</h2> <p>Next up is terrain. For this demo I just have two terrain types, stone and water. You can have both types on a single tile giving you wet stone. The rules are written so that stone and water will never touch directly but will always have wet stone in between. This also results in the occasional patch of wet stone bordered by either stone on both sides or water on both sides which helps to vary things up a bit more.</p> <p>For the actual game I'm thinking I'll just have abstract terrain types: terrain-1, terrain-2, etc. Then each level will have a randomly chosen theme and that theme will give me a terrain palette which will assign actual terrain types. So you could have fire level and ice levels and so on.</p> <h2>Enemies</h2> <p>The enemy rules are fairly straight forward. There are four types:</p> <ol> <li>Large enemies (big X)</li> <li>Small enemies (small x)</li> <li>Swarms (clusters of small enemies that attack together)</li> <li>Guardians (in a white box with a border. When you come into their<br /> area everything closes off and you can't continue until you defeat<br /> them. Basically mini-bosses.)</li> </ol> <p>The red ones will be visible as you approach and the yellow ones will be hidden and ambush you. You'll never get two large enemies right next to each other but any other combination of enemies is possible.</p> <h2>Obstacles</h2> <p>Obstacles show up on the map as locks and keys but they may not be actual locked doors requiring keys. They're just anything which requires you to do or obtain something in one part of the level in order to progress in another part. The &quot;key&quot; could be an actual key but it could also be a lever or a puzzle or a new ability. Maybe you need to drain water in one area to reveal a door in another. Whatever.</p> <p>This, in particular, is an area where I think graph rewriting shines. Since at this stage the whole level is represented as a graph and the path from start to end and back to start is a directed sub-graph, it's relatively easy to ensure that keys never end up <em>behind</em> their associated lock. The lock and key are initially spawned on the same tile with keys optionally then being moved backwards along the path but never past the start.</p> <p>At the moment there's a 50% chance of a key moving on each iteration of the rules so, on average, keys are found fairly close to their locks but I can tweak the probabilities to have them move either more or less.</p> <h2>Rooms</h2> <p>Rooms are added to empty areas near the main path. Rooms are either big or small and once placed one or more doors are added to the room connecting it to other rooms and/or the main path. </p> <p>The dark gray bars connecting the rooms to the path are just to show roughly where doors are and how they connect up to the main path. There won't be literal corridors to every room. Either the rooms will be moved closer to the main path or the surrounding terrain will be expanded to the edge of the room.</p> <p>At the moment the rooms are all just kind of plopped down on top of each other. Whether overlapping rooms merge into a bigger room or where exactly the walls between them will be will be figured out at a later stage.</p> <h2>Next steps</h2> <p>Next is to take this simple set of rules and and basic level layouts and turn them into full-fledged level maps to get the basic infrastructure in place to handle that process.</p> <p>After that I'll probably take a break to work on actual game mechanics before really diving into the level generation rules.</p> https://www.lexaloffle.com/bbs/?tid=50076 https://www.lexaloffle.com/bbs/?tid=50076 Sun, 06 Nov 2022 19:31:39 UTC 32768 and Beyond: Arbitrarily large integers <p>A few days back <a href="https://www.lexaloffle.com/bbs/?uid=15232"> @dw817</a> posted a <a href="https://www.lexaloffle.com/bbs/?tid=49971">thread</a> about the number of possible combinations in 256 bytes in which they asked:</p> <p>&gt; Is there a way to calculate 256^256 ?</p> <p>And knowing there are all sorts of tools and languages which can handle such huge numbers I cheekily posted the answer and caused a bit of a derail.</p> <p>Anyway, I had some time on my hands so I figured I'd implement large number support for Pico-8.<br /> <table><tr><td> <a href="/bbs/?pid=120151#p"> <img src="/bbs/thumbs/pico8_jd_bignum-1.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=120151#p"> jd_bignum</a><br><br> by <a href="/bbs/?uid=54967"> jasondelaat</a> <br><br><br> <a href="/bbs/?pid=120151#p"> [Click to Play]</a> </td></tr></table> </p> <h5>Edit: Fixed a bug in divide()</h5> <p>Is this likely to be of use to anybody? Probably not. Is it major overkill? You betcha! Though in fairness, at 706 tokens it came out smaller than I was expecting.</p> <h2>Features/Functions</h2> <ul> <li> <p><strong><code>bignum(n)</code>:</strong> Create a 'bignum' with value <code>n</code> which is just a regular Pico-8 number. Most operations require both numbers to be bignums. So <code>bignum(16000)*bignum(1254)</code> but not <code>bignum(16000)*1254</code>. </p> <p><code>bignum()</code> is equivalent to <code>bignum(0)</code></p> </li> <li> <p><strong><code>divide(bn)</code>:</strong> You can use the division and mod operators (/, %) on bignums if you only need one or the other. The <code>divide</code> function returns both the quotient and remainder at once.</p> <p><code>q, r = divide(bignum(3)^16, bignum(2)^15)</code></p> </li> <li><strong><code>show(bn)</code>:</strong> Converts a bignum into its decimal representation as a string.</li> <li><strong><code>factorial(n)</code>:</strong> Calculates the factorial of <code>n</code> and returns the result as a bignum. Factorials get very big, <em>very</em> fast so the input to <code>factorial</code> is just a regular number.</li> <li><strong><code>+ - / % ^</code>:</strong> Arithmetic operators behave how you would expect. All require both arguments to be bignums with the exception of <code>^</code> where only the first argument is a bignum and the second is a regular number. Division is integer division only so <code>bignum(3)/bignum(4)==bignum(0)</code>.</li> <li><strong><code>&gt; &gt;= &lt;= &lt; ==</code>:</strong> Comparisons similarly require both arguments to be bignums.</li> </ul> <h2>Issues</h2> <ul> <li>The arithmetic operators are all fairly efficent but probably not optimally so.</li> <li>I wasn't taking negative numbers into account at all so I make no promises about what will happen if you try to use them. Maybe it will work! (It probably won't work.)</li> <li><code>show</code> is <em>extremely</em> inefficient. I'm sure there are better ways to convert very long binary strings to decimal but I'm not familiar with them so this is what I've got. Large numbers (like 52!) may take a bit of time and <em>very</em> large numbers will crash the program as it runs out of memory. (bignum(256)^256 does this, sadly. It calculates the number fine but crashes when you try to convert it.)</li> </ul> https://www.lexaloffle.com/bbs/?tid=50060 https://www.lexaloffle.com/bbs/?tid=50060 Sat, 05 Nov 2022 13:35:22 UTC Testo-8: Test Framework <p>I know a lot of people, myself included, usually write their pico~8 code a little off the cuff tinkering with it until it works. Which tends to be more fun in my experience. But it can also be incredibly frustrating if I'm working on sometime more complex where every time I change something I break something else. And in those cases planning out some formal tests helps me maintain my sanity and get the thing actually working much faster than I probably would otherwise. And since I'm working on something fairly complex at the moment, I took a bit of a detour and put together a little test framework and thought I'd make it available for anybody else who might find it useful.</p> <p>The code is on github: <a href="https://github.com/jasondelaat/pico8-tools/tree/release/testo-8">https://github.com/jasondelaat/pico8-tools/tree/release/testo-8</a><br /> Or you can just copy it here:<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>-------------------------------- -- testo-8: testing framework -- copyright (c) 2022 jason delaat -- mit license: https://github.com/jasondelaat/pico8-tools/blob/release/license -------------------------------- do local all_tests = {} local function smt(t, mt) return setmetatable(t, {__index=mt}) end local function shallow_copy(lst) local copy = {} for l in all(lst) do add(copy, l) end return copy end local function filter(f, lst) local results = {} for l in all(lst) do if f(l) then add(results, l) end end return results end local execute_meta = { execute=function(self) local result = self[4](self[3](self[2]())) if self._cleanup[1] then self._cleanup[1]() end return { result, self[1]..self.when_txt..self.result_txt } end } local when_result_meta local result_meta = { result=function(self, txt, fn) local t = shallow_copy(self) t.when_txt = self.when_txt t.result_txt = 'result '..txt..'\n' t._cleanup = self._cleanup add(t, fn) add(all_tests, smt(t, execute_meta)) return smt(self, when_result_meta) end } local _cleanup local when_meta = { when=function(self, txt, fn) _cleanup = {} local t = shallow_copy(self) t.when_txt = 'when '..txt..'\n' t[3] = fn t._cleanup = _cleanup return smt(t, result_meta) end } when_result_meta = { when=when_meta.when, result=result_meta.result, cleanup=function(self, f) add(_cleanup, f) return self end } local given_meta = { given=function(self, txt, fn) local msg = self[1]..'given '..txt..'\n' return smt({msg, fn}, when_meta) end } function test(name) _cleanup = {} local t = smt({name..':\n', _cleanup=_cleanup}, given_meta) return t end local function run_tests() cls() cursor(0, 7) local results = {} for t in all(all_tests) do add(results, t:execute()) end local failures = results and filter(function(r) return not r[1] end, results) or 0 if #failures == 0 then print('all '..#all_tests..' tests passed!', 0, 0, 11) else for f in all(failures) do print(f[2]) end rectfill(0, 0, 127, 6, 0) print(#failures..'/'..#all_tests..' tests failed:\n', 0, 0, 8) cursor(0, 127) end end function _init() run_tests() end end -- end testo-8 ------------------------------</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>And here's a cart with some simple functions containing errors and a few tests, most of which fail just to give you an idea of how it works.<br /> <table><tr><td> <a href="/bbs/?pid=119475#p"> <img src="/bbs/thumbs/pico8_testo8_demo-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=119475#p"> testo8_demo</a><br><br> by <a href="/bbs/?uid=54967"> jasondelaat</a> <br><br><br> <a href="/bbs/?pid=119475#p"> [Click to Play]</a> </td></tr></table> </p> <h3>Testo-8</h3> <p>The framework is pretty simple. It's just a single function <code>test</code> which returns an object exposing methods&mdash; <code>given</code>, <code>when</code>, <code>result</code> and <code>cleanup</code> &mdash;for defining the test. </p> <p>Testo-8 defines an <code>_init</code> function which automatically runs the tests. Just <code>#include testo-8.lua</code>, write tests and run the cart. If you've defined your own <code>_init</code> you'll probably need to comment it out to get the tests to run.</p> <p>A very simple test might look something like 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>test('some simple addition') :given('the number one', function() return 1 end) :when('adding 2', function(n) return n+2 end) :result('should equal 3', function(r) return r == 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>The methods <code>given</code>, <code>when</code> and <code>result</code> &mdash;which I'll call clauses&mdash;all take a string as their first argument and a function as their second, while <code>test</code> takes a string argument only. The strings are used to build the output message if the test fails.</p> <p>The function arguments taken by the other methods serve different purposes:</p> <ul> <li><code>given</code> should return the object(s) being tested. (1 in the example)</li> <li><code>when</code> takes the object(s) being tested as input and does something with it returning the result(s) (add 2)</li> <li><code>result</code> takes the result(s) and should return a boolean, true if the test passes and false if it fails. (does 1+2 == 3?)</li> </ul> <p>Each test has exactly one <code>given</code> clause below which will be one or more <code>when</code> clauses. Each <code>when</code> clause contains one or more <code>result</code> clauses and can optionally be ended with a <code>cleanup</code> clause. More on that later. So an actual test might look something like 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>-- the ...'s are just a placeholders for some appropriate function test('some test') :given('an object to test', ...) :when('1st when', ...) :result('result 1', ...) :result('result 2', ...) :result('result 3', ...) :when('2nd when', ...) :result('result 4', ...) :result('result 5', ...) :result('result 6', ...) :cleanup(...) :when('3rd when', ...) :result('result 7', ...) :result('result 8', ...)</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 number of <code>result</code> clauses is the actual number of tests that will be run so the above example would be eight tests. Each <code>result</code> clause is executed as follows: The <code>given</code> clause is executed to generate the object(s) to test. The test object(s) are passed to the <code>when</code> clause which appears <em>above</em> the <code>result</code> and finally the results are passed to the <code>result</code> clause which determines whether the test passes or fails.</p> <p>So in the above example the <code>given</code> clause will run eight times, once for every <code>result</code>. The first <code>when</code> clause will be called three times and so will the second while the third <code>when</code> clause will only be called twice.</p> <p><code>cleanup</code> takes a single function as its argument and is used to clean up after a test if, for instance, the test modifies some global state which needs to be reset in-between tests. The <code>cleanup</code> clause is optional but if it exists will be called <em>after</em> each <code>result</code> clause inside the same <code>when</code>. The <code>cleanup</code> in the above example would therefore be called three times, once after each of <code>result</code>s 4, 5 and 6.</p> <p>For anyone interested in seeing actual tests, here are some I wrote for a simple s-expression parser.<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 test_given(s) return s, function() return s end end function len(n) return function(lst) return #lst == n end end function index_eq(n, s) return function(lst) return lst[n] == s end end function is_table(r) return type(r) == 'table' end function is_nil(r) return r == nil end test('empty s-exp') :given(test_given('()')) :when('parsed', parse) :result('should be a table', is_table) :result('should have 0 elements', len(0)) test('single element s-exp') :given(test_given('(atom)')) :when('parsed', parse) :result('should have 1 element', len(1)) :result('element should be atom', index_eq(1, 'atom')) test('multi-element s-exp') :given(test_given('(a b c d e)')) :when('parsed', parse) :result('should have 5 elements', len(5)) :result('1st should be a', index_eq(1, 'a')) :result('2nd should be b', index_eq(2, 'b')) :result('3rd should be c', index_eq(3, 'c')) :result('4th should be d', index_eq(4, 'd')) :result('5th should be e', index_eq(5, 'e')) test('nested s-exp') :given(test_given('((atom))')) :when('parsed', parse) :result('should have 1 element', len(1)) :result('element should be a table', function(r) return is_table(r[1]) end) :result('element should have length 1', function(r) return #r[1] == 1 end) :result('nested element should be atom', function(r) return r[1][1] == 'atom' end ) test('multi-element nested s-exp') :given(test_given('((a b) (c d) (e f))')) :when('parsed', parse) :result('should have 3 elements', len(3)) :result('each element should be length 2', function(r) for i in all(r) do if #i != 2 then return false end end return true end ) :result('1st contains a and b', function(r) return r[1][1] == 'a' and r[1][2] == 'b' end ) :result('2nd contains c and d', function(r) return r[2][1] == 'c' and r[2][2] == 'd' end ) :result('3rd contains e and f', function(r) return r[3][1] == 'e' and r[3][2] == 'f' end ) test('multiply nested s-exp') :given(test_given('((a (b c)))')) :when('parsed', parse) :result('should have 1 element', len(1)) :result('element length 2', function(r) return #r[1] == 2 end ) :result('element [1][1] == a', function(r) return r[1][1] == 'a' end ) :result('element [1][2] is table', function(r) return is_table(r[1][2]) end ) :result('element #[1][2] == 2', function(r) return #r[1][2] == 2 end ) :result('element [1][2][1] == b', function(r) return r[1][2][1] == 'b' end ) :result('element [1][2][2] == c', function(r) return r[1][2][2] == 'c' end ) test('empty string fails to parse') :given(test_given('')) :when('parsed', parse) :result('should be nil', is_nil) test('unclosed parens fails to parse') :given(test_given('((a b) (c)')) :when('parsed', parse) :result('should be nil', is_nil) test('too many closed parens fails to parse') :given(test_given('((a b) (c)))')) :when('parsed', parse) :result('should be nil', is_nil) test('parsing with newlines') :given(test_given('(a \n (b c))')) :when('parsed', parse) :result('should have 2 elements', len(2)) :result('1st element should be a', index_eq(1, 'a')) :result('r[2][1] == b', function(r) return r[2][1] == 'b' end) :result('r[2][2] == c', function(r) return r[2][2] == 'c' 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> <h3>Modules and Testing</h3> <p>I rely heavily on an external editor and spreading all my code around a bunch of files. If that's not how you work this may not be super practical. But here's a quick run-down of how I (currently) work on a project.</p> <p>Even though Pico-8 Lua doesn't technically have modules I generally try to write things in a modular way and <code>#include</code> with the help of <code>do...end</code> gives me something module-like.</p> <p>A vastly oversimplified example would be something like 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>-- player.lua local pos = {x=64, y=64} local s = 1 local function move(var, dist) return function() pos[var] += dist end end move_left = move('x', -2) move_right = move('x', 2) move_up = move('y', -2) move_down = move('y', 2) function draw_player() spr(s, pos.x, pos.y) 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 I include inside of a <code>do...end</code> block like so:</p> <img style="margin-bottom:16px" border=0 src="/media/54967/demo_0.png" alt="" /> <p>Writing modules like this doesn't really cost much extra because:</p> <ol> <li>These are all functions I'd write anyway</li> <li>The <code>local</code> keyword doesn't use any tokens</li> <li>The <code>do...end</code> costs just a single token</li> <li>The added encapsulation given module local variables means I can't accidentally mess of things like the player position from other parts of my code because <code>pos</code> doesn't exist outside of the module.</li> </ol> <p>Importantly, I don't put the surrounding <code>do...end</code> in the module file itself. Because when it come to writing the actual tests, I'll put those in another separate file and then include it inside the same <code>do...end</code> block as before.</p> <img style="margin-bottom:16px" border=0 src="/media/54967/demo_1.png" alt="" /> <p>This makes the tests part of the same module so they can access and test all the local data and functions. Once I'm sure everything is working properly I can just comment out the <code>#include</code> for the test file and free up all those tokens. </p> <h3>Issues</h3> <ol> <li>Since Lua doesn't have exception handling capabilities like <code>try...catch</code> or similar, I'm not able to intercept certain errors and report them as test failures. So things like attempting to index a nil value, etc. will still cause the cart to crash and you'll have to fix those problems before the test will run.</li> <li>The above can also lead to occasionally cryptic error messages saying that there's an error with testo-8 itself. This is certainly possible but usually it means you've passed nil, or something else, where testo-8 is expecting a function. If you're frequently commenting out parts of your code make sure you haven't commented out a function which you're using in a test.</li> </ol> https://www.lexaloffle.com/bbs/?tid=49888 https://www.lexaloffle.com/bbs/?tid=49888 Sun, 23 Oct 2022 14:00:43 UTC Untitled WIP:push your luck, dice builder, roguelike/RPG...thing. <p> <table><tr><td> <a href="/bbs/?pid=119177#p"> <img src="/bbs/thumbs/pico8_jd_dice_wip-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=119177#p"> jd_dice_wip</a><br><br> by <a href="/bbs/?uid=54967"> jasondelaat</a> <br><br><br> <a href="/bbs/?pid=119177#p"> [Click to Play]</a> </td></tr></table> </p> <p>So I've started working on this. It's still very early and I've only got some of the UI elements and the very beginnings of procedural generation in place but I think there's enough to make it worth sharing.</p> <h3>Controls</h3> <p>While moving around:</p> <ul> <li>🅾️ to switch to the ability selector. (Coloured boxed bottom-middle)</li> </ul> <p>In ability selector:</p> <ul> <li>🅾️ to select highlighted item</li> <li>❎ to cancel</li> </ul> <p>Choosing 'END TURN' pulls up a 'Dice Selector' menu which doesn't do anything yet.<br /> Press either ❎ or 🅾️ to dismiss it.</p> <h3>Notes</h3> <ul> <li>Actual movement in the game will be tile based but I wanted people to be able to zoom around the big empty levels fairly quickly for now because it's not like there's much to see.</li> <li>At the moment the levels are just a big loop of straight, boring grey hallways surrounded by blackness but eventually room will be able to expand into the available empty spaces, there will be locks, keys, enemies, puzzles, etc.</li> <li>The mini-map may or may not be in the actual game but I wanted to give an overall idea of the shape of the generated levels.</li> <li>The green square in-level is the level entrance and the red one the level exit. Nothing happens yet when you get to them.</li> <li>The stats on the bottom left are hard-coded and don't do/mean anything yet.</li> </ul> <p>I'm trying my hand at procedural generation using graph re-writing. There's a fairly straight-forward summary of the process here for anyone interested: <a href="https://www.boristhebrave.com/2021/04/02/graph-rewriting/">https://www.boristhebrave.com/2021/04/02/graph-rewriting/</a></p> <p>This version isn't actually using re-writing yet which is why all the interesting stuff is missing from the level. It's just a graph with a basic loop. I've got the system written and&mdash;I think&mdash;working as expected so next steps are to start testing some rules for slightly more interesting levels to shake out all the bugs and then take it from there. Oh yeah, and all the actual game mechanics. Those will probably help too.</p> https://www.lexaloffle.com/bbs/?tid=49807 https://www.lexaloffle.com/bbs/?tid=49807 Sun, 16 Oct 2022 15:21:50 UTC Local functions. Who knew? <p>So maybe this is old news to everybody but me but I just discovered it by accident. Turns out you can define local functions! It makes sense but it somehow never occurred to me to try before. </p> <p>Here's a quick 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>do local function a() print('in a') end function b() print('in b: calling a') a() end print('in local scope') print(a) print(b) a() b() print('leaving local scope\n\n') end print('in global scope') print(a) print(b) b()</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Output:</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>in local scope [function] [function] in a in b: calling a in a leaving local scope in global scope [nil] [function] in b: calling a in a</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>That's it. Just thought it was interesting.</p> https://www.lexaloffle.com/bbs/?tid=49582 https://www.lexaloffle.com/bbs/?tid=49582 Sun, 02 Oct 2022 18:10:54 UTC Text adventures on Pico-8. For some reason. <p>I had a bit of time to tinker yesterday. I'm not sure what made me think of the old Infocom text adventures but here we are.</p> <p>If you're familiar with Text Adventures (or Interactive Fiction) you may know that one of&mdash;maybe <em>the</em>&mdash;most popular tool for creating them has been the <a href="https://en.wikipedia.org/wiki/Inform">Inform</a> language. The current version is <a href="https://ganelson.github.io/inform-website/">Inform 7</a> but <em>waaaaay</em> back when I was first learning, it is was <a href="https://github.com/DavidKinder/Inform6">Inform 6.</a></p> <p>Anyway, I decided to throw together a quick little <a href="https://github.com/jasondelaat/pico8-tools/tree/release/pico-informant">IF authoring API loosely based on Inform 6.</a> It is by no means complete or particularly advanced. Basically, I followed the first tutorial for the game <em>Heidi</em> from <a href="https://ifarchive.org/if-archive/infocom/compilers/inform6/manuals/IBG.pdf">The Inform Beginner's Guide</a> and implemented just enough to make it work. But work it does! Mostly. I think...</p> <p>The command parser is <em>extremely</em> simple so don't expect too much from it. All commands should be in the form:</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>verb [noun] [noun]</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Where the nouns are optional depending on what you're trying to do. So <code>east</code> or <code>e</code> to walk to the east; <code>take object</code> to pick up an object; and <code>put object target</code> to put an object on/in something else. There are enough verbs, including synonyms, to complete this game and the API even lets you add new ones but don't try to be too creative with your commands. It was mostly just something to do on a Saturday afternoon so I may or may not develop it further.</p> <p> <table><tr><td> <a href="/bbs/?pid=117587#p"> <img src="/bbs/thumbs/pico8_jd_heidi_if-3.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=117587#p"> jd_heidi_if</a><br><br> by <a href="/bbs/?uid=54967"> jasondelaat</a> <br><br><br> <a href="/bbs/?pid=117587#p"> [Click to Play]</a> </td></tr></table> </p> <p>The API clocks in at 926 tokens while the example game itself is only 173.</p> <p>For simplicity's sake here's the code for just the game itself if you're interested in how it works. You can find the original in Appendix B of The Inform Beginner's Guide linked above if you want to see how the two versions compare.</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>--&gt;8 -- example game: heidi -- adapted from: -- 'the inform beginner's guide' -- https://ifarchive.org/if-archive/infocom/compilers/inform6/manuals/IBG.pdf -------------------------------- -- defining the title screen story( 'heidi', &quot;a simple text adventure written\nby roger firth and sonja\nkesserich.\n\nadapted to pico-8 from\nthe inform beginner's guide.\nby jason delaat.\n\n\npress enter to begin.&quot;) -- rooms and objects before_cottage = object('in front of a cottage') description(&quot;you stand outside a cottage.\nthe forest stretches east.\n&quot;) has(light) forest = object('deep in the forest') description(&quot;through the dense foliage you\nglimpse a building to the west.\na track heads to the northeast.&quot;) has(light) bird = object('baby bird', forest) description(&quot;too young to fly, the nestling\ntweets helplessly.&quot;) name('baby', 'bird', 'nestling') clearing = object('a forest clearing') description(&quot;a tall sycamore stands in the\nmiddle of this clearing. the\npath winds southwest through the\ntrees.&quot;) has(light) nest = object(&quot;bird's nest&quot;, clearing) description(&quot;the nest is carefully woven of \ntwigs and moss.\n &quot;) name(&quot;bird's&quot;, 'nest', 'twigs', 'moss') has(container|open) tree = object('tall sycamore tree', clearing) description(&quot;standing proud in the middle of \n the clearing, the stout tree \n looks easy to climb.\n &quot;) name('tall', 'sycamore', 'tree', 'stout', 'proud') has(scenery) top_of_tree = object('at the top of the tree') description(&quot;you cling precariously to the \ntrunk.&quot;) has(light) each_turn(function(_ENV) if contains(branch.contents, nest) then print('you win!') stop() end end) branch = object('wide firm bough', top_of_tree) description(&quot;it's flat enough to support a \nsmall object.\n &quot;) name('wide', 'firm', 'flat', 'bough', 'branch') has(static|supporter) -- connecting the rooms before_cottage :e_to(forest) forest :w_to(before_cottage) :ne_to(clearing) clearing :sw_to(forest) :u_to(top_of_tree) top_of_tree :d_to(clearing) -- initialization function _init() location(before_cottage) max_carried = 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> <h3>Walkthrough</h3> <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 /> This example game is very short with only four rooms and a handful of objects. The goal is to return the baby bird to its nest in the tree. You can only carry one object at a time&mdash;as defined by the max_carried variable in the _init() function&mdash;but an object inside of something else counts as a single object.</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>in front of a cottage -------------------------------- you stand outside a cottage. the forest stretches east. &gt; east deep in the forest -------------------------------- through the dense foliage you glimpse a building to the west. a track heads to the northeast. you see a baby bird. &gt; take bird you take the baby bird. &gt; ne a forest clearing -------------------------------- a tall sycamore stands in the middle of this clearing. the path winds southwest through the trees. you see a bird's nest. &gt; put bird nest you put the baby bird in the bird's nest. &gt; take nest you take the bird's nest. &gt; climb at the top of the tree -------------------------------- you cling precariously to the trunk. you see a wide firm bough. &gt; put nest bough you win!</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Some of the words have multiple aliases. Instead of 'bough' you could type 'put nest branch' and that would also work.<br /> </div></div></div></p> https://www.lexaloffle.com/bbs/?tid=49378 https://www.lexaloffle.com/bbs/?tid=49378 Sun, 18 Sep 2022 10:44:36 UTC