Log In  
Follow
Overkill
SHOW MORE

Cart #48050 | 2018-01-11 | Code ▽ | Embed ▽ | No License
11

I made this the other day when just playing around with making an ellipse function for PICO-8 https://twitter.com/eggboycolor/status/949173853045043200 -- but I've decided to post on the BBS.

The core of the effect is a scanline-based ellipse drawing function, based on an algorithm that uses Bresenham style error calculation. The code for the ellipse function is described in a paper called "A Fast Bresenham Algorithm for Drawing Ellipses" by John Kennedy, and I've ported the pseudo-code to work with pico8.

Here's the ellipse function code:

-- based on:
-- "a fast bresenham type
-- algorithm for drawing
-- ellipses" by john kennedy
function ellipse(
 cx,cy,xr,yr,c,hlinefunc
)
 xr=flr(xr)
 yr=flr(yr)
 hlinefunc=hlinefunc or rectfill
 local xrsq=shr(xr*xr,16)
 local yrsq=shr(yr*yr,16)
 local a=2*xrsq
 local b=2*yrsq
 local x=xr
 local y=0
 local xc=yrsq*(1-2*xr)
 local yc=xrsq
 local err=0
 local ex=b*xr
 local ey=0
 while ex>=ey do
  local dy=cy-y
  hlinefunc(cx-x,cy-y,cx+x,dy,c)
  dy+=y*2
  hlinefunc(cx-x,dy,cx+x,dy,c)
  y+=1
  ey+=a
  err+=yc
  yc+=a
  if 2*err+xc>0 then
   x-=1
   ex-=b
   err+=xc
   xc+=b
  end
 end

 x=0
 y=yr
 xc=yrsq
 yc=xrsq*(1-2*yr)
 err=0
 ex=0
 ey=a*yr
 while ex<=ey do
  local dy=cy-y
  hlinefunc(cx-x,cy-y,cx+x,dy,c)
  dy+=y*2
  hlinefunc(cx-x,dy,cx+x,dy,c)
  x+=1
  ex+=b
  err+=xc
  xc+=b
  if 2*err+yc>0 then
   y-=1
   ey-=a
   err+=yc
   yc+=a
  end
 end
end

Could probably be optimized further.

The main difference from the original pseudo-code is just that rather than a "plot four ellipse points" function, I made the program a callback for each horizontal line segment to be drawn, and call that function twice for the top and bottom halves of the ellipse. This function defaults to rectfill but could be any function that takes (x, y, x2, y2, color), which is what I do for the wavy effect pictured in my cart. And if you poke certain settings, as you can see, fill patterns can be passed along too -- I guess really, you could interpret color any way you want for your particular scanline fill function.

Oh, also, it shifts the x and y radius down a bunch before squaring to avoid signed 16.16 fixed-point overflow.

And if you're more curious about the wavy effect, feel free to hit the view source (Code) button. I bet there are much cooler effects possible with a scanline based fill algorithm, but I was just trying to throw something together really quick.

P#48051 2018-01-10 20:08 ( Edited 2018-01-11 01:50)

SHOW MORE

This is my WIP cart for Wandering Magic, a little ys-like action rpg I was working on for #3cjam.

I ran out of time for the Jam deadline, but I tried to submit a playable work-in-progress. It will sort of abruptly end once you complete a specific quest, but the game up to that point should be playable.

O (Z on keyboard) = opens the menu
X (X on keyboard) = use magic attack (if you have it)

The Great Fiend has returned, and you need to stop them before it's too late. Along the way, you fight monsters, meet various people who will help you on your travels.

Walk into enemies to use your melee attack.
Defeating enemies gives you money to buy new items.
If you defeat enough enemies you will level up, which also restores your HP and gradually gives you small stat boosts.
Buy items to increase your attack and defense.
Be ready to use cure potions during battles and save often.
This is a bit more of an RPG than an action game, so evading attacks while important isn't the only key to survival. Boosting your attack/defense and healing is required to progress.

Anyway, I hope to release a more complete version some day, I just ran out of time for this jam. Hope you enjoy!

