Log In  


aka writing down how my cart works before I forget.

This is about my game, Halloween Horrors, which is here.

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 Demystify the Christmas Tree 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.

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(!).

General Notes

Sprite sheets

There are 3 sprite sheets:

  • "game" the built-in PICO-8 one which has the kittens, the breakable objects, candles and monsters.
  • "house" the map tiles for the house background
  • "title" the letters for the intro titles

The latter two are encoded in "dbi" format - i.e. the output of my Python image downsampling and compression code (dbi=="drake blue image"; 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 "upper memory" above 0x8000, but it was slower.

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.

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 :)

Editor

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 Tiled using the plugin here. This worked very well. I use Aseprite for most sprite work, but for some reason I did all drawing in PICO-8 for this game.

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 "off-grid".
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.

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.

I liked adding the rectfill areas that paint the walls so much that I used that to draw the "paintings" instead of using more sprites. I considered adding a bunch of shadows this way, but ran out of time/patience.
PICO-8 draws rectangles so fast that this didn't seem to dent performance at all.

In retrospect, I would have liked to do one of two things:

  1. implement the editor in another environment eg. Love2D

Primarily so I could take advantage of a bigger resolution window and make the interface a bit nicer.
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.

  1. make the editor and game one cart

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.
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.
So I wrote a little bash script to run between saves. Less annoying.
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...
This has also made me wonder about making user-definable levels for a future game...

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 "dev mode" 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 "dev mode only" banner or "over token limit" that flashes up every ten seconds or something when run like that...

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.

Cart #hh_editor-0 | 2021-11-01 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
4

X shows platforms, walls, floors and highlights current selection for those (and the "paints").
N shows monsters, candles, dec(orations).
There's a bunch of other obscure key commands.

Worst bit: delete (-) has no "are you sure?" and there's no undo.

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.

Initialisation

I pull in the two "extra" sprite sheets first ("house", "title") then store the cart's one ("game") 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.

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.

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.

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.

During this project I wrote about how I was processing data stored in strings into tables: see here. 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).
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.

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:

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

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:

g_things=unpack_data'5,x,y,vx,vy,col,10,10,0,0,12,20,20,0,0,11'

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.
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.

Having an editor to automatically output the large strings involved in this approach is quite helpful.

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.

Game modes, _draw and _update

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...

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.
_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).

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 ;) ):

-- 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)<0.1 then
  g_ss.x,g_ss.y=0,0
 end

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 "stuck" slightly off centre.
My "cam" 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.
There's a "shake" function that actually kicks it off.

Then it calls whatever update function is currently set.

_draw()
This resets the palette and camera and calls the current draw function. It also does the lightning - which is the same in every mode.
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.

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.

In the menu, you can switch the storm off and that restores the normal palette and stops the lightning.

Title

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.

Snow to rain

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 "caught" on the letters to the extent that the rain appeared to stop. So I hijacked the "simpler snow" (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.

Titles

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.
For ages it still talked about Christmas after that until I could face updating the title data to be the right words...
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.

The code is still there, commented out.

This mode also uses the screen fade routine with one of the variants described here for added wibbliness. This is the same effect as used in PICO Space.

Start

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.

Play

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.
I considered various strategies to bucket entities, update on alternating frames etc.

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.

I was disappointed and relieved at the same time :)

Update

  • 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.
  • move kittens and check how they're interacting with platforms, floors etc.
    -- 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).
    -- floors are impassible from above and below
    -- walls are impassible from left or right
    There are many, many platforms and not many floors or walls
  • check if kittens have hit anything with their bops and react
    -- spawns some particles
    -- converts decorations to "fallers"
    I've just noticed I still have some of the bonus code in there even though it isn't used in this game...

Also, you can see where I hacked the collision routine for the kittens (in_rect) with an unexpected "+8" 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(!).

Draw

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:

  • draw background
  • load game sprites
  • draw decorations, candles
  • draw monsters
  • draw kittens
  • draw HUD
  • load house sprites
  • draw stairs and parts of doorways in foreground

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...

When the game ends, I use the title sprites for the "Happy Halloween" 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.

Conclusion

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 "down".

The editor worked really well and I was pleased with the data (un)packing functions.

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...

Unminified Code

Warning: this is pretty messy and not v efficient. I haven't really done any token optimisation on it at all.
If you have any questions about it, please comment and we can puzzle out what on earth it does together :)

For reference only...


-- halloween_horrors
-- by drakeblue


--returns random integer (vs float from rnd)
function rint(...) return rndr(...)&-1 end


-- rndr(t,r)==rnd(t)+r
function rndr(t,r)
return rnd(t)+(r and r or 0)
end


-- generates a random direction
function rnd_dir(m)
local angle,d=rnd(),rnd(m)
return dsin(angle),dcos(angle)
end


function buttonp(b)
local p=btnp(b)
if b==❎ and p then
sfx(62,3,10,2)
elseif b==🅾️ and p then
sfx(62,3,7,2)
elseif p then
sfx(62,3,11,1)
end
return p
end


function use_sprites(sprites)
if curr_spr!=sprites then
curr_spr=sprites
local data=sprites.data -- v slightly faster
poke4(0,unpack(data))
end
pal(sprites.pl)
end


decode={

function(rle)
out={}
for i=1,#rle\2 do
for j=0,rle[i2-1] do
add(out,rle[i
2])
end
end
return out
end,
-- decodes data where 0s are rle only
function(rle0)
local out,i,len={},1,#rle0
while i<=len do
local data=rle0[i]
if data==0 then
local rep=rle0[i+1]
for j=0,rep do
add(out,0)
end
i+=2
else
add(out,data)
i+=1
end
end
return out
end
}


-- take list of raw pixel colour values
-- and pack into 4byte chunks for poke4
function pack_sprite_data(data)
local out,val={},0
for m,d in pairs(data) do
val=(val>>>4)|(d<<12)
if m%8==0 then
-- note horrendous swizzle to get pixels in correct order for p8
-- isn't needed below since built into decoding
-- out[(m>>3)-1]=(band(val,0xf0f0.f0f0)>>4) + (band(val,0x0f0f.0f0f)<<4)
add(out,val)
val=0
end
end
return out
end


expand8bit={
--expands 8bit data already decoded from str
[2]=function(data,ptr,d_len)
local out={}
for i=ptr,#data do
local val=data[i]
for j=3,0,-1 do
add(out,(val>>>(j2))&0x3)
d_len-=1
if d_len==0 then return out end
end
end
return out
end,
--expands 8bit data already decoded from str
[3]=function(data,ptr,d_len)
local out,val,md={},0,(ptr+2)%3
for i=ptr,#data do
val=(val<<8)+(data[i]>>8)
if i%3==md then
for j=0,7 do
add(out,(val>>>((7-j)
3-8))&7)
d_len-=1
if(d_len==0) return out
end
end
end
return out
end,
--expands 8bit data already decoded from str
[4]=function(data,ptr,d_len)
local out={}
for i=ptr,#data do
add(out,(data[i]>>>4)&0xf)
add(out,data[i]&0xf)
end
return out
end
}


function unpack_dbi(dbi_data)
-- read first value to get trans
local trans, raw=ord(sub(dbi_data,1,1)),{}
for r=2,#dbi_data do
raw[r-2]=(ord(sub(dbi_data,r,r))-trans)&0xff
end
local width,height,cols,pl,ptr=raw[0],raw[1],raw[2],{},3
local bpp=(cols-8>0) and 4 or (cols-4>0) and 3 or (cols-2>0) and 2 or 1
-- printh("w:"..width.." h:"..height.." cols:"..cols)
for i=0,cols-1 do
pl[i]=raw[ptr]
ptr+=1
-- printh(pl[i])
end
local comp=raw[ptr]
ptr+=1
--16bit value for num values to extract from 8bit data
local d_len=(raw[ptr]<<8)+raw[ptr+1]
ptr+=2
-- print("d_len:"..d_len)
local d_comp = expand8bitbpp
-- debug expand 8bit
-- local d_str=''
--for d=1,#d_rle0 do
-- d_str=d_str..d_rle0[d]
--end
--printh(d_str)

