Kaius [Lexaloffle Blog Feed]https://www.lexaloffle.com/bbs/?uid=65920 Picotron theme: PICO-8 Nostalgia <p>A custom theme for Picotron that tries to recreate the iconic colors of PICO-8's editors.</p> <img style="margin-bottom:16px" border=0 src="/media/65920/Picotron custom PICO-8 theme.png" alt="" /> <p>There are two ways to get this theme into your copy of Picotron. The first one is the more complicated manual process that I originally posted, and the second is just copy-pasting a command or two into the terminal:</p> <h3>Method 1: The manual way</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> <ul> <li>If you don't already have <code>/appdata/system/themes/</code>, the easiest way to get it is to open up two filenav processes, with one at <code>/system/</code> and the other at <code>/appdata/system/</code>, and drag the <code>themes</code> folder from the former into the latter. (I recommend doing it this way because if <code>/appdata/system/themes/</code> exists, then the contents of <code>/system/themes/</code> will be ignored, so simply creating an empty <code>themes</code> folder in <code>/appdata/system/</code> will result in all of the system themes vanishing. Note that the same goes for wallpapers and screensavers, so while you have both of those filenavs open you might as well copy those over.)</li> <li>open up your system settings by clicking on the Picotron logo on the top-left, then double-click the <code>[custom]</code> option in the <code>themes</code> chooser (or any of the themes; double-clicking any of them will work) to pull up the theme editor.</li> <li>Manually copy the colors from the image above. (This is the tedious part. If you want to play with the colors a bit, go ahead.)</li> <li>Click the menu button on the theme editor (the one in the top-left corner of the window that looks like three horizontal bars) and select <code>Save File As</code>.</li> <li>Navigate to the <code>/appdata/system/themes/</code> folder, type <code>pico8.theme</code> (or whatever you want to call it, but don't forget the <code>.theme</code>) into the prompt, and click <code>Save</code>.</li> <li>Your theme will now show up in the list of available themes, though you may have to close and reopen the settings app if you left it open from earlier.<br /> </div></div></div> <h3>Method 2: The copy-paste-into-the-terminal-way</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 /> If you don't already have <code>/appdata/system/themes/</code>, the easiest way to get it is to copy this command into the terminal:</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>cp /system/themes/ /appdata/system/themes/</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>(I recommend doing this because if <code>/appdata/system/themes/</code> exists, then the contents of <code>/system/themes/</code> will be ignored, so simply creating an empty <code>themes</code> folder in <code>/appdata/system/</code> will result in all of the system themes vanishing. Note that the same goes for wallpapers and screensavers, so don't forget to copy them over if you plan to add more of those.)</p></li> </ul> <p>After that, just paste in this command to install the theme:</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(&quot;/appdata/system/themes/pico8.theme&quot;,unpod(&quot;b64:bHo0AE0BAAACAgAA8BJ7ZGVza3RvcF9wYXR0ZXJuPXB4dQBDIAwMBEcQBxAHADcGADAQNwAGABFXEgBBBxAHkAIACAgA4YAsZG9yX2ZyYW1lPTEzDQCTYnV0dG9uPTUsYgAxMT01GABxdGl0bGU9NhcAc19zaGFkb3clAEQwPTE3LgAyMD0xRgBxb3JkZXI9Mg4ASG1hbnQSAAIrAARFAAMSAPEHMT0xNCxpY29uMT02LHRvb2xiYXI9NxIAEzKcAAFIAAKtABEyGQA1MD03GAACoAAkMzE8ABAxNQAAJQA1Mz0xJQAD3QAVMiYAAVsAAwwAxV9zZWxlY3RlZD0xNRQAAEIACCQAZWl0ZW09Mg8ApGJhY2s9OCx3aW7hABIxDQACaQAhMTMOAAO0ABE5DQACqQAQNwwANGRvdzcAFTAQAAOjAAQQAAI8ABQ4DwCQdGl0bGU9MTV9&quot;))</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p></div></div></div><br /> Enjoy!</p> https://www.lexaloffle.com/bbs/?tid=141136 https://www.lexaloffle.com/bbs/?tid=141136 Wed, 27 Mar 2024 06:15:32 UTC Fractal SPLIT() <p>On <a href="https://www.youtube.com/watch?v=7vScOGi_Yv8">the latest episode</a> of <a href="https://www.lexaloffle.com/bbs/?tid=51847">his advanced shmup tutorial</a>, <a href="https://www.lexaloffle.com/bbs/?uid=16423"> @Krystman</a> showed off a function for splitting a string into a 2D array, and then said there could be more ways to make it more compact and token-efficient. Guess what I... <em>didn't</em> do?</p> <p>Well, I didn't make it more token-efficient - my new version is close to double that of the original 28-token function at <strong>55 tokens</strong> - but I DID make it more versatile; It can now handle:</p> <ul> <li>3,4,5 and onwards dimensional arrays (the original could only do 2-dimensional arrays)</li> <li>Any character for use as a separator (the original could only use &quot;|&quot; and &quot;,&quot;)</li> <li>Optional tonum() conversion (the original had that forced on)</li> </ul> <p>In fact, you could use this function exclusively instead of the built-in split() and you might not even notice. In order to get multi-dimensional arrays, the <code>sep</code> parameter should be given a string that is more than a single character long, <strong>with the most significant separator characters coming first</strong>. Oh, uh, <em>after</em> you paste the function into your code, that is. Speaking of which...</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 splitf(str,sep,tnm) local cnt=type(sep)==&quot;string&quot; and #sep&gt;1 local arr=split(str,sep,not cnt and tnm!=false) if cnt then for k,v in pairs(arr) do arr[k]=splitf(v,sub(sep,2),tnm) end end return arr end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>if you want to get a good idea of how it behaves, paste the function definition into an empty cart, and then follow it up with this testing snippet:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>function recursive_print(t,d) d=d or 0 for k,v in pairs(t) do for i=1,9 do flip() end ?&quot;\f2\*&quot;..d..&quot;|\fb&quot;..k..&quot;\f7: \fc&quot;..tostr(v)..&quot;\f6 (&quot;..type(v)..&quot;)&quot; if type(v)==&quot;table&quot; then if d&lt;8 then recursive_print(v,d+1) else ?&quot;\f2\*&quot;..1+d..&quot;|\f8[ ** max depth reached ** ]&quot; end end end end cls() ?&quot;printing split string&quot;,13 recursive_print(splitf(&quot;1,2,3|1,2,3|1,2,3 1,2,3|1,2,3|1,2,3 1,2,3|1,2,3|1,2,3&quot;,&quot; |,&quot;,true))</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Of course, I'm sure I'm not the first person to come up with this function, and it's probably overkill in most cases. Still, if anyone wants to mess around with the function and make it more token-efficient, then I'll leave off by saying that the <code>cnt</code> local variable is the variable that determines if a recursive function call is necessary. (<code>cnt</code> for 'continue'. Previously I went with <code>lst</code> for 'last', but the continue flag was better at capturing the nuances of what happens when you give <code>split()</code> a non-string value for its separator.)</p> https://www.lexaloffle.com/bbs/?tid=52785 https://www.lexaloffle.com/bbs/?tid=52785 Thu, 18 May 2023 19:57:58 UTC Copying map data does not output to the clipboard <p>Here's a gif of me copying GFX data to code:</p> <img style="margin-bottom:16px" border=0 src="/media/65920/jelpi_0.gif" alt="" /> <p>Here's a gif of me copying SFX data to code:</p> <img style="margin-bottom:16px" border=0 src="/media/65920/jelpi_2.gif" alt="" /> <p>And here's a gif of me Copying music data to code:</p> <img style="margin-bottom:16px" border=0 src="/media/65920/jelpi_3.gif" alt="" /> <p>So far, so good. Every bit of data copied also edits the clipboard, allowing these to be copy-pasted between instances of PICO-8.</p> <p>But not so for map data! So here's a gif of me attempting to copy map data...</p> <img style="margin-bottom:16px" border=0 src="/media/65920/jelpi_1.gif" alt="" /> <p>No edit to the clipboard was made. (Prior to each of these I had copied the <code>-- TEST STRING TO COPY</code> line to make it clearer.) Admittedly, you're not likely to ever need to copy map data <strong>between</strong> carts, but if you do (such as when working on a multicast game, or if you're transferring ideas from a prototype cart to the cart you plan to use for the full game) then it's gonna be <em>real</em> tricky to do so.</p> <p>This issue was brought to my attention by the (as of now) latest episode from <a href="https://www.lexaloffle.com/bbs/?uid=16423"> @Krystman</a> on <a href="https://www.lexaloffle.com/bbs/?tid=51847">his advanced Shmup tutorial</a>. Look around <a href="https://youtu.be/a7zSbY2YrRs?t=332">the 5:30 mark</a>; he can easily copy the sprite sheet, but map data requires him to edit the P8 files directly, which works... but if you want to make the copied map data appear somewhere else rather than exactly where it was or only copy a small portion of the map, then that still presents a problem.<br /> <object width="640" height="400"><param name="movie" value="https://www.youtube.com/v/a7zSbY2YrRs&hl=en&fs=1&rel=0"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="https://www.youtube.com/v/a7zSbY2YrRs&hl=en&fs=1&rel=0" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="640" height="400"></embed></object></p> https://www.lexaloffle.com/bbs/?tid=52550 https://www.lexaloffle.com/bbs/?tid=52550 Sat, 29 Apr 2023 19:36:07 UTC Picotron Minesweeper v.3! <img style="margin-bottom:16px" border=0 src="/media/65920/Picotron Minesweeper 5.png" alt="" /> <p>Yes! If you remember, back in January I had posted a code snippet that would let you play Minesweeper in the <a href="https://www.lexaloffle.com/picotron.php?page=playground">Picotron Playground</a>. I later updated that snippet to have some comments describing the settings needed to recreate the three difficulty levels, but otherwise nothing changed. Well now, after months of (not that) hard (but still a little tricky) work, the third version of my Minesweeper game on Picotron is OUT! New features include:</p> <ul> <li>Easy windowed mode: Running the program will instantly switch to the desktop. No more faffing about with <code>run_program_inside_terminal</code>! Who wants to type that out every single time?</li> <li>A help screen: Just click the handy little blue question mark (?) and you can reference the rules anytime. Also shows up the first time the program is run.</li> <li>In-game minefield customization: <em>Nobody</em> wants to edit the code every time just to change the difficulty. Nobody.</li> <li>Highscores: Though they can't be saved between sessions, they CAN be saved if you close the program and re-run it without refreshing the browser tab, and it should work on the executable version of Picotron when that's released.</li> <li>A screenshot button: Though it sucks and I wouldn't use it, since the BBS won't see colors #16-31 as anything other than black, so you can't post them here. :/</li> </ul> <h2>HOW TO GET STARTED</h2> <p>First, copy the following hidden block of code: (The <code>-- end of program</code> line should be #702)<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>-- Minesweeper (v3) -- By: Kai -- Other things I might want to do: -- o Unique font for 7-seg displays? (Dunno how to use custom fonts on Picotron yet...) -- o Release to SPLORE. (When Picotron is released, obviously.) -- o Make the screenshot function return a PNG file instead? (If it's possible...) -- o Maybe online time submissions? (Again, if it's possible...) -- -- GFX decoder function decode(str) -- necessary so the forum doesn't screw up the code return userdata(chr(91,103,102,120,93)..str..chr(91,47,103,102,120,93)) end -- -- GFX: faces smile=decode&quot;101077777777777777767666666666666665766660000006666576660aaaaaa066657660aaaaaaaa0665760aa00aa00aa065760aa00aa00aa065760aa00aa00aa065760aaaaaaaaaa065760aaaaaaaaaa065760aa0aaaa0aa0657660aa0000aa066576660aaaaaa06665766660000006666576666666666666656555555555555555&quot; curious=decode&quot;101077777777777777767666666666666665766660000006666576660aaaaaa066657660aaaaaaaa0665760aa00aa00aa065760aa00aa00aa065760aa00aa00aa065760aaaaaaaaaa065760aaaaaaaaaa065760aaaa00aaaa0657660aaa00aaa066576660aaaaaa06665766660000006666576666666666666656555555555555555&quot; frown=decode&quot;101077777777777777767666666666666665766660000006666576660aaaaaa066657660aaaaaaaa0665760aa0aaaa0aa065760aaa0aa0aaa065760aa0aaaa0aa065760aaaaaaaaaa065760aaa0000aaa065760aa0aaaa0aa0657660aaaaaaaa066576660aaaaaa06665766660000006666576666666666666656555555555555555&quot; cool=decode&quot;101077777777777777767666666666666665766660000006666576660aaaaaa066657660aaaaaaaa0665760a000aa000a0657600000000000065760a000aa000a065760aaaaaaaaaa065760aa0aaaa0aa065760aaa0000aaa0657660aaaaaaaa066576660aaaaaa06665766660000006666576666666666666656555555555555555&quot; -- GFX: tiles boom=decode&quot;08088888888m8686868m8855588m8656568m8855588m8686868m8888888mmmmmmmmm&quot; block=decode&quot;08087777776m7666665m7666665m7666665m7666665m7666665m6555555mmmmmmmmm&quot; empty=decode&quot;0808kkkkkkkmk444444mk444444mk444444mk444444mk444444mk444444mmmmmmmmm&quot; flag=decode&quot;08087777776m7668665m7688665m7888665m766i665m76iii65m6555555mmmmmmmmm&quot; mine=decode&quot;0808kkkkkkkmk646464mk455544mk656564mk455544mk646464mk444444mmmmmmmmm&quot; falseflag=decode&quot;08087777778m7662685m7622865m7228665m768i665m78iii65m8555555mmmmmmmmm&quot; -- GFX: numbers numbercols={12,27,24,16,2,17,13,0} numbers={ decode&quot;08080000000000770000000700000007000000070000007770000000000000000000&quot;, decode&quot;08080000000000777000000070000077700000700000007770000000000000000000&quot;, decode&quot;08080000000000777000000070000007700000007000007770000000000000000000&quot;, decode&quot;08080000000000707000007070000077700000007000000070000000000000000000&quot;, decode&quot;08080000000000777000007000000077700000007000007770000000000000000000&quot;, decode&quot;08080000000000700000007000000077700000707000007770000000000000000000&quot;, decode&quot;08080000000000777000000070000000700000007000000070000000000000000000&quot;, decode&quot;08080000000000777000007070000077700000707000007770000000000000000000&quot;} -- GFX: buttons wrench=decode&quot;0808777777767666m6657666m665766mmmm576mmm6657m6m66657mm6666565555555&quot; help=decode&quot;08087777777676hhhh657hh6hhh57666hhh5766hh66576666665766hh66565555555&quot; left=decode&quot;0808777777767666mm65766mmm6576mmmm6576mmmm65766mmm657666mm6565555555&quot; right=decode&quot;08087777777676mm666576mmm66576mmmm6576mmmm6576mmm66576mm666565555555&quot; pause=decode&quot;08087777777676m66m6576m66m6576m66m6576m66m6576m66m6576m66m6565555555&quot; fishhook=decode&quot;08087777777676mmmm657m6666m5766666m576mm66m57m6mmm6576mm666565555555&quot; unusable=decode&quot;0808777777767o6666o576o66o65766oo665766oo66576o66o657o6666o565555555&quot; cam=decode&quot;080877777776766mm6657mmmmmm57mm66mm57mm66mm57mmmmmm57666666565555555&quot; -- -- adjustables -- beginner: 9x9/10 -- intermediate: 16x16/40 -- expert: 30x16/99 -- WARNING!!! Setting the size of the minefield too high will cause Picotron to start -- flashing the screen, rendering the game virtually unplayable and pose a risk to those -- vulnerable to said flashing. User discretion is advised. -- DO NOT ADJUST as of v3. PLEASE keep these at 9x9/10 and -- use the in-game editor to change these. width=9 -- min: 9 | max: 59 height=9 -- min: 1 | max: 28 mines=10 -- idealy ~10-20% of total tiles -- -- other setup lmb=0 dead=false won=false start=true paused=false remainingmines=mines timer=0 set_window(width*8+1,height*8+20) cheatinput={} -- highscores setup highscores_txt=fetch&quot;/best_minesweeper_times.txt&quot; if not highscores_txt then highscores_txt=&quot;999,999,999\n999,999,999\n999,999,999&quot; store(&quot;/best_minesweeper_times.txt&quot;,highscores_txt) first_time=true end highscores_arr=split(highscores_txt,&quot;\n&quot;) for i=1,3 do this_diff=split(highscores_arr[i],&quot;,&quot;) for j=1,3 do this_diff[j]=tonumber(this_diff[j]) end highscores_arr[i]=this_diff end difficulty=1 -- -- data format: -- bit 0: mine -- bit 1: uncovered -- bit 2: flagged -- bit 3: UNUSED -- bit 4: UNUSED -- bit 5: UNUSED -- bit 6: UNUSED -- bit 7: UNUSED -- minefield=userdata(&quot;u8&quot;,width,height) -- -- debug: randomized test board --for x=0,width-1 do --for y=0,height-1 do --set(minefield,x,y,flr(rnd(2))) --end --end --dead=true -- /debug -- function place_mines(cx,cy) local goodcandidates={} local nearcandidates={} for x=0,width-1 do for y=0,height-1 do if abs(x-cx)&gt;1 or abs(y-cy)&gt;1 then add(goodcandidates,{x,y}) elseif x~=cx or y~=cy then add(nearcandidates,{x,y}) end end end for i=1,mines do --local position=candidates[flr(rnd(#candidates))+1] local position if #goodcandidates&gt;0 then position=deli(goodcandidates,flr(rnd(#goodcandidates))+1) else position=deli(nearcandidates,flr(rnd(#nearcandidates))+1) end set(minefield,position[1],position[2],1) --del(candidates,position) end end -- function bit(n,b,c) if type(c)==&quot;boolean&quot; then return c and n|2^b or n&amp;-1-2^b else return n&amp;2^b&gt;0 end end -- function uncover(tx,ty) if (bit(get(minefield,tx,ty),1)) return -- already uncovered if (bit(get(minefield,tx,ty),2)) return -- flagged if (tx&lt;0 or tx&gt;=width or ty&lt;0 or ty&gt;=height) return -- out of bounds set(minefield,tx,ty,get(minefield,tx,ty)+2) local nearbymine for xdelta=-1,1 do for ydelta=-1,1 do if (bit(get(minefield,tx+xdelta,ty+ydelta),0)) nearbymine=true end end if not nearbymine then for xdelta=-1,1 do for ydelta=-1,1 do local x=tx+xdelta local y=ty+ydelta if (x==mid(0,x,width-1) and y==mid(0,y,height-1) and not bit(get(minefield,x,y),1) and not bit(get(minefield,x,y),2)) uncover(x,y) end end elseif bit(get(minefield,tx,ty),0) then dead=true end end -- function checkwon() if (dead) return local coveredtiles=0 for x=0,width-1 do for y=0,height-1 do if (not bit(get(minefield,x,y),1)) coveredtiles+=1 end end if coveredtiles==mines then won=true remainingmines=0 local best_times=highscores_arr[difficulty] local score=flr(timer) rank=0 if cheatinput and difficulty&gt;0 and score&lt; best_times[3] then best_times[3]=score rank=3 if (best_times[3]&lt; best_times[2]) best_times[3]=best_times[2] best_times[2]=score rank=2 if (best_times[2]&lt; best_times[1]) best_times[2]=best_times[1] best_times[1]=score rank=1 --if (score&lt; world_recoreds[difficulty]) rank=&quot;*&quot; scoresub() -- world record! highscores_arr[difficulty]=best_times -- probably unnecessary highscores_txt=highscores_arr[1][1]..&quot;,&quot;..highscores_arr[1][2]..&quot;,&quot;..highscores_arr[1][3]..&quot;\n&quot;..highscores_arr[2][1]..&quot;,&quot;..highscores_arr[2][2]..&quot;,&quot;..highscores_arr[2][3]..&quot;\n&quot;..highscores_arr[3][1]..&quot;,&quot;..highscores_arr[3][2]..&quot;,&quot;..highscores_arr[3][3] rm&quot;/best_minesweeper_times.txt&quot; store(&quot;/best_minesweeper_times.txt&quot;,highscores_txt) end if (not cheatinput) rank=&quot;x&quot; if (difficulty==0) rank=&quot;?&quot; end end -- function draw_game() if (not (dead or won or start or paused)) timer=min(timer+1/60,999) local tx=(mx-1)\8 local ty=(my-20)\8 if tx==mid(0,tx,width-1) and ty==mid(0,ty,height-1) and not dead and not won and not paused then -- on grid if bit(mbp,0) and not bit(get(minefield,tx,ty),1) and not bit(get(minefield,tx,ty),2) then if (start) place_mines(tx,ty) start=false --set(minefield,tx,ty,bit(get(minefield,tx,ty),1,true)) uncover(tx,ty) checkwon() elseif bit(mbp,2) and not bit(get(minefield,tx,ty),1) and not start then set(minefield,tx,ty,get(minefield,tx,ty)^^4) remainingmines+=bit(get(minefield,tx,ty),2) and -1 or 1 elseif bit(mbp,1) and bit(get(minefield,tx,ty),1) then local closemines=0 local closeflags=0 for xdelta=-1,1 do for ydelta=-1,1 do if (bit(get(minefield,tx+xdelta,ty+ydelta),0)) closemines+=1 if (bit(get(minefield,tx+xdelta,ty+ydelta),2)) closeflags+=1 end end if closemines==closeflags then for xdelta=-1,1 do for ydelta=-1,1 do uncover(tx+xdelta,ty+ydelta) end end checkwon() end end elseif mx==mid(width*4-8,mx,width*4+7) and my==mid(2,my,17) and bit(mbp,0) then -- clicked on face -- reset puzzle dead=false won=false start=true paused=false remainingmines=mines timer=0 for x=0,width-1 do for y=0,height-1 do set(minefield,x,y,0) end end rank=nil elseif mx==mid(width*4+10,mx,width*4+17) and my==mid(6,my,13) and bit(mbp,0) then -- clicked on wrench if start then draw_function=draw_menu if (not (dead or won or start)) paused=true cheatinput={} elseif dead or won then -- screenshot function local screenshot=get_draw_target() local clipboard_txt=chr(91,103,102,120,93) local w,h=tww,twh local function to_p8scii_num(n) if n&lt;10 then return tostr(flr(n)) else return chr(flr(n+87)) end end clipboard_txt..=to_p8scii_num(flr(w/16))..to_p8scii_num(w%16)..to_p8scii_num(flr(h/16))..to_p8scii_num(h%16) for i=0,w*h-1 do clipboard_txt..=to_p8scii_num(get(screenshot,i%w,i\w)) end clipboard_txt..=chr(91,47,103,102,120,93) set_clipboard_text(clipboard_txt) cls() return else paused=not paused end elseif mx==mid(width*4-18,mx,width*4-11) and my==mid(6,my,13) and bit(mbp,0) then -- clicked on help draw_function=draw_help if (not (dead or won or start)) paused=true end -- -- draw game cls() rectfill(0,0,width*8,19,22) rectfill(0,19,width*8,height*8+19,32) palt(0,false) local emote=dead and frown or won and cool or (bit(mb,0) or bit(mb,1)) and tx==mid(0,tx,width-1) and ty==mid(0,ty,height-1) and curious or smile spr(emote,width*4-8,2) spr(start and wrench or (dead or won) and cam or paused and right or pause,width*4+10,6) spr(help,width*4-18,6) palt(0,true) if paused then print(&quot;** PAUSED **&quot;,width*4-30,height*4+15,7) else for x=0,width-1 do for y=-0,height-1 do local tile local tdata=get(minefield,x,y) local showneighbors if bit(tdata,1) then -- uncovered if bit(tdata,0) then -- mine tile=boom else -- no mine tile=empty showneighbors=true end else -- covered if bit(tdata,2) or won then -- flag if bit(tdata,0) or not dead then -- correct or still playing tile=flag else -- game over corrections tile=falseflag end else -- no flag if bit(tdata,0) and dead then -- game over clairvoyance tile=mine else -- nothing special tile=block end end end spr(tile,x*8+1,y*8+20) if showneighbors then local closemines=0 for xdelta=-1,1 do for ydelta=-1,1 do if (bit(get(minefield,x+xdelta,y+ydelta),0)) closemines+=1 end end pal(7,numbercols[closemines]) spr(numbers[closemines],x*8+1,y*8+20) pal(7,7) end end end end --rect(0,0,width*8,19,1) rect(0,19,width*8,height*8+19,17) rectfill(1,5,16,13,2) local str=mid(-99,flr(remainingmines),999) if str&lt;-9 then -- do nothing elseif str&lt;0 then str=&quot;-0&quot;..-str elseif str&lt;10 then str=&quot;00&quot;..str elseif str&lt;100 then str=&quot;0&quot;..str else -- do nothing end print(({[0]=&quot;non&quot;,&quot;1St&quot;,&quot;2nd&quot;,&quot;3rd&quot;,[&quot;*&quot;]=&quot;tOP&quot;,[&quot;x&quot;]=&quot;CHt&quot;,[&quot;!&quot;]=str,[&quot;?&quot;]=&quot;CUS&quot;})[rank or &quot;!&quot;],2,6,8) --print(&quot;888\f8\-1&quot;..({[0]=&quot;non&quot;,&quot;1St&quot;,&quot;2nd&quot;,&quot;3rd&quot;,[&quot;*&quot;]=&quot;tOP&quot;,[&quot;x&quot;]=&quot;CHt&quot;,[&quot;!&quot;]=str,[&quot;?&quot;]=&quot;CUS&quot;})[rank or &quot;!&quot;],2,6,24) rectfill(width*8-16,5,width*8-1,13,2) local str=mid(-99,flr(timer),999) if str&lt;-9 then -- do nothing elseif str&lt;0 then str=&quot;-0&quot;..-str elseif str&lt;10 then str=&quot;00&quot;..str elseif str&lt;100 then str=&quot;0&quot;..str else -- do nothing end print(str,width*8-15,6,8) --print(&quot;888\f8\-1&quot;..str,width*8-15,6,24) if cheatinput then for i=97,122 do if (get_key_pressed(chr(i))) add(cheatinput,i) end while #cheatinput&gt;5 do deli(cheatinput,1) end if (#cheatinput&gt;=5 and cheatinput[1]..cheatinput[2]..cheatinput[3]..cheatinput[4]..cheatinput[5]==&quot;120121122122121&quot; and get_key_state&quot;shift&quot;) cheatinput=nil else -- cheating!!! pset(0,0,(tx&lt;0 or tx&gt;=width or ty&lt;0 or ty&gt;=height) and 5 or bit(get(minefield,tx,ty),0) and 32 or 7) --pset(width*8,0) end -- debug: display mouse statistics --local ww,wh=window_size() --cursor(width*8+2,1) color(27) --?&quot;mx: &quot;..mx --?&quot;my: &quot;..my --?&quot;mb: &quot;..mb --?&quot;mbp: &quot;..mbp --?&quot;tx: &quot;..tx --?&quot;ty: &quot;..ty --?&quot;ww: &quot;..ww --?&quot;wh: &quot;..wh --?&quot;b: &quot;..highscores_arr[1] --?&quot;i: &quot;..highscores_arr[2] --?&quot;e: &quot;..highscores_arr[3] -- /debug -- tww=width*8+1 twh=height*8+20 end -- function draw_menu() if mbp%2==1 then if mx==mid(150,mx,157) and my==mid(70,my,77) then draw_function=draw_game remainingmines=mines minefield=userdata(&quot;u8&quot;,width,height) elseif mx==mid(0,mx,6) and my==mid(21,my,63) then for i=2,5 do -- check if we are close to the appropriate difficulty selector if abs(mx-3)^2+abs(my-i*12)^2&lt;9 then difficulty=i-1 if difficulty==4 then difficulty=0 else width= ({09,16,30})[difficulty] height=({09,16,16})[difficulty] mines= ({10,40,99})[difficulty] end break end end elseif difficulty==0 and mx==mid(131,mx,157) and my==mid(13,my,44) then local change=get_key_state&quot;ctrl&quot; and get_key_state&quot;shift&quot; and 100 or get_key_state&quot;ctrl&quot; and 10 or get_key_state&quot;shift&quot; and 5 or 1 if (mx==mid(131,mx,138) and my==mid(13,my,20)) width-=change if (mx==mid(150,mx,157) and my==mid(13,my,20)) width+=change if (mx==mid(131,mx,138) and my==mid(25,my,32)) height-=change if (mx==mid(150,mx,157) and my==mid(25,my,32)) height+=change if (mx==mid(131,mx,138) and my==mid(37,my,44)) mines-=change if (mx==mid(150,mx,157) and my==mid(37,my,44)) mines+=change width=mid(9,width,30) height=mid(9,height,16) mines=mid(10,mines,min(99,width*height-1)) end end -- cls(22) ?&quot;Minefield Customization Menu&quot;,1,1,23 spr(fishhook,150,70) ?&quot;Beginner\nIntermediate\nExpert\nCustom&quot;,7,21,6 for i=2,5 do local y=i*12 if (i-1==difficulty or i==5 and difficulty==0) circfill(3,y,2,27) circ(3,y,2,6) end if difficulty==0 then ?&quot; width:\nheight:\n mines:&quot;,96,13,6 for y=12,36,12 do rectfill(130,y,158,y+9,32) ?y==12 and (width&lt;10 and &quot;0&quot;..width or width) or y==24 and (height&lt;10 and &quot;0&quot;..height or height) or mines,140,y+1,6 end spr(width&gt;9 and left or unusable,131,13) spr(width&lt;30 and right or unusable,150,13) spr(height&gt;9 and left or unusable,131,25) spr(height&lt;16 and right or unusable,150,25) spr(mines&gt;10 and left or unusable,131,37) spr(mines&lt; min(99,width*height-1) and right or unusable,150,37) else local best_times=highscores_arr[difficulty] ?&quot;1st: &quot;..best_times[1]..&quot;\n2nd: &quot;..best_times[2]..&quot;\n3rd: &quot;..best_times[3],111,13,6 end -- tww=160 twh=80 end -- function draw_help() page=page or 1 local pages={{[[ Welcome to Picotron Minesweeper! Click the to continue reading. Click the to go back a page. Click the to exit this screen. ]], spr,right,49,25, spr,left,49,37, spr,fishhook,49,49}, {[[ Note that the game is paused in this menu, so don't worry about looking back here in the middle of a game when you need help. Click the to get back here. ]], spr,help,49,49 },{[[ Click a \0121tile\012s ( ) to uncover it. A number will be revealed. This number shows how many \0121mines\012s ( ) surround the tile. ]], rectfill,71,0,79,8,22, spr,block,72,1, rectfill,36,48,44,56,22, spr,boom,37,49, function() rectfill(1,24,73,32,22) for i=0,8 do local x=i*8+2 spr(empty,x,25) pal(7,numbercols[i]) spr(numbers[i],x,25) pal(7,7) end end}, {[[ \0121Uncovering a mine will end the game.\012s The goal is to uncover all non-mine tiles as \0121quickly as possible.\012s ]]},{[[ Right-click a tile to \0121flag\012s ( ) it as having a mine underneath. Use this to keep track of what you think is and isn't a mine. ]], rectfill,141,0,149,8,22, spr,flag,142,1}, {[[ The number in the upper-left displays \0121an approximation of how many mines are remaining.\012s The number in the upper-right displays your current \0121time.\012s ]]},{[[ If a tile with \0121zero\012s neighboring mines is uncovered, \0121all\012s nearby tiles will automatically be uncovered. Your first click is \0121guaranteed\012s to land on such a tile, if at all possible. ]]},{[[ To restart the game, click the \0121face\012s in the top-middle of the screen. Its face also \0121corres- ponds to the current game state.\012s ]], pal,0,0, spr,smile,35,50, spr,curious,60,50, spr,frown,85,50, spr,cool,110,50, palt,0,true}, {[[ If you middle-click a revealed tile, \0121all surrounding non- flagged tiles\012s will be opened \0121IF\012s the \0121number of the clicked tile is equal to the number of surrounding flags.\012s ]]}, {[[ To the left of the face is that that you can click to get back to this help screen, but you probably figured that out already. ]], spr,help,24,13}, {[[ To the \0121right\012s of the face, however, is a button that has multiple functions \0121depending on the game's state.\012s ]], spr,wrench,100,37, spr,pause,110,37, spr,right,120,37, spr,cam,130,37}, {[[ When the game ends (one way or another), the \0121camera button\012s ( ) shows up to let you get a \0121screenshot\012s as a \0121Picotron GFX string copied to your clipboard.\012s (experimental!) ]], spr,cam,7,25}, {[[ In the middle of a game, the \0121pause\012s ( ) and \0121unpause\012s ( ) buttons can be used if you need to take a break for whatever reason. ]], spr,pause,57,13, spr,right,142,13}, {[[ The most interesting option though is at the beginning of a round with the \0121Minefield Customization Menu.\012s ( ) ]], spr,wrench,107,37}, {[[ In this menu, you can switch to one of \0121three difficulty levels\012s and see your \0121top three scores\012s for each of them. ]]},{[[ You can also create a minefield with \0121custom parameters,\012s such as \0121width, height and mines\012s using the and buttons. ]], spr,left,19,37, spr,right,49,37}, {[[ After winning the game, the \0121estimated mine count\012s will switch to showing how well you did. \0121'non'\012s means no best time, while \0121'1St'\012s, \0121'2nd'\012s and \0121'3rd'\012s mean the appropriate place. ]]}, {[[ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ \0121That's about all there is to Picotron Minesweeper. Have fun!\012s ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ]]} -- ...Aside from the hidden *CHEAT CODE*, that is... --,{[[ --\0121Push\012s button --Recieve \0121bacon\012s --]]} } -- if mbp%2==1 and my==mid(70,my,77) then if mx==mid(150,mx,157) and page&lt;#pages then page+=1 first_time=nil elseif mx==mid(140,mx,147) and page&gt;1 then page-=1 elseif mx==mid(120,mx,127) and not first_time then --page=nil -- reset current page number (I decided against this later) draw_function=draw_game --if (not page) return -- avoid a crash if we reset the page number end end -- cls(17) local to_draw=pages[page] local i=0 local text=to_draw[1] while i&lt;#text do i+=1 if sub(text,i,i)==&quot;\\&quot; then text=sub(text,1,i-1)..chr(tonumber(sub(text,i+1,i+3)))..sub(text,i+4,#text) end end ?text,1,1,28 --palt(22,true) --for i=2,#to_draw,3 do --spr(to_draw[i],to_draw[i+1],to_draw[i+2]) --end --if page==3 then --pal(22,22) rectfill(1,24,73,32,22) palt(22,true) --for i=0,8 do --local x=i*8+2 --spr(empty,x,25) --pal(7,numbercols[i]) --spr(numbers[i],x,25) --pal(7,7) --end --end --pal(22,22) i=2 while i&lt;=#to_draw do local func=to_draw[i] i+=1 local args={} while type(to_draw[i])~=&quot;function&quot; and to_draw[i]~=nil do add(args,to_draw[i]) i+=1 end func(table.unpack(args)) end ?&quot;Page: &quot;..page..&quot;/&quot;..#pages,2,71,12 spr(first_time and unusable or fishhook,120,70) spr(page==1 and unusable or left,140,70) spr(page==#pages and unusable or right,150,70) -- tww=160 twh=80 end -- draw_function=first_time and draw_help or draw_game -- function _draw() -- mouse handling mx,my,mb=get_mouse() mbp=mb&amp;~lmb -- mb pressed lmb=mb -- last mb -- draw_function() -- varies -- -- housekeeping stuff (their necessity is mostly Picotron's fault) rnd() -- sufficiently jumbles up the RNG seeds -- (necessary due to Picotron not randomizing the seed on startup) -- next up: a bunch of stuff to prevent you from resizing the window manually local ww,wh=window_size() if (tww~=ww or twh~=wh) set_window(tww,twh) -- more complicated than it needs to be because set_window() has WEEEEEIIIIIRRRRRD -- effects on how the drawing works. for example, sometimes it will prevent all -- future drawing actions from taking place. the solution would be to set the window -- last. HOWEVER, sometimes it will allow future drawing, but CLEAR THE SCREEN TOO. -- in that case, you would want to set the window first thing! long story short, -- to make thing easier we only fix the window size if we really, really have to. end -- end of program</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><br /> Then paste it into a blank codebase in Picotron Playground, hit Ctrl+R and... <strong><em>THAT'S IT.</em></strong></p> <img style="margin-bottom:16px" border=0 src="/media/65920/Picotron Minesweeper 6.png" alt="" /> <p>By default, you will be booted into the help screen, but if you don't care for that, just click the 'next page' button, and then the back button will show up so you can skip straight to playing the game.</p> <h2>Technical notes</h2> <p>(Warning: Long wall of text follows)<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 /> Most of the streamlining goes to <code>set_window(w,h)</code>. With this function, the program will automatically make it's own window, set it's size, and flip to the desktop view, creating the desktop if it hasen't been booted up this session. If it's called multiple times, it will instead adjust the window it had already made earlier, though with <em>some</em> inconsistency as to how it treats anything already on the screen, and how it treats newly drawn stuff. (In my experience, one or the other will continue working, but <strong>never</strong> both, but which one works and which on doesen't seem to switch randomly whenever you do something like <code>_draw=draw_game</code> and I have no idea why. Earlier in development I edited the <code>_draw</code> function like this, but I decided against that when I needed some stuff like the mouse variables to always be updated, which inadvertently fixed the inconsistency issue, as it I just tested it now and it seem to be stuck on the 'lock future drawing, don't clear screen' mode, whereas before when I switched the <code>_draw</code> function around it seemed to switch modes. Still, I don't want to risk anything going wrong, so I still do the check to make sure it's actually necessary to adjust the window, just in case.) Credit to <a href="https://www.lexaloffle.com/bbs/?uid=27691"> @pancelor</a> for their function-lister, as otherwise this would not have been possible.</p> <p>I also changed the font to be smaller, because that old font looked waayyy too big, and didn't go well at ALL with the shading on the holes, particularly with some colors (for example, the purple of the 4 combined with the shading to make it look like an 'A').</p> <p>Did you know that the original Minesweeper had a cheat code? This version has that cheat code implemented too, so if you know the code you can solve puzzles that would of otherwise been impossible without guessing. (Though, you won't be able to save your score if do so.) Want to know the code? Well... for that, you're gonna have to look in the game's code. You gotta EARN it, you know?</p> <p>Pasting the program into PICO-8 shows that this program is consists of 2944 tokens, 24445 chars, and 7642 compressed bytes (48% of .p8.png capacity). Of course, the program won't work in PICO-8 - it crashes just from attempting to define the sprites - but it's still a good metric for how much work I put in. (Answer: Actually not that much, at least compared to some other people.)</p> <p>If you have too many empty tiles onscreen at any one time, this happens:</p> <img style="margin-bottom:16px" border=0 src="/media/65920/Picotron Minesweeper rendering glitch.png" alt="" /> <p>Actually, it's even worse, as it constantly swaps between this and the normal desktop at <em>sixty frames a second</em>. So, uh, I suppose I should give out an epilepsy warning, I guess?</p> <p>Also, posting this on here was... harder than you would think. For starters, there's that gosh-darned <code>decode</code> function at the top of the codebase. Why is that? Well...</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>jelpi=userdata&quot; <table><tr><td width=32> <img src="https://www.lexaloffle.com/bbs/gfxc/65920_26.png" width=32 height=32> </td> <td valign=bottom> <a style="cursor:pointer;font-size:8pt" onclick=' var el = document.getElementById("gfxcode_65920_26"); if (el.style.display == "none") el.style.display = ""; else el.style.display = "none"; microAjax("https://www.lexaloffle.com/bbs/gfxc/65920_26.txt", function (retdata){ var el = document.getElementById("gfxcode_65920_26"); el.innerHTML = retdata; el.focus(); el.select(); } ); '> [8x8]</a> </td></tr> <tr><td colspan=2> <textarea rows=3 class=lexinput id="gfxcode_65920_26" style="width:640px;background-color:#fed;display:none;overflow:hidden; font-size:6pt;"></textarea> </td> </tr> </table> </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 looks horrible! And yes, that is what happens when you try to put a {gfx}..{/gfx} tag (imagine those as being square brackets and not curly ones) in a post, even in a section marked as code. The <code>decode</code> function's job is to synthesize one of those tags without actually having one of them in the code. So to make that Jelpi sprite:</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 decode(str) return userdata(chr(91,103,102,120,93)..str..chr(91,47,103,102,120,93)) end jelpi=decode&quot;0808000000000f000f000ffffff00f1fff100effffe0002220000088800000f0f000&quot;</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>You can see that the <code>chr</code> function is helping out greatly here by letting us encode the <code>gfx</code> portion as a series of numbers, and then decoding them to feed them to <code>userdata</code>.</p> <p>The other issue I had when posting this code here was statements like <code>blah&lt; foo</code>. If you remove the space between <code>&lt;</code> and <code>foo</code>, it will remove ALL code between that point and whenever it finally sees a <code>&gt;</code>. This doesn't seem to happen when a number or a space is immediatly following the <code>&lt;</code> however, so fortunately this was relatively easy to fix.<br /> </div></div></div></p> <p>That's about all I had. Like others, I probably won't continue to work on anything major in Picotron until a new version comes out (particularly the 0.1 release), but I still enjoyed working on it nonetheless. Special thanks to <a href="https://www.lexaloffle.com/bbs/?uid=13822"> @Liquidream</a>, <a href="https://www.lexaloffle.com/bbs/?uid=15232"> @dw817</a> and <a href="https://www.lexaloffle.com/bbs/?uid=41031"> @merwok</a> for the kind words spoken about this project that helped me motivate myself to continue working on it.</p> https://www.lexaloffle.com/bbs/?tid=52328 https://www.lexaloffle.com/bbs/?tid=52328 Fri, 07 Apr 2023 21:33:19 UTC Functions for copying objects without linkage <p>One nice thing about objects is that objects set with the <code>=</code> operator or as arguments are all 'linked' meaning, among other things, you can provide them as arguments for a function call and that function will be able to edit the local copy of that object and have it affect the original one too. But sometimes, you <em>don't</em> want that. Sometimes you want to copy an object, make changes to it, and then discard the new copy without affecting the old one. That's why I've created a little helper function for this circumstance. Actually, I've created several. Here's the fully-featured 'default' function:</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 cpy_obj(obj,recursion) if type(obj)==&quot;table&quot; then local return_value={} for k,v in pairs(obj) do if recursion then v=cpy_obj(v,true) end return_value[k]=v end return return_value else return obj 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>Usage: <code>copy= cpy_obj(𝘰𝘣𝘫𝘦𝘤𝘵)</code> or <code>function_call( cpy_obj(𝘰𝘣𝘫𝘦𝘤𝘵) )</code></p> <p><code>return_value</code> is the new object being created. Note that since we are creating it a piece at a time, it has <em>no</em> affiliation with the previous object. If you give it something that is not an object, it will just give you the input variable back. <code>recursion</code> is a boolean that asks if we want to make nested objects be decoupled as well. You <strong>usually</strong> want this if you have nested objects, but keep in mind that this comes with a performance cost, and although it's rather small, you might not want that if you're pushing up against the limits of PICO-8.</p> <h2>Variants</h2> <p>Every project is different, and with that comes different requirements. One person will be working on a game jam that has no risk of reaching the token limit, and another will be working on a 3D engine that needs to be as compact as possible. As such, I have a few different variants that may cater to your project's particular demands...</p> <h3>Forced recursion</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>function cpy_obj(obj) if type(obj)==&quot;table&quot; then local return_value={} for k,v in pairs(obj) do v=cpy_obj(v) return_value[k]=v end return return_value else return obj 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><br /> This one shaves off five tokens (plus one every time you need recursion!), but comes at the downside of a forced performance cost. Still, this one is probably closer to being the more useful one than the default one due to the token savings.</p> <h3>No recursion</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>function cpy_obj(obj) if type(obj)==&quot;table&quot; then local return_value={} for k,v in pairs(obj) do return_value[k]=v end return return_value else return obj 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><br /> This one saves five <strong>more</strong> tokens than the previous one, but <em>lacks</em> recursion entirely. Of course, you won't always (and usually don't) need recursion, but if you do then this function won't cut it for you.</p> <h3>No non-object return values</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>function cpy_obj(obj) local return_value={} for k,v in pairs(obj) do return_value[k]=v end return return_value 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><br /> This one save <strong>TEN</strong> more tokens than even the last one, but non-object inputs will result in a crash as the API function <code>pairs()</code> won't know what to do.</p> <h3>Ultra-cheap snippet</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>𝘯𝘦𝘸_𝘰𝘣𝘫={} for k,v in pairs(𝘰𝘭𝘥_𝘰𝘣𝘫) do 𝘯𝘦𝘸_𝘰𝘣𝘫[k]=v 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><br /> This one is cheaper than all of the others at just thirteen tokens, but must be copy-pasted everywhere you intend to use it meaning it only saves tokens if you only use it in one or two spots (which very well may be the case). Whatever you do, be sure to replace <code>𝘰𝘭𝘥_𝘰𝘣𝘫</code> and <code>𝘯𝘦𝘸_𝘰𝘣𝘫</code> with your object names.</p> <h2>Comparisons</h2> <p>Lastly, here's a list of the token costs of each function, assuming all arguments are one variable:</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>Default : 42 to define , 3 or 4 per call Forced recursion : 37 to define , 3 per call No recursion: : 32 to define , 3 per call No non-object return values : 22 to define , 3 per call Ultra-cheap snippet : no definition , 13 per call</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=50606 https://www.lexaloffle.com/bbs/?tid=50606 Sun, 11 Dec 2022 07:27:31 UTC