P#45717 2017-10-31 12:44 ( Edited 2018-09-16 00:01)

SHOW MORE

A cover of the boss theme from Lufia II: Rise of the Sinistrals. Only the first 25 seconds or so. Not sure if I'll finish this, or if it's even possible to fit the whole 1 minute loop within the pattern limit of pico8, but I had fun messing with the tracker for a bit.

P#41293 2017-06-04 18:08 ( Edited 2017-06-04 22:15)

SHOW MORE

This quirk exists in Pico8 0.1.10C, and possibly other versions. Try this code out, and you'll see the results for i>=16 are the same:

for i=1,20 do
 print(i .. ' ' .. 1.11^i)
end

1.11 raised to the 20th power (equal to ~8.062) is still well within the overflow limits of pico8, but currently pico8 limits the possible exponent to 16 (resulting in ~5.309).

Here's a workaround for raising to higher power (if y is a positive power), that takes advantage of the property x^m * x^n = x^(m+n):

function pow(x,y)
local r=1
while y>0 do
r*=x^min(y,16)
y-=16
end
return r
end

Using this you can get the correct results. Just wondering, is this limitation intentional? It makes sense for integers, because 2^15-1 is the greatest positive integer you can express, and occasionally you might want to do 2^16 (if you're careful about the sign bit), but for fractions it seems a little strange. It's kind of uncommon to do this kind of math in pico8, so I could maybe live with it being a quirk of pico-8 though. If this behaviour isn't changed, could this limitation be documented in the manual?

P#40887 2017-05-23 03:00 ( Edited 2017-05-27 05:27)

SHOW MORE

Hey everyone! I made a some little helper functions for allowing some basic handling of positive integers greater than 32767. Feel free to expand/modify these. Use for whatever, no attribution required.

This could be useful for a score counter in a game or experience points in an RPG. There's a minor overhead in code-size and in performance, so these are when you really need a little bit of of extra precision for your numbers.

Unsigned Integer Decimal Strings

This first method handles numeric strings holding arbitrary unsigned integers. I haven't added sign handling or fraction handling, so this is for unsigned integers only, but may come in handy for score / experience counter sort of things. I also added a comparison function so you can tell if a decimal string is greater than/less than/equal to another one.

The disadvantage is number of allocations required for calculating intermediate results, lots of concenation and substring ops. But if you're doing this for non-time critical functionality, and want the extra digits, it's pretty handy.

function decdigit(s,i)
 local c=sub(s,-i,-i)
 return c~='' and 0+c or 0
end

function declen(s)
 for i=1,#s do
  if(sub(s,i,i)~='0')return #s-i+1
 end
 return 1
end

function decadd(a,b)
 local s=''
 local c=0
 for i=1,max(declen(a),declen(b)) do
  local v=decdigit(a,i)+decdigit(b,i)+c
  c=0
  if(v>9)v-=10 c=1
  if(#s>0 or v>0 or c>0 or i==1)s=v..s
 end
 if(c>0)s='1'..s
 return s
end

function deccmp(a,b)
 local m=declen(a)
 local n=declen(b)
 if(m<n)return -1
 if(m>n)return 1
 for i=1,n do
  local u=decdigit(a,i)
  local v=decdigit(b,i)
  if(u<v)return -1
  if(u>v)return 1
 end
 return 0
end

-- Addition test cases
print(decadd('1','1')) -- 2
print(decadd('999','1')) -- 1000
print(decadd('123456789','99999')) -- 123556788
print(decadd('10009','20009')) -- 30018

-- Comparison test cases
print(deccmp('2000','200')) -- 1 (a>b)
print(deccmp('2000','1000')) -- 1 (a>b)
print(deccmp('2000','2000')) -- 0 (equal)
print(deccmp('1','00001')) -- 0 (equal)
print(deccmp('200','2000')) -- -1 (a<b)
print(deccmp('1000','2000')) -- -1 (a<b)

Binary Coded Decimals

A second method takes advantage of a fairly ancient number format, called Binary Coded Decimal notation. It stores values in so-called "packed BCD notation", where one decimal digit 0-9 is stored per 4-bit hex digit of the number. The format used by this library supports numbers up to 7 places (0 .. 9999999). To accomplish this, it uses 3 nybbles from the integer part and 4 nybbles from the fractional part of the number. In this notation, 1 is 0x0.0001, 123 is 0x0.0123, 12345 is 0x1.2345, 1234567 is 0x123.4567, which is a bit clumsy to write, but keeps numbers to single tokens.

The possible advantage that BCD has over numeric strings is that it avoids allocations and string slicing/coercion, and just manipulates nybbles of numbers directly. Also, because the BCD value is still a number, regular comparison operations can be used (as long as both sides are in BCD format), saving some tokens.

When printing, the numbers must be converted back into strings. Also, as mentioned before, there are only 7 digits to work with.

function bcddigit(a,n)
 local r=n<4
  and shl(a,(4-n)*4)
  or shr(a,(n-4)*4)
 return band(r,15)
end

function bcdtostr(a)
 local s=''
 for i=6,0,-1 do
  local v=bcddigit(a,i)
  if(#s>0 or v>0 or i==0)s=s..v
 end
 return s
end

function bcdfromint(n)
return 0+('0x'..flr(n/10000)..'.'..sub(10000+n%10000,-4))
end

function bcdadd(a,b)
 local r=0
 local c=0
 for i=0,6 do
  local v=bcddigit(a,i)+bcddigit(b,i)+c
  c=0
  if(v>9)v-=10 c=1
  r+=shl(shr(v,16),i*4)
 end
 if(c>0)return 0x999.9999
 return r
end

function bcdsub(a,b)
 local r=0
 local c=0
 for i=0,6 do
  local v=bcddigit(a,i)-bcddigit(b,i)-c
  c=0
  if(v<0)v+=10 c=1
  r+=shl(shr(v,16),i*4)
 end
 if(c>0)return 0
 return r
end

-- Examples:
print(bcdtostr(0x000.0000)) -- 0
print(bcdtostr(0x000.0001)) -- 1
print(bcdtostr(0x10.0000)) -- 100000
print(bcdtostr(0x123.4567)) -- 1234567
print(bcdtostr(bcdadd(0x1.0009, 0x2.0009))) -- 10009 + 20009 = 30018
print(bcdtostr(bcdsub(0x0.0090, 0x0.0017))) -- 90 - 17 = 73

So far, I've written support to add/subtract two BCD numbers, as well as convert from BCD representation to a string. Note that this only handles positive integers (no sign bit or fractional part). It also does not error-check, so make sure all values passed are already in packed BCD notation it expects.

Ideas:

  • (code removal) To perform arithmetic with wraparound rather than clamping when out-of-range, remove the carry-checks after the for loops in bcdadd/bcdsub.
  • (code removal) If you don't need subtraction, you can delete the code for it. Acceptable for score systems or RPG experience, but probably not gold.
  • (code removal) If you never need to convert normal integers into BCD format, you can remove the code for it.
  • (small code required) To allow even larger integers, the carry flag could also be returned and somehow fed into further calculations. Perhaps an optional parameter to bcdadd/sub that specifies the initial carry flag (defaults to 0), and have an optional second return value for the result carry. Then you could chain together additions to make numbers with even more digits. However, when you print this, you will need a way to force leading zeros to be included on any lower "words" of the number, or it will incorrectly skip them.
  • (medium/large code required) Another way to allow even larger BCD integers without exposing the rest of the code to the carry flag would be to always pass multiple "words", like say two values (one containing the upper 7 digits, one containing the lower 7 digits), and return them all as one unit.
  • (lots of code) put them in a array of 7-digit values and go for a full-on variable-length bigint type. Would require too much code to be worth it, probably.
  • (small code change, possibly new code if comparision is required) You could use 8 digits instead of 7 digits inside the BCD value with some minor adjustments, but then you will need to provide your own order-comparison functions rather than using the builtin comparison operators, because of the sign bit. Equality checks should still work though. Score counters would probably be fine without a compare operator, but RPG experience and gold would require a new function to handle comparing 8-digit BCD.
P#40881 2017-05-22 19:23 ( Edited 2018-08-07 12:29)

SHOW MORE

Interesting discovery today: Because the latest version of pico8 supports more punctuation characters, it's now possible to encode binary strings as base64!

I prototyped this on pico8 0.1.8. By using this we can slightly improve the size of encoding binary data in text form, it's 1.33333x bigger compared to hex's 2x size. Granted, pico8 does some compression to the code, so not sure if the added entropy of packing things closer together will work in everyone's favor, but this is worth a shot. It won't be standard base64, because there's no distinction between uppercase and lowercase, but there's enough special characters to make up a custom 64-character dictionary for the encoding.

-- Set up the encoding/decoding lookup tables.
b64d={['']=0}b64e={}b64c='0123456789abcdefghijklmnopqrstuvwxyz-_+[]{}|:;=.<>?/~`!@#$%^&*()'
for i=1,#b64c do
local c=sub(b64c,i,i)
b64e[i-1]=c
b64d[c]=i-1
end

-- encodes a table containing 8-bit numbers (binary data) into a base64 string
function encode(t)
local s,e='',b64e
for i=1,flr(#t/3)*3,3 do
s=s..e[flr(t[i]/4)]..e[bor(t[i]%4*16,flr(t[i+1]/16))]..e[bor(t[i+1]%16*4,flr(t[i+2]/64))]..e[t[i+2]%64]
end
if(#t%3==1)s=s..e[flr(t[#t]/4)]..e[t[#t]%4*16]
if(#t%3==2)s=s..e[flr(t[#t-1]/4)]..e[bor(t[#t-1]%4*16,flr(t[#t]/16))]..e[t[#t]%16*4]
return s
end

-- decodes a base64 string into a table of numbers
function decode(s)
local t,d={},b64d
for i=1,#s,4 do
local x,y,z,w=sub(s,i,i),sub(s,i+1,i+1),sub(s,i+2,i+2),sub(s,i+3,i+3)
add(t,bor(d[x]*4,flr(d[y]/16)))
if(#z>0)add(t,bor(d[y]%16*16,flr(d[z]/4)))
if(#w>0)add(t,bor(d[z]%4*64,d[w]))
end
return t
end

-- testing it out
for i in all(decode(encode{1,2,3,4,5}))do
print(i)
end

I plan to try this on a project of mine which has lots of data tables stored in strings. Not sure if it's a huge improvement over hex when compressed yet, but I thought it was worth trying! Feel free to use / adjust for your own projects. Depending on how things are set up, you can remove the encoder entirely and just have the base64 decoder.

P#24032 2016-06-30 20:46 ( Edited 2016-07-17 03:32)

SHOW MORE

Cart #20597 | 2016-05-15 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
18

I made a map maker for Pico-8! Unlike the built in map editor, it represents maps a little differently -- the format is a collection of rectangular tile fills + a bunch of entities you can place on top. It saves all maps together in a custom data format to the cartridge's shared map/tileset area. It has undo, redo functionality on the tile layer. And there's fun sfx as you edit stuff! The main benefit of this tool is that for maps with simpler layouts, expressing a map as a set of rectangular fills is much smaller than a 16x16 tilemap, so you can hold a lot more screens of map data than using the direct map editor. The less objects, the less map space required.

Anyone can feel free to alter this or reuse bits for their own maps. I made this for a little rpg project I've been planning out, and just to have fun. In-game instructions are included, but assume a default keyboard layout, rather than describing things in terms of player 1 and player 2 buttons. But I've included them here as well, for good measure!

DRAW MODE
Z = draw
X = undo
S = redo
A = tileset

ENTITY MODE
Z = draw
A = tileset

MENUS
Z = acccept
X = cancel

OTHER
F = file menu
E = toggle between draw and entity mode

There's no import/export cart functionality, but it's easy enough to add with reload()/cstore(), or just copying the data out of the gfx section in the p8 file.

P#20596 2016-05-15 00:36 ( Edited 2016-05-17 06:59)

SHOW MORE

I posted this PICO-8 theme + syntax coloring file for Sublime Text 2 if anybody wants! The syntax highlighting supports PICO-8 Lua code including the PICO-8 library functions. The color scheme uses the PICO-8 palette, and gets pretty close to the look of the editor.

Finally, for an extra touch, I included a settings file to use an unofficial TTF Font by RhythmLynx, reduce the font size, disable anti-aliasing, increase line padding, and force indentation to be a single space like in native PICO-8. (Uses a font size of 7.5, but a font size of 3.5 seems to be a 1x scale font, for those who want extremely hi-res views of their code)

You can get it here!

P#19435 2016-03-27 16:56 ( Edited 2016-04-02 20:45)

SHOW MORE

Hey, just thought I'd post this code in case anyone else finds it useful.

If you need a few extra sfx slots in your pico8 cart and can spare the code, here's a little thing that will load a sound into RAM. It takes a table of 168 4-bit values in the .p8 sfx format, and writes out a 68-byte sfx block into one of the sfx slots in memory.

It's a complete test program, tweak, adjust and reuse to your liking.

-- unpack a hex string into a
-- table of numbers.
function unhex(s,n)
 n = n or 2
 local t = {}
 for i = 1, #s, n do
  add(t, ('0x' ..
   sub(s, i, i+n-1)) + 0)
 end
 return t
end

-- unpack a data string
-- including two sounds
-- taken out of a p8 file.
sfxdata = unhex(
   '010800003c6751f473234731347'
.. '3006253c615006253c6050c6150'
.. '00000c615000000000000000000'
.. '000000000000000000000000000'
.. '000000000000000000000000000'
.. '000000000000000000000000000'
.. '000000010300003c63515073182'
.. '701f271232711c2711827113271'
.. '0e2710021124300243002430024'
.. '300243002430024300243002430'
.. '024300243002430024300243002'
.. '430024300243002430024300243'
.. '002430024300', 1)

-- load a new sound into sfx
-- memory at runtime.
--
-- arguments:
-- d = dest sfx slot 0 .. 63
-- s = source sfx number in
--     sfxdata table, 0-based
--     index. each sound is 168
--     values of the table.
-- note: sfxdata must exist as
--       a global or upvalue.
function loadsfx(d, s)
 local t = sfxdata
 d = 0x3200 + 68 * d
 s *= 168
 for i = 0, 3 do
  poke(d + 64 + i, t[s + i * 2 + 1] * 16 + t[s + i * 2 + 2])
 end
 for i = 0, 31 do
  local a = d + i * 2
  local b = s + i * 5 + 9
  poke(a, (t[b] * 16 + t[b + 1]) % 64 + t[b + 2] * 64)
  poke(a + 1, band(t[b + 2], 4) / 4 + t[b + 3] % 8 * 2 + t[b + 4] * 16)
 end
end

-- load sound 0 from sfxdata
-- into the sfx ram at slot #2.
loadsfx(2, 0)
-- play sfx slot 2.
sfx(2)

-- loop forever so we get
-- to hear the sound play.
while true do flip() end

(also thanks to dansanderson, these notes were handy!)

To use your own sounds in this code, change the string in the sfxdata = unhex('...', 1) line with new sfx taken out of a sfx section of a p8 cartridge file. You can open a .p8 file from your carts folder in any text editor, such as notepad, and each line under the sfx section is one sound that you can copy and paste. To specify multiple sounds simply paste them back-to-back in the string, and then use loadsfx() with a different source index to address the various sounds.

Someone should do something to load song data also, and then you could do stuff to load extra songs from code, or generate procedural music!

(note: some of the formatting was to get the BBS to display things correctly with long lines of code, ideally you'd have have one big line with the sfx data all mashed together to save characters and tokens, and to make it easier to copy-and-paste the hex from another p8's sfx data)

P#18828 2016-02-14 02:45 ( Edited 2016-02-14 19:52)

Follow Lexaloffle:          
Generated 2024-03-19 01:41:55 | 0.109s | Q:23