-- debug rle decode
--local d_raw=decode_rle(d_rle0)
--local d_str=''
--for d=1,#d_raw do
--d_str=d_str..d_raw[d]
--end
--printh(d_str)
local sprites = {data=pack_sprite_data(decodecomp),pl=pl,w=width,h=height}
--use_sprites(sprites)
--cls()
--spr(0,0,0,16,16)
--while not btnp(❎) do end
return sprites
end

-- resets the draw palette
-- without touching the screen palette
-- like pal() would

function reset_dr_pal()
poke4(0x5f00,0x0302.0110,0x0706.0504,0x0b0a.0908,0x0f0e.0d0c)
end


function wait(w)
for i=0,w do flip() end
end


-- called from fade, try not to call directly
function set_upd_func(new_upd_func)
upd_func=new_upd_func
end


-- called from fade, try not to call directly
function set_dr_func(new_dr_func)
dr_func=new_dr_func
end


function set_fade(speed,
down_funcs,down_params,
up_funcs,up_params)
set_upd_func(blank)
set_dr_func(blank)
fd={
val=1,
dir=speed,
down_funcs=down_funcs,
down_params=down_params,
up_funcs=up_funcs,
up_params=up_params
}
end


function fade()
if fd.val==-10 then
return --not fading
else
if fd.val<=0 then -- end of fade
-- do changes for end of fade
if fd.up_funcs then for i in pairs(fd.up_funcs) do fd.up_funcsi end end
fd.val=-10
return
--todo: restore buttonp(❎) or --skip
elseif fd.val>8 then
-- faded to black
-- so do fade down change while invisible
-- and start fade up
if fd.down_funcs then for i in pairs(fd.down_funcs) do fd.down_funcsi end end
fd.dir=-fd.dir
fd.val=8 -- make sure won't get hit again
end
-- set the colours (actual fade)
fade_pal(fd.val)
fd.val+=fd.dir
end
end


-- fades screen palette for light levels (global)
function fade_pal(lvl)
local ad=0x5f10
for c=0,15 do
-- for all colours
for i=1,lvl do
-- for requested levels
poke(ad,scpal_map[@ad])
end
ad+=1
end
end


-- gets length of printed string
-- takes into account special chars etc
-- doesn't work over multiple lines
-- or with strings that print longer than
-- 256 pixels...
-- assumes y=128 is off screen
-- be careful of camera()
function len_print(s)
print(s..'\0',0,128)
return @0x5f26
end


-- doesn't use replaced m and w for speed
function print_centre(s,y,c,out)
fun=out and pout or print
fun(s,63-len_print(s)\2,y,c)
end


function unpack_split(s)
return unpack(split(s))
end


function pout(s,x,y,c,o) -- 30 tokens, 5.7 seconds
color(o)
?'-f'..s..'\^g-h'..s..'\^g|f'..s..'\^g|h'..s,x,y
?s,x,y,c
end


-- checks if two points are in a rect with sides t and s
-- if t is not passed then treated as square

-- note the "-8" for the y axis is an enormous hack to get round
-- whatever the hell you were doing drawing the kittens
-- like you did, nowhere near their origin. This func is only
-- used for kitten collision atm so you can get away with it.

-- todo - remove the many adjustments for kitten position
function in_rect(a,b,s,t)
return abs(a.x-b.x)<s and abs(a.y-b.y-8)<(t or s)
end


--returns random integer (vs float from rnd)
function rint(...) return rndr(...)&-1 end


-- rndr(t,r)==rnd(t)+r
function rndr(t,r)
return rnd(t)+(r or 0)
end

function _update60()
pulse=(pulse+1)%192
fr=pulse\8%3

-- update_ss()
g_ss.x+=g_ss.vx
g_ss.y+=g_ss.vy

g_ss.r,g_ss.b=g_ss.vx0.6-g_ss.x/5,g_ss.vy0.6-g_ss.y/5
if abs(g_ss.vx)+abs(g_ss.vy)<0.1 then
g_ss.x,g_ss.y=0,0
end
upd_func()

end

function blank()
end

-- screen shake

function shake(d)
-- r=vx, b==vy
local x,y=rnd_dir(d)
g_ss.r+=x
g_ss.b+=y
end

function cam(x,y)
camera((x or 0)+g_ss.x,(y or 0)+g_ss.y)
end

function kit_flinch(k,e)
if k.flinch==0 then
sfx(60,3,rnd{20,24,28},4)
k.flinch=45
k.face=k.x<e.x
k.vx=k.face and -1.5 or 1.5
shake(0.2)
end
end

-- has kitten hit something
function kit_hit(k,t,w,h)
return abs(k.y-t.y-8)<(h or 16) and abs(k.x-t.x+(k.face and 18 or -10))<(w or 16)
end

function up_play()

if not fin then
-- move baddies
for g,ghost in pairs(g_ghosts) do
local newx=ghost.x+ghost.vx
if ghost.x>1000 then
ghost.vx=-1
elseif (ghost.y<128 and ghost.x<260) or ghost.x<128 then
ghost.vx=1
end
ghost.x=ghost.x+ghost.vx
ghost.y+=sin(pulse/192)/7
end

for s,spid in pairs(g_spids) do
local smin, smax
if spid.y<128 then -- upstairs spid
smin,smax=16,108
else -- downstairs spid
smin,smax=144,240
end

if spid.y<smin then
 spid.vy=1
 spid.y=smin
elseif spid.y>smax then
 spid.vy=-1
 spid.y=smax
else
 spid.y+=spid.vy
end

end

-- bats
local bat=g_bats[pulse&15]
if bat and rint(2)>0 and bat.y>20 then
bat.vy=-rint(3,1)
end
for b,bat in pairs(g_bats) do
bat.vy=min(1.5,bat.vy+0.1)
bat.y=mid(2,bat.vy0.8+bat.y,100)
bat.vx=mid(-0.8,bat.vx
0.8+rndr(0.2,-0.1),0.8)
bat.x+=bat.vx
if bat.x<260 then bat.x=260 bat.vx=1 end
if bat.x>604 then bat.x=604 bat.vx=-1 end
end
end -- not fin

-- pumpkins and skulls
for p,pumpkin in pairs(g_pumpkins) do
local dist,dx,dy,kit,best_dist=32767
if not fin and abs(g_cam.x-pumpkin.hx)<100 and abs(g_cam.y-pumpkin.hy)<100 then
for pl=st_pl,end_pl do
-- find closest kitten
local f=kits[pl]
local this_dx,this_dy=f.x-pumpkin.x,f.y-pumpkin.y
local this_dist=this_dxthis_dx+this_dythis_dy
if this_dist<dist then
kit,dist,dx,dy=f,this_dist,this_dx,this_dy
end
end

if pumpkin.c>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>1 and {10,8,7,6} or {9,11})})

else
-- send back to home
pumpkin.x+=(pumpkin.hx-pumpkin.x)0.05
pumpkin.y+=(pumpkin.hy-pumpkin.y)
0.05
end
end

-- for each player's kitten 'f' for frankie
for pl=st_pl,end_pl do
-- get kitten and get 0 or 1 for current player
local f,player=kits[pl],(st_pl==end_pl) and 0 or pl-1

-- collisions with platforms
f.gr=false
if f.vy>=0 then
for pl,plat in pairs(g_plats) do
if plat.x<f.x+14 and plat.r>f.x+6 and plat.y<f.y+4 and plat.b>f.y+4 then
if btn(⬇️,player) and f.flinch<30 then
f.gr=false
f.vy=0.2
f.y+=2
else
f.gr=true
f.vy=0
f.y=plat.y-4
end
end
end
end

