drakeblue [Lexaloffle Blog Feed]https://www.lexaloffle.com/bbs/?uid=47206 Oust Demo <p> <table><tr><td> <a href="/bbs/?pid=137870#p"> <img src="/bbs/thumbs/pico8_db_oust_demo-1.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=137870#p"> db_oust_demo</a><br><br> by <a href="/bbs/?uid=47206"> drakeblue</a> <br><br><br> <a href="/bbs/?pid=137870#p"> [Click to Play]</a> </td></tr></table> </p> <h2>This is the demo of my latest game: Oust. It has the first 12 asteroids of the full game and a final 13th one, unique to the demo.</h2> <p>Fly your ship within the hollow asteroids of Corvus to retrieve precious, glowing Clystron orbs and yellow jump-suited prisoners. Deliver them to green transmitters that will beam them away to safety.</p> <p>Destroy the security systems that seem to have spun out of control and search for the keycards needed to access deeper areas beyond your insertion point. It's believed that there is a store of advanced Nova weaponry somewhere in the facility that you may be able to utilise.</p> <h2>Controls</h2> <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/47206/37_OustControls.png" alt="" /> <p>The full game is uploaded on itch now:</p> <p><a href="https://drake-blue.itch.io/oust">https://drake-blue.itch.io/oust</a></p> <p><img loading="lazy" style="margin-bottom:16px" border=0 src="/media/47206/oust_stripped_63.gif" alt="" /><img loading="lazy" style="margin-bottom:16px" border=0 src="/media/47206/oust_stripped_25.gif" alt="" /></p> <p>OustEd (Level Editor) is also now available on itch.io. Extra levels on their way.</p> <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/47206/ousted2_6.gif" alt="" /> <p>A dev breakdown, including some of my more nasty token-saving tricks, is here: <a href="https://drakeblue.com/how_to_oust">https://drakeblue.com/how_to_oust</a></p> <p>v1.1.0: Rotation sensitivity controls, OustEd editor release, bug fixes.</p> https://www.lexaloffle.com/bbs/?tid=55129 https://www.lexaloffle.com/bbs/?tid=55129 Fri, 24 Nov 2023 23:57:57 UTC ENV bug in 0.2.5d <h2>edit after 0.2.5e</h2> <p>The cart works fine in local 0.2.5e PICO-8, but uploading to the BBS (i.e. inserting in this post) is still broken. I think the _ENV is being changed to _env still by some processing after upload(?).</p> <h3>earlier post</h3> <p>The following prints out 1 twice on 0.2.5c, but crashes on 0.2.5d with:</p> <p>runtime error line 10 tab 0<br /> return function_a(_ENV)<br /> attempt to call global 'func_a'<br /> (a nil value)<br /> in func_b line 10 (tab 0)<br /> at line 17 (tab 0)</p> <p> <table><tr><td> <a href="/bbs/?pid=122351#p"> <img src="/bbs/thumbs/pico8_rudekiwobu-2.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=122351#p"> rudekiwobu</a><br><br> by <a href="/bbs/?uid=47206"> drakeblue</a> <br><br><br> <a href="/bbs/?pid=122351#p"> [Click to Play]</a> </td></tr></table> </p> <p>Code:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>function get_thing() return { func_a=function(_ENV) return 1 end, func_b=function(_ENV) return func_a(_ENV) end } end thing=get_thing() print(thing:func_a()) print(thing:func_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>Makes some of the OO/class stuff (based on <a href="https://www.lexaloffle.com/bbs/?pid=116282">https://www.lexaloffle.com/bbs/?pid=116282</a>) that I've been doing a little limited.</p> https://www.lexaloffle.com/bbs/?tid=50638 https://www.lexaloffle.com/bbs/?tid=50638 Tue, 13 Dec 2022 22:12:33 UTC PICO Space Track 2 <p> <table><tr><td> <a href="/bbs/?pid=118160#p"> <img src="/bbs/thumbs/pico8_drakeblue_picospacetrack2-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=118160#p"> drakeblue_picospacetrack2</a><br><br> by <a href="/bbs/?uid=47206"> drakeblue</a> <br><br><br> <a href="/bbs/?pid=118160#p"> [Click to Play]</a> </td></tr></table> </p> <p>The second music track from <a href="https://www.lexaloffle.com/bbs/?pid=89915">PICO Space</a>. This swaps with the main track every time you earn a reward from completing a mission.</p> <p>There's a (short) third track too, but you need to finish the game for that...</p> https://www.lexaloffle.com/bbs/?tid=49533 https://www.lexaloffle.com/bbs/?tid=49533 Thu, 29 Sep 2022 14:33:55 UTC 1day1k <h1>1day1k</h1> <p> <table><tr><td> <a href="/bbs/?pid=117861#p"> <img src="/bbs/thumbs/pico8_drakeblue_1day1k-3.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=117861#p"> drakeblue_1day1k</a><br><br> by <a href="/bbs/?uid=47206"> drakeblue</a> <br><br><br> <a href="/bbs/?pid=117861#p"> [Click to Play]</a> </td></tr></table> </p> <p>You successfully hyperspaced behind the enemy bug alien invaders to their incubation caves, but alas - your ship's controls are horribly damaged. It only fires when you turn and when you turn it slowly gets faster and faster. What's more, you can't stop the ship...</p> <p>Try to take out as many of the enemy egg-pods before you crash into the cave walls.<br /> Use LEFT and RIGHT to steer - and shoot.<br /> Made for the Pico1K game jam 2022. The game fits into 1KB of compressed PICO-8 code thanks to cutting out anything extra and to Shrinko-8's minifying mode.</p> <p>This is the same as the entry, but without the code minification and some explanatory comments added.</p> <h2>&quot;How it works&quot; summary</h2> <h3>Sprites</h3> <p>The game starts off by drawing some sprites for itself to use for background tiles. These are just triangles drawn line by line with rectfill and blocks (also rectfill).</p> <h3>Map generation</h3> <p>Then it generates a 128*128 map of random data and applies some cellular automata calculations to it so that it's less noisy and more &quot;chunky&quot;. I took the method from <a href="https://gamedevelopment.tutsplus.com/tutorials/generate-random-cave-levels-using-cellular-automata--gamedev-9664">URL here</a>. I cheat slightly and make sure there are no solid squares near the centre of the map where the ship will spawn.<br /> As it iterates it draws on the screen to serve as the title sequence. I kinda wish I'd made it use the screen as an actual data buffer, but then I wouldn't have been able to put the title on it...</p> <p>The game then matches each square in the map with one of the sprites already drawn. These are in the sprite sheet in careful order so that full/empty squares can be mapped to the number of the sprite, as follows. By considering the neighbours of a square in the sequence up, right, down, left with a full square having a value of 1 and an empty one of 0 a number can be generated by treating each position as a binary digit. The value generated points to the appropriate sprite which is already in sprite memory.</p> <p>e.g. a square with empty space to the top and left, but not to the bottom and right has encoding 0011 (3) (it might be 1100 (12), I can't remember which way round I used to get it working in the end). The sprite that looks like a triangle against the right bottom is sprite 3 (or maybe 12).</p> <p>The seed for rnd is taken by combining the global time, seconds, minutes...years so hopefully players won't encounter the same level too many times. In theory, if you can destroy all the enemies in a game then it will end. I don't think it's very possible.</p> <h3>Game loop</h3> <p>There's a simple table of direction vectors that's used to update the velocity of the ship. Hit the left or right arrow and a different entry is used, the speed multiplier is increased and a bullet is added to the bullet table.</p> <p>To save bytes and to allow checking against pixel colours from the screen, the drawing and updating code is quite interleaved. This isn't great practice these days and I wouldn't recommend it, but it was more common in the past for games of this ilk and allows some corner cutting to save space. Near the end of dev to get under the size limit I removed the _draw function entirely - this may mean it behaves strangely on some (slow?) devices.</p> <p>The bullet positions are updated multiple times a frame so that they can appear to move faster - this is a handy trick that avoids having to do &quot;proper&quot; collision detection and still get fast bullets.</p> <p>There's more line by line info in the code.</p> <hr /> <p>V1: fixed a bug where bullets wouldn't pass through explosions - and saved a byte. Not sure how.<br /> V2: fixed a bug where bullets' l (life) values weren't ever decremented. If someone actually played for long enough there would be massive slowdowns and eventually PICO-8 would run out of memory.</p> https://www.lexaloffle.com/bbs/?tid=49452 https://www.lexaloffle.com/bbs/?tid=49452 Fri, 23 Sep 2022 20:35:24 UTC Multiple Displays &amp; Sprites <p> <table><tr><td> <a href="/bbs/?pid=117127#p"> <img src="/bbs/thumbs/pico8_db_multi_disp_spr-1.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=117127#p"> db_multi_disp_spr</a><br><br> by <a href="/bbs/?uid=47206"> drakeblue</a> <br><br><br> <a href="/bbs/?pid=117127#p"> [Click to Play]</a> </td></tr></table> </p> <p>-- update: fixed a bug where I was still drawing 16x16 sprites from testing that.</p> <p>Cart showing how to exploit the undocumented multiple display functions to set up extra sprite buffers that can be mapped back into memory for negligible cost.</p> <p>Use at your own risk ;)</p> <h3>Procedure</h3> <h4>Init</h4> <ul> <li> <p><code>poke(0x5f36,1) -- enable multidisplay</code></p> </li> <li>write sheet 0 of sprite graphics to screen however you want</li> <li><code>_map_display(1)</code></li> <li>write sheet 1 of sprite graphics to screen</li> <li><code>_map_display(2)</code></li> <li>write sheet 2 of sprite graphics to screen</li> <li><code>_map_display(3)</code></li> <li> <p>write sheet 3 of sprite graphics to screen</p> </li> <li>swap screen and sprite addresses <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>poke(0x5f55,0) -- map display to where sprites are to start poke(0x5f54,0x60) -- map sprites to where screen mem was</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div></li> </ul> <p>Obviously your cart doesn't get any more storage space for sprites this way so how you fill the extra buffers is a bit of a challenge.</p> <h4>Use sprites</h4> <ul> <li>map in display containing the sprite you want (_map_display seems to map to 0x6000 regardless of whether you've poked the screen or sprites address to somewhere else)</li> <li>draw the sprite as normal with spr() (or sspr() or map() or tline() etc.)</li> </ul> <h4>Example cart</h4> <p>In the example cart there's a simple wrapper function that takes a sprite number from 0 to 1023, maps in the buffer and chooses the sprite for spr() to use.</p> <p>This is used to draw 256 sprites across the whole screen each frame. Looking at the performance with ctrl-P on my system, it costs approx 0.02-0.04 per frame compared to reverting to spr() with no mapping.</p> <h4>Background</h4> <p>I've been storing extra sprite sheets in strings that I decoded into packed tables to dump into memory with a line like <code>poke4(0,unpack(sprites))</code>. This works okay, but took a significant amount of performance each frame. To stay at 60fps with other stuff going on I'd had to restrict changing sprite sheets pretty carefully e.g. twice a frame: background sprites, character/object sprites. Otherwise, I'd tried only mapping portions of the sprite sheet as needed, but it gets fiddly and still hurts performance.</p> <p>I've also played about with the multidisplay functionality (take my Christmas Chaos game on itch for instance). During dev on that I realised that the different display buffers are persistent and accessible whether PICO-8 is actually in multi display mode or not, but it didn't seem very useful at the time.</p> <p>I knew we were going to get to change the address of screen and sprite memory with 0.2.4, but I haven't got round to playing with it until now.</p> <p>Perhaps there's some other fun things to do with this, but I haven't thought of them yet.</p> <p>I hope zep doesn't mind me posting this :)</p> https://www.lexaloffle.com/bbs/?tid=49254 https://www.lexaloffle.com/bbs/?tid=49254 Fri, 09 Sep 2022 23:33:24 UTC Howto Halloween Horrors <p>aka writing down how my cart works before I forget.</p> <p>This is about my game, Halloween Horrors, which is <a href="https://www.lexaloffle.com/bbs/?tid=45181">here</a>.</p> <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/47206/halloween_11.gif" alt="" /> <p>This game was written very much based on previous work, with functions dating all the way back to P8C-BUN, my first game. I started from the <a href="https://www.lexaloffle.com/bbs/?tid=40826">Demystify the Christmas Tree</a> code because I wanted the kittens. It's not very token efficient at all, but I didn't come close to running out of tokens in the end (6447/8192). Originally, I was making another Christmas game - it was only very late on that my gf persuaded me to make it into something for Halloween - thus it's a little bit messy and rushed.</p> <p>The unminified source is at the bottom of this post in a spoiler - it's too big to make into a cart without minification. Even after minifying, I did run out of space: v1.0.1 takes up 99.8% of the compressed cart size(!).</p> <h2>General Notes</h2> <h3>Sprite sheets</h3> <p>There are 3 sprite sheets:</p> <ul> <li>&quot;game&quot; the built-in PICO-8 one which has the kittens, the breakable objects, candles and monsters.</li> <li>&quot;house&quot; the map tiles for the house background</li> <li>&quot;title&quot; the letters for the intro titles</li> </ul> <p>The latter two are encoded in &quot;dbi&quot; format - i.e. the output of my Python image downsampling and compression code (dbi==&quot;drake blue image&quot;; I needed a name of some sort...). This will encode 4 bit PICO-8 images as 1,2 or 3 bit data if few enough colours are used, apply a couple of run length encoding schemes and encode the smallest result as 8 bit unicode for pasting into source code as a string. I wrote it for my attempt at a Dungeon Master clone at the end of last year and tweaked it since then (one day I'll finish that game too). The PICO-8 code for it unpacks this format to lua memory as a table with palette and image data. Once unpacked, the palette for the image can be set by a simple pal call, the data by using poke4(dest,unpack(data)) - very simple and, surprisingly, faster than a memcpy. I tried unpacking to the &quot;upper memory&quot; above 0x8000, but it was slower.</p> <p>I drew the letters sheet using only 8 colours for the Christmas game which lets that image be encoded as 3 bits per pixel. I think I could have used 4 colours (i.e. 2 bit) and it would have looked fine for this game and saved up to a third of the data size.</p> <p>At the end of the project I found that I'd run over the compressed size limit, but also that I hadn't used all of these two extra sprite sheets. I cropped them to the portions I was using, re-encoded and that was enough to get under the bar no code changes necessary :)</p> <h3>Editor</h3> <p>I knew I would use a separate sprite sheet for the house graphics (as opposed to the characters, decorations) so I began by making the house in a separate cart. I started off using PICO-8's sprite and map editor, but I swapped the latter for <a href="https://www.mapeditor.org/">Tiled</a> using the plugin <a href="https://t.co/BOPRJAEpIY?amp=1">here</a>. This worked very well. I use <a href="https://www.aseprite.org/">Aseprite</a> for most sprite work, but for some reason I did all drawing in PICO-8 for this game.</p> <p>For the last game I'd generated the platforms by hand: add some values, run, look at a debug rectangle, repeat. That was okay for one Christmas tree, but didn't appeal for a whole house. I considered having no separate platforms and using impassable tiles, but having experimented a little with drawing the house interior already I was having enough trouble making it look as nice as I wanted without adding any more limitations. Besides, I wanted to see if it would work &quot;off-grid&quot;.<br /> I was already working in a separate cart to the game so I started adding code there for editing where the platforms and walls went. Then I added a simple ball sprite in position 0 (so it wouldn't be drawn by the map) so I could test the platforms. I considered adding the kitten code and encoding their graphics for it, but never got round to it.</p> <p>Eventually I moved more and more into the editor as, even though it is clunky, it's still easier to work with for me than entering numbers by hand.</p> <p>I liked adding the rectfill areas that paint the walls so much that I used that to draw the &quot;paintings&quot; instead of using more sprites. I considered adding a bunch of shadows this way, but ran out of time/patience.<br /> PICO-8 draws rectangles so fast that this didn't seem to dent performance at all.</p> <p>In retrospect, I would have liked to do one of two things:</p> <ol> <li>implement the editor in another environment eg. Love2D</li> </ol> <p>Primarily so I could take advantage of a bigger resolution window and make the interface a bit nicer.<br /> Incidentally, I did make a double display version of the game in PICO-8 very early on so that the two kittens could play split-screen. It worked very well, but I couldn't get the html output to work nicely with the two displays, despite a few attempted hacks (I hate web-programming) so I parked it. Might have another look at that soon especially if someone can help me hack the css to display it nicely.</p> <ol start="2"> <li>make the editor and game one cart</li> </ol> <p>Getting the platform data to save so that it could be used by the editor and the game was a bit annoying. To begin with I used printh to output to console then copied and pasted into the editor and game code separately. Yep, pretty annoying.<br /> I worked out that I could printh to a file and then #include that file from both carts. Problem solved! Except PICO-8 didn't seem to like writing to a lua file that already existed (even though I told it to) and wrote out a .p8l file instead. I'd rename the file back to .lua to update the data. Annoying.<br /> So I wrote a little bash script to run between saves. Less annoying.<br /> It's kinda funny, but I only just worked out I could import the p8l file, instead of .lua and it would Just Work. Ah well, next time...<br /> This has also made me wonder about making user-definable levels for a future game...</p> <p>The big problem with putting the editor in the game would be that the editor functions obviously use tokens. It's another reason (along with test and debug code) that I would appreciate a &quot;dev mode&quot; in PICO-8 that blocked exporting to png, html, bin etc. but would work from .p8 files during dev time. Even if it put in a &quot;dev mode only&quot; banner or &quot;over token limit&quot; that flashes up every ten seconds or something when run like that...</p> <p>Anyway, here's the editor if you're interested, with data baked in so it runs - it will still output, I guess, but I don't think it will update to use the new data. I've not tested it on here much at all and be warned: it's really not intended for use by anyone but me and even I could barely use it.<br /> <table><tr><td> <a href="/bbs/?pid=99459#p"> <img src="/bbs/thumbs/pico8_hh_editor-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=99459#p"> hh_editor</a><br><br> by <a href="/bbs/?uid=47206"> drakeblue</a> <br><br><br> <a href="/bbs/?pid=99459#p"> [Click to Play]</a> </td></tr></table> </p> <p>X shows platforms, walls, floors and highlights current selection for those (and the &quot;paints&quot;).<br /> N shows monsters, candles, dec(orations).<br /> There's a bunch of other obscure key commands.</p> <p>Worst bit: delete (-) has no &quot;are you sure?&quot; and there's no undo.</p> <p>Interface weirdness aside, it's still been a much nicer way of working than typing the code in by hand so I would definitely use this approach again.</p> <h2>Initialisation</h2> <p>I pull in the two &quot;extra&quot; sprite sheets first (&quot;house&quot;, &quot;title&quot;) then store the cart's one (&quot;game&quot;) so I can restore it from lua memory when needed. There's a few palettes for fading to black, lightning and the darker palette that's used for most of the game.</p> <p>Because the game never really restarts and when you finish it the cart is just run again, the state of the music is pulled in from one of the stat values. This is the downside of that approach. The upside is that there's no need to write a reset routine and test it. It's worked okay for other game for me.</p> <p>Writing this post has made me remember that switching the storm off doesn't survive restarting the game so maybe I should fix that if I find space in future.</p> <p>Most of the rest is run of the mill initialising of variables and tables, except for setting up the majority of the entities in the game: the platforms, monsters, items etc.</p> <p>During this project I wrote about how I was processing data stored in strings into tables: see <a href="https://www.lexaloffle.com/bbs/?tid=45114">here</a>. I improved this a few times, but it always relied on feeding to the same constructor function that meant every entity had the same elements: x,y,r,b,c (if I remember correctly).<br /> x and y were fine - position. But r and b (right, bottom), for instance, only made sense for the platforms and meant different things for, um, different things e.g. velocity for the bats.</p> <p>I wanted to change that so I considered different constructor functions and triggering those depending on the data - that might have worked, but they'd have cost a lot of tokens and I would have needed to put those functions in a table (or mess about with _ENV) anyway and it occurred to me that I could implement what's now in the code:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>function unpack_data(raw_data) local raw_data=split(raw_data) local num_elements,data,names,item=raw_data[1],{},{},{} for p=2,num_elements+2 do add(names,raw_data[p]) end for p=num_elements+2,#raw_data do item[names[(p-2)%num_elements+1]]=raw_data[p] if p%num_elements==1 then add(data,item) item={} end end return data end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>This lets me describe and initialise a table of tables (or entities as I think of them) with the element names that I want 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>g_things=unpack_data'5,x,y,vx,vy,col,10,10,0,0,12,20,20,0,0,11'</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 first value is the number of elements for each item, then a list of the element names and then the values themselves; for two items in this case. Add more entities by adding more numbers to the end of the string. It works for string values too of course.<br /> That line costs 5 tokens and each table subsequently costs 4 more if you put them on the same line. You can see that the vx, and vy values burn a lot of characters - there's nothing to stop you adding those values later. I needed those elements to be non-nil so I put them in there. I might try adding them later if I need to extend the game as I'm in the weird position where I've run out of space before tokens. Of course, this string data, and the encoded images, are probably why it's got like that.</p> <p>Having an editor to automatically output the large strings involved in this approach is quite helpful.</p> <p>I guess the next step may be to encode the values as unicode (assuming they fit in a byte or two) to save even more space, but I haven't tried that yet and I don't know how the PICO-8 compression would react; it might not result in much of a saving.</p> <h2>Game modes, _draw and _update</h2> <p>There are three game modes: title, start and play. I re-used a set_fade function to move between these which deals with fading the palette in and out and changing the mode. You can pass it various functions and arguments to execute as soon as it's called, when it's faded to black and when it's faded up. I think. I can't remember how it works, but it seems to do what I want it to... </p> <p>As with most of my carts, in the _update and _draw functions there are a few things that get done every frame, whatever the mode.<br /> _update60: pulse and fr variables that control animation and periodic effects. I've used these since P8C-BUN, they work very well for little PICO-8 games (when trying to write bigger things e.g. using Love2D they don't and they are a pain to remove, but that's a whole different story).</p> <p>Screen shake is updated here as well, inline since there's no need to call it in a function (and some token-saving habits are hard to shake ;) ):</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>-- update_ss() g_ss.x+=g_ss.vx g_ss.y+=g_ss.vy g_ss.r,g_ss.b=g_ss.vx*0.6-g_ss.x/5,g_ss.vy*0.6-g_ss.y/5 if abs(g_ss.vx)+abs(g_ss.vy)&lt;0.1 then g_ss.x,g_ss.y=0,0 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>There's no real reason not to make this separate variables instead of a table (g_ss_s vs g_ss.vx), but I never needed to do it. The conditional at the end of this snippet makes sure that the camera doesn't get &quot;stuck&quot; slightly off centre.<br /> My &quot;cam&quot; function that wraps the PICO=8 camera() native function adds the screen shake values any time I change drawing origin and ensure that everything shakes.<br /> There's a &quot;shake&quot; function that actually kicks it off.</p> <p>Then it calls whatever update function is currently set.</p> <p>_draw()<br /> This resets the palette and camera and calls the current draw function. It also does the lightning - which is the same in every mode.<br /> Randomly, a lightning strike is triggered, which consists of the screen being cleared to white, sfx triggered and countdown set. For each frame the countdown hasn't reached zero, the screen palette is set to a pre-determined set of lighter colours. Very simple, but works quite nicely. Looking at it now, I'm tempted to see if I could make the lightning fade more gradual by having more stages. I could feed the palette through the lighter map more than once, just like how the fade to black works or set up a table with gradually lighter palettes.</p> <p>Most of the time, the palette is darkened to make the lightning more of a contrast and attempt to give the game a slightly more spooky atmosphere. Yellow is the exception to this (the orange is fairly bright too) so I had to be careful where I used that - it's supposed to highlight the candles that the kittens need to find and make the monster's eyes (and the pumpkin) a bit more scary.</p> <p>In the menu, you can switch the storm off and that restores the normal palette and stops the lightning.</p> <h3>Title</h3> <p>As mentioned above, I started with the DtCT code and was writing a Christmas game, so for ages this section was untouched from that. When I embarked on the Halloween project this was the first thing I played with though.</p> <h4>Snow to rain</h4> <p>I altered the snow code to update multiple times per iteration. This makes two things happen: the raindrops appear to fall faster and makes them appear to be elongated. I tweaked the number of drops to make sure that it still ran fast enough and noticed that they were getting &quot;caught&quot; on the letters to the extent that the rain appeared to stop. So I hijacked the &quot;simpler snow&quot; (which also became rain) I'd added to the play game mode to fall constantly in the background and not interact with the letters. Speeding up the letters moving helps to hide this problem too.</p> <h4>Titles</h4> <p>There are some pal calls to make the letters a more spooky colour - I didn't change the image asset at all apart from making a K into an H and the dots on the 'i's into skulls.<br /> For ages it still talked about Christmas after that until I could face updating the title data to be the right words...<br /> For the previous game I'd entered this all by hand with a lot of trial and error, but having used an editor for the rest of the game I couldn't be bothered doing it by hand again. I also couldn't be bothered adding all the code to my editor that I'd need to draw the titles so instead I added it straight into the game mode and commented out the lines that move the titles about. It's pretty crude, but got the job done far faster than doing it by hand.</p> <p>The code is still there, commented out. </p> <p>This mode also uses the screen fade routine with one of the variants described <a href="https://www.lexaloffle.com/bbs/?tid=41149">here</a> for added wibbliness. This is the same effect as used in <a href="https://www.lexaloffle.com/bbs/?tid=42279">PICO Space</a>.</p> <h3>Start</h3> <p>I pulled in the simpler rain code and screen fade for this screen too. Writing text and getting it to fit on the screen takes ages - I really should find a nicer way to edit it e.g. in PICO-8 itself. P8SCII means that the whole description is a single string. I added the control descriptions, ghosts and kitten heads at the end when I knew I wasn't short of tokens.</p> <h3>Play</h3> <p>I implemented this constantly watching out for the moment when performance would drop enough that I'd need to do some optimisation, most likely to draw/update portions of the house at a time, instead of just updating and drawing everything every frame.<br /> I considered various strategies to bucket entities, update on alternating frames etc.</p> <p>The moment never came. The game draws and updates everything, every single frame and only rarely breaks 90% CPU, even now I stopped worrying about it.</p> <p>I was disappointed and relieved at the same time :)</p> <h4>Update</h4> <ul> <li>move enemies: note the pumpkin and the skulls are all considered to be pumpkins. I probably didn't need to do that in the end, but that's the way it is.</li> <li>move kittens and check how they're interacting with platforms, floors etc.<br /> -- platforms are only impassible from above if the player isn't pushing down 'Mario'-style (I think, never played Mario much as I never had a Nintendo as a kid).<br /> -- floors are impassible from above and below<br /> -- walls are impassible from left or right<br /> There are many, many platforms and not many floors or walls</li> <li>check if kittens have hit anything with their bops and react<br /> -- spawns some particles<br /> -- converts decorations to &quot;fallers&quot;<br /> I've just noticed I still have some of the bonus code in there even though it isn't used in this game...</li> </ul> <p>Also, you can see where I hacked the collision routine for the kittens (in_rect) with an unexpected &quot;+8&quot; because for some reason the kittens are drawn 8 pixels lower than you'd expect and I had to add that to make the collisions with the monsters work(!).</p> <h4>Draw</h4> <p>The trickiest bit of this was deciding when to swap between the house and game sprites. In the end it uses something like the following sequence, starting with the house sprites loaded into PICO-8 sprite memory:</p> <ul> <li>draw background</li> <li>load game sprites</li> <li>draw decorations, candles</li> <li>draw monsters</li> <li>draw kittens</li> <li>draw HUD</li> <li>load house sprites</li> <li>draw stairs and parts of doorways in foreground</li> </ul> <p>This means there's only two loads, mid-frame. If you look closely you can see that the scores go behind the stairs, but to fix that I'd need two more loads and it'd never run at 60fps. Maybe for Picotron...</p> <p>When the game ends, I use the title sprites for the &quot;Happy Halloween&quot; message so they need to be loaded in and that pushes the CPU just over the 60fps limit into 30fps too :( It's not too bad - you can still run about the house okay, I think - and I didn't have time to fix it. I'm not sure that I even can.</p> <h2>Conclusion</h2> <p>On the whole it works and people seem to like it. I could improve the code a lot and if I could work out a way to save compressed size and take advantage of the spare tokens I suspect I could add some more - maybe more options for more varied gameplay would be nice. I think I need to work on gameplay and prototyping that earlier and worry less about coding, squeezing in assets and graphics until I have that a bit more &quot;down&quot;.</p> <p>The editor worked really well and I was pleased with the data (un)packing functions.</p> <p>Now I have to decide if I still want to make the Christmas game with the kittens and how I could make it better. I guess I could mahe the house/playing area even bigger. Only the background graphics are restricted by the map grid - just saying...</p> <h2>Unminified Code</h2> <p>Warning: this is pretty messy and not v efficient. I haven't really done any token optimisation on it at all.<br /> If you have any questions about it, please comment and we can puzzle out what on earth it does together :)</p> <p>For reference only...</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;"><br /> -- halloween_horrors<br /> -- by drakeblue</p> <hr /> <p>--returns random integer (vs float from rnd)<br /> function rint(...) return rndr(...)&amp;-1 end</p> <hr /> <p>-- rndr(t,r)==rnd(t)+r<br /> function rndr(t,r)<br /> return rnd(t)+(r and r or 0)<br /> end</p> <hr /> <p>-- generates a random direction<br /> function rnd_dir(m)<br /> local angle,d=rnd(),rnd(m)<br /> return d<em>sin(angle),d</em>cos(angle)<br /> end</p> <hr /> <p>function buttonp(b)<br /> local p=btnp(b)<br /> if b==❎ and p then<br /> sfx(62,3,10,2)<br /> elseif b==🅾️ and p then<br /> sfx(62,3,7,2)<br /> elseif p then<br /> sfx(62,3,11,1)<br /> end<br /> return p<br /> end</p> <hr /> <p>function use_sprites(sprites)<br /> if curr_spr!=sprites then<br /> curr_spr=sprites<br /> local data=sprites.data -- v slightly faster<br /> poke4(0,unpack(data))<br /> end<br /> pal(sprites.pl)<br /> end</p> <hr /> <p>decode={</p> <p>function(rle)<br /> out={}<br /> for i=1,#rle\2 do<br /> for j=0,rle[i<em>2-1] do<br /> add(out,rle[i</em>2])<br /> end<br /> end<br /> return out<br /> end,<br /> -- decodes data where 0s are rle only<br /> function(rle0)<br /> local out,i,len={},1,#rle0<br /> while i&lt;=len do<br /> local data=rle0[i]<br /> if data==0 then<br /> local rep=rle0[i+1]<br /> for j=0,rep do<br /> add(out,0)<br /> end<br /> i+=2<br /> else<br /> add(out,data)<br /> i+=1<br /> end<br /> end<br /> return out<br /> end<br /> }</p> <hr /> <p>-- take list of raw pixel colour values<br /> -- and pack into 4byte chunks for poke4<br /> function pack_sprite_data(data)<br /> local out,val={},0<br /> for m,d in pairs(data) do<br /> val=(val&gt;&gt;&gt;4)|(d&lt;&lt;12)<br /> if m%8==0 then<br /> -- note horrendous swizzle to get pixels in correct order for p8<br /> -- isn't needed below since built into decoding<br /> -- out[(m&gt;&gt;3)-1]=(band(val,0xf0f0.f0f0)&gt;&gt;4) + (band(val,0x0f0f.0f0f)&lt;&lt;4)<br /> add(out,val)<br /> val=0<br /> end<br /> end<br /> return out<br /> end</p> <hr /> <p>expand8bit={<br /> --expands 8bit data already decoded from str<br /> [2]=function(data,ptr,d_len)<br /> local out={}<br /> for i=ptr,#data do<br /> local val=data[i]<br /> for j=3,0,-1 do<br /> add(out,(val&gt;&gt;&gt;(j<em>2))&amp;0x3)<br /> d_len-=1<br /> if d_len==0 then return out end<br /> end<br /> end<br /> return out<br /> end,<br /> --expands 8bit data already decoded from str<br /> [3]=function(data,ptr,d_len)<br /> local out,val,md={},0,(ptr+2)%3<br /> for i=ptr,#data do<br /> val=(val&lt;&lt;8)+(data[i]&gt;&gt;8)<br /> if i%3==md then<br /> for j=0,7 do<br /> add(out,(val&gt;&gt;&gt;((7-j)</em>3-8))&amp;7)<br /> d_len-=1<br /> if(d_len==0) return out<br /> end<br /> end<br /> end<br /> return out<br /> end,<br /> --expands 8bit data already decoded from str<br /> [4]=function(data,ptr,d_len)<br /> local out={}<br /> for i=ptr,#data do<br /> add(out,(data[i]&gt;&gt;&gt;4)&amp;0xf)<br /> add(out,data[i]&amp;0xf)<br /> end<br /> return out<br /> end<br /> }</p> <hr /> <p>function unpack_dbi(dbi_data)<br /> -- read first value to get trans<br /> local trans, raw=ord(sub(dbi_data,1,1)),{}<br /> for r=2,#dbi_data do<br /> raw[r-2]=(ord(sub(dbi_data,r,r))-trans)&amp;0xff<br /> end<br /> local width,height,cols,pl,ptr=raw[0],raw[1],raw[2],{},3<br /> local bpp=(cols-8&gt;0) and 4 or (cols-4&gt;0) and 3 or (cols-2&gt;0) and 2 or 1<br /> -- printh(&quot;w:&quot;..width..&quot; h:&quot;..height..&quot; cols:&quot;..cols)<br /> for i=0,cols-1 do<br /> pl[i]=raw[ptr]<br /> ptr+=1<br /> -- printh(pl[i])<br /> end<br /> local comp=raw[ptr]<br /> ptr+=1<br /> --16bit value for num values to extract from 8bit data<br /> local d_len=(raw[ptr]&lt;&lt;8)+raw[ptr+1]<br /> ptr+=2<br /> -- print(&quot;d_len:&quot;..d_len)<br /> local d_comp = expand8bit<a href="raw,ptr,d_len">bpp</a><br /> -- debug expand 8bit<br /> -- local d_str=''<br /> --for d=1,#d_rle0 do<br /> -- d_str=d_str..d_rle0[d]<br /> --end<br /> --printh(d_str)</p> <p>-- debug rle decode<br /> --local d_raw=decode_rle(d_rle0)<br /> --local d_str=''<br /> --for d=1,#d_raw do<br /> --d_str=d_str..d_raw[d]<br /> --end<br /> --printh(d_str)<br /> local sprites = {data=pack_sprite_data(decode<a href="d_comp">comp</a>),pl=pl,w=width,h=height}<br /> --use_sprites(sprites)<br /> --cls()<br /> --spr(0,0,0,16,16)<br /> --while not btnp(❎) do end<br /> return sprites<br /> end</p> <h2>-- resets the draw palette<br /> -- without touching the screen palette<br /> -- like pal() would</h2> <p>function reset_dr_pal()<br /> poke4(0x5f00,0x0302.0110,0x0706.0504,0x0b0a.0908,0x0f0e.0d0c)<br /> end</p> <hr /> <p>function wait(w)<br /> for i=0,w do flip() end<br /> end</p> <hr /> <p>-- called from fade, try not to call directly<br /> function set_upd_func(new_upd_func)<br /> upd_func=new_upd_func<br /> end</p> <hr /> <p>-- called from fade, try not to call directly<br /> function set_dr_func(new_dr_func)<br /> dr_func=new_dr_func<br /> end</p> <hr /> <p>function set_fade(speed,<br /> down_funcs,down_params,<br /> up_funcs,up_params)<br /> set_upd_func(blank)<br /> set_dr_func(blank)<br /> fd={<br /> val=1,<br /> dir=speed,<br /> down_funcs=down_funcs,<br /> down_params=down_params,<br /> up_funcs=up_funcs,<br /> up_params=up_params<br /> }<br /> end</p> <hr /> <p>function fade()<br /> if fd.val==-10 then<br /> return --not fading<br /> else<br /> if fd.val&lt;=0 then -- end of fade<br /> -- do changes for end of fade<br /> if fd.up_funcs then for i in pairs(fd.up_funcs) do fd.up_funcs<a href="fd.up_params[i]">i</a> end end<br /> fd.val=-10<br /> return<br /> --todo: restore buttonp(❎) or --skip<br /> elseif fd.val&gt;8 then<br /> -- faded to black<br /> -- so do fade down change while invisible<br /> -- and start fade up<br /> if fd.down_funcs then for i in pairs(fd.down_funcs) do fd.down_funcs<a href="fd.down_params[i]">i</a> end end<br /> fd.dir=-fd.dir<br /> fd.val=8 -- make sure won't get hit again<br /> end<br /> -- set the colours (actual fade)<br /> fade_pal(fd.val)<br /> fd.val+=fd.dir<br /> end<br /> end</p> <hr /> <p>-- fades screen palette for light levels (global)<br /> function fade_pal(lvl)<br /> local ad=0x5f10<br /> for c=0,15 do<br /> -- for all colours<br /> for i=1,lvl do<br /> -- for requested levels<br /> poke(ad,scpal_map[@ad])<br /> end<br /> ad+=1<br /> end<br /> end</p> <hr /> <p>-- gets length of printed string<br /> -- takes into account special chars etc<br /> -- doesn't work over multiple lines<br /> -- or with strings that print longer than<br /> -- 256 pixels...<br /> -- assumes y=128 is off screen<br /> -- be careful of camera()<br /> function len_print(s)<br /> print(s..'\0',0,128)<br /> return @0x5f26<br /> end</p> <hr /> <p>-- doesn't use replaced m and w for speed<br /> function print_centre(s,y,c,out)<br /> fun=out and pout or print<br /> fun(s,63-len_print(s)\2,y,c)<br /> end</p> <hr /> <p>function unpack_split(s)<br /> return unpack(split(s))<br /> end</p> <hr /> <p>function pout(s,x,y,c,o) -- 30 tokens, 5.7 seconds<br /> color(o)<br /> ?'-f'..s..'\^g-h'..s..'\^g|f'..s..'\^g|h'..s,x,y<br /> ?s,x,y,c<br /> end</p> <hr /> <p>-- checks if two points are in a rect with sides t and s<br /> -- if t is not passed then treated as square</p> <p>-- note the &quot;-8&quot; for the y axis is an enormous hack to get round<br /> -- whatever the hell you were doing drawing the kittens<br /> -- like you did, nowhere near their origin. This func is only<br /> -- used for kitten collision atm so you can get away with it.</p> <p>-- todo - remove the many adjustments for kitten position<br /> function in_rect(a,b,s,t)<br /> return abs(a.x-b.x)&lt;s and abs(a.y-b.y-8)&lt;(t or s)<br /> end</p> <hr /> <p>--returns random integer (vs float from rnd)<br /> function rint(...) return rndr(...)&amp;-1 end</p> <hr /> <p>-- rndr(t,r)==rnd(t)+r<br /> function rndr(t,r)<br /> return rnd(t)+(r or 0)<br /> end</p> <p>function _update60()<br /> pulse=(pulse+1)%192<br /> fr=pulse\8%3</p> <p>-- update_ss()<br /> g_ss.x+=g_ss.vx<br /> g_ss.y+=g_ss.vy</p> <p>g_ss.r,g_ss.b=g_ss.vx<em>0.6-g_ss.x/5,g_ss.vy</em>0.6-g_ss.y/5<br /> if abs(g_ss.vx)+abs(g_ss.vy)&lt;0.1 then<br /> g_ss.x,g_ss.y=0,0<br /> end<br /> upd_func()</p> <p>end</p> <p>function blank()<br /> end</p> <p>-- screen shake</p> <p>function shake(d)<br /> -- r=vx, b==vy<br /> local x,y=rnd_dir(d)<br /> g_ss.r+=x<br /> g_ss.b+=y<br /> end</p> <p>function cam(x,y)<br /> camera((x or 0)+g_ss.x,(y or 0)+g_ss.y)<br /> end</p> <p>function kit_flinch(k,e)<br /> if k.flinch==0 then<br /> sfx(60,3,rnd{20,24,28},4)<br /> k.flinch=45<br /> k.face=k.x&lt;e.x<br /> k.vx=k.face and -1.5 or 1.5<br /> shake(0.2)<br /> end<br /> end</p> <p>-- has kitten hit something<br /> function kit_hit(k,t,w,h)<br /> return abs(k.y-t.y-8)&lt;(h or 16) and abs(k.x-t.x+(k.face and 18 or -10))&lt;(w or 16)<br /> end</p> <p>function up_play()</p> <p>if not fin then<br /> -- move baddies<br /> for g,ghost in pairs(g_ghosts) do<br /> local newx=ghost.x+ghost.vx<br /> if ghost.x&gt;1000 then<br /> ghost.vx=-1<br /> elseif (ghost.y&lt;128 and ghost.x&lt;260) or ghost.x&lt;128 then<br /> ghost.vx=1<br /> end<br /> ghost.x=ghost.x+ghost.vx<br /> ghost.y+=sin(pulse/192)/7<br /> end</p> <p>for s,spid in pairs(g_spids) do<br /> local smin, smax<br /> if spid.y&lt;128 then -- upstairs spid<br /> smin,smax=16,108<br /> else -- downstairs spid<br /> smin,smax=144,240<br /> end</p> <pre><code>if spid.y&lt;smin then spid.vy=1 spid.y=smin elseif spid.y&gt;smax then spid.vy=-1 spid.y=smax else spid.y+=spid.vy end</code></pre> <p>end</p> <p>-- bats<br /> local bat=g_bats[pulse&amp;15]<br /> if bat and rint(2)&gt;0 and bat.y&gt;20 then<br /> bat.vy=-rint(3,1)<br /> end<br /> for b,bat in pairs(g_bats) do<br /> bat.vy=min(1.5,bat.vy+0.1)<br /> bat.y=mid(2,bat.vy<em>0.8+bat.y,100)<br /> bat.vx=mid(-0.8,bat.vx</em>0.8+rndr(0.2,-0.1),0.8)<br /> bat.x+=bat.vx<br /> if bat.x&lt;260 then bat.x=260 bat.vx=1 end<br /> if bat.x&gt;604 then bat.x=604 bat.vx=-1 end<br /> end<br /> end -- not fin</p> <p>-- pumpkins and skulls<br /> for p,pumpkin in pairs(g_pumpkins) do<br /> local dist,dx,dy,kit,best_dist=32767<br /> if not fin and abs(g_cam.x-pumpkin.hx)&lt;100 and abs(g_cam.y-pumpkin.hy)&lt;100 then<br /> for pl=st_pl,end_pl do<br /> -- find closest kitten<br /> local f=kits[pl]<br /> local this_dx,this_dy=f.x-pumpkin.x,f.y-pumpkin.y<br /> local this_dist=this_dx<em>this_dx+this_dy</em>this_dy<br /> if this_dist&lt;dist then<br /> kit,dist,dx,dy=f,this_dist,this_dx,this_dy<br /> end<br /> end</p> <pre><code>if pumpkin.c&gt;0 then dx,dy=-dx,-dy end pumpkin.c=max(pumpkin.c-1) -- fly at closest kitten dist=sqrt(dist) pumpkin.vx=mid(1,0.2*dx/dist+pumpkin.vx*0.8,-1) pumpkin.vy=mid(1,0.2*dy/dist+pumpkin.vy*0.8,-1) local an=pulse+p*60 pumpkin.x+=pumpkin.vx+cos(an/192) pumpkin.y=min(236,pumpkin.y+pumpkin.vy+sin(an/96)) add(parts,{x=pumpkin.x+pumpkin.w\2,y=pumpkin.y+5,vx=rndr(4,-2),vy=rndr(4,-4),life=rndr(30,30), c=rnd(p&gt;1 and {10,8,7,6} or {9,11})})</code></pre> <p>else<br /> -- send back to home<br /> pumpkin.x+=(pumpkin.hx-pumpkin.x)<em>0.05<br /> pumpkin.y+=(pumpkin.hy-pumpkin.y)</em>0.05<br /> end<br /> end</p> <p>-- for each player's kitten 'f' for frankie<br /> for pl=st_pl,end_pl do<br /> -- get kitten and get 0 or 1 for current player<br /> local f,player=kits[pl],(st_pl==end_pl) and 0 or pl-1</p> <p>-- collisions with platforms<br /> f.gr=false<br /> if f.vy&gt;=0 then<br /> for pl,plat in pairs(g_plats) do<br /> if plat.x&lt;f.x+14 and plat.r&gt;f.x+6 and plat.y&lt;f.y+4 and plat.b&gt;f.y+4 then<br /> if btn(⬇️,player) and f.flinch&lt;30 then<br /> f.gr=false<br /> f.vy=0.2<br /> f.y+=2<br /> else<br /> f.gr=true<br /> f.vy=0<br /> f.y=plat.y-4<br /> end<br /> end<br /> end<br /> end</p> <p>-- collisions with floors (including stairs)<br /> -- can't go through floors from bottom<br /> for pl=#g_floors,1,-1 do<br /> -- for pl=4,1,-1 do<br /> local plat=g_floors[pl]<br /> plat.draw=nil<br /> if plat.x&lt;f.x+14 and plat.r&gt;f.x+6 and plat.y&lt;f.y+4 and plat.b&gt;f.y+2 then<br /> local dl,dr,dt,db=f.x+14-plat.x,plat.r-f.x+6,f.y+4-plat.y,plat.b-f.y+2<br /> if dt&lt;db then -- dt&lt;db<br /> f.gr=true<br /> f.floor=1<br /> f.vy=0<br /> f.y=plat.y-4<br /> plat.draw=1<br /> else<br /> f.vy=0.3<br /> f.y=max(plat.b,f.y)<br /> end<br /> end<br /> end</p> <p>-- special line for stairs<br /> -- 648,232,860,128<br /> local sx,sy,ex,ey=unpack(g_stairs_line)<br /> -- grad=(ey-sy)/(ex-sx)<em>(f.x-sx)+sy<br /> if sx&lt;f.x+14 and ex&gt;f.x+6 then<br /> -- check if under line<br /> -- todo: simplify maths<br /> local ly=(ey-sy)/(ex-sx)</em>(f.x-sx)+sy<br /> -- if 634&lt;f.x and 851&gt;f.x then<br /> -- local ly=-0.49760*(f.x-648)+232<br /> if f.y&lt;ly+4 and f.y&gt;ly then<br /> f.y=ly+5<br /> f.vy=0.3<br /> -- f.above=1<br /> else<br /> -- f.above=0<br /> end<br /> end</p> <p>-- collisions with walls<br /> for pl,plat in pairs(g_walls) do<br /> if plat.y&lt;f.y+4 and plat.b&gt;f.y then<br /> if plat.x&lt;f.x+24 and plat.r&gt;f.x then<br /> f.vx=0<br /> f.vy=max(f.vy)<br /> if f.x-plat.x&lt;plat.r-f.x then<br /> f.x=plat.x-24<br /> else<br /> f.x=plat.r<br /> end<br /> end<br /> end<br /> end</p> <p>-- &quot;input&quot;<br /> -- x<br /> local traction=(f.y&gt;240 or not f.gr) and 0.05 or 0.2</p> <p>if btn(⬅️,player) and f.flinch&lt;30 then<br /> -- run/move left<br /> f.vx=max(-2,f.vx-traction)<br /> f.face=false<br /> f.sit=0<br /> elseif btn(➡️,player) and f.flinch&lt;30 then<br /> -- run/move right<br /> f.vx=min(2,f.vx+traction)<br /> f.face=true<br /> f.sit=0<br /> else<br /> f.vx*=0.9<br /> f.sit=min(100,f.sit+1)<br /> end</p> <p>-- y<br /> if btnp(⬆️,player) or btnp(🅾️,player) and f.flinch&lt;30 then<br /> f.sit=0<br /> if f.gr then<br /> f.vy=-3.49<br /> sfx(62,3,28,4)<br /> else<br /> f.vy+=0.2<br /> end<br /> elseif btnp(⬇️,player) and f.flinch&lt;30 and f.gr and not f.floor then<br /> f.sit=0<br /> else<br /> f.vy=min(3,f.vy+0.2)<br /> end</p> <p>-- hitting<br /> if btnp(❎,player) and not fin and f.flinch&lt;30 and f.hit==0 then<br /> f.hit=20<br /> f.vx=0<br /> f.sit=0<br /> sfx(62,3,rnd{0,3},4)<br /> end</p> <p>f.hit=max(f.hit-1)</p> <p>-- kitten &quot;physics&quot;</p> <p>if st_pl==end_pl then<br /> f.x+=f.vx<br /> f.y+=f.vy<br /> else<br /> -- resolve kittens getting too far from each other<br /> local other=pl==st_pl and kits[end_pl] or kits[st_pl]<br /> local dx=abs(f.x+f.vx-other.x)<br /> local dy=abs(f.y+f.vy-other.y)<br /> if dx&lt;120 then<br /> f.x+=f.vx<br /> end<br /> if dy&gt;120 then<br /> f.x=other.x<br /> f.y=other.y<br /> else<br /> f.y+=f.vy<br /> end<br /> end </p> <p>-- check if kitten has hit anything<br /> local hit</p> <p>if f.hit&gt;0 then<br /> for c,candle in pairs(g_candles) do<br /> if not candle.out then<br /> if kit_hit(f,candle) then<br /> candle.out=1<br /> g_num_candles-=1<br /> f.score+=10<br /> if g_num_candles==0 then<br /> fin=60<br /> end<br /> end<br /> end<br /> end</p> <p>for s,sweet in pairs(g_sweets) do<br /> if not sweet.hit and not sweet.dead and kit_hit(f,sweet,8,8) then<br /> sweet.hit,hit=1,1<br /> if sweet.spr&gt;111 then -- double height<br /> add(g_sweets,{x=sweet.x,y=sweet.y-8,vx=rndr(2,f.face and 0 or -2),vy=rndr(2,-1),spr=sweet.spr+48,hit=1})<br /> end<br /> sweet.spr+=64<br /> sweet.vx,sweet.vy=rndr(2,f.face and 0 or -2),rndr(2,-1)<br /> f.score+=1<br /> g_num_broken+=1<br /> end<br /> end</p> <p>for s,spid in pairs(g_spids) do<br /> if kit_hit(f,spid) then<br /> spid.vy=f.y&gt;spid.y+6 and -2 or 2<br /> spid.y+=spid.vy*3<br /> hit=1</p> <pre><code>end</code></pre> <p>end</p> <p>for g,ghost in pairs(g_ghosts) do<br /> if kit_hit(f,ghost) then<br /> ghost.vx=f.x&gt;ghost.x and -1 or 1<br /> ghost.x+=ghost.vx*5<br /> hit=1<br /> end<br /> end</p> <p>local function bat_hit(f,bat,w)<br /> if kit_hit(f,bat,w) then<br /> -- get direction away from kit<br /> local dx,dy=bat.x-f.x,bat.y-f.y<br /> local n,hit=sqrt(dx<em>dx+dy</em>dy)<em>0.1,1<br /> bat.vx=dx/n<br /> bat.vy=dy/n<br /> bat.x+=bat.vx</em>2<br /> bat.y+=bat.vy*2<br /> return 1<br /> end<br /> end</p> <p>for b,bat in pairs(g_bats) do<br /> hit=bat_hit(f,bat) or hit<br /> end<br /> for p,pumpkin in pairs(g_pumpkins) do<br /> if bat_hit(f,pumpkin,pumpkin.w) then<br /> hit=1<br /> pumpkin.c=40<br /> end<br /> end<br /> end</p> <p>if hit then<br /> sfx(62,2,rnd{12,16},4)<br /> local px=f.face and (f.x+24) or (f.x-7)<br /> for i=0,15 do<br /> add(parts,{x=px+rndr(6,-3),y=f.y+rnd(5),vx=rndr(4,-2),vy=rndr(4,-3),life=rndr(30,30),<br /> c=rnd(15)})<br /> end<br /> end</p> <p>f.bon_cool=max(f.bon_cool-1)<br /> if(f.bon_cool==0)f.bonus=1</p> <p>-- collisions with monsters<br /> if g_end_cool&lt;1 then<br /> if buttonp(❎) then<br /> set_fade(0.2,{run},{mus and 1 or 0})<br /> end<br /> else -- not fin<br /> -- bats<br /> for s,spid in pairs(g_bats) do<br /> if in_rect(f,spid,16) then<br /> kit_flinch(f,spid)<br /> end<br /> end</p> <p>-- ghosts<br /> for g,ghost in pairs(g_ghosts) do<br /> if in_rect(f,ghost,16,12) then<br /> kit_flinch(f,ghost)<br /> end<br /> end</p> <p>-- spids<br /> for s,spid in pairs(g_spids) do<br /> if in_rect(f,spid,16) then<br /> kit_flinch(f,spid)<br /> end<br /> end</p> <p>for p,pumpkin in pairs(g_pumpkins) do<br /> if in_rect(f,pumpkin,pumpkin.w) then<br /> kit_flinch(f,pumpkin)<br /> end<br /> end<br /> end -- if fin<br /> end -- kits</p> <p>-- decorations that are falling<br /> for s,sweet in pairs(g_sweets) do<br /> if sweet.hit then<br /> local hit_floor<br /> for pl,plat in pairs(g_floors) do<br /> if plat.x-4&lt;sweet.x and plat.r+4&gt;sweet.x then<br /> if plat.y-4&lt;sweet.y and plat.b+4&gt;sweet.y then<br /> hit_floor=1<br /> end<br /> end<br /> end</p> <p>if hit_floor then<br /> sweet.vy=-sweet.vy<em>0.5<br /> sfx(62,3,27,1)<br /> if abs(sweet.vy)&lt;0.5 then sweet.dead,sweet.hit=1 end<br /> else<br /> sweet.vy=min(sweet.vy+0.2,4)<br /> end<br /> sweet.vx</em>=0.9<br /> sweet.y+=sweet.vy<br /> sweet.x+=sweet.vx<br /> end<br /> -- removing fallers hasn't been required so far<br /> -- faller.life-=1<br /> -- if faller.life&lt;=0 then<br /> -- -- overwrite with last faller<br /> -- fallers[f]=fallers[#fallers]<br /> -- fallers[#fallers]=nil<br /> -- end<br /> end</p> <p>if fin==1 then<br /> g_end_time=time()-g_time<br /> end</p> <p>-- bonus<br /> -- for b=#bons,1,-1 do<br /> -- local bon=bons[b]<br /> -- bon.life-=1<br /> -- if bon.life==0 then<br /> -- bons[b]=bons[#bons]<br /> -- bons[#bons]=nil<br /> -- else<br /> -- bon.y+=bon.vy<br /> -- end<br /> -- end</p> <p>end</p> <p>--function break_dec(k,baub)<br /> -- -- spawn particles, bonuses and falling dec here<br /> -- local decx,decy=baub.dec.x,baub.dec.y<br /> -- if not fin then<br /> -- k.score+=k.bonus<br /> -- k.bon_cool=60<br /> -- add(bons,{x=decx,y=decy,vy=-0.2,life=30,sp=65+k.bonus})<br /> -- k.bonus=min(k.bonus+1,5)<br /> -- end<br /> -- local faller=add(fallers,{x=decx,y=decy,vx=k.face and rnd(2) or -rnd(2),vy=0,spr=baub.spr+80,live=true})<br /> -- for i=0,20 do<br /> -- add(parts,{x=decx,y=decy,vx=faller.vx+rnd(4)-2,vy=rnd(4)-2,life=rnd(30)+30,<br /> -- c=sget((faller.spr%16)<em>8+2+rint(4),(faller.spr\16)</em>8+2+rint(4))})<br /> -- end<br /> --<br /> --end</p> <p>function _draw()<br /> pal()</p> <p>-- lightning?<br /> dr_func()<br /> if rint(300)==1 and not g_storm then<br /> lightning=rint(20)+5<br /> cls(7)<br /> shake(1)<br /> if not mus then sfx(rnd{61,63},0) end<br /> elseif lightning&gt;0 then<br /> local lightning_cols=g_lightning_cols<br /> for l,col in pairs(lightning_cols) do<br /> pal(l-1,col,1)<br /> end<br /> else<br /> fade()<br /> if not g_storm and fd.val&lt;1 then<br /> local dark_cols=g_dark_cols<br /> for l,col in pairs(dark_cols) do<br /> pal(l-1,col,1)<br /> end<br /> if dr_func~=dr_play then pal(2,2,1) pal(8,8,1) end<br /> end<br /> end<br /> lightning=max(lightning-1)<br /> cam()<br /> -- print(stat(0),0,112,7)<br /> end</p> <p>function dr_start()<br /> scr_fade(pulse)<br /> local wind=sin(pulse/433)*sin(pulse/192)/6<br /> for i=0,2 do<br /> for s,sno in pairs(snowback) do<br /> sno[2]=(sno[2]+1)%128<br /> sno[1]=(sno[1]+wind)%128<br /> pset(sno[1],sno[2],rnd{1,5,6})<br /> end<br /> end</p> <p>palt(15)<br /> palt(0,1)<br /> spr(15,pulse&lt;20 and 1 or 0,119,1,1,pulse&lt;20)<br /> pal(7,6)<br /> spr((pulse\8)%2==1 and 192 or 194,pulse-16,60+sin(pulse/192)<em>6,2,2)<br /> spr((pulse\8)%2==1 and 194 or 192,144-pulse,30+cos(pulse/96)</em>6,2,2,1)<br /> pal(7,7)<br /> pal(5,4)<br /> pal(6,15)<br /> spr(15,pulse&gt;170 and 121 or 120,119,1,1,pulse&gt;170)<br /> pal(5,5)<br /> pal(6,6)</p> <p>print(&quot;the fan-enily have decorated the\nhouse for hallov-eve'en tonight\nbut then gone out for food.\n|jalas, the \faspooky candles \f7they\nbought are genuinely n-enagical\nand have n-enade \f8creepy n-enonsters\f7\nappear nov-ev that it's dark.\n|jput out the candles and banish\nthe n-enonsters.\n|jand if son-ene things get broken\nit serves then-en right for\nleaving you at hon-ene alone!&quot;,3,3,7)</p> <p>rectfill(54,106,54,104,13)<br /> rectfill(55,106,70,106)<br /> rectfill(71,104,71,106)<br /> pout(&quot;hit&quot;,58,104,7)</p> <p>rectfill(36,93,36,96,13)<br /> rectfill(20,92,107,92)<br /> rectfill(91,93,91,96)<br /> line(19,93,16,94)<br /> line(108,93,111,94)<br /> pout(&quot;jun-enp&quot;,52,90,7)</p> <p>pout('p1',1,88,7)<br /> pout('p2',120,88,10)</p> <p>print(&quot;⬅️-m➡️|d-1⬆️-8|m⬇️&quot;,1,98,6)<br /> print(pulse&lt;96 and &quot;\f7+\f6🅾️(z)\f7+\f6❎&quot; or &quot;\f7+\f6🅾️(n)\f7+\f6n-en&quot;,23,98)<br /> --p2 instructions<br /> print(pulse&lt;96 and &quot;⬅️-m➡️|d-1⬆️-8|m⬇️&quot; or &quot;s-sf|d-5e-c|md&quot;,106,98,15)<br /> print(pulse&lt;96 and &quot;\ff❎\f7+\ff🅾️tab\f7+&quot; or &quot;\ffq\f7+\ff🅾️(v-ev)\f7+&quot;,70,98)</p> <p>print('⬅️ \f6play frankie \ffplay philly',1,112,7+fr,1)<br /> print('➡️',120,112,10-fr,1)<br /> print_centre('❎ for a 2 kitten game',122,11+fr, 1)</p> <p>for i=0,3 do<br /> for s=1,#snowfront\2 do<br /> local sno=snowfront[s]<br /> sno[2]=(sno[2]+1)%128<br /> sno[1]=(sno[1]+wind)%128<br /> pset(sno[1],sno[2],rnd{5,13,12})<br /> end<br /> end</p> <p>if buttonp(⬅️) then st_pl=1 end_pl=1 set_fade(0.2,{set_dr_func,music,decorate_house},{dr_play,mus and 22 or -1},{set_upd_func},{up_play}) end<br /> if buttonp(➡️) then st_pl=2 end_pl=2 set_fade(0.2,{set_dr_func,music,decorate_house},{dr_play,mus and 22 or -1},{set_upd_func},{up_play}) end<br /> if buttonp(❎) then st_pl=1 end_pl=2 set_fade(0.2,{set_dr_func,music,decorate_house},{dr_play,mus and 22 or -1},{set_upd_func},{up_play}) end</p> <p>end</p> <h2>function dr_title()<br /> --sc=4<br /> --if btnp(⬅️,0) then if btn(❎) then ls[sc][g_letter][4]-=1 else ls[sc][g_letter][2]-=1 end end<br /> --if btnp(➡️,0) then if btn(❎) then ls[sc][g_letter][4]+=1 else ls[sc][g_letter][2]+=1 end end<br /> --if btnp(⬆️,0) then if btn(❎) then ls[sc][g_letter][5]-=1 else ls[sc][g_letter][3]-=1 end end<br /> --if btnp(⬇️,0) then if btn(❎) then ls[sc][g_letter][5]+=1 else ls[sc][g_letter][3]+=1 end end</h2> <p>--if btnp(⬅️,1) then ls[sc][g_letter][1]-=1 end<br /> --if btnp(➡️,1) then ls[sc][g_letter][1]+=1 end<br /> --if btnp(⬆️,1) then g_letter=max(1,g_letter-1) end<br /> --if btnp(⬇️,1) then if g_letter&lt;#ls[sc] then g_letter+=1 else add(ls[sc],{0,0,0,2,2}) g_letter+=1 end end</p> <p>scr_fade(pulse)<br /> cool-=2.5<br /> if cool&lt;0 then sc=sc%(#ls-1)+1 cool=512 end<br /> pal(title.pl)<br /> local offset=abs(2560/(256-(cool-256)%512))/2 - 5<br /> -- local offset=0</p> <p>local wind=sin(pulse/433)*sin(pulse/192)/6<br /> for i=0,2 do<br /> for s,sno in pairs(snowback) do<br /> sno[2]=(sno[2]+1)%128<br /> sno[1]=(sno[1]+wind)%128<br /> pset(sno[1],sno[2],6)<br /> end<br /> end</p> <p>for lnum=1,#ls[sc] do<br /> pal(1,2)<br /> pal(3,8)<br /> pal(2,1)<br /> pal(4,1)<br /> local l=ls[sc][lnum]<br /> spr(l[1],l[2],l[3]+offset,l[4],l[5])<br /> end</p> <p>for i=0,2 do</p> <p>for p=#snow,1,-1 do<br /> local s=snow[p]<br /> if s.fall then<br /> local olds={x=s.x,y=s.y}<br /> s.x+=wind<br /> local pix=pget(s.x,s.y+1)<br /> if s.y&gt;126 then<br /> -- fallen off bottom<br /> -- pset(olds.x,olds.y,0)<br /> snow[p].x=rnd(128)<br /> snow[p].y=0<br /> elseif pix&gt;0 then<br /> -- hit something<br /> pix=pget(s.x-1,s.y+1)<br /> if pix&gt;0 then<br /> local pix=pget(s.x+1,s.y+1)<br /> if pix&gt;0 then<br /> s.fall=false<br /> else<br /> s.x+=1<br /> snow_fall(s,olds)<br /> end<br /> else<br /> s.x-=1<br /> snow_fall(s,olds)<br /> end<br /> else<br /> -- falling<br /> snow_fall(s,olds)<br /> end<br /> else<br /> -- pset(s.x,s.y,12)<br /> if pget(s.x,s.y+1)&lt;1 then<br /> s.fall=true<br /> end<br /> end<br /> end<br /> end</p> <p>if pulse\32%2==0 then<br /> pout(&quot;press ❎ &quot;,48,122,8,2)<br /> end<br /> if buttonp(❎) then set_fade(0.2,{set_dr_func,use_sprites},{dr_start,game},{set_upd_func},{blank}) end<br /> --if buttonp(🅾️) then<br /> -- local out=''<br /> -- for l in all(ls[sc]) do<br /> -- out..=l[1]..','..l[2]..','..l[3]..','..l[4]..','..l[5]..','<br /> -- end<br /> -- printh(out)<br /> --end<br /> end</p> <p>function snow_fall(s,olds)<br /> -- pal(5,5)<br /> -- pset(olds.x,olds.y,5)<br /> s.y+=(rint(1.4,2))<br /> pset(s.x,s.y,rnd{12,13})<br /> end</p> <p>-- formats a passed time as m:ss<br /> function time_string(t)<br /> local t=flr(t)<br /> return t\60 ..&quot;:&quot;..(t%60&lt;10 and 0 or '')..t%60<br /> end</p> <p>function dr_play()</p> <p>local ball={x=(kits[st_pl].x+kits[end_pl].x+20)\2,<br /> y=(kits[st_pl].y+kits[end_pl].y+20)\2}</p> <p>g_cam.x+=(ball.x-g_cam.x)\4<br /> if ball.x&gt;700 and ball.x&lt;900 then<br /> -- stairwell<br /> g_cam.y=mid(62,g_cam.y+(ball.y-g_cam.y)/28,192)<br /> else<br /> if ball.y&gt;129 then<br /> -- downstairs<br /> g_cam.x=mid(60,g_cam.x,964)<br /> g_cam.y+=mid(-2,(193-g_cam.y)/28,2)<br /> else<br /> -- upstairs<br /> g_cam.x=mid(318,g_cam.x,964)<br /> g_cam.y+=mid(-2,(62-g_cam.y)/28,2)<br /> end<br /> end</p> <p>cam(g_cam.x-64,g_cam.y-64) </p> <p>cls()</p> <p>-- rain<br /> local wind=sin(pulse/192)/8<br /> for i=0,2 do<br /> for _,s in pairs(snowback) do<br /> s[1],s[2]=(s[1]+wind)%128,(s[2]+1)%120<br /> pset(s[1],s[2]+128,13)<br /> end<br /> end</p> <p>-- paint walls<br /> for p,paint in pairs(g_paints) do<br /> rectfill(paint.x,paint.y,paint.r,paint.b,paint.c)<br /> end</p> <p>use_sprites(house)<br /> -- floor sprites<br /> for i=44,124 do<br /> -- upper floor skirting boards<br /> spr(1,i<em>8,112,1,1)<br /> end<br /> for i=33,126 do<br /> -- upper floor carpet<br /> spr(40,i</em>8,120,1,1)<br /> end<br /> for i=91,93 do<br /> -- lower coving<br /> spr(1,i<em>8,136,1,1)<br /> end<br /> for i=104,105 do<br /> -- lower coving<br /> spr(1,i</em>8,136,1,1)<br /> end</p> <p>for i=33,124 do<br /> -- lower skirting boards<br /> spr(1,i<em>8,240,1,1)<br /> end<br /> for i=14,126 do<br /> -- lower floorboards<br /> spr(17,i</em>8,248,1,1)<br /> end</p> <p>-- outlines for floors<br /> -- rectfill(256,125,750,126,6)--1st floor<br /> -- rectfill(840,125,1028,126)--1st floor<br /> -- rectfill(-4,256,1028,264,6)--ground</p> <p>-- background (pretty much everything apart from stairs and doorframes)<br /> map(0,0,0,0,128,32,1)</p> <p>rectfill(256,128,750,128,13)--1st floor shadow<br /> rectfill(840,128,1028,128)--1st floor shadow</p> <p>-- walls<br /> -- rectfill(-4,-4,0,260,6)-- left wall of garden<br /> -- rectfill(252,-4,256,128,6)-- left wall of house<br /> -- rectfill(1024,-4,1028,255,6)--right wall </p> <p>reset_dr_pal()</p> <p>use_sprites(game)</p> <p>-- bonus indicators<br /> for b=1,#bons do<br /> local bon=bons[b]<br /> if bon.life\4%3==1 then<br /> spr(bon.sp,bon.x,bon.y)<br /> end<br /> end</p> <p>-- draw candles<br /> -- draw monsters<br /> palt(15,1)<br /> palt(0,false)</p> <pre><code>-- draw decorations</code></pre> <p>for s,sweet in pairs(g_sweets) do<br /> local sp=sweet.spr<br /> if sp&gt;111 and sp&lt;128 then<br /> spr(sp-16,sweet.x,sweet.y-8)<br /> end<br /> spr(sp,sweet.x,sweet.y)<br /> end</p> <p>for c,candle in pairs(g_candles) do<br /> if candle.out then<br /> pal(10,9)<br /> pal(9,8)<br /> end<br /> if not candle.out then<br /> spr(236,candle.x,candle.y,2,2)<br /> local p=(pulse+c*7)\4<br /> spr(46+p%2,candle.x+5,candle.y-4,1,1,fr%2==0)<br /> else<br /> spr(234,candle.x,candle.y,2,2)<br /> pal(10,10)<br /> pal(9,9)<br /> end<br /> end</p> <p>local dim=1<br /> if fin then<br /> dim=flr(fin/60)<br /> end</p> <p>for s,spid in pairs(g_spids) do<br /> local weby=spid.y&lt;120 and 16 or 144<br /> spr(202,spid.x-8,spid.y&lt;120 and 16 or 144,4,2,s&amp;1==0)-- web<br /> rectfill(spid.x+7,weby+4,spid.x+7,spid.y+1,7)--thread<br /> spr(230+(fin and 0 or 2<em>(pulse\4%8&lt;3 and 1 or 0)),spid.x,fin and (spid.y-weby)</em>dim+weby or spid.y,2,2)-- spid<br /> if not fin and rint(30)==0 then<br /> add(parts,{x=spid.x+(rint(2)==0 and 6 or 9),y=spid.y+10,vx=0,vy=1,life=spid.y&lt;128 and (128-spid.y)\3.5 or (250-spid.y)\3.5,c=11})<br /> end<br /> --pout(s,spid.x,spid.y,11)<br /> end<br /> for g,ghost in pairs(g_ghosts) do<br /> spr(fin and 192 or 192+2<em>(pulse\8%2),ghost.x,ghost.y,2,2,ghost.vx&lt;0)<br /> --rect(ghost.x,ghost.y,ghost.x+16,ghost.y+12,9)<br /> --pout(g,ghost.x,ghost.y,7)<br /> end<br /> for b,bat in pairs(g_bats) do<br /> local s=224+2</em>((pulse\8+b)%4)<br /> if s==230 then s=226 end<br /> spr(fin and 226 or s,bat.x,bat.y,2,2)<br /> --pout(b,bat.x,bat.y,8)<br /> if not fin and rint(30)==0 then<br /> add(parts,{x=bat.x+(rint(2)==0 and 6 or 9),y=bat.y+10,vx=0,vy=1,life=bat.y&lt;128 and (128-bat.y)\3.5 or (250-spid.y)\3.5,c=8})<br /> end<br /> end</p> <p>-- pumpkins and skulls<br /> for p,pumpkin in pairs(g_pumpkins) do<br /> spr((fin or fr%3~=1) and pumpkin.sp1 or pumpkin.sp2,pumpkin.x,pumpkin.y,pumpkin.w\8,2)<br /> end</p> <p>-- end draw monsters</p> <p>if not fin or fin&gt;0 then<br /> -- statuses<br /> cam()</p> <p>-- candle for status<br /> spr(236,52,11,2,2)<br /> spr(46+fr%2,57,7,1,1,(pulse\4)%2==0)<br /> local prog=100-flr((#g_decs-g_num_broken)/#g_decs*100)<br /> if prog&lt;100 then<br /> if prog&lt;10 then<br /> prog=' '..prog<br /> else<br /> prog=' '..prog<br /> end<br /> end<br /> pout((g_num_candles&lt;10 and ' ' or '') .. g_num_candles..&quot; &quot;..prog..&quot;%&quot;,47,15,fr%2==0 and 10 or 11)<br /> -- show time elapsed<br /> local t=time()-g_time<br /> print_centre(time_string(t),2,10,7)<br /> for pl=st_pl,end_pl do<br /> local f=kits[pl]<br /> if pl==2 then<br /> pal(6,15)<br /> pal(5,4)<br /> end</p> <p>-- kitten heads<br /> cam(pl==1 and -1 or -118)<br /> spr(15)<br /> if pulse\8==pl*5 then<br /> -- shifty eyes<br /> pset(2,4,3)<br /> pset(5,4)<br /> pset(3,4,1)<br /> pset(6,4)<br /> end<br /> rectfill(8,1,8,4,1)<br /> cam()<br /> pout(f.name,pl==1 and 12 or 93,2,6)<br /> pout(&quot;score &quot;..(f.score\100)..((f.score%100)\10)..(f.score%10),pl==1 and 2 or 91,10,f.bon_cool&gt;1 and fr!=1 and 7 or 6)</p> <p>-- local str<br /> -- if f.above==1 then str='above' else str='below' end<br /> -- print(str,0,100,8)<br /> -- circ(f.x,grad,2,10)<br /> end -- draw kittens (for pl)</p> <p>pal(5,5)<br /> pal(6,6)<br /> cam(g_cam.x-64,g_cam.y-64)<br /> end</p> <p>-- draw kittens<br /> for pl=st_pl,end_pl do<br /> if pl==2 then<br /> -- alt colours for philly<br /> pal(5,4)<br /> pal(6,15)<br /> end</p> <p>local f=kits[pl]<br /> if f.flinch&gt;0 then<br /> f.flinch-=1<br /> if fr!=0 then spr(38,f.x-(f.face and -4 or 8),f.y-10,3,2,f.face) end<br /> elseif f.hit&gt;0 then<br /> spr(f.hit&gt;12 and 38 or 41,f.x-(f.face and -4 or 8),f.y-10,3,2,f.face)<br /> elseif f.gr then<br /> if abs(f.vx)&lt;0.1 and f.sit&gt;10 then<br /> -- sit<br /> spr(9+(((pulse&lt;48 and pl==1)or(pulse&gt;144 and pl==2)) and fr<em>2 or 0),f.x+(f.face and 3 or 0),f.y-8,2,2,f.face)<br /> else<br /> -- run<br /> spr(fr</em>3,f.x,f.y-10,3,2,f.face)<br /> end<br /> else<br /> if f.vy&lt;0 then<br /> -- jump<br /> spr(32,f.x,f.y-10,3,2,f.face)<br /> else<br /> -- fall<br /> spr(35,f.x,f.y-12,3,2,f.face)<br /> end<br /> end<br /> --pset(f.x,f.y,10)<br /> end</p> <p>-- dark blue-&gt;black for outlines<br /> -- pal(1,0)</p> <p>-- put palette back<br /> pal(5,5)<br /> pal(6,6)</p> <p>palt(0,1)<br /> palt(15,false)</p> <p>for p=#parts,1,-1 do<br /> part=parts[p]<br /> part.life-=1<br /> if part.life&lt;0 then<br /> parts[p]=parts[#parts]<br /> parts[#parts]=nil<br /> end<br /> part.x+=part.vx<br /> part.y+=part.vy<br /> part.vy+=.2<br /> pset(part.x,part.y,part.c)<br /> end</p> <p>-- draw stairs<br /> use_sprites(house)</p> <p>-- foreground map items<br /> -- mostly stairs and doorframes<br /> map(0,0,0,0,128,32,2)</p> <p>reset_dr<em>pal()<br /> -- snow<br /> for i=0,3 do<br /> -- local ymax=120+rint(10)<br /> for </em>,s in pairs(snowfront) do<br /> s[1],s[2]=(s[1]+wind)%112,(s[2]+1)%128<br /> pset(s[1],s[2]+128,rnd{12,13})<br /> end<br /> end</p> <p>-- show progress in destroying decorations<br /> cam()</p> <p>if fin==0 then<br /> if g_end_cool&lt;1 then<br /> rectfill(0,118,127,124,2)<br /> print_centre(&quot; press ❎ to play again&quot;,119,8+fr)<br /> end</p> <p>-- show scores<br /> local str=''<br /> if st_pl==end_pl and st_pl==1 then<br /> str=&quot;frankie scores &quot;..kits[1].score..&quot;!&quot;<br /> elseif st_pl==end_pl and st_pl==2 then<br /> str=&quot;philly scores &quot;..kits[2].score..&quot;!&quot;<br /> elseif kits[1].score&gt;kits[2].score then<br /> str=&quot;frankie v-evins: &quot;..kits[1].score..&quot; to &quot;..kits[2].score..&quot;!&quot;<br /> elseif kits[2].score&gt;kits[1].score then<br /> str=&quot;philly v-evins: &quot;..kits[2].score..&quot; to &quot;..kits[1].score..&quot;!&quot;<br /> else<br /> str=&quot;both kittens drav-ev v-evith: &quot;..kits[1].score..&quot;!&quot;<br /> end<br /> rectfill(0,8,127,14,9)<br /> print_centre(str,9,10+fr)</p> <p>-- show time<br /> rectfill(0,16,127,22,11)<br /> print_centre(&quot;your tin-ene: &quot;..time_string(g_end_time),17,13+fr)</p> <p>-- happy halloween<br /> use_sprites(title)</p> <p>pal(drpal_map)<br /> for l in all(ls[#ls]) do<br /> spr(l[1],l[2]+3,l[3]+3,l[4],l[5])<br /> end<br /> pal(title.pl)<br /> for l,letter in pairs(ls[#ls]) do<br /> pal(1,g_title_cols[(l+pulse\16)%3+1])<br /> spr(letter[1],letter[2],letter[3],letter[4],letter[5])<br /> end</p> <p>g_end_cool=max(g_end_cool-1)</p> <p>elseif fin then<br /> fin-=1<br /> end -- if fin</p> <p>-- line(g_stairs_line[1],g_stairs_line[2],g_stairs_line[3],g_stairs_line[4],8)<br /> cam()<br /> -- print(grad,0,108,8)</p> <p>end -- dr_play</p> <hr /> <p>-- fades a quarter of the screen at a time<br /> -- scan line by scan line, left pixel then right pixel byte by byte<br /> -- which line, side of pair is dictated by p<br /> function scr_fade(p)<br /> -- local tables seem to be faster.<br /> -- change start line based on oddness value<br /> local d,m=0x6000+p%2*64,p&amp;2==0 and g80 or g81</p> <p>-- for half of the 128 lines on the screen<br /> for j=0,0x1f80,128 do<br /> -- for every 4bytes of this line<br /> for a=d+j,j+d+60,4 do</p> <p>-- map every pair of pixels to a mapped pair<br /> -- 4 bytes at a time<br /> -- shorter and quicker<br /> poke(a,m[@a],m[@(a+1)],m[@(a+2)],m[@(a+3)])<br /> end<br /> end<br /> end</p> <hr /> <p>-- fills house with decorations for kittens to destroy<br /> -- also init function for game<br /> function decorate_house()<br /> -- dump g_sweets into dec locations for the moment<br /> g_sweets={}<br /> for d,dec in pairs(g_decs) do<br /> local sweet=add(g_sweets,{x=dec.x,y=dec.y,vx=0,vy=0,spr=64+rint(48)})<br /> if sweet.spr&gt;95 then sweet.spr+=16 end<br /> end</p> <p>g_time=time()<br /> end</p> <p>-- new unpack function<br /> function unpack_data(raw_data)<br /> local raw_data=split(raw_data)<br /> local num_elements,data,names,item=raw_data[1],{},{},{}<br /> for p=2,num_elements+2 do<br /> add(names,raw_data[p])<br /> end<br /> for p=num_elements+2,#raw_data do<br /> item[names[(p-2)%num_elements+1]]=raw_data[p]<br /> if p%num_elements==1 then<br /> add(data,item)<br /> item={}<br /> end<br /> end<br /> return data<br /> end</p> <hr /> <p>-- init</p> <p>house_dbi=&quot;し%%ふしたとてつそせぬのはなねひちすにすっAふすほすふ「▥▥%そたそたそたし&yen;わすつEてちせやゆにてちわゆし ンや▮としすりなせつし)のこリ)%そたそたそたしゅてちてむわすつわ◀てちせやゆにてちせふゆに セやふ▮とすねまねなせつ「たのこリ)%ヘたしちてそもちてちわすつわ&bull;てちせやゆにてちせやゆに みやコ▮とせねそりなせつ「たのこリ)Eみわちそチむわすつレたのわてちせやゆにてちせやゆに やレ▮とすなりゆせは「たのこリ\tやEそたわちもちてちてちわすつレたのわてちせやゆにてちせやゆに ‖▮とつせウせすは「たのこリホやeそたわちムむわすつレたはわてちせやゆにてちせやゆに!‖!しつんすはし「たのこリゃやコ「わそたわむアちてちサつレとはわ◀てちせやゆに「‖「ふはつれふ)のこリたやレ」わそたわ&yen;ミE&bull;てちせやゆに」‖」‖ヨたりたのこリ)レまわそたレたのレすつレすつをこ#ちつなす゛すつはの‖カみねたねたのこリ)コまみわそたレたのレすつレすつをれうちてむれのつへ゛すつはの‖ねみニみのこリ)ふまみハそたレたはレすつレすつをはもちのちのちのちのちのちのちのちのちのむはのつすなす◜へつはの◀た■たのこリ)まゃレそたレとはレすつレすつをはてちのちのちのちのちのちのちのちのちのちのちはのつへモすなへつれ&bull;ねた&sup1;たのこリIしそたレそたuすつレすつわはてのちのちのちのちのちのちのちのちのちのむれつへテすなへつすれ‖りたヨたのこリ)わそたレそたuすつレすつわはむのちのちのちのちのちのちのちのちのちのちれつ&amp;つすつれ‖カたニたのこリ)わそたレそたuヒわすつわはてのちのちのちのちのちのちのちのちのちのむれめすつすつすャれ‖ニたカたのこリ)わそたレそたuすタわすつわはむのちのちのちのちのちのちのちのちのちのちはャはつはつはつはめは%すつすつすつしたのこリゃしそたuそたわねな&sup1;Vつわはちのちのちのちのちのちのちのちのちのちのむはˇ%すつすつすつしたのこリたわそたuそたわ!すめへめヒつわはむのちのちのちのちのちのちのちのちのちのちはすuす%ヒつし▤「わそたuそたわ&sup1;なねすつサつヒつわはちのちのちのちのちのちのちのちのちのちのむはにすしてちつハてちつにすEめわ▥」わそたuそたわ!VつわはむのちのちのちのちのちのちのちのちのちのちはへつてちつヒてちつへEすつわ▤「わそた%まわそたわ!EすつわはちのちのちのちのちのちのちのちのちのちのむはへちてちャてちつへEすつわ▤「わそた⁵まみカそたりなりなヨEすつわはむのちのちのちのちのちのちのちのちのちのちはすつちてむはわはてちめ&amp;わすつわえ。わそたハまみふ「!Eすつわはちのちのちのちのちのちのちのちのちのちのむはつはちてちるはしはのてむは+わすつˇuそたわまゃわ」ニなカEすつわはむのちのちのちのちのちのちのちのちのちのちはしはちもちヌちもちはし▤Hたスた⁵スたヲた「たHサスレへわすつEと▮はむもちるはむもちる▤Hたスた⁵スたヲた「たHすそにすスコへめわすつEと▮るもゅはのちもゅは▤Hたスた⁵スたヲた「たH◀ふへめハすつEと▮はむもちるゅもちるっ▥みヲン⁵ホヲた「たHすそよまにをょレすつEと▮るもゅはのちもゅはった▤たHた⁵Hた「たHすよをにすめしすつレすつ%へと▮はむもちるしのちものはしった▤たHた⁵Hた「たHサますそわすつレすつ⁵へめ!るもゅはふのちのはふった▤たHた⁵Hた「たHすよすスわすつレすつハへめふ「はむもちる‖った▤たHた⁵Hた「たHサスわすつレすつわへょわ」るもゅは‖ と█-ぬたぬたぬたぬたふツˇコツハたとハとたとのコたえとたのふのソのれ」 と█-たぬたぬたぬたぬふとそたのˇコとそたのハたとコみとるわたえイたのしのちもちるはヘゃ と█-ぬたぬたぬたぬたふとたとのˇコとたとのハたとわたとネのふ▥ホとしのちもむのはヘゃ と█-たぬたぬたぬたぬふるれˇコるれハたとわとれやのはのしたとけるたとしのちもちるはヘゃ と█-ぬたぬたぬたぬたわたとˇレたとハツふとはとキれしたとのはのはるはるはのはるはキたとしのちもむのはヘゃ と█-たぬたぬたぬたぬわたとIわ)とわみとそたのみしはキれふたとるはるはのはるはるはのはるたとしのちもちるはヘゃ と█-ぬたぬたぬたぬたわた]わた\rたとわイたとのやふはのれわたとけるたとしむもむるヘゃけ\&quot;。たぬたぬたぬたぬわたとuたとレたとハるれEたとけるたとしのちもゅのヘゃわすつ&and;ロつすわサつをわたとˇレたとレたとEみˇEをヘゃわすつロやvつすわサつをわたとˇレたとレたとEセˇレをょヘゃわすつヒとそたとfつすわサつをわたとっ」まセはっのたとレたとEンˇふへタれヘゃわすつヒのたとのつVつすわサつをわたと▤たはたまのたとレたとE」ˇょリヘゃわすつヒつるめVつすわサつをわたとた☉たはたそのはたとレたとE」‖みレ#ヘゃわすつロょfつすわサつをわたとた☉たはたそのはたとレたとわホハン‖セコ#ヘゃわすつ&and;ロつすわサつをわたとたxみのたそのはたとレたとわメ⁵セヘたふンふ#ヘゃわすい&bull;すわ&bull;わたとたxみのたそのはたとレたとレたと%みヘI#ヘゃ5にひにE⁸たコたとハたと▤たとたそのはたとわよそるトヌにへまuヘゃふラはヘゃレイつすにすつはとのはレ⁸たコたとハたと▤たのたそのはたとわにのそのトヌよすはみまUヘゃしのちてむのはヘゃハとまやょはやぬのはハったったコたとハたと☉みのたそのはたとわにのそトヌエすはまみま5ヘゃのちもゅのヘゃハとそぬとのとそとのとぬたとのハたまたそたそたわたやのコたとたxみのたそのはたとわるひエヌトれスみま‖ヘゃしのもちるはヘゃコとぬたぬるそたぬとのとらのはコまみったとたとたとたとたわたとセまホそホはみのはたとわるひよヌトのれヲみまレヘゃ□はヘゃわのとたぬとのぬたらやのとぬるはわ⁸た。わたとぬ▥はゃのたとわのにひにヌトるはす「みまコヘゃふヌれヘゃコのやるとらやるやのはコ⁸た◀わたときぬはオのたとわのにひヌトキはす8みまふヘみ⁵はしヘゃハひのはにのイのへるにハ」&bull;わたけ□たとわよてキトヌへXみ⁸Eヘゃコケにひ◝すつエˇコをハにヌトヌエ&bull;ふキエすヌすつしみXみヘみし&yen;Uそろにルエすエˇコょをふスノヌト%るそノスひにすつふみ▤そわ\nのEそろよろそケよすにひよˇわれタへキトヌトの%のそひエヌエすつコみ8‖マるEノよひそルにすにろよˇふリょるトヌトる‖るひエヌトれレみ「‖ゅるEろそろよケそノよすにひエひわたとたとたとわとたとし#のトヌトキ‖るひよヌトのれ‖みヲ‖ちるハとたとたとたとたしノよろそ⁴にすにひそろわ。ふイし#トヌトヌ‖のにひにヌトるはす5みス‖の⁵。わひエひそひそひそケにすにろわ&and;#エヌトヌに‖のにひヌトキはすUみまˇ◀レ4エレい#よヌトヌよ‖よそキトヌへuみˇ&bull;u▤▤まお‖つな⁶なサなつわたとわスサつすつへはへˇUまみxセxNねテすなをなへちつ^つのわたとわスすそにへつすのすはつすˇ5まみxみスみXお゛のつなねなりゆねウすつのわたとわ&amp;るすつはすのˇ‖まみxみ「み8おなカなりなちつNすつのわたとわすまよそにす#「uまみxみXみ「ウすおテのつ&gt;へつのわたとわすにをよすつのすのすつすは⁸ぬUまみxみ▤みヲ◀モす&gt;ちつす◜すゆすめのふとたとのふそすまロめへつはぬそぬそぬそぬそ5まみxみ▤スみスすつすつすつすつ&gt;すモのつすゆす◜すつはのとスみのスすよへはのへのすつ 8みxみ▤「みま&bull;おちつす.すつはのとそホのスサsしれ9XみˇぬとにたとてちなEたとレたとEのちつすモをつはのちˇたのこリたXみˇふぬとにたとてちな⁵なわたとレたとEちのつすᵉすつはるˇたのこリた8みˇコぬとにたとてちなわたとふなわたとレたとEのちつすなすゆねゆすつはのちˇたのこリた「みˇレぬとにたとてちなわたとてちなわたとレたとEちのつすᵉすつはるやUやたのこリたヲみˇ‖ぬとにたとてちなぬとしたとてちなわたとレたとEのちつす◜すめはのちそたしてちのハてちのそみのこリたスみˇ5ぬとにたとてちなせつしたとてちなわ)とEちのつすテすなすめはるやのてちのホてちのやたのこリたまみUサつレつVぬとにたとてちなわ=Eのちつすᵉすつはのちるちてちのメてちキ▥9uミレkぬとにたとてちなˇ‖ちのつすᵉすつはるれちてむはわはてちのれ▥」ふのとのはふ「ˇˇˇわきぬのレくねのˇハとたとのふ「ˇˇˇわけるレけるˇコやたとるし「ˇˇˇわきぬのレくねのˇわイたとキ」ˇˇˇわ▮キ▮のレ■キ■のˇわイたとキ「ˇˇˇわ の のレ!の!のˇわイたとキそンそˇˇˇわきぬのレくねのˇわ(たスたそˇˇˇ&and;&and;&and;ˇ」そンそˇˇˇいいいˇˇˇˇˇˇヒめれˇˇˇˇˇˇˇ⁵のへつすょはˇˇˇˇˇˇˇ⁵ロつれˇˇˇˇˇˇˇ⁵ロめれ%タれˇˇˇˇˇˇ⁵つへつすつへ[へネˇˇˇˇˇˇレサの&and;すつクˇˇˇˇˇˇレロつヒのへつをのつすれつはˇˇˇˇˇˇレへのヒのfつクˇˇˇˇeつサね⁶ミはレ6つすつすつのつすつすめネˇˇˇˇeつfタはレすつへのサのすᵇすめすネˇˇˇˇeつ6めのタれハ◀ょのはつれつはつはめはつれˇˇˇˇeつをのをねすのすャれハすのヒめCしリˇˇˇˇuめFつすつすクハヒめのはつネˇˇˇˇˇ%つロねサタすはつはハヒつすリˇˇˇˇˇEつ&amp;つへタすはレすつへめれのれˇˇˇˇˇUつをね⁶つすミレサょクˇˇuゅれるゅるソˇ✽つすのサのをつすミはレへつすめれつはˇˇuちてキはてちのむるゅˇ✽めFミはレサょクˇˇuてのむはゅのむのはのむˇ✽つヒねヒつすタはレをつすタはˇˇuむのはのてちてちのむのはのちˇ✽つ&amp;つへミはレすつへタれˇˇuてむはマのゅのはのわむˇ5つすつ6のつすめはレサのつすめはˇˇuむるてマのゅれふちてのはˇ%つヒのロミはレサミはˇˇuもちるもちのむのゅのしちてちるはˇ‖つ6つへタはレへつへタはˇˇuるリてはるクのはしてゅのはˇ⁵めをねサねをょのつれハねをょのつれˇˇuもゅるてむのはるちヌむのはˇレつへの6ミれˇˇˇuるてむるちてちるはてむのはるむのはˇハつヒの◀タれˇˇˇeちてゅのはちてゅはゅのちのはのむのはˇハつすつ◀つへのタれˇˇˇeゅのちるてむのはのてちてちるれのむのはˇわめ⁶ねヒミれˇˇˇUちてゅるてちてむは\nのはのゅのはˇふつすつサねヒつすミれˇˇˇUむのちるてちてむるてゅのむのれのゅれˇし&and;つすょれˇˇˇ5てゅるアちもちるもむのむのれのゅのは✽つすね&amp;のをャれˇˇˇ‖てソのれキてるリてのれキ3eヒつFミれˇˇˇ%もちのちのちてソるてちキむるソるはˇつをの◀つをミのれˇˇˇ%キむるゅるはてむのはるむるソるは✽つへねめすのね6タクˇˇˇ⁵ちてゅのはてちてむのゅのちのはのむのはのソる✽サめVのへょれˇˇˇ⁵ゅのちるゅキてちてむのれのむのはのソのはeサつのつヒねサのは⁶れˇˇˇハちてゅるてちてむは\nるはゅのはるゅのはEつのつねミへょヒつれしめへつれˇˇˇコむのちるむてむるてソのちるはのゅクるむのれ‖つのすょネょはタへつすつクへつすつはˇˇˇふてゅるてちてむてちるもマるはのゅのクるむる⁵をねsャネすょはˇˇˇてソのれるゅるれるてちこリレサネふSめすふクめすˇˇˇ&quot;<br /> once_dbi=&quot;こ#⁙ちことしてすせさしろ⌂よ⁘lん5ミ⬇️5ムよわムを#ムろ5フん5ムを#ムん‖ムよ⁘gょY|を5~⁷⁙て▮Uケ⁸は|ろ9ル▮UホっYミoEタ●+ルに4,⁴Tl⁴4;そひてぬ#ルぬ5ろエゃミhひjよハ+⁷-ルよハ+⁸1さフハヘょ+lつEをんレ+●1ろフコヘょ1ろフわjせEろわ7ヘんわ+G-ルよ⁘gょ)ミHひgょ%ャ░ほフんレ+G'ルほ4メを1タ(はlほEタ(はなよコ+'-ルほ3-わ5アフ⁘jはEもわ7マはEャ✽ほフんレ+G'ルほ4メを1タ(はlほEタ(みタHひてほEアろほヘんレ+●1ろフコヘょ1ろフ⁘-わふアフレハょ-ミHひjよハ+⁷#ヒ⁷3ko5もよし+'#ヒょ+モて5ろエレ+●1ろフコムひ5ノん」ムせ4cょ)ムんコ+⁷Tもろふiょ4jよセミᶠ9ウん5jさ5ム⁶#lん5ュ⁴7ムんT&lt;ん9テん1タ✽セを⁶Y~よゃ~⁶'~▮YすはE~ᶠ⁙ゆ▮1なᶠ⁘jよ⁘jよ⁘jはEタ●1タ●1タ●1タ●+ルよ⁘jよ⁘eん4gん5てん5ミ⬇️5ミ⁷⁘-を'ムん5ムん1タ●%mを)◜オ3もヘYk&hearts;Ykフ1アフ⁙&lt;ヘYル▮5タ●1てフ⁙kんふてフセケフゃひフ⁘とを)ルつEひを1タ⬇️7マに#ュこ%ルよコ+hひeれほマにEひフわマよ⁘dょ1ゆエたャっひfょ/ルこ1さっひfょ'ルつ4jよ⁙メを+kそ'ルよコ+G5ャ⬇️コモよコ+ヘは,よ⁘jせEタ(9ハょ1ろん3モんTe▮1ろフわ+フ1タ●%ルよホすさ'ルよン|れクとを1タ⁸は-ろふタ●1てフ⁘gょ1タせ'ルよ⁘fょ'ルつ4jよ⁙メを1ろフ⁘cんわ+●1もフわ+フ1タ●%ルよ⁘とを)ヒんわ+●1もフしさフしされひjよ‖+●+ルよコハんわ+●1もフ3とん#ムを1タ☉ひjひ5タᶠ5モつEタ●)lエたム&sup3;コャ●1チフ⁘hᶠ⁘ゆ▮)ルよ⁘g▮'~てTjよ⁙とを1タ●-ルよ⁘jよ⁘jよコ+●1タ✽7ハを1タ●1もん5ムん1ルん5ホん5タん3,を#lせ4jは1タ✽ふ5アほ4をほ◜▮5ろ▮YマこEへん⁙ュんTjは#マよ⁙ムフ5ムんeヘょ+ミ/ハマょ)ミ░セ{●-ネ⁶1タせE▮Y~ム'+ルは4eん‖+⁷1タ&hearts;3つせ'ムつ#ミ●'メゃ2ム')ルは4eん‖+⁷1タhUすオ3な⁷しすオ4jこ7ユよやヤエわ+'/ムん1ルに4jめ%ノれ3つんしてせ1タそふjに5ャヘひてほ3,よEもを1ケっ3つんたモんふてよ⁙てっ1も'S-わふろフわマょ)ミ●/~さ#d&sup3;Yす&sup3;Sャ●#メエ⁙o('ルは4とろふチフコホん3lん⁙lん5ムん5マよGャ✽めャヘひてはEひを7ヒん\tルぬ7マに7レヘほ4フ1ケフThっ'ルは3つヘは,こ-ルに4mわほマね5ムん5ムク4hん9タLリ-わふミヘは,んレ+フ4mわほマふ7ユムん4ク4gっみタ░Uフ⁷S&lt;ん5モま5ムん1ルはEタG5c{てこgeマにGャ●+{P+~▮Yテ▮Yミ☉ひとを-メキ&sup3;んす&sup3;て'1ひフTjよ⁘jよふマょ+ルよレ4&bull;てこ{てて(1てんTjよ⁘jよふマょ+ルよレ,&sup3;ミたcミさんTdっみタ░ふムよハム⁵ふタせ1ルはEタG5&hearts;す&sup3;んすめモよふ4⁶1ひフ5タフeャ⁷Uチを7フょ1アょ[たcミたiハjせEャ●'ムク4cんkャヘはめ&hearts;1ルはEタHわc{てこ{し⁴⁶%ムエ⁘ム&sup3;5,'/メゃみひフわアを7フょ1アフ`#3ぬ#'5jせ7ャ✽めャっわヤんハルん9ひフわヒん1ルはEタG5⧗ふさSは;モよふ4⁶)⁷エふ,Geハっふ⁴エわ+ヤ5ムエ‖+(ひhっみせ#ᶠせゃへマつEャ░5モせ5\0ゃふさょ=⁷エわ+▮Y~よし+(ひhアふムん5ムシ⁙lっ1も'Sムヘ$ム'7ユこ;ャヘひjにEろフ⁘メっほ4ヘんᶠよコルエ⁙ム3Sメヘ&amp;ム(へノん9さこEろん4dょ+ルん5マん5ムん5ムを-メっ1ヤゃ'ルエイメゃ'⁴エたムんわ◜⁷1さフハ.▮Uホっや5ムんヤん⁘ムフTム'slっ*ムシサろ&sup3;Uモれふハ⁷1ルはEろをほ.cミたcハ⁴を-メん5ムク=ろょTアEへなつEすこEもを7フょ+ミhふc3ぬ#3しム⁶-ヨ{●Yゃ-レエョアんS-ろほヒん‖+(ひてめ5◝#ᶠせ#⁶て⁶/ムん5ムFほモよヒて⁴ほハょ)ミ☉ひとろ5iっむこ{てこ{kャ●G5ムんチんTgコみひフわ+⁷1ルはEひエ‖4&sup3;ᶠせ#ᶠさクTjよ⁙ムヘ1ろクS-ろほヒん‖+(はム⁶#ルキ&sup3;んす&sup3;ろんTdん5ム⁴5ムんSメヘ1ろんS-ろほヒん‖+(はてん⁙てっ⬇️んす&sup3;ろク9タんGルエコヤん9てフTgク9ひフわ+⁷1ルはEモん1メエてこ{つヤゃ1まん5ᵇ7&lt;てDふモよヒろ⁴ほハょ)ミ☉は+そふc⁷3つ(わ&hearts;す&sup3;ろク=タ7Gャw;ャ⁷Ejは;ャせし+ヘは,を7ノんし.て5ミ⁷5ムん5ヤゃ1ケフTlっ)レエ⁘てっ#ムエゃムん5テん5c⁷+l⁴へ1{●Yク=タ&hearts;5jク9もフTgク9すっ+~▮5タぬYeᶠンfん5ムん5ムF1さょTo()ムエ⁘に(%{░5タ●/5ムん5ムんタ⬇️7.よ;ャ⁷Ejは;ャ●5タ●1タ●1さフTlっ)レエ⁘てっ1チをふマよ⁙ムん5ムん⁘cん9チ'Smっ1ろ'Tjん⁶ろよ⁘lムほ-ミ4jっみチ3Slっ1ろ3Tjん\t░よ⁘,フ5ムんeマめGャ&hearts;efっふjつ6なよしアを#ャ●-メゃ~▮W;ミ✽7モよ5ャ⁸わャ░ふム⁶#ミ⁷4nよ⁘ムフt😐ク4gん5ムん5⁴エコムエ⁙,んTc⁷5ム⁶Tjほ7ユよやヤエ⁙lフE5ょ&lt;なぬん8よん5ワ⁙◜▮Yタ●1ルエ⁙lっ1もミ5ムん5⁴⁶1タ●1タ●)ムエ⁙oっ1もフ]5ムわム⁴5ムん5ムん1ムん5ムん5ムエわムん5ムん4lヘ1も3Tfん9チ'SlヘんルムほミgE5ょG-アわeっほ4ヘん⁴をほ.よヒなよコル⁶&lt;なね5ムん5ムク4ᶜん5ムん5ムっ(ムん5ムん;ミHふjほ9タ⁸わjゃみっょ=5ムわ⁴わu,Lん5ムふャ▶E\rムん,'+ムエ⁘レF)ルエ‖ム⁵5モされんさへてほ5ャ░ふgん9cミたcゃふもょTjよハム⁶;モほ7ョネミしネへては7ャ░っフっみしネセしフeハアみタ●+メエ◀ろ⁵7.ネミしネミヤエコ4⁶1ひミW#ᶠせ#⁷edょ9タ●+レエ‖⁴⁵7モしネふしネてっ)ルエ⁘eょ9#ᶠせ#ᶠ5ャん5jよ⁘とっ1ムエレム‖こ3ふこ/っ)ムエ⁘eん9す&sup3;んす&sup3;もなせ7ャ●1ろんToっ-メカこ3ふこ43Slヘ1タフE~されゆさふモせGャ●1ろょTo(-レエせ#3せ#ゃみもミTjつG◜されゆさわ⁴&sup3;7モよ⁘fん5ホんeヘょ93ふこ3は5ャ⁸ふjよわ,&sup3;んす&sup3;んヤっ%ムエ⁵ムん5jせ?」っ)ムん5fん9#3せ#3eャ⁷5jよわムᶜこ{てへてDふ.めE4クTdん5ム⁴5ムんSlヘ$れてされたハfっみタ░ふ.ネセしフeᵇ⁸わiん5⁷エ⁙レムんKᶜん5ワコ4‖こ3ふこ,()レん5ムエ⁙-フ5ムんeᵇ(ふiんw⁴エ⁘jよコ,⁙せ#3せ,っ)ルょG-エ⁙-っほ4ホへヘん9コミふム⁶'ムれ5ミん4dん9しネミしネめモに5ムん5ャ░ふムん5ムシ⁵ルエ⁙ムクTdエEなょ%lよふルᶜされてさに()メスん4エ⁙,ホG5ムルcっふjんkャ⬇️Sとろほハん⁙メヘCんされんゃみもミTふF'レエ⁘eっふhゃめモよコ+ヘは,よふ,&sup3;ミしネミさんSmっ1タヘふjよコルん5ム'5ャ░7ハょ'ミ⬇️5モ3せ#3せ⁴エコム⁶1ひんTjは@TjᶠムクTfょ'ルつ4dっむこミてこミkャ⁷Ejよわル⁶1アん5ムゃ5モよコ+ヘは,よふ4&sup3;んせ#んさクSmヘ1タヘわjよ⁷5ムん#ムイタ⁸は-ろふタっふuしネふしふモにEャ●'ルエ⁘jよ⁘eょ'ルつ4dん9#3せ#'eャ⁷5jよわム⁶1さん1タ✽7ハょ'ミ⬇️5.しネふしへてEふ.よ⁙,ヘ1タそUタ✽ひmろほハんしチミ\こミつヤゃ-レエ⁘eアみタ⬇️はミ●)ムめEひフふムをふムん5ムク=アん5タ⬇️5ムを1さん#ムせ'ミんふネ⁸%ミヤ5モん5ュ⁵61{●Yク=アタ◆モよ⁵⁘{9タhYさᶠし+っ#ルこUさつS~▮#~▮#iん5ムん5ムFふムんTiん5ム⁶/ヒろふcんふャせしされふタ●%5ムん5ムんツムん8よ⁷5ムイタoセへんたd⁷#c&sup3;セす⁶1タ●1タ●1タ●){P1め&quot;<br /> -- init<br /> menuitem(1, &quot;music on/off&quot;, function() if mus then music(-1,1000) else music(upd_func==up_play and 22 or 0)end mus=not mus end)<br /> menuitem(2, &quot;storm on/off&quot;, function() g_storm= not g_storm end)</p> <p>g_stairs_line=split'670,231,857,138'</p> <p>-- pull native sprite sheet into lua table so can be restored quickly<br /> game={data={}}<br /> for m=0,8192,4 do<br /> game.data[m\4+1]=$m<br /> end<br /> -- set up palettes for lighting fx<br /> scpal_map={}<br /> local vals=split'0,129,130,131,132,133,13,6,136,137,9,139,140,1,13,143,0,0,0,1,130,128,5,10,2,4,11,3,1,2,4,142'<br /> for i=0,15 do<br /> scpal_map[i]=vals[i+1]<br /> scpal_map[i+128]=vals[i+17]<br /> end</p> <p>drpal_map={[0]=0,unpack_split'0,1,1,2,1,13,6,2,4,9,3,13,5,4,14'}</p> <p>g80,g81={},{}<br /> for i=0,255 do<br /> g80[i]=(i&amp;0xf0)+drpal_map[i&amp;0xf]<br /> g81[i]=drpal_map[i\16&amp;0xf]*16+i%16<br /> end</p> <p>fade_pal(8)<br /> wait(20)<br /> if sub(stat(6),1,1)!='0' then<br /> music(0)<br /> mus=true<br /> else<br /> mus=false<br /> end</p> <p>palt(0,false)</p> <p>--load title data<br /> title,house=unpack_dbi(once_dbi),unpack_dbi(house_dbi)<br /> once_dbi,house_dbi=nil</p> <p>-- unpack title sequence data (see title_gen.p8)<br /> local title_raws={<br /> split&quot;163,4,6,3,4,38,28,16,2,2,69,38,16,2,2,90,48,0,2,4,7,62,16,2,2,92,65,42,3,4,89,88,36,1,4,9,95,52,2,2,7,110,52,2,2,156,35,75,3,4,69,59,85,2,2,45,69,85,3,2,7,87,85,2,2,40,97,85,1,2,220,85,107,4,1&quot;, -- drake blue games presents<br /> split&quot;166,13,11,3,4,38,32,19,2,2,69,42,19,2,2,3,52,19,2,2,90,64,3,2,4,15,77,19,1,2,37,76,10,1,1,7,83,19,2,2,75,52,49,2,1,169,33,71,3,4,35,56,65,2,4,15,69,81,1,2,37,68,72,1,1,89,75,65,1,4,89,83,65,1,4,71,92,81,2,3,77,70,105,4,1&quot;, -- frankie and philly in<br /> split&quot;128,4,16,3,4,69,26,24,2,2,89,36,8,1,4,89,44,8,1,4,13,53,24,2,2,192,65,24,3,2,7,84,24,2,2,7,94,24,2,2,3,104,24,2,2,153,93,19,1,1,128,29,57,3,4,13,51,66,2,2,38,63,66,2,2,38,73,66,2,2,13,83,66,2,2,39,97,66,2,2,38,95,66,2,2,0&quot;,-- halloween horror<br /> --split&quot;0,9,2,3,4,3,32,12,2,2,5,45,12,2,2,7,55,12,2,2,9,40,34,2,2,11,53,34,2,3,13,67,34,2,2,3,78,34,2,2,69,100,34,2,2,64,4,61,3,4,35,27,55,2,4,38,40,71,2,2,15,50,71,1,2,37,49,62,1,1,40,56,71,1,2,41,64,66,1,3,45,72,71,3,2,69,90,71,2,2,40,101,71,1,2,41,40,95,1,3,37,47,91,1,1,15,48,100,1,2,45,53,100,3,2,7,71,100,2,2,37,81,107,1,1,37,89,107,1,1,37,97,107,1,1&quot;,<br /> split&quot;128,5,35,3,4,69,28,43,2,2,11,39,43,2,3,11,53,43,2,3,71,68,43,2,3,128,9,81,3,4,69,32,91,2,2,89,42,75,1,4,89,50,75,1,4,13,59,91,2,2,192,71,91,2,2,7,88,91,2,2,7,98,91,2,2,153,97,86,1,1,3,108,91,2,2&quot;,-- happy halloween<br /> }<br /> ls={}<br /> for t,title in pairs(title_raws) do<br /> local ptr=1<br /> ls[t]={}<br /> repeat<br /> local letter={}<br /> for i=1,5 do<br /> add(letter,title[ptr])<br /> ptr+=1<br /> end<br /> add(ls[t],letter)<br /> until ptr&gt;=#title<br /> end</p> <p>parts,snow,bons={},{},{}</p> <p>for i=0,400 do<br /> add(snow,{x=rnd(128),y=rnd(126)})<br /> end</p> <p>pulse=0<br /> kits={<br /> {x=350,y=230,vx=0,vy=0,gr=true,face=true,hit=0,flinch=0,sit=30,score=0,name=&quot;frankie&quot;,bonus=1,bon_cool=0},<br /> {x=370,y=230,vx=0,vy=0,gr=true,face=false,hit=0,flinch=0,sit=30,score=0,name=&quot;philly&quot;,bonus=1,bon_cool=0}<br /> }</p> <p>snowback,snowfront={},{}<br /> for i=1,100 do<br /> add(snowback,{rint(128),rint(120)})<br /> add(snowfront,{rint(112),rint(120)})<br /> end</p> <p>g_cam,g_ss=unpack_data'2,x,y,420,60'[1],unpack_data'4,x,y,vx,vy,0,0,0,0'[1] -- between two kittens, remember to update when kitten start changes</p> <p>--#include &quot;halloween_platforms.lua&quot;<br /> g_floors=unpack_data('4,x,y,r,b,-4,-10,1028,0,252,122,760,130,0,253,1024,270,840,122,1028,139,824,133,852,146,664,213,682,226,680,205,698,218,696,197,714,210,712,189,730,202,728,181,746,194,744,173,776,185,760,165,792,177,776,157,808,170,792,149,824,161,808,141,840,154,647,221,667,233,640,229,647,238')<br /> g_walls=unpack_data('4,x,y,r,b,250,-2,256,128,-4,-10,0,270,1024,-10,1028,270,200,127,204,156,112,127,116,155,584,129,588,155,880,-2,884,27,624,-2,628,28')<br /> g_paints=unpack_data('5,x,y,r,b,c,648,0,879,7,7,278,16,647,111,8,904,0,999,7,7,256,5,277,122,8,689,16,895,111,13,624,5,642,28,13,643,13,688,31,13,945,16,1000,111,15,896,16,944,31,15,-7,240,127,255,3,200,133,223,159,2,608,144,1001,239,12,265,144,583,239,2,449,209,486,239,1,401,81,430,112,1,880,5,901,27,15,1001,5,1023,36,15,1001,86,1023,121,15,1022,122,1023,123,15,208,157,211,158,4,224,144,264,159,2,584,133,607,158,12,592,157,597,159,2,224,128,583,135,7,608,129,999,135,7,1002,133,1023,154,12,1002,214,1023,250,12,112,133,135,156,4,132,157,142,158,4,136,128,199,135,7,397,40,434,61,9,752,137,823,137,6,752,143,807,143,6,751,128,839,128,13,280,0,623,7,7,752,136,823,142,7,446,168,489,187,9,446,182,489,187,12,446,171,456,187,14,455,169,461,187,4,451,173,457,187,15,447,170,451,187,7,461,172,468,186,6,471,170,476,178,15,478,174,482,183,7,473,179,480,185,6,485,168,489,172,10,467,173,472,186,7,456,178,463,187,5,485,183,487,184,7,476,184,481,185,7,397,48,434,61,5,397,50,406,53,13,397,53,400,61,14,401,56,404,61,14,405,58,408,61,14,409,60,412,61,14,397,48,434,49,3,403,48,410,48,9,417,48,426,48,9,400,51,400,55,2,398,51,398,55,5,408,55,411,61,2,409,52,412,56,7,409,56,409,56,4,412,56,412,56,4,413,53,413,57,15,408,53,408,57,15,419,54,433,60,13,410,54,411,55,15,416,42,401,42,8,413,46,426,46,8,427,44,434,44,8,418,56,418,59,13,424,53,433,53,13,401,55,402,55,14,405,57,406,57,14,277,190,322,220,1,752,137,824,137,6')<br /> g_plats=unpack_data('4,x,y,r,b,608,237,647,240,281,97,350,100,291,77,348,79,342,202,409,205,262,24,276,29,344,232,407,236,336,224,344,228,386,113,445,117,384,70,447,75,403,101,428,105,358,73,369,76,355,96,380,99,264,82,271,85,464,86,527,90,464,74,527,78,456,27,535,30,547,40,612,44,840,86,879,90,969,105,990,108,971,117,988,120,952,94,999,98,960,64,991,67,960,82,991,86,1008,82,1015,87,928,74,935,77,624,229,647,233,407,224,415,228,416,222,431,226,432,198,503,203,344,207,407,212,520,210,551,215,512,225,521,229,552,224,559,228,520,232,551,236,443,165,492,170,536,57,543,60,536,73,543,76,536,89,543,92,395,38,436,42,360,54,383,57,296,54,319,58,328,62,351,66,824,70,857,73,800,54,831,57,776,38,807,41,776,70,807,73,824,38,855,41,952,38,991,41,952,54,991,57,976,25,991,28,1004,34,1017,37,451,229,484,232,434,241,500,244,512,167,551,170,512,183,551,186,568,202,575,206,336,155,416,158,761,122,839,125,328,39,351,42,275,190,324,195,272,231,327,235,40,207,63,211,688,223,847,227,816,207,847,210,736,207,791,210,784,183,839,186,854,178,866,182,872,199,876,203,875,231,895,235,920,239,943,243,880,223,983,226,896,202,967,205,888,157,975,160,968,231,988,235,1008,210,1015,214,1003,153,1019,157,987,199,991,202,65,227,101,231,82,218,102,222,72,210,92,214,83,198,95,202,78,188,86,192,29,180,48,186,984,170,991,175')<br /> g_decs=unpack_data('2,x,y,331,54,311,46,363,46,264,74,341,30,372,88,295,89,386,62,466,78,503,78,536,81,559,32,830,62,838,30,786,62,961,46,952,86,862,78,747,165,774,214,783,198,770,198,957,194,1007,202,734,214,538,158,533,174,491,190,418,214,350,194,317,222,518,174,696,189,806,214,827,30')<br /> g_candles=unpack_data('2,x,y,294,39,430,55,941,208,908,208,594,25,780,17,952,23,810,167,980,154,515,151,433,183,273,215,30,165')<br /> g_bats=unpack_data('4,x,y,vx,vy,600,20,0,0,309,20,0,0,493,20,0,0,293,20,0,0,417,74,1,0')<br /> g_spids=unpack_data('3,x,y,vy,550,160,1,540,40,1,200,70,1,300,90,1,800,25,1,887,214,-1,962,156,1,924,174,1,136,154,1,157,176,1,182,200,1,663,38,1,727,95,1')<br /> g_ghosts=unpack_data('3,x,y,vx,600,200,1,300,67,1,500,175,-1,900,23,1,965,234,1,592,104,1')</p> <p>g_pumpkins=unpack_data'10,x,y,vx,vy,hx,hy,sp1,sp2,w,c,40,191,0,0,40,191,196,199,24,0,982,24,0,0,982,24,206,238,16,0,966,24,0,0,966,24,206,238,16,0'<br /> g_num_candles=#g_candles</p> <p>cool,sc,g_num_broken=512,1,0<br /> g_title_cols=split'11,9,0'<br /> use_sprites(title)<br /> cls()</p> <p>lightning,g_dark_cols,g_lightning_cols=0,split'0,129,130,131,132,1,13,6,136,137,10,139,140,5,4,134',split'5,12,14,10,10,7,7,7,7,7,7,7,7,7,7,7'</p> <p>set_fade(0.5,{set_dr_func},{dr_title},{set_upd_func},{blank})</p> <p>g_letter,g_end_cool=1,90 -- 30fps at end so 90=3 seconds<br /> </div></div></div></p> https://www.lexaloffle.com/bbs/?tid=45214 https://www.lexaloffle.com/bbs/?tid=45214 Mon, 01 Nov 2021 17:23:12 UTC Halloween Horrors <p> <table><tr><td> <a href="/bbs/?pid=99342#p"> <img src="/bbs/thumbs/pico8_halloween_horrors-6.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=99342#p"> halloween_horrors</a><br><br> by <a href="/bbs/?uid=47206"> drakeblue</a> <br><br><br> <a href="/bbs/?pid=99342#p"> [Click to Play]</a> </td></tr></table> </p> <h3>Banish the evil monsters from the house by putting out the magic candles.</h3> <h3>Break ornaments, cups and other items about the house as you go for extra points.</h3> <h4>Press left to play as Frankie or right to play as Philly: put out all the candles as fast as possible or with maximum destruction.</h4> <h4>For a two player game with both kittens press X: beat the other kitten or work together to get the maximum score in the fastest time.</h4> <h2>Controls</h2> <h4>Player 1</h4> <p>Swipe/Hit: X, V, M (X on gamepad)</p> <p>Jump: Up Arrow, Z, C, N (A on gamepad)</p> <p>Left - Right: Left Arrow - Right Arrow</p> <p>Drop down: Down Arrow</p> <h4>Player 2</h4> <p>Swipe/Hit: Q (X on gamepad)</p> <p>Jump: E, Tab, W (A on gamepad)</p> <p>Left - Right: A - D</p> <p>Drop down: S </p> <p>(These are the usual PICO-8 controls)</p> <p>Menu: P, Enter, Esc</p> <p>From the menu you can toggle the music and the storm - this stops the lightning flashes.</p> <h3>Go get 'em kitties!</h3> <p>If you like this game, find a problem or have any suggestions then please comment below.</p> <p>Please consider trying my other games or following me for news on new projects. Particularly, you might enjoy <a href="https://www.lexaloffle.com/bbs/?tid=40826">Demystifying the Christmas Tree</a>, an earlier game where Frankie and Philly make a mess at a different time of the year.</p> <p>And watch out for the kittens returning in the future...</p> <h3>Happy Halloween!</h3> <p>Game programming, art: Drake Blue Games</p> <p>Music: Modest Mussorgsky and Tim Follin arr. Drake Blue Games</p> <h2>1.0.1 Update</h2> <ul> <li>Reduced time kittens are unresponsive after being hit, but kept invulnerability time. Should be less likely to get &quot;caught&quot; by enemies. Also pumpkins and skulls more affected by kitten hits to help with this too.</li> <li>Displays both scores at the end of a two kitten game for polite comparison/commiseration with the runner-up. </li> </ul> <h2>1.0.3 Update</h2> <ul> <li>Changed old code that allowed more than one screen's worth of sprites to use mapping the sprite address to extended memory instead of abusing the multiple displays functionality. Should future proof this game a bit. Happy Halloween!<br /> (I don't know what happened to 1.0.2 either)</li> </ul> https://www.lexaloffle.com/bbs/?tid=45181 https://www.lexaloffle.com/bbs/?tid=45181 Fri, 29 Oct 2021 22:22:59 UTC Saving Tokens <p>These are my own notes on saving token tricks that I've used previously. I'm sure what's here is not unique to me - there are other documents about this subject (e.g. <a href="https://github.com/seleb/PICO-8-Token-Optimizations">https://github.com/seleb/PICO-8-Token-Optimizations</a>) and I recommend looking at those as well, probably before this one. I intend to add and update here as I learn more.<br /> If you see a mistake or are a true wizard that can offer wisdom to this humble novice then please feel free to comment :)</p> <h1>Starting out</h1> <p>I say &quot;starting out&quot;, because the next few ideas concern initialisation and structure, but this might be a good place to suggest that a lot of these tips produce much nastier to read code that's more of a pain with which to work and employing all this stuff straight away, especially if you're new to coding is just going to make the whole experience more difficult and probably not much fun. I tend to bear some of the things here in mind as I code and some things are becoming (bad) habits, but usually I've only used these techniques as a second, third or an in desperation when PICO-8 won't run any more pass.<br /> Coding your game in a more elegant way or even... cutting things(!) that aren't necessary are probably both better ways to save tokens.</p> <h2>Functions or &quot;to _init() or not to _init()&quot;</h2> <h4>3 tokens; 19+ characters</h4> <p>I don't use this function at all and do all initialisation at the end of my code, that is, after any other function definitions (so that they're not undefined if calling them from the initialisation code). If you're using the PICO-8 editor I'd suggest putting it in your rightmost tab.<br /> I'm going to end up using global scope anyway and I have yet to find a downside.</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>function _init() my_var=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>vs</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>my_var=1</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <h2>Initialising variables</h2> <h4>1 token per variable</h4> <p>Every time an '=' is used a token is wasted:</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,b,c=1,2,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>(7 tokens)</p> <p>vs</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=1 b=2 c=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>(9 tokens - 1 for each '=')<br /> To make this (a bit) nicer to deal with I tend to do something like the following when this kind of list gets very long (often longer than shown here):</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,b,c,d= 1, -- a 2, -- b 3, -- c 4 -- d</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 uses the same number of tokens and I've had to resort to code minification that will remove the comments, newlines etc. for the last few projects anyway. I use this one, which is great BTW: <a href="https://pahammond.itch.io/gem-minify">https://pahammond.itch.io/gem-minify</a>.</p> <h3>Aside - assigning nil</h3> <p>Need to clear a variable to nil (which conveniently evaluates as false - todo: more on booleans)?</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,n=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>4 tokens</p> <p>vs</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=1 n=nil</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>6 tokens</p> <p>Pretty desperate and calling a non-returning function after your variable assignment?</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>n=nil nil_returning_function(&quot;some text&quot;,0,0,10)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>9 tokens<br /> vs</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>n=nil_returning_function(&quot;some text&quot;,0,0,10)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>8 tokens<br /> WARNING: You need to be <em>very</em> sure that the function doesn't return anything. I got caught out by this myself writing this post as I initially used print as an example here and as GPI points out below it <em>does</em> return a value. You need to be very desperate to do this as it really, really doesn't help with code readability and is asking for trouble if you, say, change the function to return a value at a later data. But if you're right up against the token limit...</p> <h3>Back on initialisation - split() is your friend</h3> <p>Got a lot of strings you want in a table?</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>tab={'baa','baa','black','sheep'}</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>7 tokens<br /> vs</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>tab=split('baa,baa,black,sheep')</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>5 tokens<br /> vs</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>tab=split'baa,baa,black,sheep'</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>4 tokens</p> <p>Don't use brackets for function calls or maths unless you have to, since they add a token for each pair that you use.<br /> Note: the following is perfectly legal in lua:</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>rnd{1,2,4,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>(This returns a random value from the table {1,2,4,8})</p> <h3>unpack is also your friend (and itself friends with split)</h3> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>a,b,c,d= 1, -- a 2, -- b 3, -- c 4 -- d</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>11 tokens<br /> vs</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,b,c,d=unpack(split'1,2,3,4')</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>10 tokens</p> <p>Further variables cost 1 token each i.e. each further variable you add to a statement like this will save a further token compared to the version above (and 2 compared to separate a=1 b=2 etc.)<br /> Be warned, you may have trouble with your variables being initialed as strings, but pretty much every PICO-8 function I've tried (e.g. spr, print, rectfill, pal etc) Just Works. The problems I've had with this have been my own code. It's also very hard to know which number corresponds to which variable so I suggest only using this trick when you really need to.</p> <p>Of course, you can go further:</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,b,c,d=unpack_split'1,2,3,4'</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>8 tokens*</p> <p>The caveat here is, obviously, that you need a function like:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>function unpack_split(...) return unpack(split(...)) 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>10 tokens by itself.</p> <p>However, once you have that 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>print(&quot;hello world&quot;,10,20,11)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>6 tokens</p> <p>becomes</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(unpack_split&quot;hello world,10,20,11&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>4 tokens</p> <p>It's remarkably fast - I found that I only hit performance trouble if I used it within nested loops or with very high numbers of objects.</p> <p>In fact, every time you have 3 or more literal values together you can save at least a token with your unpack_split function anywhere in your program (it's a flat cost for each use in fact). I used it enough that I ran into trouble with the number of characters it used and the compressed size limit - renaming the function to US or similar solves that fairly nicely, once again at the cost of making the code ever less readable.</p> <h3>Order is everything</h3> <p>Armed with these techniques the next thing to consider is <em>when</em> to assign values in you initialisation (or anywhere else).<br /> The more you can bunch together, the better since e.g. fewer '='s are needed that way and you can group more literals into a single unpack_split'1,2,3,4'. Remember you can't reference an assigned value from within the same assignment though.<br /> This doesn't work (unless you really want y to be what x <em>was</em> previously + 3):</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,y=20,x+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>todo: add multi-dimensional table routines</p> <h2>Example from PICO Space</h2> <p>Stare into the void if you're feeling brave enough...<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>g_part_expl,g_vel,g_rand_cloud,S,g_sun_pal, g_candy, Sc2, g_syls, g_scpal_map, C, g_news, M, D, U, g_ratings, g_npc_chat, g_diff, g_diffs,g80,g81, ss1,ss2,F,-- start of literals g_near,g_far,g_edge,g_msp,g_gal_size, g_gal_time, g_fin, g_sys_p, Q,--g_progress? g_award, p, g_music, g_gal_seed, g_sys, O, g_cmdr, g_kills,g_max_energy = {vel,vel,rand_cloud},{vel},{rand_cloud},--g_part_expl {c=8,sp=.3,en=8,dam=1},-- ship { --g_sun_pal split&quot;10,9,8,4,2&quot;,-- yellow split&quot;7,10,9,8,2&quot;,-- white/yellow split&quot;7,12,13,15,1&quot;,-- white/blue split&quot;7,6,13,5,1&quot;,-- white/grey split&quot;12,13,15,5,1&quot;,-- blue split&quot;7,11,3,5,1&quot;,-- white/green split&quot;10,11,3,5,1&quot;, --yellow/ green }, split&quot;10,12,11,8&quot;,--g_candy split&quot;0,1,1,2,1,5,12,2,4,8,3,15,2,2,1&quot;,-- Sc2 split&quot;ca,bal,da,gar,non\-en,pol,der,arp,bun,duc,kit,poo,v\-evee,zir,buf,v\-evil,xan,frak,ing,out,re,far,do,tel,tri,cry,quack,er,dog,pup,sno,ger,bil,pa,n\-ena,jan\-en,es,on&quot;,--g_syls {[0]=0,unpack_split'0,1,1,2,1,13,6,2,4,9,3,15,5,4,1'},--g_scpal_map {x=0,y=0},--C { -- g_news gal={ split_comma'president #2 welcomes gerbil delegates to the #1 galaxy on state visit.|government says there are no broccoli or carrot shortages.|vice-president: reports of space weevil incursions are fake news and no cause for alarm.|recipe book by great aunt dahlia using substitutes for carrots and broccoli returns to bestseller chart, four hundred years after first edition|cats complain that kangaroo boxing title challenger hit their reigning champion, &quot;she is supposed to sit in the boxes not punch other animals&quot; ', split_comma'president dismisses rumours of a weevil invasion.|&quot;no carrot shortage,&quot; says vice-president, &quot;just eat a potatoe instead&quot;|duck wins round-galaxy race by a bill from bunny. bunny claims galaxy is not round.|president #2 denies spending entire security budget on jacuzzi: &quot;no-one will attack us anyway and i like the bubbles&quot;.|dog moral philosophy professors to discuss exactly who, if any of them, is a positive young male role model and also the location of &quot;it&quot;|bears win hugging title for twentieth year running', split_comma'&quot;things are not getting worse,&quot; says president despite weevil presence noticeably increasing.|carrots vanishingly scarce as prices rise sharply. rabbits lobby parliament|&quot;has anyone actually met a weevil?&quot; asks vice president|government officials deny that gerbil delegates left because of weevils eating their carrots|&quot;#1 shall have a universe-beating track and trace system for the weevil incidents,&quot; says #2|rhinos record victory in world rugby championship against mice team who have never qualified previously, but had elephants running scared in previous game', split_comma'president #2 says: &quot;we are following the best scientific advice on the weevil problems,&quot; despite no obvious actions being taken|top military advisers say, &quot;this is not the time to panic, but it could be soon.&quot;|&quot;i grew my own broccoli,&quot; says mouse, &quot;but weevils stole and ate it.&quot;|reports of weevils in almost every system.|what is your family doing to cope with the weevil invasion?|princess macaroon wins jousting tournament for sixth year running despite many jousters staying away due to weevils, &quot;i could joust the weevils too,&quot; says macaroon', split_comma'#1 becoming overrun by space weevil menace.|what do the weevils want? beeb news asks the experts.|raccoon caught selling parsnips dyed orange as fake carrots says &quot;most animals never even noticed.&quot; could you tell the difference?|frog croaking championship described as &quot;riveting&quot;|vice president distributes personal broccoli to poor parrots. parrots respond saying they &quot;do not eat the stuff&quot;.|our reporters present 10 tips to cope with a hostile invasion from another species in style|snakes record first win in football. defeated lion captain said after the match: &quot;they really used their heads&quot;.', split_comma'president #2: &quot;we did everything we could to stop weevils, but it has been an unprecedented situation&quot;|top military advisers say &quot;this is the time to panic!&quot;|&quot;we need a hero,&quot; says panda, &quot;someone should find the weevil base and take out the queen weevil.&quot; who could possibly do that?|president and vice-president still say carrots and broccoli supplies are fine - does anyone still believe them?|&quot;i, for one, welcome our new weevil overlords,&quot; says president #2|giraffe sees image of dog in his breakfast toast|panda says he is tired of people expecting him to pursue car thieves in his own vehicle.', split_comma'#1 galaxy saved from weevils by lone pilot. all thankful.|president and vice president arrested accused of hoarding carrots and broccoli.|galaxy looking forward to eating better again.|colonists wanted for mission to former weevil system.|sloths, snails and tortoise alliance begin discussions about weevil sightings.|cats still refusing to commit on entering #1 galactic union- are they in or out?', },x=-128,item=0}, {who=0,system=0,station=0,x=-64}, -- M dr_start, -- D {}, -- U split'\fbharn\-enless,\febit of a softy,\fca little harn\-enful,\f6son\-enetin\-enes dangerful,\f9a v\-evee spot deadly,\fabit of a predator,\fbbug hunter,\f7death incarnate,\f8the extern\-eninator', split&quot;got any carrots?|searching for some broccoli\nyou got any?|have you heard there\nare weevils invading?|i don't believe in weevils.|i heard there's a planet\nwith naked apes on it\nwho are obsessed with\na thing called 'money'\n- crazy huh?|i'm heading for the \n#1|weevils are\na government hoax|how are you liking\n#2?|my father was called\n#3|make #4\ngreat again!,#2 should be\nan independent system\noutside the\n galactic union|weevils are a government\ntactic to distract us from\nthe real issues|if #2\nwere independent we'd\nnot have this weevil\nproblem|i'm sure our government\nwill sort everything out\nsoon|aunt dahlia's recipes\nare really good\nbut i miss carrots|i think i saw a\nweevil yesterday|do you have any\nbroccoli captain\n#3?|i hope there aren't any\nweevils raiding around\nthe #1,i still haven't found any carrots|what do i have to do\nto find broccoli in\nthis galaxy?|the weevils are just the\ntip of the iceberg\nmark my words|#3 is\na nice name|i'm #2 born and bred!|the #4\nunion needs us more\nthan we need them!,h-have you s-seen\nany weevils\nround here?\nthe news scares me|that's a nice ship you\nhave captain #3|i miss broccoli more\nthan carrots but i\nstill miss carrots|the government has no\nidea what it's doing|i trust our president\nto fix this weevil\nproblem,my brother still refuses\nto believe in weevils|psst - i know\nwhere you can\nget carrots still|what kind of weevils\nhave you seen\ncaptain #3?|don't let the government\nvaccinate you against\nweevil infection\nthey'll put a chip\nin you!|there are some really nasty\nweevils out there now|i told my parents not to\ntravel anymore|i hope the #1\nis still there,i was told that the\nweevils took over an entire\nsystem as a base|this species of weevil\nis supposed to be led\nby a huge queen|#4 has\nno regard for the rights\nof #2!|when i get to\nthe #1 i\nhope they have\nbroccoli|have you seen the\npresident recently?|i heard that the\n#4 fleet has\nrun away from\nthe weevils!|i don't know what i\nwouldn't do for\na carrot right now,hey captain #3 -\nyou're the best!|thanks for saving the\n#4 galaxy|i still can't find\nany broccoli|it's a lot quieter in \n#2 now|i heard they found carrots\nat the #1|#2 would\nhave coped with the weevils\nwithout the interference\nof #4|are you the real\ncaptain #3?&quot;, 2, -- g_diff split'\fb easy,\fcnorn\-enal,\f8 hard', --g_diffs {},{}, unpack_split'0,0,0,1000,10000,10240,2,1024,0,1,0,0,0,0,0,2307,1,1,23,0,5' -- other globals</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 is the most extreme example I have and hopefully ever will commit again...</p> <h1>Functions</h1> <h2>Arguments</h2> <p>Rely on default arguments and persistent states (like the draw colour) where you can e.g.</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(a,0,23,10,7) print(b,0,23,10,7)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>could be</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(a,0,23,10,7) print(b,0,23,10)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Also:</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>camera(0,0)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>vs</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>camera()</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 explicit arguments cost every time.</p> <h3>More can be better</h3> <p>As well as passing fewer arguments to a function, you can also pass more - lua doesn't choke. How can <em>more</em> arguments save tokens?<br /> If you are calling functions that you've assigned to variables that you can call with the same code, but require different numbers of arguments then just pass all the arguments every time e.g. draw methods that change between different entities in your game might sometimes need colours or not.<br /> Also remember that a table index that hasn't been defined is treated as nil. So passing tab.foo to a function when tab.foo has never been assigned is just the same as passing nil.<br /> As long as the code in the functions doesn't choke on nil values for those arguments then you don't need any clever control flow to try and get the number of arguments &quot;right&quot;.</p> <p>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>function make_ent(dr,x,y,c1,c2) return {dr=dr,x=x,y=y,c1=c1,c2=c2} end function draw_a(x,y,c1,c2) -- some drawing code here end function draw_b(x,y) -- some drawing code here end ents={make_ent(draw_a,0,0,8,9),make_ent(draw_b,0,20,6,7),make_ent(draw_a,20,49)} for ent in all(ents) do ent.dr(ent.x,ent.y,ent.c1,ent.c2) 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>Taking this example further, I might look to use unpack_split on the multiple literal values in the make_ent calls. I'd then wonder whether I could find a way of describing all the entities in a single string that I could process with split and unpack - there's likely to be more than three entities in my game after all.</p> <p>For example, currently I'm writing a game that unpacks all the platforms, walls, floors, monsters, goodies etc. via the same function per collection (now I'm wondering whether I could do all collections together...). The number of bits of data per each entity is the first number followed by values for each entity. i.e.</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>-- each spid has 3 values x,y position and y vector g_spids=unpack_raw_data'3,550,160,1,540,40,1,200,70,1,300,90,1,800,25,1,864,200,-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>I suggest using an editor to produce strings like this rather than try to write the data by hand. The PICO-8 printh function is v handy for getting data out of an editor into a file or to the console you run PICO-8 inside. You do run PICO-8 from the command line, right? :)</p> <p>Note: a lot of characters are wasted doing it this way e.g. every ','. Hex values would be more compact as well. If you can keep all values within a byte range then encoding them as raw characters works very well too and Zep has even given us the magic function to decode them here: <a href="https://www.lexaloffle.com/bbs/?tid=38692">https://www.lexaloffle.com/bbs/?tid=38692</a>.<br /> I use this for image data and sfx data (hope to write about both soon).</p> <p>Thinking about functions again, remember that in lua the following are equivalent:</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>tab['a'] tab.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>So that by putting the draw functions in the example further up into a table you could specify which function to use via an element in a data string. Something like:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>function make_ent(dr,x,y,c1,c2) return {dr=draw_funcs[dr],x=x,y=y,c1=c1,c2=c2} end ... for ent in all(ents) do ent.dr(x,y,c1,c2) 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>Or even at call time:</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 ent in all(ents) do draw_funcs[ent.dr](x,y,c1,c2) end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>(I suspect that would be slower - maybe easier to debug though)</p> <p>If your data parsing function is clever enough then you probably don't need a separate constructor function like that at all.<br /> Along those lines, say if every entity has position (x,y) and most have velocities (vx,vy) but one type only has a colour and no velocity then it's nice to code it with a proper index name &quot;ent.col&quot;, but if it means writing separate construction code then consider just using ent.vy and a comment(or at least adding something like local col=ent.vy when you need it).</p> <p>I haven't done this, but it may be even more efficient to dispense with named elements in your tables so you could do:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>for ent in all(ents) do ent[1](unpack(ent)) 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><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 works, 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>ent=split'1,e,1,2,8' cls() dr_funcs={ function(d,...) print(...) end } dr_funcs[tonum(ent[1])](unpack(ent))</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>todo: random numbers, number indices vs named indices, caching table values in local variables (fewer tokens and faster too!)</p> <h3>Bonus: unpacking into memory</h3> <p>I'm going to add more to this post, but for now I'll end with this:</p> <p>Have a table of values e.g. an image stored as bytes? Want to dump it into memory or onto the PICO-8 screen easily? You can as of the recent PICO-8 updates:</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>poke(0x6000,unpack(data))</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>6 tokens<br /> Bang - straight onto the screen. Poke4 is even quicker and the same number of tokens if you have your image data nicely packed into table values. I use this for 'extra' sprite sheets, for instance.<br /> Be warned though: down this road lies the terror of the compressed size limit...</p> <p>(I tried this just now vs memcpy(0,0x8000,0x2000) and it's much quicker - not v scientifically though so YMMV)</p> https://www.lexaloffle.com/bbs/?tid=45114 https://www.lexaloffle.com/bbs/?tid=45114 Fri, 22 Oct 2021 21:38:55 UTC Printing Outlined Text <p>Early on in playing around with PICO-8 I wrote a function to print text with a black outline.</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 prt_out(s,x,y,c) print(s,x-1,y,0) print(s,x+1,y) print(s,x,y-1) print(s,x,y+1) print(s,x,y,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>I imagine a lot of people have also done this and I can't be the only person who found it a bit clumsy and, near the end of a project when casting about for tokens, wondered if those five very similar print calls couldn't be reduced.</p> <p>Nowadays we have P8SCII so I thought I'd have a look. Here are some candidates I've written with their token counts and times from my crude testing:</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 p1(s,x,y,c) -- 42 tokens, 5.6 seconds ?'\f0\-f'..s..'\^g\-h'..s..'\^g\|f'..s..'\^g\|h'..s..'\^g\f'..chr(c+(c&gt;9 and 87 or 48))..s,x,y end function p2(s,x,y,c) -- 26 tokens, 6.2667 seconds for i in all(split'\f0\-f,\-h,\|f,\|h') do ?i..s,x,y end ?s,x,y,c end function p3(s,x,y,c) -- 38 tokens, 5.6667 seconds ?'\f0\-f'..s..'\^g\-h'..s..'\^g\|f'..s..'\^g\|h'..s..'\^g\f'..sub(tostr(c,1),6,6)..s,x,y end function p4(s,x,y,c) -- 30 tokens, 5.7 seconds ?'\f0\-f'..s..'\^g\-h'..s..'\^g\|f'..s..'\^g\|h'..s,x,y ?s,x,y,c end function p5(s,x,y,c) -- 42 tokens, 6.1 seconds print(s,x-1,y,0) print(s,x+1,y) print(s,x,y-1) print(s,x,y+1) print(s,x,y,c) end function p6(s,x,y,c) -- 29 tokens, 6.2 seconds for _,i in pairs{'\f0\-f','\-h','\|f','\|h'} do ?i..s,x,y end ?s,x,y,c end function p7(s,x,y,c) -- 37 tokens, 6.2667 seconds for i in all{'\f0\-f','\-h','\|f','\|h','\f'..chr(c+(c&gt;9 and 87 or 48))} do ?i..s,x,y end end function p8(s,...) -- 21 tokens, 6.3 seconds for i in all(split'\-f\f0,\-h\f0,\|f\f0,\|h\f0') do ?i..s,... end ?s,... 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>'?' vs print makes no difference to performance, but does save characters and 1 token each use.</p> <p>I was rather surprised that p2 uses so few tokens and isn't <em>that</em> slow. Also that p1 is actually the fastest (but only just).</p> <p>I usually find that tokens are more precious to me than a tiny bit of performance so I'm likely to use p2 above (or p8 even). p4 is pretty cheap at 30 tokens and faster than the naive approach so perhaps it's an overall winner (so far).</p> <p>I'm very interested to know if anyone has a better way(?)</p> <h2>v1 edit:</h2> <ul> <li>freds72's suggestion is p6. 3 more tokens for .0667 seconds faster. Using pairs() instead of all() causes half of that saving.<br /> I thought removing the split might make more difference than that, but now I feel a bit better about having split all over my game code to save tokens(!)</li> <li>I removed the unnecessary '\^g' (go home) parts from the P8SCII snippets where appropriate. This saves some characters and performance (but also somehow saved the odd token and I'm not sure how).</li> </ul> <h2>v2 edit:</h2> <ul> <li>a new winner for tokens: p8 at 21! But at the cost of being the slowest at 6.3 seconds in my test. Not by much though. It still works because the P8SCII overrides the colour value being passed.</li> </ul> <p> <table><tr><td> <a href="/bbs/?pid=98796#p"> <img src="/bbs/thumbs/pico8_drakeblue_prt_out-2.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=98796#p"> drakeblue_prt_out</a><br><br> by <a href="/bbs/?uid=47206"> drakeblue</a> <br><br><br> <a href="/bbs/?pid=98796#p"> [Click to Play]</a> </td></tr></table> </p> <h3>Bonus</h3> <p>Specify the colour of the outline:</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 p4bonus(s,x,y,c,o) -- 34 tokens, 5.7 seconds color(o) ?'\-f'..s..'\^g\-h'..s..'\^g\|f'..s..'\^g\|h'..s,x,y ?s,x,y,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>(Please use any of these functions however you want)</p> https://www.lexaloffle.com/bbs/?tid=45020 https://www.lexaloffle.com/bbs/?tid=45020 Sun, 17 Oct 2021 18:19:19 UTC Reflection <p> <table><tr><td> <a href="/bbs/?pid=98701#p"> <img src="/bbs/thumbs/pico8_db_reflection-1.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=98701#p"> db_reflection</a><br><br> by <a href="/bbs/?uid=47206"> drakeblue</a> <br><br><br> <a href="/bbs/?pid=98701#p"> [Click to Play]</a> </td></tr></table> <br /> A simple demo of a reflection.</p> <p>Press X to enable/disable the reflection effect.<br /> Press Z to enable/disable the ripple effect.</p> <p>Use up/down to move the view up and down i.e. change the amount of reflection on the screen.</p> <p>Uses the extra palette to dim the portion of the screen where the reflection will be.<br /> Draws everything else like normal above the reflection surface.<br /> 'memcopy's to the lines below the reflection surface, starting with the line immediately above it.<br /> Adding an offset with some sin() calls moves each line a bit to allow the ripple effect. Downside is a little bit of mess at the edges.</p> <p>[edit] Minor bug fix to make ripple effect constant with distance from the shore.</p> https://www.lexaloffle.com/bbs/?tid=44997 https://www.lexaloffle.com/bbs/?tid=44997 Fri, 15 Oct 2021 16:09:44 UTC Fireworks <p>Fireworks<br /> <table><tr><td> <a href="/bbs/?pid=90763#p"> <img src="/bbs/thumbs/pico8_drakeblue_fireworks-3.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=90763#p"> drakeblue_fireworks</a><br><br> by <a href="/bbs/?uid=47206"> drakeblue</a> <br><br><br> <a href="/bbs/?pid=90763#p"> [Click to Play]</a> </td></tr></table> </p> <p>Some fairly simple particle fireworks just for fun.<br /> Press X to switch the screen effect on/off. For more about that see here: <a href="https://www.lexaloffle.com/bbs/?tid=41149">https://www.lexaloffle.com/bbs/?tid=41149</a></p> <p>Every sixth of a second, 50-100 Particles are emitted at a random point on the screen with random angle/velocity determined by a tiny bit of trig. They're given colours in the top half of the PICO-8 palette (7+) and as the particles get old the colour changes according to the same mapping that the screen fade effect uses to a darker colour.<br /> If they run out of life or become black then the particles are deleted.<br /> There are two possible update functions for each particle - one with a wiggle :)</p> <p>-- update: tidied code a bit, updated fade effect.<br /> -- update 2: added a flash when the fireworks detonate. Other tweaks. Happy Fifth of November :)</p> <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/47206/fireworks_0.gif" alt="" /> https://www.lexaloffle.com/bbs/?tid=42528 https://www.lexaloffle.com/bbs/?tid=42528 Sun, 18 Apr 2021 14:02:59 UTC Making PICO Space <h1>Making PICO Space</h1> <p> <table><tr><td> <a href="/bbs/?pid=89915#p"> <img src="/bbs/thumbs/pico8_drakeblue_picospace-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=89915#p"> drakeblue_picospace</a><br><br> by <a href="/bbs/?uid=47206"> drakeblue</a> <br><br><br> <a href="/bbs/?pid=89915#p"> [Click to Play]</a> </td></tr></table> </p> <p>This is a rambling description of some of what went into making <a href="https://www.lexaloffle.com/bbs/?tid=42279">PICO Space</a>. I've tried to write it for most readers to follow - there's some basic stuff and nothing very advanced. Hopefully it's not too dull and might help someone.</p> <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/47206/space_77.gif" alt="" /> <h2>When You Wish Upon a Starfield</h2> <p>Coming up to Christmas of 2020 I had been spending most of my dev time trying to squeeze image data into PICO-8 to make a Dungeon Master clone (I promise I will return to this at some point). I'd got a bit tired of writing compression and encoding routines and feeling like I was fighting PICO-8 rather than playing nicely with it. I'd seen some other nice star-fields and particles in other peoples' games and wondered how much it would take to do my own.</p> <p>I'd also read about the CAMERA function and suspected that would help - using CAMERA to transform the &quot;view window&quot; once instead of transforming the positions of every single particle many times seemed like it'd be a big win in terms of performance.<br /> I knocked up the following while the girlfriend was watching a Christmas movie to try it out. Ironically, the stars in the final game don't benefit from the CAMERA function at all - although the other particles and everything else do.</p> <p> <table><tr><td> <a href="/bbs/?pid=90118#p"> <img src="/bbs/thumbs/pico8_drakeblue_camerastars-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=90118#p"> drakeblue_camerastars</a><br><br> by <a href="/bbs/?uid=47206"> drakeblue</a> <br><br><br> <a href="/bbs/?pid=90118#p"> [Click to Play]</a> </td></tr></table> </p> <p>There's not a lot to it: use directions to &quot;fly&quot; the red line around (there's nothing there, but the star field). But this is the basis of how the ship flies in PICO Space.</p> <p>I wrote a post about the stars and fade effect here: <a href="https://www.lexaloffle.com/bbs/?tid=41149">https://www.lexaloffle.com/bbs/?tid=41149</a><br /> <table><tr><td> <a href="/bbs/?pid=86396#p"> <img src="/bbs/thumbs/pico8_fading_stars-5.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=86396#p"> fading_stars</a><br><br> by <a href="/bbs/?uid=47206"> drakeblue</a> <br><br><br> <a href="/bbs/?pid=86396#p"> [Click to Play]</a> </td></tr></table> </p> <p>I considered a rotate and thrust method, like in games like Thrust, Oids etc. but decided to keep it simpler, friendlier to those who didn't grow up in the 80s and allow the game to be more dynamic. Left, right and thrust would have saved a precious PICO-8 button though...</p> <p>In the final version, there's a constant deceleration added to the velocity components of the ship (which gets wiped out if you hold down the controls) - and a very low minimum velocity. Why?</p> <h2>Not Just a Triangle; Not Even a Triangle</h2> <p>The ship (and the NPC ships) are all drawn in the game using a combination of lines and a sprite to cover the hole in the middle (yes, really - I had some triangle drawing code sat in the p8 file for weeks without ever actually using it in the end so it got cut). The vertices used for the lines are calculated from a normalised vector of the ship's velocity. If the velocity gets too close to zero then this calculation, the vertex calculations and the lines don't come up with anything sensible and the ship doesn't draw correctly.</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>----------------------------------------------------------------------------------------- -- draws the player or an npc ship function draw_ship(s) ship_pal(s) -- calc some vertices based on current heading angle -- 0 -- (0) -- 2 1 local z,an=s.z&lt;1 and -1 or 1,s.an local s0,s1,s2,c0,c1,c2=sin(an)*z,sin(an-0.15)*z,sin(an+0.15)*z,cos(an)*z,cos(an-0.15)*z,cos(an+0.15)*z local svx0,svy0,svx1,svy1,svx2,svy2=3.5*c0,3.5*s0,-3.5*c1,-3.5*s1,-3.5*c2,-3.5*s2 line(svx0*1.2,svy0*1.2,svx1*1.2,svy1*1.2,2) line(svx0*1.2,svy0*1.2,svx2*1.2,svy2*1.2) line(svx1,svy1,svx2,svy2) line(svx0,svy0,svx1,svy1,8) line(svx0,svy0,svx2,svy2) line(0,0,svx1,svy1) line(0,0,svx2,svy2) line(0,0,svx0,svy0) spr(unpack_split'0,-4,-4') -- cockpit reset_dr_pal() 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>ship_pal sets up the correct colours for whatever ship is being drawn - there's a pair of values for every brighter colour apart from the blue used for the cockpit.</p> <p>The z variable deals with the &quot;flip&quot; ability that the ship has so you can face backwards - otherwise, while the ship does have some inertia it will always be drawn to point in the direction it's moving.</p> <p>I'll explain more about &quot;unpack_split&quot; in another post.</p> <p>Here's the ship without the cockpit sprite:</p> <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/47206/space_72.gif" alt="" /> <p>reset_dr_pal is a function that acts like a call to pal with no arguments, but doesn't affect the screen palette. I've used it for a few projects now:</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 reset_dr_pal() poke4(0x5f00,0x0302.0110,0x0706.0504,0x0b0a.0908,0x0f0e.0d0c) 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 latest PICO-8 letting consecutive values be multiple pokes at a time has let me optimise this so it's a bit opaque even to me. IIRC it dumps the equivalent of 0,1,2,...,15 into the correct part of PICO-8's memory to reset the draw palette and whatever magic needs to be done for palt too(?), but doesn't hit the screen palette range of memory.</p> <p>Feel free to lift this for your own code if you think it'll be useful - no warranty expressed or implied ;)</p> <p>I tweaked the ship drawing code a few times, but it never varied much. Once I had a ship flying about I needed somewhere for it fly to so I made a &quot;planets&quot; table that was populated with random coordinates, drew a circular planet sprite and hit go. That's gone now - it was handy to have as a placeholder, but I don't miss it.</p> <h2>Start of a Solar System</h2> <p>Once I had planets it made sense to me to add a sun/star, but I wanted it to be bigger than the planets. I'd already used 64x64 of the sprite sheet with the planet sprite so I wondered what I could do with PICO-8's circfill command.</p> <p>It turns out that circ and circfill let you draw pretty large circles for not a great deal of perf cost. I tried various sizes and settled on an 800 pixel radius circle for the sun with some extra circles around it. One is a border, the others are there just because I like how they look. My &quot;game design&quot; excuse is that they give some warning to the player that they're about to fly into a star - not v healthy for their ship.</p> <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/47206/space_70.gif" alt="" /> <p>I have another cart that draws a partial circle within another circle to show the sun on the scanner, but the token count was too much. In the end, the sun on the scanner is drawn by generating lots of points then checking if they are within both the circle of the sun and the circle of the scanner; then drawing a circle of radius 1.</p> <p>Originally I had yet another circle for the edge of each system - but as the systems got busier the performance cost was just too high so I needed something a bit more &quot;sophisticated&quot;. I noticed that the edge circle was so large that it never looked like anything else but a straight line on the little PICO-8 screen so in the game I do a little bit of maths to work out two intersection points with the edge circle and draw a line between them i.e. the edge circle segment is approximated with a line.</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>--edge of system if g_dist_to_sun&gt;g_edge-23 then -- approximate with drawing tangent line at angle through ship's coordinates -- since ship is so far from centre and edge circle is so large -- edge is a circle around 0 -- get angle to ship local an,ed=atan2(S.x,S.y),g_edge+rnd'16' -- get tangent angle at 90deg and coords of point on edge (with some jitter) local an2,ca,sa=an-0.25,cos(an)*ed,sin(an)*ed -- get vector of line using tangent angle local ca2,sa2=cos(an2)&lt;&lt;7,sin(an2)&lt;&lt;7 line(ca-ca2,sa-sa2,ca+ca2,sa+sa2,p%8+8) end </pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/47206/space_74.gif" alt="" /> <p>I use g_dist_to_sun to conditionally draw the sun when needed as well.</p> <p>Having a circular edge seemed like a nicer way to stop the ship flying too far out of the game than just clamping to a rectangle or wrapping the coordinates. I had a really fancy bit of code that would turn the ship around from anywhere outside the system and force it back. In the end just adding a vector pointing towards the centre of the system to the ship's velocity is what makes it behave like a stuck blue bottle behind glass in the game.</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> g_dist_to_sun=dist_trig(S,g_sys.sun) if g_dist_to_sun&gt;g_edge then local avx,avy=get_dir(S,g_sys.sun) vx+=avx vy+=avy end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <h2>Trigonometry for Distances</h2> <p>(As any fule kno) the distance between a point A and B is the square root of the sum of the squares of the coordinate distances i.e. d = sqrt(dx<em>dx+dy</em>dy) -- for 2D</p> <p>Except not in my game.</p> <p>In a fit of madness I decided to make the systems pretty large, 10240 pixels in radius, in fact. PICO-8 numbers are a 16-bit fixed point type with a range of -32768.0 to 32767.9999847412109375 (0x8000. 0000 to 0x7fff). Which means I was fine until I needed to calculate a large-ish distance and had to square numbers over 1000 e.g.</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>dist({0,0},{9000,8000}) = sqrt(9000*9000 + 8000*8000) = sqrt(81000000 + 64000000)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Um...</p> <p>Weirdly, I got a really long way into coding before this actually became a problem and I'm not quite sure how that happened - especially since I was expecting it all along, I'm no stranger to the limitations of number types or even to fixed point floats.</p> <p>One method I tried to get round this was to divide every value (bit shift in fact) before the squaring operation and then shift back afterwards. I'd lose precision, but in theory it'd work fine i.e. something like:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>dist = sqrt( (dx&gt;&gt;4)*(dx&gt;&gt;4)+(dy&gt;&gt;4)*(dy&gt;&gt;4) )&lt;&lt;4</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Most of the time I'd only needed the shifted squared value. But I had real trouble getting it to work well - possibly because of other code around it. And sometimes I wanted the true distance (with the sqrt) anyway.</p> <p>I'd been doing some experimentation with the SIN and COS functions and I'd noticed that they're very fast on PICO-8 and I wondered about whether this might be another solution.</p> <p>A bit of trig later and I had a candidate:</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>-- distance with trig -- to avoid large numbers -- not v accurate, but seems good enough. -- also seems v fast on pico-8 function dist_trig(a,b) local x,y=abs(b.x-a.x),abs(b.y-a.y) if x&lt;y then x,y=y,x end -- accuracy goes down massively if x is much smaller than y so swap them :) return x/sin(atan2(y,x)) end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>I discovered the accuracy problem mentioned in the code a bit later - it manifested as the ship spontaneously diving into the sun if you flew too close to the top or bottom of it - and only those two points. The code to stop the ship escaping the edge of the system was firing and pushing the ship into the centre of the system because the distance calculation was so bad it thought the ship was at the edge not right by the sun!</p> <p>Anyway, this function gets used all over the place now and was favourably comparable in accuracy and speed to any of the shifted distance functions that I tried.</p> <h2>Galaxy Generation</h2> <p>Of course, the more sensible solution would probably have been to make the star systems smaller and &quot;zoom&quot; the coordinates only for drawing, but I'd already done most of the work on galaxy creation and drawing and didn't really want to change it too much. I think I actually tried it for 30 minutes, made a mess and then gave up.</p> <p>When you choose a new game or load from a saved game in PICO Space it generates the whole galaxy from an integer seed (your captain's name is a different value; otherwise they couldn't travel to other galaxies...).</p> <p>The stars are all generated from the galaxy seed plus a fraction part e.g. the fourth star has something like id=galaxy_id+4*0.0001. This means there are potentially 65535 galaxies (I think, or at least 32767) with every system having a unique id/seed &quot;in the gaps&quot;.</p> <p>Apart from star number 1 which is always at (0,0), the stars are given random positions within a fixed distance from that star with the condition that they can't be too close to an already existing star. This is v fast.</p> <p>Then the code links the galaxies with wyrmholes (called gates in the code). First it makes a minimum spanning tree with wyrmholes:</p> <ul> <li>find the star with minimum distance from star 1</li> <li>add wyrmholes to link the two</li> <li>add the new star to star 1 in a &quot;found&quot; set</li> <li>find the closest star to any star in that set</li> <li>link with wyrmholes</li> <li>repeat last 3 steps until every star is in the &quot;found&quot; set</li> </ul> <p>This way I know that every system is connected to the galaxy as a whole. Without this some systems aren't reachable (I had considered having a jump drive upgrade that let you jump between groups of stars - maybe next time).</p> <p>Unfortunately my algorithm to calculate the minimal spanning tree gets v slow after relatively few iterations (checking the closest star in an n-size set of stars has a pretty bad O value) and acts as a mechanical limit to how big the galaxies can get.</p> <p>I'd be more worried about it, but for two things:</p> <ol> <li>The loading view covers it well. It looks like the stars are being generated, but it's actually showing the stars being added to the minimum spanning set.</li> <li>It's fast enough that even with 70 stars it's not that slow and I think 70 stars is probably enough.</li> </ol> <p>--controversial?--I've noticed in a lot of procedurally generated games there's a whole load of repetitious content where there doesn't really need to be - it doesn't add much to the game and feels a bit like it's there just because the developers could. Now PICO Space is still pretty guilty of this - there's a lot of space in PICO Space and 70 systems is probably still way more than most players will ever visit. The systems don't have that much variety - I really wanted to put in more and kinda of heart-breakingly PICO-8 is easily <em>fast</em> enough to allow it. But there's not much cart space and I just couldn't find the tokens. There were a lot of things I would have added without the token limit and even some stuff I had to cut before release. I never found myself wanting to add more star systems. In fact, for a lot of dev time I reduced the number to make testing faster.--/controversial--</p> <p>Anyway, long story short: I didn't waste any more time (or tokens) optimising this, instead I slapped in a logo and a few star spr calls and pretended it was a deliberately designed intro sequence...</p> <p>Once that's done, there are a few iterations after the spanning tree search looking for short distances between stars and adding more gates between them so that there are &quot;loops&quot; and it's a bit easier to get about. This is pretty fast too.</p> <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/47206/space_76.gif" alt="" /> <h2>Generating Systems</h2> <p>Then the systems themselves are fleshed out. Originally I generated each system in the galaxy only when the player entered them. In fact, all I had was the current system and some wyrmholes in it. When the player entered a wyrmhole I took the seed value associated with it and generated the new system. I even added a wyrmhole back to the previous system using its seed to give the illusion of persistence. For the galaxy map I was going to run this generation on the fly when it was opened to a certain depth of wyrmholes and that way I'd have a near to infinite galaxy and tiny memory footprint!</p> <p>Typing it out now it still seems like it might have worked, especially as I know the scope of what's in the game. At the time though, I ditched that idea since I didn't. The final straw was wanting to put &quot;missions&quot; into the game.</p> <p>Each system has wyrmholes and planets which also may have space stations orbiting them. The wyrmholes, only really have a position, a destination id and a draw function. They are called wyrmholes because I was hoping to have &quot;space wyrms&quot; in the game. Sadly, that never happened.</p> <h3>Planets</h3> <p>Planets have radii and colours and are added in distance &quot;slots&quot; (with the wyrmholes) radiating out from the sun - this way there shouldn't be any collisions between them, even as the planets orbit around the star. Yep, the planets do orbit in the game, but their positions only update when entering a system. I had them update along with everything else for a long time though.</p> <p>As mentioned elsewhere in this post, the scale of each system is quite large. The planets orbit in circles determined by sine and cosine functions combined with their distance from the star (I balked at setting up stable systems with realistic gravity calculations if only because I'd disappeared down enough wyrmholes already). Unfortunately, the precision of these functions isn't high enough to allow smooth movement when any distance from the sun. It caused the planets to &quot;jump&quot; between positions on their orbit. Unperturbed I wrote a routine to interpolate between the imprecise positions so that the planets would still orbit smoothly. It worked really well and I was sad to have to cut it to regain the tokens it needed.</p> <p>I was able to leave the scribbly planetary rings in place though, which are drawn by generating a bunch of vertices using sine and cosine values (with random jitter) and drawing lines between pairs of them. Draw the back half of a ring first, draw the planet, then draw the front half of the ring.</p> <p>Like the planet interpolation, I designed them in a separate test cart first:</p> <p> <table><tr><td> <a href="/bbs/?pid=90118#p"> <img src="/bbs/thumbs/pico8_drakeblue_rings-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=90118#p"> drakeblue_rings</a><br><br> by <a href="/bbs/?uid=47206"> drakeblue</a> <br><br><br> <a href="/bbs/?pid=90118#p"> [Click to Play]</a> </td></tr></table> </p> <p>The code in the game is very similar. Knocking up little test carts is nice in PICO-8 since it's v lightweight to get going. Doing this also means less messing up of existing code, especially if you change your mind and it means less flying about trying to find the thing you want to test.</p> <h3>Space Stations</h3> <p>The space stations do orbit as you fly, around the planets. A planet has a station entry in its table so there's only one per planet (early in dev I had up to 4!) and the space station positions get updated with everything else that updates. When you're docked the ship has to match this (that was a fun bug).</p> <p>The space stations have three different sprites, a few circles and a pal call in their draw function to provide some cosmetic variety. I had written a little cart which would allow me to specify shapes, sprites and map commands in a string that was interpreted at runtime to give many more looks for them, but tokens and performance stopped me using that. And I hit the compressed size limit quite badly too.</p> <p>At one time there was a ship's garage with mechanic NPCs in every station but alas tokens...</p> <h2>Missions</h2> <p>(kinda)</p> <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/47206/21_space_57.gif" alt="" /> <p>I wanted to add a whole different variety of these, but eventually I only made the passengers on the noticeboards at the stations - the simplest missions I could think of. They're even more basic than fetch quests (find something <em>and</em> bring it back - sounds complicated!) or even deliveries (plausibly you only have room for one passenger in your small ship so I only needed to track one mission at a time). I ran out of tokens before I could attempt adding any more types.</p> <p>To make the passenger missions work I needed to have some knowledge of not just the name of the destination system, but also of the objects in the system; specifically the space stations. I'd hoped to have docks on planets, but, again, the token limit got the better of me.</p> <p>It was so much easier to have the destination systems pre-created and in memory for this. PICO-8 has a generous 2MB for such data (my first computer had 64KB and David Braben and Ian Bell made do with a fraction of that). PICO-Space only ends up using about half of that at any time (when it's working correctly).</p> <p>The game takes a seed of the current space station id and the progress that the player has made through the game and uses that to generate a number of notices with destinations and a seed for the animal wanting a ride. That way, if you return to the notice board it keeps the same notices, at least until you complete a mission.</p> <p>When you finish enough journeys a progress global is increased, an upgrade is awarded, different news &amp; NPC messages are unlocked and more dangerous weevils may appear. There's an upper limit to how many journeys are needed for each progress point (four I think) so every player should progress reasonably quickly.</p> <h2>Silly Names</h2> <p>Nonsense word generators are something I've written a few times in the past. They're very effective (and entertaining) for something so easy to do. If you've never written one I highly recommend it, especially for newer programmers.</p> <p>There is a single list of syllables and a single function that generates all the names in PICO Space. If I had more space/tokens then having different syllable sets etc. per species or system or something might have been fun, but I never felt like this was a weak part of the game and garnered an embarrassingly large amount of amusement out of some of the silly things it came out with.</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>------------------------------------------------------------------------------------------ -- generate some wacky random names function gen_name(s) stash_n_set_seed(s) local n,nm=rnd_spl_str&quot;1|2|2|2|3|3|3&quot;,'' for i=1,n do if i&gt;1 and rnd(15)&lt;1 then nm..=' ' end nm..=rnd(g_syls) end unstash_seed() return nm 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 gist of this is:</p> <ul> <li>choose length for the name in number of syllables</li> <li>for each syllable up to thate length choose a syllable string and append it</li> <li>occasionally append a space instead of a syllable</li> </ul> <p>The stash_n_set_seed function is my hack to get around the trials and tribble-ations of working with a random number generator for procedural generation (as opposed to a noise function). See below for more about this.</p> <p>Here's the syllables:</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>split&quot;ca,bal,da,gar,non\-en,pol,der,arp,bun,duc,kit,poo,v\-evee,zir,buf,v\-evil,xan,frak,ing,out,re,far,do,tel,tri,cry,quack,er,dog,pup,sno,ger,bil,pa,n\-ena,jan\-en,es,on&quot;,--g_syls</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>For more serious names, don't put in such silly syllables e.g. poo.</p> <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/47206/space_66.gif" alt="" /> <h2>Ms and Ws</h2> <p>The eagle eyed amongst you may have noticed that PICO Space has &quot;wide&quot; 5x5 pixel M and W letters instead of the usual 5x3 characters that PICO-8 has by default. My other games, e.g. P8C-BUN have similar wide Ms and Ws too. In those games, I have a custom print function that iterates through whatever string is to be printed and when it finds an M or W it replaces it with a sprite that has the wide version of the letter. I added this after I got some negative feedback about the legibility of the text in a game.</p> <p>Originally PICO Space used the same function, but I found it was a bit of a performance problem. PICO-8 has a much more &quot;spiky&quot; processing time per frame than my other games have had.</p> <p>Fortunately, zep's addition of P8SCII in the newer version of PICO-8 came at exactly the right moment and allowed me to replace my routine with these magic codes:</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=&gt; n\-en [print an n then shift the cursor back 2 pixels (\-e) and print another n] w=&gt; v\-ev [u\-eu looks okay too]</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 long as a find/replace is done for those characters in a string to be printed then it &quot;just works&quot; and I was able to ditch my custom print routine at last. This can be done &quot;offline&quot; even. The only tricky part was trying to centre text when the width was no longer as predictable - I do have a mostly working solution for that. Ask me if you're interested.</p> <h2>Taming RND</h2> <p>PICO-8 has no noise function, but it does have RND and a seed set function SRAND. I considered writing a noise function, but I suspected I'd want the tokens for something else and I was concerned about performance. I'd already used RND for the first hacky progress in the game when I thought about this so I gave up on noise and set up a function that saved the current random number (or at least whatever RND returned at that moment) and called SRAND with a value I'd passed to it. Then I was careful with the order I called RND in and did a lot of testing to make sure that I got consistent or random results as appropriate.</p> <p>For a particular seed, RND always returns the same sequence of subsequent results so, for instance, setting the seed before choosing portrait sprites and generating a bunch of names using RND means you'll always get the same output. That's why there's always the same animal minding each station and the same notices each time you look on a station's noticeboard (at least until you ferry another passenger). I'd restore a &quot;random&quot; seed after using RND in this way so that, for example, the NPC generation stayed unpredictable.</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>-- set seed and store a random number for later function stash_n_set_seed(seed) g_old_seed=rnd() srand(seed) end -- restore &quot;randomness&quot; function unstash_seed() srand(g_old_seed) end </pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>This works pretty well all considered and I never felt I was having performance problems from it. I call the time function for the galaxy seed and pilot id just to mix it up even more.</p> <h2>Animals</h2> <p>I needed characters for the game and I considered identikit faces like Elite 2 uses for a bit. In the end, I drew some animals as placeholders and then never looked back.</p> <p>Animals feature in my games. Get over it.</p> <p>I wanted the portraits to move so initially I was going to add animation frames, but horizontally flipping was easier and meant I could have more unique pictures. I figured it was &quot;good enough&quot;.<br /> <table><tr><td width=32> <img src="https://www.lexaloffle.com/bbs/gfxc/47206_0.png" width=32 height=32> </td> <td valign=bottom> <a style="cursor:pointer;font-size:8pt" onclick=' var el = document.getElementById("gfxcode_47206_0"); if (el.style.display == "none") el.style.display = ""; else el.style.display = "none"; microAjax("https://www.lexaloffle.com/bbs/gfxc/47206_0.txt", function (retdata){ var el = document.getElementById("gfxcode_47206_0"); el.innerHTML = retdata; el.focus(); el.select(); } ); '> [8x8]</a> </td></tr> <tr><td colspan=2> <textarea rows=3 class=lexinput id="gfxcode_47206_0" style="width:640px;background-color:#fed;display:none;overflow:hidden; font-size:6pt;"></textarea> </td> </tr> </table> </p> <p>There are 59 animals in total.</p> <h2>News and NPCs</h2> <p>The Beeb News was my first attempt to communicate some story in the game and add a bit of narrative colour (alongside the colour colour). The news stories develop as the player progresses in the game - there's a different set after each upgrade is achieved. I used a <em>very</em> basic template filling function to generate a bit of customisation for each game - e.g. the galaxy name and the president's name change depending on which galaxy you're in.</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>------------------------------------------------------------------------------------------ -- substitutes values (#n) into a string for the corresponding entry in data (data[n]) function template_fill(t,data) local i,out=1,'' while i&lt;=#t do local c=sub(t,i,i) if c=='#' then i+=1 out..=data[tonum(sub(t,i,i))] elseif c=='m' then out..='n\-en' elseif c=='w' then out..='v\-ev' else out..=c end i+=1 end return out 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>You'll notice the M and W substitutions in this - in the last week of development I had to add this to reduce the size of the strings used for the news and NPC chat in order to fit the game under PICO-8's compressed size limit. Fortunately, it works very well and turned out to be quick enough.</p> <p>The news is drawn using the same scrolling message system that is used for the upgrades and notice board, just with different colour parameters.</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>---------------------------------------------------------------------- -- draws a bar of scrolling text function draw_scroller(scroller,y,x,c,bc) clip(x,y,128,y+6) rectfill(x,y,128,y+6,bc) print(scroller.message,x-scroller.x,y+1,c) clip() scroller.x+=1 if scroller.x-x&gt;scroller.len then scroller.x=-64 return 1 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> <h2>NPCs</h2> <p>The other attempt at story telling comes from the NPC ships. These will send you random messages when they're on-screen. Like the news, the template function adds a bit of customisation e.g. for the current system and there are different sets of messages for each stage of progress in the game.</p> <p>The NPCs are (obviously) drawn in the same way as the player ship, but on top of basic attributes like position, velocity, colour etc. they also have a target that they head towards; either a station or a wyrmhole. They're spawned from a station or from a random position (with hyperspace particles) to pretend that they've just jumped in. I'd love to have more persistent NPCs, but I probably spent too many tokens, performance and cart space on them already.</p> <p>One of the more interesting problems to solve was how to stop NPCs spawning in a position then flying towards a target on the other side of the system's sun without them hitting it. I don't check them for collisions so they'd just fly right on through blissfully unaware of any problem. Given the player can't do that it was a bit non-ideal to witness ships emerge from the superhot plasma if you happened to be flying close to the sun at the time. It got even more obvious when I stopped pausing the simulation in the System Map since you could watch the NPCs sun-diving:</p> <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/47206/space_20.gif" alt="" /> <p>I tried various clever solutions with trigonometry and mathematics, but they never worked very well. In the end, I solved this problem with a hack. Whenever an NPC is generated their journey is quickly simulated, start to finish, but with coarser framerate. If they get too close to the sun then that journey is ditched and the NPC journey is generated again until a valid journey is produced. This does make for the occasional performance spike, but on the whole works quite well and for few tokens.</p> <p>There's a cap on the number of NPCs per system that takes into account the number of weevils to try and make sure there's quite a bit of performance headroom, just in case.</p> <h2>Enemies</h2> <p>To be honest, I was having so much fun making a pretend universe that I left it quite late to add in a challenge to the game. Eventually I admitted that I needed villains so I drew on personal history.</p> <p>A few years ago we found weevils in our kitchen. All over our kitchen. Eventually we tracked them back to a bag of birdseed that now moved and crackled by itself...</p> <p>The next hours and days consisted of rooting out weevil and destroying it - weevils became a kind of bogeyman in our house. Nothing is more evil than weevil in our home.</p> <p> <table><tr><td width=0> <img src="https://www.lexaloffle.com/bbs/gfxc/47206_1.png" width=0 height=0> </td> <td valign=bottom> <a style="cursor:pointer;font-size:8pt" onclick=' var el = document.getElementById("gfxcode_47206_1"); if (el.style.display == "none") el.style.display = ""; else el.style.display = "none"; microAjax("https://www.lexaloffle.com/bbs/gfxc/47206_1.txt", function (retdata){ var el = document.getElementById("gfxcode_47206_1"); el.innerHTML = retdata; el.focus(); el.select(); } ); '> [0x0]</a> </td></tr> <tr><td colspan=2> <textarea rows=3 class=lexinput id="gfxcode_47206_1" style="width:640px;background-color:#fed;display:none;overflow:hidden; font-size:6pt;"></textarea> </td> </tr> </table> </p> <p>I wanted to make each weevil unique, but in the end they are all based on the same code with varying attributes. As the game goes on more and more dangerous weevils are generated ahead of the ship within a random circle depending on how dangerous the system is and the difficulty level of the game. Some weevils can hit the ship and damage it with their collision, all weevils fire missiles at the ship at different rates and for different amounts of damage.</p> <h3>Space is Big, even PICO Space</h3> <p>One of the biggest problems with the weevils was how to make them possible to fight on the tiny 128x128 PICO-8 screen without making them too slow to stop a player just flying past them. I didn't want to slow down the player either, since getting between locations was taking long enough already. Besides, when I tried slowing the ship, it just felt... bad.</p> <p>I think the weevils are still a bit too fast to be fair - I'm not convinced that I really solved this problem entirely.</p> <p>WARNING SPOILERS - DO NOT READ IF YOU WANT TO LEAVE THE ENDING AS A SURPRISE<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> <h2>Weevil Queen</h2> <p>I wanted an ending and a &quot;boss&quot; and so I did some research into big weevils (as you do). It turns out that there are some big ones out there and there are even some that live like ants with a queen. Not to the same scale as ants though.<br /> Add a bit of poetic licence and I came up with the weevil queen that's in the game.<br /> What do you mean: &quot;there isn't one?&quot;<br /> Are you saying you've not completed enough missions to gain the Prototype Scanner upgrade, located the Weevil Base and jumped into the system to fight the Weevil Queen and save the galaxy?<br /> At time of writing, I'm not sure anyone has except me (many testing times). If you have I'd really like to know (please?).</p> <p>The Weevil Queen is generated in a system that has only one wyrmhole in it (a leaf node in the galaxy &quot;tree&quot;) and is by default hidden in the Galaxy Map (and the wyrmhole to it is hidden in the system containing it too). Some of the changes for the weevil system happen during galaxy generation. Most of it is done on entry. There are no space stations or NPC ships and the weevil queen is added. She behaves a lot like a normal weevil, but she has hitboxes at the back of her that don't take damage and she moves more slowly. She fires the most deadly missiles, most frequently. She may also teleport randomly if hit.<br /> The weevil queen is drawn using several sprites together and she gyrates at a speed according to how close to destruction she is.<br /> <table><tr><td width=0> <img src="https://www.lexaloffle.com/bbs/gfxc/47206_2.png" width=0 height=0> </td> <td valign=bottom> <a style="cursor:pointer;font-size:8pt" onclick=' var el = document.getElementById("gfxcode_47206_2"); if (el.style.display == "none") el.style.display = ""; else el.style.display = "none"; microAjax("https://www.lexaloffle.com/bbs/gfxc/47206_2.txt", function (retdata){ var el = document.getElementById("gfxcode_47206_2"); el.innerHTML = retdata; el.focus(); el.select(); } ); '> [0x0]</a> </td></tr> <tr><td colspan=2> <textarea rows=3 class=lexinput id="gfxcode_47206_2" style="width:640px;background-color:#fed;display:none;overflow:hidden; font-size:6pt;"></textarea> </td> </tr> </table> <table><tr><td width=0> <img src="https://www.lexaloffle.com/bbs/gfxc/47206_3.png" width=0 height=0> </td> <td valign=bottom> <a style="cursor:pointer;font-size:8pt" onclick=' var el = document.getElementById("gfxcode_47206_3"); if (el.style.display == "none") el.style.display = ""; else el.style.display = "none"; microAjax("https://www.lexaloffle.com/bbs/gfxc/47206_3.txt", function (retdata){ var el = document.getElementById("gfxcode_47206_3"); el.innerHTML = retdata; el.focus(); el.select(); } ); '> [0x0]</a> </td></tr> <tr><td colspan=2> <textarea rows=3 class=lexinput id="gfxcode_47206_3" style="width:640px;background-color:#fed;display:none;overflow:hidden; font-size:6pt;"></textarea> </td> </tr> </table> </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(q) pal(15,0) local en=5-q.life/10 local sn48,cs48,sn32,cs32=sin(p/48*en),cos(p/48*en),sin(p/32*en),cos(p/32*en) q.x+=sn32 circ_orig(p%96,g_sys.sun.pl[q.life\10+1]) spr(135,-25+sn48,-10+cs48,2,2) -- right front leg spr(135,11-sn32,-10-sn48,unpack_split'2,2,1') -- left front leg spr(135,-29,cs32-52,2,2,nil,1) -- right back leg spr(135,13,cs48-52,unpack_split'2,2,1,1') -- left back leg spr(137,12+sn48,-26+sn32,unpack_split'2,2,1,1') -- left back leg spr(137,-27-cs32,-26+cs48,2,2,nil,1) -- right back leg spr(128,sn48-15,unpack_split'-50,2,4') -- right wing spr(130,-sn48,unpack_split'-50,2,4') -- left wing spr(132,cs48-13,unpack_split'-29,3,4') -- thorax spr(139,-10+cs48,-8-sn32,3,3) -- head reset_dr_pal() 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>When she is destroyed, she is changed into the Intergalactic Wyrmhole that leads to the next (harder) galaxy, the weevil system loses its weevil flag and all the weevils are destroyed.</p> <p>I spent a lot of time getting all this to work, all the time wondering if anyone will ever see it.<br /> </div></div></div></p> <h2>Tables and &quot;Objects&quot;</h2> <p>I'm not much of an object oriented zealot (learning to program in BASIC in the 80s does that to you), but for the entities in PICO Space I used tables as objects of a kind.<br /> Things like planets, stations, weevils (called aliens in the code) and NPC ships generally have &quot;standard&quot; attributes (x,y for position, bounding box for draw culling etc.) and functions (update and draw).<br /> On entering a system an &quot;updateables&quot; and a &quot;drawables&quot; table is populated with appropriate things from those tables in the current star system and the update and draw functions loop through these and call them as appropriate. Using the CAMERA function I set the origin for drawing to reflect the object's position then the draw function draws from there. Things with a map function get drawn on the System Map etc. It works quite well and feels a bit like adding components to entities.<br /> I had a lot more separate lists and loops for these early on just to get going - I find doing things &quot;the dumb way&quot; first and improving it later works well for me (along with, if it ain't broke don't fix it...).</p> <p>Some things e.g. particles are kept separate still, for speed.</p> <h2>Particles</h2> <p>There are two big types of particle in PICO Space. Points and circles. There's a function to add both of these in a single line each. They have a lifetime, position and update functions.</p> <p>Points have two update functions - a typical velocity update one and a more abstract randomly swapping x and y velocity update. The former is used in the explosions. The latter is most obvious in the hyperspace cloud at the very start of the game.<br /> Circle particles draw at a different size corresponding to the life left of the particle.</p> <p>Why do they look the way they do? It's the screen fade effect.</p> <p>See <a href="https://www.lexaloffle.com/bbs/?tid=41149">https://www.lexaloffle.com/bbs/?tid=41149</a> for more information.</p> <h2>Menus</h2> <p>All the menus use the same code and are driven by data passed to them. There's nothing amazingly clever about them (or that), but I found it was much nicer in every respect to work with them once I took this approach. So I suggest writing generic menu routines soon if you find you're needing more than one of the things in a project - don't put it off.</p> <h2>Memory and Performance</h2> <p>PICO-8 makes both of these things really easy - Ctl/Cmd P along with a constant print of stat(0) if it went above a threshold value (usually 1024).<br /> The latter helped me find a nasty problem very near release. For a long time I'd paused the game except when flying the ship in space, but particles were drawn and deleted in the draw function. When I enabled the game to update, even while in the maps and docked, I was generating particles (NPC ship engines, NPC hyperspace, sun flares), but never deleting them. So I'd run out of memory. Sometimes after a very long time. It happened much faster when I moved the cursor about in the map views so I spent ages debugging those to no joy only to eventually realise that I was running out of memory due to the ship engines firing a bunch of particles out into my dwindling memory...</p> <h2>Loading and Saving</h2> <p>I was very worried about getting the load and save functions to work, especially with the web player, but in the end it was very easy. Dump some values into cartdata to save, read them back when loading. I'm presuming the browser cache gets these when playing in a browser(?).<br /> One slight downer is the paste codes don't work on the web. They seem to work fine in &quot;native&quot; PICO-8 though and the &quot;binary&quot; versions. It's the only thing that doesn't work there.</p> <p>The best thing about having save games was for testing during development. I built up a text file with a long list of different saves that allowed me to test different stages in the game, different galaxies and systems etc. I could edit them a bit to quickly test some things as well.<br /> I'd highly recommend implementing something like this if you have a longer game and want to test it - especially since it's something your players might be able to use too.</p> <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/47206/space_37.gif" alt="" /> <h2>Tokens</h2> <p>As you may have gathered if you read this far, I ran out of tokens.<br /> Procedural generation produces content from code in preference to pre-defined content in storage so it wasn't overly surprising. I've seen others struggle with memory for proc gen, but I found the 2MB limit wasn't ever a problem.</p> <p>A large amount of the end of development involved trying to save tokens.</p> <p>Look out for my followup post about that, working title: &quot;A desperate programmer's attempts to squeeze galaxies into a PICO-8 cart&quot;</p> <p>--<br /> If you have any questions about the above post or anything else to do with PICO Space please ask them below and I'll try to answer them (or maybe even extend this article even further).</p> https://www.lexaloffle.com/bbs/?tid=42353 https://www.lexaloffle.com/bbs/?tid=42353 Fri, 09 Apr 2021 15:46:00 UTC PICO Space <h1>PICO Space</h1> <p> <table><tr><td> <a href="/bbs/?pid=89915#p"> <img src="/bbs/thumbs/pico8_drakeblue_picospace-2.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=89915#p"> drakeblue_picospace</a><br><br> by <a href="/bbs/?uid=47206"> drakeblue</a> <br><br><br> <a href="/bbs/?pid=89915#p"> [Click to Play]</a> </td></tr></table> </p> <p><em>v1.2</em><br /> Defend the animals of the galaxy from the space weevil invasion in your grandad's space taxi!</p> <p>Check the notice boards at space stations for prospective passengers. Gain rewards when they're delivered safely and discover how to defeat the weevil menace.</p> <p>The galaxy is quickly becoming a more and more dangerous place so be careful out there - with 50-70 systems and some questionable government policies an animal needs to look after themselves!</p> <p><strong><em> Please report any bugs or if something is unclear below. Feedback helps me improve and is always welcome :) </em></strong></p> <h2>Controls</h2> <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/47206/spacecontrols.png" alt="" /> <h3>Main Menu</h3> <ul> <li>Use Up, Down and X to select options in the menus. </li> <li>Return/Start/P will always take you to the Main Menu</li> <li>Hold Return/Start/P for longer to access the normal PICO-8 menu.</li> </ul> <p>I recommend a gamepad if you have one, but keys work fine too. Holding the X and O buttons together is useful to use the ship flip ability successfully. On mobile, I've found swiping my thumb backwards and forwards between the X and O buttons works okay, but it does seem easier to play with keys or a gamepad (to me at least).</p> <h2>Starting the Game</h2> <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/47206/space_46.gif" alt="" /> <p>Use the up and down directions to highlight an option and X to select it.</p> <ul> <li>Begin New Game - starts a new game with the currently show captain in a new galaxy (Captain Magerquack by default).</li> <li>Change Captain - chooses a new captain and galaxy with which to start a new game using the Begin New Game option. This doesn't affect the currently stored game or any game from a pasted code.</li> <li>Continue Last Game - this retrieves the previously auto-saved game from your browser's cache or PICO-8's cartdata. The game is saved at the Dock Menu and on entering a system via a hyperjump.<br /> If you want to preserve further game states beyond this then use the next option.</li> <li>Load From Pasted Code - every time the game is saved PICO Space places a save game code into the clipboard. By pasting this code into a text file or similar then multiple game saves can be maintained. To restore from one of these codes:<br /> &gt; - copy the code into the clipboard from the application you've used to store it<br /> &gt; - switch back to PICO Space running in PICO-8 and paste it<br /> &gt; - Use X to select the Load From Pasted Code option in the menu</li> </ul> <p>NOTE: THIS DOESN'T SEEM TO WORK IN THE BROWSER - YOU WILL NEED TO DOWNLOAD THE STANDALONE PICO-8 CONSOLE VERSION TO LOAD FROM SAVE CODES OR ACCESS THE GAME THROUGH SPLORE<br /> THE GAME WILL BEHAVE UNPREDICTABLY (PROBABLY CRASH) IF A MALFORMED CODE OR OTHER DATA IS PASTED INTO IT</p> <ul> <li>Set Difficulty - Adjusts how dangerous the galaxy is in the game. This option affects new games only so needs to be set before choosing &quot;Begin New Game&quot;.<br /> &gt; - Easy - for casual or inexperienced players, but still with some challenge<br /> &gt; - Normal - a reasonable challenge, success is not guaranteed on each journey<br /> &gt; - Hard - a difficult game likely to require multiple reloads</li> </ul> <p><strong><em> Please tell me if you think these ratings aren't very correct so I can try to tweak them </em></strong></p> <h2>How to Get Around the Galaxy</h2> <p><em>(the following is an extract from the dog-eared manual you found in the glove compartment of your grandad's ship)</em></p> <p>Congratulations on your purchase of a Farwilre P1C0 Star Taxi!</p> <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/47206/space_47.gif" alt="" /> <p>Your ship will enable you to fly anywhere within a system's boundary by directing it with the arrow keys or control stick (if fitted). Be careful of flying into a system's sun as there may be undesirable explosive consequences.</p> <ul> <li>The Short Range Scanner is useful to see what's close to you and is located in the bottom right of the viewscreen.</li> <li>The current system or current target (with distance in standard galactic units) is shown in the top left.</li> <li>Your ship's remaining shield energy is displayed in the bottom left. If this is depleted then it will flash to indicate that you should dock at the nearest space station for recharging.</li> <li>To locate space stations or other objects within a star system press Return, Start or P to bring up the main menu.</li> </ul> <h2>Main Menu (Pause)</h2> <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/47206/space_48.gif" alt="" /> <p>Select the System Map (with Up, Down and X) from the options shown.</p> <h3>System Map</h3> <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/47206/space_53.gif" alt="" /> <p>Solar systems are large so to avoid excessive journey time it is advised to utilise your ship's hyperdrive from the System Map (where traffic laws permit).</p> <p>To do this:</p> <ul> <li>Locate the cursor where you wish to jump to by using the direction keys</li> <li>Press O, Z or V to execute a hyper jump</li> <li>Press X to target the nearest system object. The target is shown in the top left and also indicated by a red marker.</li> </ul> <p>Note: be sure to peruse the System Map in a safe location to avoid possible accidents while it has your attention. Last year 45% of ship accidents happened while pilots were viewing their map screens - don't let the next one happen to you!</p> <p>Systems in PICO Space are linked by Hyperspace Wyrmholes (of as yet unknown origin). These can be used to traverse between stars even in ships that are unequipped with an interstellar-capable drive. Thus to travel to another system simply fly into the centre of a wyrmhole and your ship will travel through hyperspace to a point in the system indicated in the wyrmhole's name.</p> <p>If you know which adjacent star you wish to travel to, it is easiest to find where the wyrmhole to it is by using the System Map.</p> <p>To navigate about the greater galaxy and hence determine which wyrmholes to use it is advisable to examine the ship's Galaxy Scanner.</p> <h3>Galaxy Scanner</h3> <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/47206/space_50.gif" alt="" /> <p>The Galaxy Scanner will open centred on your current location - shown by the ship icon and expanding circle indicator. Adjacent stars (i.e. those which are connected by a pair of wyrmholes) have lines drawn between them. Systems adjacent to your current location have green lines drawn to them. Corresponding wyrmholes should be visible on the System Map.</p> <p>The view and cursor in the centre may be moved using the direction keys. The currently highlighted system will be the closest to the cursor position.</p> <p>The galaxy is quite large so for optimal viewing it may be necessary to zoom the view in or out by holding the X key and using the Up and Down directions to change the scale of the view.</p> <p>If your ship is fitted with an Interstellar Drive then it is possible to hyperjump (without traversing a wyrmhole) to adjacent systems by highlighting them with the cursor in this view and pressing O, Z or C.</p> <h3>Status</h3> <p>At any time your current status can be checked from the Main Menu by selecting this option.</p> <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/47206/space_55.gif" alt="" /> <p>The screen shows:</p> <ul> <li>You, the captain of the ship and your combat rating</li> <li>Any current message</li> <li>Your current passenger if one is present and to where they wish to be taken.</li> <li>Information about any upgrades that have been added to your ship</li> <li>The energy level remaining for you ship's shields</li> <li>The danger level of this galaxy</li> </ul> <h2>Weaponry</h2> <p>Should you be unfortunate enough to encounter a hostile threat on your travels your P1C0 Space Taxi is fitted with a standard P3W Blaser; a small but adequate defensive weapon.</p> <ul> <li>Tap X (and release) and the P3W will fire</li> <li>Hold X to charge multiple shots that will rapidly fire on releasing the trigger.</li> <li>Note: while energy is diverted to charging the P3W your shields are unable to recover strength.</li> </ul> <h2>Dock Menu</h2> <p>When docked at a space station the following standard options will be available:</p> <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/47206/space_56.gif" alt="" /> <h3>Notice Board</h3> <p>Waiting passengers are listed here for an enterprising space taxi owner to transport to their desired destination.</p> <img loading="lazy" style="margin-bottom:16px" border=0 src="/media/47206/space_57.gif" alt="" /> <p>Use the Up, Down and X keys to select a passenger that you wish to transport. Be careful to choose a journey you can complete - an animal never backs out of a deal!</p> <h3>New Paint</h3> <p>Choose from a range of desirable colour schemes to customise your Farwilre pride and joy!</p> <h3>Launch to Space</h3> <p>The station will launch you back to space so you may continue on your journeying.</p> <p><em>extract ends</em></p> <h2>Tips</h2> <ul> <li>Choose your passengers wisely - some wish to travel further than others. Check on where they want to go via the Galaxy Map before committing to a journey you aren't sure you want to take.</li> <li>Plan your journey before leaving the safety of the space stations.</li> <li>If your shields are damaged it may be wise to stop at a station on the way to your destination to recharge them.</li> <li>Charge your weapon before encountering enemies, but don't hold X constantly as it stops your shields recharging as you fly.</li> <li>Use your hyperspace ability within systems to save on journey time or to escape a tough spot, especially as the galaxy becomes more and more dangerous.</li> <li>Note that the game is only paused in the Main Menu, other screens don't freeze time.</li> </ul> <p>If you are not sure what to do:</p> <ul> <li>try ferrying passengers</li> <li>later in the game, try reading the Beeb News or listening to what the NPC ships say to you.</li> </ul> <p>Good Luck, Captain!</p> <h2>V1.1</h2> <ul> <li> <p>UI colour changes</p> </li> <li> <p>fade effect improved</p> </li> <li> <p>screen shake added</p> </li> <li>difficulty reduced (a bit); enemies are slower, shots do less damage</li> </ul> <h2>V1.2</h2> <ul> <li> <p>UI improvements: menus</p> </li> <li> <p>bad loads shouldn't crash the game so badly</p> </li> <li> <p>a little music for the title screen</p> </li> <li>bug fix: re-calculated system object placing so shouldn't ever be inside a system's star</li> </ul> https://www.lexaloffle.com/bbs/?tid=42279 https://www.lexaloffle.com/bbs/?tid=42279 Sat, 03 Apr 2021 16:56:27 UTC Fading Stars <h1>Fading Stars Demo</h1> <p> <table><tr><td> <a href="/bbs/?pid=86396#p"> <img src="/bbs/thumbs/pico8_fading_stars-6.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=86396#p"> fading_stars</a><br><br> by <a href="/bbs/?uid=47206"> drakeblue</a> <br><br><br> <a href="/bbs/?pid=86396#p"> [Click to Play]</a> </td></tr></table> </p> <p>Demo showing the combination of the stars and fade effect I used in PICO Space: <a href="https://www.lexaloffle.com/bbs/?tid=42279">https://www.lexaloffle.com/bbs/?tid=42279</a>.</p> <p>[UPDATE 2021-4-12: 8bit and 16bit cached modes (explanation below and in code), some other small tweaks]<br /> [UPDATE 2021-5-6: added interleaved 8bit mode]</p> <h2>Stars</h2> <p>The stars are just simple particles that have x,y,z coordinates.</p> <p>In this demo I use a couple of sin functions to give them some movement combined with a divide by the z coord for a bit of parallax. In game, I feed in the player's position.</p> <p>Then I clamp the resulting x,y values to the screen with the modulus operator so they're always visible (%128). It does mean that the same stars go past constantly, but otherwise I was processing a lot of particles that don't get seen very often (not aiming for realism here).</p> <h2>Fade Effect</h2> <p>This works by mapping the colour of every pixel on the screen to another colour that tends to a target e.g. 0/black.<br /> You can use a similar mapping with the pal(x,1) function to e.g. do fades to black between screens etc. but that fades everything including anything drawn that frame.</p> <p>In this demo I process the pixels already in screen memory so that the screen is faded by a step, then draw fresh stars on top of that.</p> <p>It's pretty expensive to do the whole screen (IIRC about 90% of performance at 60fps) so I've set it up to do every fourth scan line, starting from a different point each frame. Effectively a quarter of the screen is faded at a time. It takes 4 frames to fade the whole screen one step.</p> <p>I initially tried fading in quarter strips top to bottom, but the tearing on bigger objects like planets looked pretty bad.</p> <p>Using the order 0,2,1,3 for the scanlines does some rough dithering to make the effect look a bit more uniform. A random value flr(rnd(4)) works quite well too, but is messier looking.</p> <p>Since I found using poke4 to work on 8 pixels at a time was fastest (not surprising really) dithering horizontally is limited and isn't in the demo. Nevertheless, I keep meaning to try a &quot;Z&quot; pattern i.e.</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> 0000000011111111 2222222233333333</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'm concerned it might cost too much more in performance/tokens for too little visual improvement.</p> <p>[edit]<br /> Of course, as soon as I write about it the old subconscious starts working away and it takes 5 minutes to implement just that - a reverse N pattern as it turns out. Same performance, same tokens. See the new cart.</p> <h3>Pros of Effect</h3> <ul> <li>You can draw whatever you want really and the effect essentially &quot;just works&quot; as a replacement for a cls().</li> <li>Cross-fading out from a scene just happens &quot;free&quot;.</li> <li>very simple particles look much more complicated than they really are</li> </ul> <h3>Cons</h3> <ul> <li>You can't draw anything that moves without the fade effect &quot;catching&quot; it. It can be mitigated by drawing around your objects (e.g. black borders), but if the view moves more than the width of the border you're out of luck.</li> <li>Conversely, the effect only works where you don't draw that frame - so if your game has e.g. a full-screen scrolling background that's drawn every frame then you won't see any effect at all. For a <em>space</em> game this isn't a huge problem, but it's still visible here and there. </li> <li>If nothing moves then there's no effect - try hacking the stars to be still in the demo.</li> <li>Performance cost is approx 21% at 60fps.</li> <li>Obv costs some tokens.</li> </ul> <h2>Caching</h2> <p>The effect works fine by extracting each pixel's colour value via shifting and masking then dumping the mapped values back onto the screen, but it's still pretty performance heavy.<br /> When I was writing PICO Space I'd read a few times that procedurally generated content used a lot of memory so I didn't want to try anything like the following, but now I have a much better idea of the game's memory requirements I thought I'd give it a go.</p> <h3>8-bit Mapping</h3> <p>Pixels in PICO-8's screen are determined by a 4-bit value, but peeking and poking only works with 8-bit granularity at best i.e. a pair of pixels or more at once. The mappings I have contain 16 values for each possible colour of a pixel.</p> <p>Considering pairs of pixels instead of single pixels, there are 16 * 16 = 256 possible combination of colours that need to be mapped. Why not store a table with each of these values - it can't be that large, right?</p> <p>Turns out it isn't, especially when compared to the 2MB of space lua is given in PICO-8. In fact the demo seems to only use about 2K or so (which is still a lot more than the 256 bytes it <em>should</em> take, but still pretty small).</p> <p>This means that a lot of masking and shifting isn't as necessary inside the inner loop. It even takes fewer tokens. The performance improvement is enough that half or even all of the screen being processed per frame isn't too bad.</p> <h3>16-bit Mapping</h3> <p>The next step was obviously to try mapping 4 pixels at a time using 16-bit values.</p> <p>This would need a table of 16^4 = 65536 entries which isn't very big for a modern machine, but is pushing it pretty far for PICO-8. It's possible - take a look at the code. It also takes up a <em>lot</em> more memory: about 1200KB it seems. That's well over half of the total space available and for my purposes in PICO Space is enough to give me sporadic out of memory errors as it stands (PICO Space takes about 600-900KB depending on the size of the current galaxy and how much is going on in it at any particular moment). For other games it may be absolutely fine and it's tempting since there's about a 2x speed-up compared to my original implementation of the effect using this technique.</p> <h3>A Bit Too Far</h3> <p>PICO-8's number format is 16bit.16bit fixed point so every value I've been storing so far is actually 32 bits in size whether I use all of those bits or not. Why not use them all?</p> <p>Storing mappings for 8 pixels isn't going to work: 16^8 = 4,294,967,296 - a bit too much for PICO-8.</p> <p>Instead, the last implementation that I've tried (so far) stores two 16-bit values in each number in the cache table so that the same amount of mapping values as in the previous section takes half the entries and hence half the space. The upper 16 bits take the even values; lower 16 bits the odd values.</p> <p>This brings the memory usage down to about 600KB or so, which is fairly reasonable.</p> <p>Unfortunately, the two mapping values packed into a single PICO-8 table value need to be unpacked to be used in the inner loop of the effect. By the time shifts and masks are applied to do this I couldn't get the performance to really be any better than the original effect (without any caching of values), never mind faster than the other cached value versions.</p> <h3>Yet Another Way</h3> <p>Up until this point I'd only considered making the effect faster and not &quot;better&quot;. Two horizontally adjacent pixels are represented by each byte in the screen so one of the first compromises I'd made was to assume I couldn't fade these separately per frame and so fading the whole screen over four frames was done with at least chunks of two horizontally adjacent pixels at a time.<br /> Since the 8bit cache version uses so little memory, is faster and deals with all combinations of two pixels both fading on the same iteration it struck me that there wouldn't be much cost to keeping two caches of 8bit values, one with the left side pixel faded, one with the right and swapping which cache is used per frame. When combined with alternating which rows are processed, this allows a dither pattern that works on a block of 2x2 pixels - no more horizontal chunking:</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>01 23</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=41149 https://www.lexaloffle.com/bbs/?tid=41149 Tue, 12 Jan 2021 15:57:44 UTC Demystifying the Christmas Tree <p> <table><tr><td> <a href="/bbs/?pid=85468#p"> <img src="/bbs/thumbs/pico8_demystify-4.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=85468#p"> demystify</a><br><br> by <a href="/bbs/?uid=47206"> drakeblue</a> <br><br><br> <a href="/bbs/?pid=85468#p"> [Click to Play]</a> </td></tr></table> </p> <h1>Demystifying the Christmas Tree</h1> <p>Help your kitten destroy as many decorations as you can before the tree is fully decorated or their energy runs out and they nap.</p> <p>Try to destroy multiple decorations within a short time to get combo bonuses and improve your score. Use your energy wisely, young feline.</p> <p>Play as Philly or Frankie on their own or with a friend against each other. See who can cause the most chaos!</p> <h2>Controls</h2> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>Action Frankie (Player 1) Philly (Player 2) jump up E run left/right S, F descend down D poke O(Z) W swipe/hit X Q Return menu, including music on/off and restart.</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>(These are the standard PICO-8 controls so some other keys work too)</p> <h2>Background</h2> <p>Demystifying the Christmas Tree recreates a real event from a Christmas past, although in real life the kittens not only broke decorations, but also took out parts of the tree itself. They also didn't run out of energy (as anyone who has had kittens would know).</p> <p>If you like this game, find a problem or have any suggestions then please comment below.</p> <p>Please consider trying my other games or following me for news on new projects.</p> <p>Happy Holidays :)</p> <p><strong>1.0.1 update:</strong> after testing with the owner of the kittens (my girlfriend's mother aka &quot;mother-outlaw&quot;) I've added an option to change the speed that the arms decorate the tree.</p> <p><strong>1.0.2 update:</strong> added fade effect to intro. Because I felt like it.</p> <p><strong>1.0.3 update:</strong> added combo indicator after user feeback.</p> <p><strong>1.0.4 update:</strong> gameplay exploit patch</p> https://www.lexaloffle.com/bbs/?tid=40826 https://www.lexaloffle.com/bbs/?tid=40826 Wed, 16 Dec 2020 13:32:01 UTC Silly Snow <h1>Silly Snow</h1> <p> <table><tr><td> <a href="/bbs/?pid=84926#p"> <img src="/bbs/thumbs/pico8_db_silly_snow-3.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=84926#p"> db_silly_snow</a><br><br> by <a href="/bbs/?uid=47206"> drakeblue</a> <br><br><br> <a href="/bbs/?pid=84926#p"> [Click to Play]</a> </td></tr></table> </p> <p>(update: added 2 new methods that incorporate gusts of wind)</p> <h2>Controls</h2> <p>UP/DOWN chooses different behaviour for the snow flakes<br /> X changes the colour of the falling snow so you can see the &quot;trick&quot; a bit easier<br /> O/Z toggles drawing the non-snow bits of the graphics each frame on and off</p> <h2>What it is</h2> <p>--</p> <p>TL;DR<br /> Only track falling snow, add lying snow to the background.</p> <p>--</p> <p>As it's that time of year again, I thought I'd write some winter-themed stuff.</p> <p>This is my take on ye olde faithful falling snow demo, which I'm using for the intro screen for another cart (on its way soon). I've cleaned it up a bit, added way more comments than I normally do and added some options to show what's happening a bit and show some variations. Click &quot;Code&quot; below the display above if you're interested.</p> <p>I first typed in a version of this demo from a magazine back in the mid 80s on an Atari 800XL. In the 35ish years since then I'm not sure I've ever implemented it again though.</p> <p>The gist of how it works:<br /> Falling snow is represented by a collection of simple particles that are represented by a position (x,y). Each frame that position is updated (mostly downwards i.e. y+=some_dist).<br /> If the snow flake encounters an obstacle (i.e. a pixel that isn't empty or the bottom of the screen) then the snow flake has landed. </p> <p>Tracking snow flakes after they've landed isn't practical (even with PICO-8 and it certainly wasn't with an 800XL) and is also a bit pointless since once a snow flake has landed, it aint going anywhere. Well, at least not in this demo. Instead, the particle is destroyed and the pixel where it landed is set to white and becomes part of the background (in this demo that means it has a colour greater than 1).<br /> This is an incredibly basic version of how a lot of &quot;physics&quot; engines work: updating only moving or otherwise interesting parts of the simulation and &quot;parking&quot; the rest. Usually they'd check the stationary particles in some way for whether they need to be activated. If you wanted to simulate collapsing drifts etc. there's nothing really to stop you keeping a record of where the snow has fallen or scanning for white pixels and perhaps checking to see if a condition to collapse has been met. Then you could find all the snow pixels affected by the collapsing pixel and &quot;wake&quot; them up. You would probably need to check at a slower than per frame rate for performance reasons.</p> <p>As with most sims the fun is in playing about with it, hence the 9 method variations in the cart e.g.</p> <ul> <li>the exact behaviour of the flakes in the air</li> <li>whether they land or slide to the side on encountering the background can be tweaked.</li> <li>wind gusts...</li> </ul> <h2>Blustery Blizzards</h2> <p>Methods 8 and 9 add a wind factor to the updated position for each flake generated by a value derived from complex and deep trigonometric knowledge (aka trial and error). The nice thing about this is it's only calculated once per frame and only added per snow flake so it's very cheap. Adding sin values per flake based on the flake's position gives some odd results.</p> <h2>Limitations</h2> <ul> <li> <p>for there to be stationary, landed snow relies on not clearing the screen. If you clear the screen then your pretty snow drifts all disappear. You can still have snow flakes falling, but they will disappear a frame after they land...</p> </li> <li> <p>The methods in this cart are fairly non-destructive of the background, but it's fairly easy to make a version that &quot;eats&quot; into what's already on the screen (acid snow!). I've included an option to re-draw the background each frame. As long as your background doesn't move then it works fine (remember, no screen clear).</p> </li> <li>&quot;tunneling&quot; through thin parts of the background varies in frequency depending on your method. Generally, the more random the snow flake movement, the more problems you'll have.</li> </ul> <p>On the Atari ST there was a game based around this that I got from a magazine cover disk called &quot;Downfall&quot;. I could only find one video of it and the uploader doesn't seem to really understand how to play it sadly.<br /> You were supposed to compete against a friend to draw lines to funnel snow from your side out of a hole at the bottom that would then be sent to their side (and vice versa). You could also funnel snow to the sides to give you energy for &quot;special powers&quot; that let you e.g. draw lines on your opponent's side.</p> <p>I expect that explanation's as clear as a blizzard; hopefully the code isn't as bad.</p> <p>Happy holidays :)</p> <p><object width="640" height="400"><param name="movie" value="https://www.youtube.com/v/SHZB9nIzZ64&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/SHZB9nIzZ64&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=40631 https://www.lexaloffle.com/bbs/?tid=40631 Wed, 02 Dec 2020 12:47:35 UTC The Pico Mermaid <h1>The Pico Mermaid</h1> <p> <table><tr><td> <a href="/bbs/?pid=84034#p"> <img src="/bbs/thumbs/pico8_thepicomermaid-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=84034#p"> thepicomermaid</a><br><br> by <a href="/bbs/?uid=47206"> drakeblue</a> <br><br><br> <a href="/bbs/?pid=84034#p"> [Click to Play]</a> </td></tr></table> </p> <p>Use (X) to control the Pico Mermaid as she fetches pearls from the bottom of the sea back to the surface. Avoid the piranhas that will swim faster and faster as the mermaid retrieves more pearls.</p> <p>This is my entry into Tweet Tweet Jam 5 and so the code fits into 560 characters (two tweets).</p> <h3>Features:</h3> <ul> <li>Single-button controls (X)</li> <li>Animated and multi-colour pixel art sprites*</li> <li>air and water physics*</li> <li>Two particle systems*</li> <li>Difficulty ramp*</li> <li>Score effect*</li> <li>Death effect*</li> <li>start animation*</li> <li>current score and high score display*</li> <li>in-game instructions*</li> </ul> <p>(* kinda)</p> <p>Here's the code:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>P=pset::A::x=64y=0v=-9t=0w=127e=0d=.6s=0for z=0,29do poke(z\6*x+z%6+1,'0x'..sub('e0e800800880ce0800444404e008002444043b0000444480b000004088',1+z*2,2+z*2))end::B::P(69,e&lt;1and w or y,7)H=max(s,H)e=y&gt;119and 1or e ?'tAP❎ gET●:'..s..' hI:'..H v+=.5C=cls if(y&gt;8)v-=max(btnp(❎)and 2or.4,v-.6) flip()C(1)rectfill(0,0,w,9,12)t=(t+d)%w y=min(120,y+v)spr(0,x,y,1,1,t&amp;8&lt;1,e&lt;1)for i=3,14do d=-d t=-t h=40*i-t-4&amp;w pal(4,i)k=i*8k+=7*sin(h/w)if((h+4)\8==8and 4&gt;abs(k-y-2))C(8)goto A spr(1,h,k,1,1,d&lt;0)P(rnd(8)+x,h*d%y+3)P(k*d/.7,h\d)end if(y&lt;9and e&gt;0)e=0s+=1d*=-1.2C(7) goto B</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=40311 https://www.lexaloffle.com/bbs/?tid=40311 Mon, 09 Nov 2020 11:37:59 UTC P8C-BUN on Itch.io <p>P8C-BUN is now uploaded to itch.io here: <a href="https://drake-blue.itch.io/p8c-bun"><a href="https://drake-blue.itch.io/p8c-bun">https://drake-blue.itch.io/p8c-bun</a></a><br /> <object width="640" height="400"><param name="movie" value="https://www.youtube.com/v/QmOxLX-82RY&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/QmOxLX-82RY&hl=en&fs=1&rel=0" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="640" height="400"></embed></object></p> <p>I've also been working on something else that I really want to share a preview of soon, but I need to clean it up just a little more first.</p> https://www.lexaloffle.com/bbs/?tid=40116 https://www.lexaloffle.com/bbs/?tid=40116 Mon, 02 Nov 2020 14:36:10 UTC P8C-BUN <h1>P8C-BUN</h1> <p> <table><tr><td> <a href="/bbs/?pid=83276#p"> <img src="/bbs/thumbs/pico8_drakeblue_p8cbun-3.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=83276#p"> drakeblue_p8cbun</a><br><br> by <a href="/bbs/?uid=47206"> drakeblue</a> <br><br><br> <a href="/bbs/?pid=83276#p"> [Click to Play]</a> </td></tr></table> </p> <p>Help your chosen bunny cover each level with poo then escape down a rabbit hole. Don't get caught by the fox or anything else that's out to get you!</p> <p>Finish all 16 levels without restarting to achieve &quot;Iron Bun&quot; or just try to post a high score. Start at whatever level you like.</p> <h3>Controls</h3> <ul> <li>Use the d-pad/arrow keys to direct your bunny.</li> <li>z/c/(O) to show where you haven't pooed yet.</li> <li>x to paws and return to the title screen.</li> <li>You can toggle the music or return to the title screen from the menu as well.</li> </ul> <h3>Tips</h3> <ul> <li>The buns will keep running in the direction you choose until there's no clear path in front of them so there's no need to hold down the arrow keys.</li> <li>If you choose a new direction before a junction or corner they'll remember that and turn immediately so corner early for maximum speed.</li> <li>The bananas will give you a speed boost, but leave skins behind that you (or the fox) may slip on.</li> <li>Macaroon is too tough to be caught by the fox or anything else so if you just want to play through the levels (or practice) choose to play as her. Real-life Macaroon has seen off cats and kills blankets on a regular basis.</li> <li>The fox gets faster and the red kite will fly over as you cover more levels so choose which level you start from wisely if you're aiming for Iron Bun.</li> <li>Manipulate the fox, especially using the rabbit holes, to make your life easier.</li> </ul> <p><object width="640" height="400"><param name="movie" value="https://www.youtube.com/v/5PR46S5bCCA&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/5PR46S5bCCA&hl=en&fs=1&rel=0" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="640" height="400"></embed></object></p> <p>1.1 Update: text legibility and efficiency improvements<br /> 1.1.1: restored kite spawn to be after a few levels and not right from the beginning.<br /> 1.1.3: tweaks to menu, sfx. Not sure what happened to 1.1.2</p> <p>This is my first PICO-8 game (in fact, my first full game for anything) so any feedback is welcome. More info about P8C-BUN is here: <a href="https://drakeblue.com">DrakeBlue.com</a>.<br /> This game was inspired by our real-life pet rabbits (especially Oreo, who does tend to poo everywhere) and exists thanks to the patience of my gf (who runs the rabbit-oriented website mentioned on the title screen <a href="https://rabbitretail.co.uk">RabbitRetail.co.uk</a> and uses it to help donate to rabbit charities and rescues as well as feed ourselves and our own bunnies).</p> <p>Purchase to download and support more development and bunnies here: <a href="https://rabbitretail.co.uk/products/p8c-bun-game"><a href="https://rabbitretail.co.uk/products/p8c-bun-game">https://rabbitretail.co.uk/products/p8c-bun-game</a></a></p> <p>Or here: <a href="https://drake-blue.itch.io/p8c-bun">Drake Blue on Itch.io</a></p> https://www.lexaloffle.com/bbs/?tid=40018 https://www.lexaloffle.com/bbs/?tid=40018 Sat, 24 Oct 2020 11:46:09 UTC