-- collisions with floors (including stairs)
-- can't go through floors from bottom
for pl=#g_floors,1,-1 do
-- for pl=4,1,-1 do
local plat=g_floors[pl]
plat.draw=nil
if plat.x<f.x+14 and plat.r>f.x+6 and plat.y<f.y+4 and plat.b>f.y+2 then
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
if dt<db then -- dt<db
f.gr=true
f.floor=1
f.vy=0
f.y=plat.y-4
plat.draw=1
else
f.vy=0.3
f.y=max(plat.b,f.y)
end
end
end

-- special line for stairs
-- 648,232,860,128
local sx,sy,ex,ey=unpack(g_stairs_line)
-- grad=(ey-sy)/(ex-sx)(f.x-sx)+sy
if sx<f.x+14 and ex>f.x+6 then
-- check if under line
-- todo: simplify maths
local ly=(ey-sy)/(ex-sx)
(f.x-sx)+sy
-- if 634<f.x and 851>f.x then
-- local ly=-0.49760*(f.x-648)+232
if f.y<ly+4 and f.y>ly then
f.y=ly+5
f.vy=0.3
-- f.above=1
else
-- f.above=0
end
end

-- collisions with walls
for pl,plat in pairs(g_walls) do
if plat.y<f.y+4 and plat.b>f.y then
if plat.x<f.x+24 and plat.r>f.x then
f.vx=0
f.vy=max(f.vy)
if f.x-plat.x<plat.r-f.x then
f.x=plat.x-24
else
f.x=plat.r
end
end
end
end

-- "input"
-- x
local traction=(f.y>240 or not f.gr) and 0.05 or 0.2

if btn(⬅️,player) and f.flinch<30 then
-- run/move left
f.vx=max(-2,f.vx-traction)
f.face=false
f.sit=0
elseif btn(➡️,player) and f.flinch<30 then
-- run/move right
f.vx=min(2,f.vx+traction)
f.face=true
f.sit=0
else
f.vx*=0.9
f.sit=min(100,f.sit+1)
end

-- y
if btnp(⬆️,player) or btnp(🅾️,player) and f.flinch<30 then
f.sit=0
if f.gr then
f.vy=-3.49
sfx(62,3,28,4)
else
f.vy+=0.2
end
elseif btnp(⬇️,player) and f.flinch<30 and f.gr and not f.floor then
f.sit=0
else
f.vy=min(3,f.vy+0.2)
end

-- hitting
if btnp(❎,player) and not fin and f.flinch<30 and f.hit==0 then
f.hit=20
f.vx=0
f.sit=0
sfx(62,3,rnd{0,3},4)
end

f.hit=max(f.hit-1)

-- kitten "physics"

if st_pl==end_pl then
f.x+=f.vx
f.y+=f.vy
else
-- resolve kittens getting too far from each other
local other=pl==st_pl and kits[end_pl] or kits[st_pl]
local dx=abs(f.x+f.vx-other.x)
local dy=abs(f.y+f.vy-other.y)
if dx<120 then
f.x+=f.vx
end
if dy>120 then
f.x=other.x
f.y=other.y
else
f.y+=f.vy
end
end

-- check if kitten has hit anything
local hit

if f.hit>0 then
for c,candle in pairs(g_candles) do
if not candle.out then
if kit_hit(f,candle) then
candle.out=1
g_num_candles-=1
f.score+=10
if g_num_candles==0 then
fin=60
end
end
end
end

for s,sweet in pairs(g_sweets) do
if not sweet.hit and not sweet.dead and kit_hit(f,sweet,8,8) then
sweet.hit,hit=1,1
if sweet.spr>111 then -- double height
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})
end
sweet.spr+=64
sweet.vx,sweet.vy=rndr(2,f.face and 0 or -2),rndr(2,-1)
f.score+=1
g_num_broken+=1
end
end

for s,spid in pairs(g_spids) do
if kit_hit(f,spid) then
spid.vy=f.y>spid.y+6 and -2 or 2
spid.y+=spid.vy*3
hit=1

end

end

for g,ghost in pairs(g_ghosts) do
if kit_hit(f,ghost) then
ghost.vx=f.x>ghost.x and -1 or 1
ghost.x+=ghost.vx*5
hit=1
end
end

local function bat_hit(f,bat,w)
if kit_hit(f,bat,w) then
-- get direction away from kit
local dx,dy=bat.x-f.x,bat.y-f.y
local n,hit=sqrt(dxdx+dydy)0.1,1
bat.vx=dx/n
bat.vy=dy/n
bat.x+=bat.vx
2
bat.y+=bat.vy*2
return 1
end
end

for b,bat in pairs(g_bats) do
hit=bat_hit(f,bat) or hit
end
for p,pumpkin in pairs(g_pumpkins) do
if bat_hit(f,pumpkin,pumpkin.w) then
hit=1
pumpkin.c=40
end
end
end

if hit then
sfx(62,2,rnd{12,16},4)
local px=f.face and (f.x+24) or (f.x-7)
for i=0,15 do
add(parts,{x=px+rndr(6,-3),y=f.y+rnd(5),vx=rndr(4,-2),vy=rndr(4,-3),life=rndr(30,30),
c=rnd(15)})
end
end

f.bon_cool=max(f.bon_cool-1)
if(f.bon_cool==0)f.bonus=1

-- collisions with monsters
if g_end_cool<1 then
if buttonp(❎) then
set_fade(0.2,{run},{mus and 1 or 0})
end
else -- not fin
-- bats
for s,spid in pairs(g_bats) do
if in_rect(f,spid,16) then
kit_flinch(f,spid)
end
end

-- ghosts
for g,ghost in pairs(g_ghosts) do
if in_rect(f,ghost,16,12) then
kit_flinch(f,ghost)
end
end

-- spids
for s,spid in pairs(g_spids) do
if in_rect(f,spid,16) then
kit_flinch(f,spid)
end
end

for p,pumpkin in pairs(g_pumpkins) do
if in_rect(f,pumpkin,pumpkin.w) then
kit_flinch(f,pumpkin)
end
end
end -- if fin
end -- kits

-- decorations that are falling
for s,sweet in pairs(g_sweets) do
if sweet.hit then
local hit_floor
for pl,plat in pairs(g_floors) do
if plat.x-4<sweet.x and plat.r+4>sweet.x then
if plat.y-4<sweet.y and plat.b+4>sweet.y then
hit_floor=1
end
end
end

if hit_floor then
sweet.vy=-sweet.vy0.5
sfx(62,3,27,1)
if abs(sweet.vy)<0.5 then sweet.dead,sweet.hit=1 end
else
sweet.vy=min(sweet.vy+0.2,4)
end
sweet.vx
=0.9
sweet.y+=sweet.vy
sweet.x+=sweet.vx
end
-- removing fallers hasn't been required so far
-- faller.life-=1
-- if faller.life<=0 then
-- -- overwrite with last faller
-- fallers[f]=fallers[#fallers]
-- fallers[#fallers]=nil
-- end
end

if fin==1 then
g_end_time=time()-g_time
end

-- bonus
-- for b=#bons,1,-1 do
-- local bon=bons[b]
-- bon.life-=1
-- if bon.life==0 then
-- bons[b]=bons[#bons]
-- bons[#bons]=nil
-- else
-- bon.y+=bon.vy
-- end
-- end

end

--function break_dec(k,baub)
-- -- spawn particles, bonuses and falling dec here
-- local decx,decy=baub.dec.x,baub.dec.y
-- if not fin then
-- k.score+=k.bonus
-- k.bon_cool=60
-- add(bons,{x=decx,y=decy,vy=-0.2,life=30,sp=65+k.bonus})
-- k.bonus=min(k.bonus+1,5)
-- end
-- 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})
-- for i=0,20 do
-- add(parts,{x=decx,y=decy,vx=faller.vx+rnd(4)-2,vy=rnd(4)-2,life=rnd(30)+30,
-- c=sget((faller.spr%16)8+2+rint(4),(faller.spr\16)8+2+rint(4))})
-- end
--
--end

function _draw()
pal()

-- lightning?
dr_func()
if rint(300)==1 and not g_storm then
lightning=rint(20)+5
cls(7)
shake(1)
if not mus then sfx(rnd{61,63},0) end
elseif lightning>0 then
local lightning_cols=g_lightning_cols
for l,col in pairs(lightning_cols) do
pal(l-1,col,1)
end
else
fade()
if not g_storm and fd.val<1 then
local dark_cols=g_dark_cols
for l,col in pairs(dark_cols) do
pal(l-1,col,1)
end
if dr_func~=dr_play then pal(2,2,1) pal(8,8,1) end
end
end
lightning=max(lightning-1)
cam()
-- print(stat(0),0,112,7)
end

function dr_start()
scr_fade(pulse)
local wind=sin(pulse/433)*sin(pulse/192)/6
for i=0,2 do
for s,sno in pairs(snowback) do
sno[2]=(sno[2]+1)%128
sno[1]=(sno[1]+wind)%128
pset(sno[1],sno[2],rnd{1,5,6})
end
end

palt(15)
palt(0,1)
spr(15,pulse<20 and 1 or 0,119,1,1,pulse<20)
pal(7,6)
spr((pulse\8)%2==1 and 192 or 194,pulse-16,60+sin(pulse/192)6,2,2)
spr((pulse\8)%2==1 and 194 or 192,144-pulse,30+cos(pulse/96)
6,2,2,1)
pal(7,7)
pal(5,4)
pal(6,15)
spr(15,pulse>170 and 121 or 120,119,1,1,pulse>170)
pal(5,5)
pal(6,6)

print("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!",3,3,7)

rectfill(54,106,54,104,13)
rectfill(55,106,70,106)
rectfill(71,104,71,106)
pout("hit",58,104,7)

rectfill(36,93,36,96,13)
rectfill(20,92,107,92)
rectfill(91,93,91,96)
line(19,93,16,94)
line(108,93,111,94)
pout("jun-enp",52,90,7)

pout('p1',1,88,7)
pout('p2',120,88,10)

print("⬅️-m➡️|d-1⬆️-8|m⬇️",1,98,6)
print(pulse<96 and "\f7+\f6🅾️(z)\f7+\f6❎" or "\f7+\f6🅾️(n)\f7+\f6n-en",23,98)
--p2 instructions
print(pulse<96 and "⬅️-m➡️|d-1⬆️-8|m⬇️" or "s-sf|d-5e-c|md",106,98,15)
print(pulse<96 and "\ff❎\f7+\ff🅾️tab\f7+" or "\ffq\f7+\ff🅾️(v-ev)\f7+",70,98)

print('⬅️ \f6play frankie \ffplay philly',1,112,7+fr,1)
print('➡️',120,112,10-fr,1)
print_centre('❎ for a 2 kitten game',122,11+fr, 1)

for i=0,3 do
for s=1,#snowfront\2 do
local sno=snowfront[s]
sno[2]=(sno[2]+1)%128
sno[1]=(sno[1]+wind)%128
pset(sno[1],sno[2],rnd{5,13,12})
end
end

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
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
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

end

function dr_title()
--sc=4
--if btnp(⬅️,0) then if btn(❎) then ls[sc][g_letter][4]-=1 else ls[sc][g_letter][2]-=1 end end
--if btnp(➡️,0) then if btn(❎) then ls[sc][g_letter][4]+=1 else ls[sc][g_letter][2]+=1 end end
--if btnp(⬆️,0) then if btn(❎) then ls[sc][g_letter][5]-=1 else ls[sc][g_letter][3]-=1 end end
--if btnp(⬇️,0) then if btn(❎) then ls[sc][g_letter][5]+=1 else ls[sc][g_letter][3]+=1 end end

--if btnp(⬅️,1) then ls[sc][g_letter][1]-=1 end
--if btnp(➡️,1) then ls[sc][g_letter][1]+=1 end
--if btnp(⬆️,1) then g_letter=max(1,g_letter-1) end
--if btnp(⬇️,1) then if g_letter<#ls[sc] then g_letter+=1 else add(ls[sc],{0,0,0,2,2}) g_letter+=1 end end

scr_fade(pulse)
cool-=2.5
if cool<0 then sc=sc%(#ls-1)+1 cool=512 end
pal(title.pl)
local offset=abs(2560/(256-(cool-256)%512))/2 - 5
-- local offset=0

local wind=sin(pulse/433)*sin(pulse/192)/6
for i=0,2 do
for s,sno in pairs(snowback) do
sno[2]=(sno[2]+1)%128
sno[1]=(sno[1]+wind)%128
pset(sno[1],sno[2],6)
end
end

for lnum=1,#ls[sc] do
pal(1,2)
pal(3,8)
pal(2,1)
pal(4,1)
local l=ls[sc][lnum]
spr(l[1],l[2],l[3]+offset,l[4],l[5])
end

for i=0,2 do

for p=#snow,1,-1 do
local s=snow[p]
if s.fall then
local olds={x=s.x,y=s.y}
s.x+=wind
local pix=pget(s.x,s.y+1)
if s.y>126 then
-- fallen off bottom
-- pset(olds.x,olds.y,0)
snow[p].x=rnd(128)
snow[p].y=0
elseif pix>0 then
-- hit something
pix=pget(s.x-1,s.y+1)
if pix>0 then
local pix=pget(s.x+1,s.y+1)
if pix>0 then
s.fall=false
else
s.x+=1
snow_fall(s,olds)
end
else
s.x-=1
snow_fall(s,olds)
end
else
-- falling
snow_fall(s,olds)
end
else
-- pset(s.x,s.y,12)
if pget(s.x,s.y+1)<1 then
s.fall=true
end
end
end
end

if pulse\32%2==0 then
pout("press ❎ ",48,122,8,2)
end
if buttonp(❎) then set_fade(0.2,{set_dr_func,use_sprites},{dr_start,game},{set_upd_func},{blank}) end
--if buttonp(🅾️) then
-- local out=''
-- for l in all(ls[sc]) do
-- out..=l[1]..','..l[2]..','..l[3]..','..l[4]..','..l[5]..','
-- end
-- printh(out)
--end
end

function snow_fall(s,olds)
-- pal(5,5)
-- pset(olds.x,olds.y,5)
s.y+=(rint(1.4,2))
pset(s.x,s.y,rnd{12,13})
end

-- formats a passed time as m:ss
function time_string(t)
local t=flr(t)
return t\60 ..":"..(t%60<10 and 0 or '')..t%60
end

function dr_play()

local ball={x=(kits[st_pl].x+kits[end_pl].x+20)\2,
y=(kits[st_pl].y+kits[end_pl].y+20)\2}

g_cam.x+=(ball.x-g_cam.x)\4
if ball.x>700 and ball.x<900 then
-- stairwell
g_cam.y=mid(62,g_cam.y+(ball.y-g_cam.y)/28,192)
else
if ball.y>129 then
-- downstairs
g_cam.x=mid(60,g_cam.x,964)
g_cam.y+=mid(-2,(193-g_cam.y)/28,2)
else
-- upstairs
g_cam.x=mid(318,g_cam.x,964)
g_cam.y+=mid(-2,(62-g_cam.y)/28,2)
end
end

cam(g_cam.x-64,g_cam.y-64)

cls()

-- rain
local wind=sin(pulse/192)/8
for i=0,2 do
for _,s in pairs(snowback) do
s[1],s[2]=(s[1]+wind)%128,(s[2]+1)%120
pset(s[1],s[2]+128,13)
end
end

-- paint walls
for p,paint in pairs(g_paints) do
rectfill(paint.x,paint.y,paint.r,paint.b,paint.c)
end

use_sprites(house)
-- floor sprites
for i=44,124 do
-- upper floor skirting boards
spr(1,i8,112,1,1)
end
for i=33,126 do
-- upper floor carpet
spr(40,i
8,120,1,1)
end
for i=91,93 do
-- lower coving
spr(1,i8,136,1,1)
end
for i=104,105 do
-- lower coving
spr(1,i
8,136,1,1)
end

for i=33,124 do
-- lower skirting boards
spr(1,i8,240,1,1)
end
for i=14,126 do
-- lower floorboards
spr(17,i
8,248,1,1)
end

-- outlines for floors
-- rectfill(256,125,750,126,6)--1st floor
-- rectfill(840,125,1028,126)--1st floor
-- rectfill(-4,256,1028,264,6)--ground

-- background (pretty much everything apart from stairs and doorframes)
map(0,0,0,0,128,32,1)

rectfill(256,128,750,128,13)--1st floor shadow
rectfill(840,128,1028,128)--1st floor shadow

-- walls
-- rectfill(-4,-4,0,260,6)-- left wall of garden
-- rectfill(252,-4,256,128,6)-- left wall of house
-- rectfill(1024,-4,1028,255,6)--right wall

reset_dr_pal()

use_sprites(game)

-- bonus indicators
for b=1,#bons do
local bon=bons[b]
if bon.life\4%3==1 then
spr(bon.sp,bon.x,bon.y)
end
end

-- draw candles
-- draw monsters
palt(15,1)
palt(0,false)

-- draw decorations

for s,sweet in pairs(g_sweets) do
local sp=sweet.spr
if sp>111 and sp<128 then
spr(sp-16,sweet.x,sweet.y-8)
end
spr(sp,sweet.x,sweet.y)
end

for c,candle in pairs(g_candles) do
if candle.out then
pal(10,9)
pal(9,8)
end
if not candle.out then
spr(236,candle.x,candle.y,2,2)
local p=(pulse+c*7)\4
spr(46+p%2,candle.x+5,candle.y-4,1,1,fr%2==0)
else
spr(234,candle.x,candle.y,2,2)
pal(10,10)
pal(9,9)
end
end

local dim=1
if fin then
dim=flr(fin/60)
end

for s,spid in pairs(g_spids) do
local weby=spid.y<120 and 16 or 144
spr(202,spid.x-8,spid.y<120 and 16 or 144,4,2,s&1==0)-- web
rectfill(spid.x+7,weby+4,spid.x+7,spid.y+1,7)--thread
spr(230+(fin and 0 or 2(pulse\4%8<3 and 1 or 0)),spid.x,fin and (spid.y-weby)dim+weby or spid.y,2,2)-- spid
if not fin and rint(30)==0 then
add(parts,{x=spid.x+(rint(2)==0 and 6 or 9),y=spid.y+10,vx=0,vy=1,life=spid.y<128 and (128-spid.y)\3.5 or (250-spid.y)\3.5,c=11})
end
--pout(s,spid.x,spid.y,11)
end
for g,ghost in pairs(g_ghosts) do
spr(fin and 192 or 192+2(pulse\8%2),ghost.x,ghost.y,2,2,ghost.vx<0)
--rect(ghost.x,ghost.y,ghost.x+16,ghost.y+12,9)
--pout(g,ghost.x,ghost.y,7)
end
for b,bat in pairs(g_bats) do
local s=224+2
((pulse\8+b)%4)
if s==230 then s=226 end
spr(fin and 226 or s,bat.x,bat.y,2,2)
--pout(b,bat.x,bat.y,8)
if not fin and rint(30)==0 then
add(parts,{x=bat.x+(rint(2)==0 and 6 or 9),y=bat.y+10,vx=0,vy=1,life=bat.y<128 and (128-bat.y)\3.5 or (250-spid.y)\3.5,c=8})
end
end

-- pumpkins and skulls
for p,pumpkin in pairs(g_pumpkins) do
spr((fin or fr%3~=1) and pumpkin.sp1 or pumpkin.sp2,pumpkin.x,pumpkin.y,pumpkin.w\8,2)
end

-- end draw monsters

if not fin or fin>0 then
-- statuses
cam()

-- candle for status
spr(236,52,11,2,2)
spr(46+fr%2,57,7,1,1,(pulse\4)%2==0)
local prog=100-flr((#g_decs-g_num_broken)/#g_decs*100)
if prog<100 then
if prog<10 then
prog=' '..prog
else
prog=' '..prog
end
end
pout((g_num_candles<10 and ' ' or '') .. g_num_candles.." "..prog.."%",47,15,fr%2==0 and 10 or 11)
-- show time elapsed
local t=time()-g_time
print_centre(time_string(t),2,10,7)
for pl=st_pl,end_pl do
local f=kits[pl]
if pl==2 then
pal(6,15)
pal(5,4)
end

-- kitten heads
cam(pl==1 and -1 or -118)
spr(15)
if pulse\8==pl*5 then
-- shifty eyes
pset(2,4,3)
pset(5,4)
pset(3,4,1)
pset(6,4)
end
rectfill(8,1,8,4,1)
cam()
pout(f.name,pl==1 and 12 or 93,2,6)
pout("score "..(f.score\100)..((f.score%100)\10)..(f.score%10),pl==1 and 2 or 91,10,f.bon_cool>1 and fr!=1 and 7 or 6)

-- local str
-- if f.above==1 then str='above' else str='below' end
-- print(str,0,100,8)
-- circ(f.x,grad,2,10)
end -- draw kittens (for pl)

pal(5,5)
pal(6,6)
cam(g_cam.x-64,g_cam.y-64)
end

-- draw kittens
for pl=st_pl,end_pl do
if pl==2 then
-- alt colours for philly
pal(5,4)
pal(6,15)
end

local f=kits[pl]
if f.flinch>0 then
f.flinch-=1
if fr!=0 then spr(38,f.x-(f.face and -4 or 8),f.y-10,3,2,f.face) end
elseif f.hit>0 then
spr(f.hit>12 and 38 or 41,f.x-(f.face and -4 or 8),f.y-10,3,2,f.face)
elseif f.gr then
if abs(f.vx)<0.1 and f.sit>10 then
-- sit
spr(9+(((pulse<48 and pl==1)or(pulse>144 and pl==2)) and fr2 or 0),f.x+(f.face and 3 or 0),f.y-8,2,2,f.face)
else
-- run
spr(fr
3,f.x,f.y-10,3,2,f.face)
end
else
if f.vy<0 then
-- jump
spr(32,f.x,f.y-10,3,2,f.face)
else
-- fall
spr(35,f.x,f.y-12,3,2,f.face)
end
end
--pset(f.x,f.y,10)
end

-- dark blue->black for outlines
-- pal(1,0)

-- put palette back
pal(5,5)
pal(6,6)

palt(0,1)
palt(15,false)

for p=#parts,1,-1 do
part=parts[p]
part.life-=1
if part.life<0 then
parts[p]=parts[#parts]
parts[#parts]=nil
end
part.x+=part.vx
part.y+=part.vy
part.vy+=.2
pset(part.x,part.y,part.c)
end

-- draw stairs
use_sprites(house)

-- foreground map items
-- mostly stairs and doorframes
map(0,0,0,0,128,32,2)

reset_drpal()
-- snow
for i=0,3 do
-- local ymax=120+rint(10)
for
,s in pairs(snowfront) do
s[1],s[2]=(s[1]+wind)%112,(s[2]+1)%128
pset(s[1],s[2]+128,rnd{12,13})
end
end

-- show progress in destroying decorations
cam()

if fin==0 then
if g_end_cool<1 then
rectfill(0,118,127,124,2)
print_centre(" press ❎ to play again",119,8+fr)
end

-- show scores
local str=''
if st_pl==end_pl and st_pl==1 then
str="frankie scores "..kits[1].score.."!"
elseif st_pl==end_pl and st_pl==2 then
str="philly scores "..kits[2].score.."!"
elseif kits[1].score>kits[2].score then
str="frankie v-evins: "..kits[1].score.." to "..kits[2].score.."!"
elseif kits[2].score>kits[1].score then
str="philly v-evins: "..kits[2].score.." to "..kits[1].score.."!"
else
str="both kittens drav-ev v-evith: "..kits[1].score.."!"
end
rectfill(0,8,127,14,9)
print_centre(str,9,10+fr)

-- show time
rectfill(0,16,127,22,11)
print_centre("your tin-ene: "..time_string(g_end_time),17,13+fr)

-- happy halloween
use_sprites(title)

pal(drpal_map)
for l in all(ls[#ls]) do
spr(l[1],l[2]+3,l[3]+3,l[4],l[5])
end
pal(title.pl)
for l,letter in pairs(ls[#ls]) do
pal(1,g_title_cols[(l+pulse\16)%3+1])
spr(letter[1],letter[2],letter[3],letter[4],letter[5])
end

g_end_cool=max(g_end_cool-1)

elseif fin then
fin-=1
end -- if fin

-- line(g_stairs_line[1],g_stairs_line[2],g_stairs_line[3],g_stairs_line[4],8)
cam()
-- print(grad,0,108,8)

end -- dr_play


-- fades a quarter of the screen at a time
-- scan line by scan line, left pixel then right pixel byte by byte
-- which line, side of pair is dictated by p
function scr_fade(p)
-- local tables seem to be faster.
-- change start line based on oddness value
local d,m=0x6000+p%2*64,p&2==0 and g80 or g81

-- for half of the 128 lines on the screen
for j=0,0x1f80,128 do
-- for every 4bytes of this line
for a=d+j,j+d+60,4 do

-- map every pair of pixels to a mapped pair
-- 4 bytes at a time
-- shorter and quicker
poke(a,m[@a],m[@(a+1)],m[@(a+2)],m[@(a+3)])
end
end
end


-- fills house with decorations for kittens to destroy
-- also init function for game
function decorate_house()
-- dump g_sweets into dec locations for the moment
g_sweets={}
for d,dec in pairs(g_decs) do
local sweet=add(g_sweets,{x=dec.x,y=dec.y,vx=0,vy=0,spr=64+rint(48)})
if sweet.spr>95 then sweet.spr+=16 end
end

g_time=time()
end

-- new unpack function
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


-- init

house_dbi="し%%ふしたとてつそせぬのはなねひちすにすっAふすほすふ「▥▥%そたそたそたし¥わすつEてちせやゆにてちわゆし ンや▮としすりなせつし)のこリ)%そたそたそたしゅてちてむわすつわ◀てちせやゆにてちせふゆに セやふ▮とすねまねなせつ「たのこリ)%ヘたしちてそもちてちわすつわ•てちせやゆにてちせやゆに みやコ▮とせねそりなせつ「たのこリ)Eみわちそチむわすつレたのわてちせやゆにてちせやゆに やレ▮とすなりゆせは「たのこリ\tやEそたわちもちてちてちわすつレたのわてちせやゆにてちせやゆに ‖▮とつせウせすは「たのこリホやeそたわちムむわすつレたはわてちせやゆにてちせやゆに!‖!しつんすはし「たのこリゃやコ「わそたわむアちてちサつレとはわ◀てちせやゆに「‖「ふはつれふ)のこリたやレ」わそたわ¥ミE•てちせやゆに」‖」‖ヨたりたのこリ)レまわそたレたのレすつレすつをこ#ちつなす゛すつはの‖カみねたねたのこリ)コまみわそたレたのレすつレすつをれうちてむれのつへ゛すつはの‖ねみニみのこリ)ふまみハそたレたはレすつレすつをはもちのちのちのちのちのちのちのちのちのむはのつすなす◜へつはの◀た■たのこリ)まゃレそたレとはレすつレすつをはてちのちのちのちのちのちのちのちのちのちのちはのつへモすなへつれ•ねた¹たのこリIしそたレそたuすつレすつわはてのちのちのちのちのちのちのちのちのちのむれつへテすなへつすれ‖りたヨたのこリ)わそたレそたuすつレすつわはむのちのちのちのちのちのちのちのちのちのちれつ&つすつれ‖カたニたのこリ)わそたレそたuヒわすつわはてのちのちのちのちのちのちのちのちのちのむれめすつすつすャれ‖ニたカたのこリ)わそたレそたuすタわすつわはむのちのちのちのちのちのちのちのちのちのちはャはつはつはつはめは%すつすつすつしたのこリゃしそたuそたわねな¹Vつわはちのちのちのちのちのちのちのちのちのちのむはˇ%すつすつすつしたのこリたわそたuそたわ!すめへめヒつわはむのちのちのちのちのちのちのちのちのちのちはすuす%ヒつし▤「わそたuそたわ¹なねすつサつヒつわはちのちのちのちのちのちのちのちのちのちのむはにすしてちつハてちつにすEめわ▥」わそたuそたわ!VつわはむのちのちのちのちのちのちのちのちのちのちはへつてちつヒてちつへEすつわ▤「わそた%まわそたわ!EすつわはちのちのちのちのちのちのちのちのちのちのむはへちてちャてちつへEすつわ▤「わそた⁵まみカそたりなりなヨEすつわはむのちのちのちのちのちのちのちのちのちのちはすつちてむはわはてちめ&わすつわえ。わそたハまみふ「!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たとわイたとのやふはのれわたとけるたとしむもむるヘゃけ\"。たぬたぬたぬたぬわたとuたとレたとハるれEたとけるたとしのちもゅのヘゃわすつ∧ロつすわサつをわたとˇレたとレたとEみˇEをヘゃわすつロやvつすわサつをわたとˇレたとレたとEセˇレをょヘゃわすつヒとそたとfつすわサつをわたとっ」まセはっのたとレたとEンˇふへタれヘゃわすつヒのたとのつVつすわサつをわたと▤たはたまのたとレたとE」ˇょリヘゃわすつヒつるめVつすわサつをわたとた☉たはたそのはたとレたとE」‖みレ#ヘゃわすつロょfつすわサつをわたとた☉たはたそのはたとレたとわホハン‖セコ#ヘゃわすつ∧ロつすわサつをわたとたxみのたそのはたとレたとわメ⁵セヘたふンふ#ヘゃわすい•すわ•わたとたxみのたそのはたとレたとレたと%みヘI#ヘゃ5にひにE⁸たコたとハたと▤たとたそのはたとわよそるトヌにへまuヘゃふラはヘゃレイつすにすつはとのはレ⁸たコたとハたと▤たのたそのはたとわにのそのトヌよすはみまUヘゃしのちてむのはヘゃハとまやょはやぬのはハったったコたとハたと☉みのたそのはたとわにのそトヌエすはまみま5ヘゃのちもゅのヘゃハとそぬとのとそとのとぬたとのハたまたそたそたわたやのコたとたxみのたそのはたとわるひエヌトれスみま‖ヘゃしのもちるはヘゃコとぬたぬるそたぬとのとらのはコまみったとたとたとたとたわたとセまホそホはみのはたとわるひよヌトのれヲみまレヘゃ□はヘゃわのとたぬとのぬたらやのとぬるはわ⁸た。わたとぬ▥はゃのたとわのにひにヌトるはす「みまコヘゃふヌれヘゃコのやるとらやるやのはコ⁸た◀わたときぬはオのたとわのにひヌトキはす8みまふヘみ⁵はしヘゃハひのはにのイのへるにハ」•わたけ□たとわよてキトヌへXみ⁸Eヘゃコケにひ◝すつエˇコをハにヌトヌエ•ふキエすヌすつしみXみヘみし¥Uそろにルエすエˇコょをふスノヌト%るそノスひにすつふみ▤そわ\nのEそろよろそケよすにひよˇわれタへキトヌトの%のそひエヌエすつコみ8‖マるEノよひそルにすにろよˇふリょるトヌトる‖るひエヌトれレみ「‖ゅるEろそろよケそノよすにひエひわたとたとたとわとたとし#のトヌトキ‖るひよヌトのれ‖みヲ‖ちるハとたとたとたとたしノよろそ⁴にすにひそろわ。ふイし#トヌトヌ‖のにひにヌトるはす5みス‖の⁵。わひエひそひそひそケにすにろわ∧#エヌトヌに‖のにひヌトキはすUみまˇ◀レ4エレい#よヌトヌよ‖よそキトヌへuみˇ•u▤▤まお‖つな⁶なサなつわたとわスサつすつへはへˇUまみxセxNねテすなをなへちつ^つのわたとわスすそにへつすのすはつすˇ5まみxみスみXお゛のつなねなりゆねウすつのわたとわ&るすつはすのˇ‖まみxみ「み8おなカなりなちつNすつのわたとわすまよそにす#「uまみxみXみ「ウすおテのつ>へつのわたとわすにをよすつのすのすつすは⁸ぬUまみxみ▤みヲ◀モす>ちつす◜すゆすめのふとたとのふそすまロめへつはぬそぬそぬそぬそ5まみxみ▤スみスすつすつすつすつ>すモのつすゆす◜すつはのとスみのスすよへはのへのすつ 8みxみ▤「みま•おちつす.すつはのとそホのスサsしれ9XみˇぬとにたとてちなEたとレたとEのちつすモをつはのちˇたのこリたXみˇふぬとにたとてちな⁵なわたとレたとEちのつすᵉすつはるˇたのこリた8みˇコぬとにたとてちなわたとふなわたとレたとEのちつすなすゆねゆすつはのちˇたのこリた「みˇレぬとにたとてちなわたとてちなわたとレたとEちのつすᵉすつはるやUやたのこリたヲみˇ‖ぬとにたとてちなぬとしたとてちなわたとレたとEのちつす◜すめはのちそたしてちのハてちのそみのこリたスみˇ5ぬとにたとてちなせつしたとてちなわ)とEちのつすテすなすめはるやのてちのホてちのやたのこリたまみUサつレつVぬとにたとてちなわ=Eのちつすᵉすつはのちるちてちのメてちキ▥9uミレkぬとにたとてちなˇ‖ちのつすᵉすつはるれちてむはわはてちのれ▥」ふのとのはふ「ˇˇˇわきぬのレくねのˇハとたとのふ「ˇˇˇわけるレけるˇコやたとるし「ˇˇˇわきぬのレくねのˇわイたとキ」ˇˇˇわ▮キ▮のレ■キ■のˇわイたとキ「ˇˇˇわ の のレ!の!のˇわイたとキそンそˇˇˇわきぬのレくねのˇわ(たスたそˇˇˇ∧∧∧ˇ」そンそˇˇˇいいいˇˇˇˇˇˇヒめれˇˇˇˇˇˇˇ⁵のへつすょはˇˇˇˇˇˇˇ⁵ロつれˇˇˇˇˇˇˇ⁵ロめれ%タれˇˇˇˇˇˇ⁵つへつすつへ[へネˇˇˇˇˇˇレサの∧すつクˇˇˇˇˇˇレロつヒのへつをのつすれつはˇˇˇˇˇˇレへのヒのfつクˇˇˇˇeつサね⁶ミはレ6つすつすつのつすつすめネˇˇˇˇeつfタはレすつへのサのすᵇすめすネˇˇˇˇeつ6めのタれハ◀ょのはつれつはつはめはつれˇˇˇˇeつをのをねすのすャれハすのヒめCしリˇˇˇˇuめFつすつすクハヒめのはつネˇˇˇˇˇ%つロねサタすはつはハヒつすリˇˇˇˇˇEつ&つへタすはレすつへめれのれˇˇˇˇˇUつをね⁶つすミレサょクˇˇuゅれるゅるソˇ✽つすのサのをつすミはレへつすめれつはˇˇuちてキはてちのむるゅˇ✽めFミはレサょクˇˇuてのむはゅのむのはのむˇ✽つヒねヒつすタはレをつすタはˇˇuむのはのてちてちのむのはのちˇ✽つ&つへミはレすつへタれˇˇuてむはマのゅのはのわむˇ5つすつ6のつすめはレサのつすめはˇˇuむるてマのゅれふちてのはˇ%つヒのロミはレサミはˇˇuもちるもちのむのゅのしちてちるはˇ‖つ6つへタはレへつへタはˇˇuるリてはるクのはしてゅのはˇ⁵めをねサねをょのつれハねをょのつれˇˇuもゅるてむのはるちヌむのはˇレつへの6ミれˇˇˇuるてむるちてちるはてむのはるむのはˇハつヒの◀タれˇˇˇeちてゅのはちてゅはゅのちのはのむのはˇハつすつ◀つへのタれˇˇˇeゅのちるてむのはのてちてちるれのむのはˇわめ⁶ねヒミれˇˇˇUちてゅるてちてむは\nのはのゅのはˇふつすつサねヒつすミれˇˇˇUむのちるてちてむるてゅのむのれのゅれˇし∧つすょれˇˇˇ5てゅるアちもちるもむのむのれのゅのは✽つすね&のをャれˇˇˇ‖てソのれキてるリてのれキ3eヒつFミれˇˇˇ%もちのちのちてソるてちキむるソるはˇつをの◀つをミのれˇˇˇ%キむるゅるはてむのはるむるソるは✽つへねめすのね6タクˇˇˇ⁵ちてゅのはてちてむのゅのちのはのむのはのソる✽サめVのへょれˇˇˇ⁵ゅのちるゅキてちてむのれのむのはのソのはeサつのつヒねサのは⁶れˇˇˇハちてゅるてちてむは\nるはゅのはるゅのはEつのつねミへょヒつれしめへつれˇˇˇコむのちるむてむるてソのちるはのゅクるむのれ‖つのすょネょはタへつすつクへつすつはˇˇˇふてゅるてちてむてちるもマるはのゅのクるむる⁵をねsャネすょはˇˇˇてソのれるゅるれるてちこリレサネふSめすふクめすˇˇˇ"
once_dbi="こ#⁙ちことしてすせさしろ⌂よ⁘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<ん9テん1タ✽セを⁶Y~よゃ~⁶'~▮YすはE~ᶠ⁙ゆ▮1なᶠ⁘jよ⁘jよ⁘jはEタ●1タ●1タ●1タ●+ルよ⁘jよ⁘eん4gん5てん5ミ⬇️5ミ⁷⁘-を'ムん5ムん1タ●%mを)◜オ3もヘYk♥Ykフ1アフ⁙<ヘ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エたム³コャ●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タ♥3つせ'ムつ#ミ●'メゃ2ム')ルは4eん‖+⁷1タhUすオ3な⁷しすオ4jこ7ユよやヤエわ+'/ムん1ルに4jめ%ノれ3つんしてせ1タそふjに5ャヘひてほ3,よEもを1ケっ3つんたモんふてよ⁙てっ1も'S-わふろフわマょ)ミ●/~さ#d³Yす³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<ん5モま5ムん1ルはEタG5c{てこgeマにGャ●+{P+~▮Yテ▮Yミ☉ひとを-メキ³んす³て'1ひフTjよ⁘jよふマょ+ルよレ4•てこ{てて(1てんTjよ⁘jよふマょ+ルよレ,³ミたcミさんTdっみタ░ふムよハム⁵ふタせ1ルはEタG5♥す³んすめモよふ4⁶1ひフ5タフeャ⁷Uチを7フょ1アょ[たcミたiハjせEャ●'ムク4cんkャヘはめ♥1ルはEタHわc{てこ{し⁴⁶%ムエ⁘ム³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メヘ&ム(へノん9さこEろん4dょ+ルん5マん5ムん5ムを-メっ1ヤゃ'ルエイメゃ'⁴エたムんわ◜⁷1さフハ.▮Uホっや5ムんヤん⁘ムフTム'slっ*ムシサろ³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³ᶠせ#ᶠさクTjよ⁙ムヘ1ろクS-ろほヒん‖+(はム⁶#ルキ³んす³ろんTdん5ム⁴5ムんSメヘ1ろんS-ろほヒん‖+(はてん⁙てっ⬇️んす³ろク9タんGルエコヤん9てフTgク9ひフわ+⁷1ルはEモん1メエてこ{つヤゃ1まん5ᵇ7<てDふモよヒろ⁴ほハょ)ミ☉は+そふc⁷3つ(わ♥す³ろク=タ7Gャw;ャ⁷Ejは;ャせし+ヘは,を7ノんし.て5ミ⁷5ムん5ヤゃ1ケフTlっ)レエ⁘てっ#ムエゃムん5テん5c⁷+l⁴へ1{●Yク=タ♥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ャ♥efっふjつ6なよしアを#ャ●-メゃ~▮W;ミ✽7モよ5ャ⁸わャ░ふム⁶#ミ⁷4nよ⁘ムフt😐ク4gん5ムん5⁴エコムエ⁙,んTc⁷5ム⁶Tjほ7ユよやヤエ⁙lフE5ょ<なぬん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ヘん⁴をほ.よヒなよコル⁶<なね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す³んす³もなせ7ャ●1ろんToっ-メカこ3ふこ43Slヘ1タフE~されゆさふモせGャ●1ろょTo(-レエせ#3せ#ゃみもミTjつG◜されゆさわ⁴³7モよ⁘fん5ホんeヘょ93ふこ3は5ャ⁸ふjよわ,³んす³んヤっ%ムエ⁵ムん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ゃめモよコ+ヘは,よふ,³ミしネミさんSmっ1タヘふjよコルん5ム'5ャ░7ハょ'ミ⬇️5モ3せ#3せ⁴エコム⁶1ひんTjは@TjᶠムクTfょ'ルつ4dっむこミてこミkャ⁷Ejよわル⁶1アん5ムゃ5モよコ+ヘは,よふ4³んせ#んさク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³セす⁶1タ●1タ●1タ●){P1め"
-- init
menuitem(1, "music on/off", function() if mus then music(-1,1000) else music(upd_func==up_play and 22 or 0)end mus=not mus end)
menuitem(2, "storm on/off", function() g_storm= not g_storm end)

g_stairs_line=split'670,231,857,138'

-- pull native sprite sheet into lua table so can be restored quickly
game={data={}}
for m=0,8192,4 do
game.data[m\4+1]=$m
end
-- set up palettes for lighting fx
scpal_map={}
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'
for i=0,15 do
scpal_map[i]=vals[i+1]
scpal_map[i+128]=vals[i+17]
end

drpal_map={[0]=0,unpack_split'0,1,1,2,1,13,6,2,4,9,3,13,5,4,14'}

g80,g81={},{}
for i=0,255 do
g80[i]=(i&0xf0)+drpal_map[i&0xf]
g81[i]=drpal_map[i\16&0xf]*16+i%16
end

fade_pal(8)
wait(20)
if sub(stat(6),1,1)!='0' then
music(0)
mus=true
else
mus=false
end

palt(0,false)

--load title data
title,house=unpack_dbi(once_dbi),unpack_dbi(house_dbi)
once_dbi,house_dbi=nil

-- unpack title sequence data (see title_gen.p8)
local title_raws={
split"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", -- drake blue games presents
split"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", -- frankie and philly in
split"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",-- halloween horror
--split"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",
split"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",-- happy halloween
}
ls={}
for t,title in pairs(title_raws) do
local ptr=1
ls[t]={}
repeat
local letter={}
for i=1,5 do
add(letter,title[ptr])
ptr+=1
end
add(ls[t],letter)
until ptr>=#title
end

parts,snow,bons={},{},{}

for i=0,400 do
add(snow,{x=rnd(128),y=rnd(126)})
end

pulse=0
kits={
{x=350,y=230,vx=0,vy=0,gr=true,face=true,hit=0,flinch=0,sit=30,score=0,name="frankie",bonus=1,bon_cool=0},
{x=370,y=230,vx=0,vy=0,gr=true,face=false,hit=0,flinch=0,sit=30,score=0,name="philly",bonus=1,bon_cool=0}
}

snowback,snowfront={},{}
for i=1,100 do
add(snowback,{rint(128),rint(120)})
add(snowfront,{rint(112),rint(120)})
end

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

--#include "halloween_platforms.lua"
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')
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')
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')
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')
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')
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')
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')
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')
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')

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'
g_num_candles=#g_candles

cool,sc,g_num_broken=512,1,0
g_title_cols=split'11,9,0'
use_sprites(title)
cls()

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'

set_fade(0.5,{set_dr_func},{dr_title},{set_upd_func},{blank})

g_letter,g_end_cool=1,90 -- 30fps at end so 90=3 seconds

4


I would help with JS and CSS to make the wide web version!
I’m on pico8 discord.


Ah, sorry merwok, just saw this.

I had a look at how far I'd got with the double screen version. I'd been able to hack it to work okayish on desktop, but I gave up when it came to the mobile version and touch controls etc.

I might have another go, but I have to confess that one of the reasons I like using PICO-8 is specifically because I don't have to do any web programming...

Changes I tried are in the spoiler:


After exporting to html like normal in PICO-8, enabling the second display to be viewed just needed a line in the html:

	Module = {};
	Module.canvas = canvas;
->	Module.arguments = ["-displays_x","2","-displays_y","2"];

That bit's okay. Its the next bit I don't think is very good: I hacked a couple of other values

		canvas.style.width = csize;
		canvas.style.height = csize;

to

		canvas.style.width = csize*1.5;
		canvas.style.height = csize*1.5;

and

->			margin_left = (frame_width - csize)/2;

			if (p8_touch_detected)
			{
				if (window.innerWidth < window.innerHeight)
				{
					// portrait: keep at y=40 (avoid rounded top corners / camera nub thing etc.)
					margin_top = Math.min(40, frame_height - csize);
				}
				else
				{
					// landscape: put a little above vertical center
					margin_top = (frame_height - csize)/4;
				}
			}
			else{
				// non-touch: center vertically
->				margin_top = (frame_height - csize)/2;

to

->			margin_left = (frame_width - csize*1.5)/2;

			if (p8_touch_detected)
			{
				if (window.innerWidth < window.innerHeight)
				{
					// portrait: keep at y=40 (avoid rounded top corners / camera nub thing etc.)
					margin_top = Math.min(40, frame_height - csize);
				}
				else
				{
					// landscape: put a little above vertical center
					margin_top = (frame_height - csize)/4;
				}
			}
			else{
				// non-touch: center vertically
->				margin_top = (frame_height - csize*0.75)/2;

Then I gave up.


Can you share the cart with multi-display?


This is as far as I got:

Cart #db_multi_xmas2-0 | 2021-11-08 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

It won't work as multi-display if clicked here. It's also pretty broken and is still Christmas-themed, but you get the idea.

These args are needed to launch pico8 with the desired layout (same as in the html changes in my last reply)
-displays_x 2 -displays_y 1

If I get the chance I might work a bit on getting the Halloween game working with split screen.


Just realised that 0.2.3 isn't letting multi-display be enabled in HTML exports. It did work in 0.2.2b and I'm hopeful it will be re-enabled in a coming update.


1

I Love Red Ball Game, I Wish I Can Play It All Day!!!


Marvelous animation of the cats, @drakeblue. Very cute game ! Find an extra gold star in your goody bag. :)



[Please log in to post a comment]