P#72149 2020-01-21 06:21 ( Edited 2020-01-21 06:21)
A chilling survival horror experience awaits you in this game about keeping a ball up by bouncing it with a stick that spins as you move it.
Press Left/Right to move the stick AND spin it.
How long can you delay the inevitable?
found a bug in 0.2.1b (also current bbs version) while using tline() to shear rectangles
see the cart
observe that there's only 2 configurations that get chopped incorrectly (one where the startpoint is below the screen and one where the endpoint is above the screen) (although others also have ugly artifacts)
An attempt to cross Tetris and Super Meat Boy. Or something like that.
You can control the Tetris with arrow keys, and the Meat Boy with ESDF. You can challenge your multitasking skills, or try playing with another person, either cooperatively or competitevely, either might be fun.
Gravity increases linearly as you clear lines in this version.
There's no proper fail state yet, if Meat Boy dies, he disappears, if the well fills, it keeps spawning new pieces in crazy ways.
...but you might not like it. Here it is:
Here's a video: https://youtu.be/C2ui4anDwBc
The idea is very simple: make 2 versions of the cart, called A and B, running essentially the same game, but have them display different parts of the game (in this demo, based on Jelpi, I simply shifted the camera); cart A is the one you actually play on, it receives inputs and sends them to cart B, which uses them, ensuring that exactly the same thing is going on in both. (as long as the game is deterministic, otherwise you'll have to send over the random seed or something)
I first had this idea a few months ago, but couldn't figure out how to send the data from one cart to another in real time; I hoped to accomplish this using the system clipboard, but it turned out that it can only be read whenever the user presses Ctrl+V, so you can't just do it every single frame.
With the release of PICO-8 v0.2.1 it became possible to (easily) do this using the serial() function, because it now allows you to use stdin and stdout IO streams of the current PICO-8 process. Stdin is 0x804 and stdout is 0x805. All you need to do is create a linking program that will launch both carts, connect to the stdout of A and to the stdin of B and pass the data. I wrote the following Python script:
import subprocess as sp p8A = sp.Popen([r"C:\Program Files (x86)\PICO-8\pico8.exe", "-width", "512", "-height", "512", "-run", r"C:\Users\user\AppData\Roaming\pico-8\carts\ipc\a.p8"], stdin=sp.PIPE, stdout=sp.PIPE) p8B = sp.Popen([r"C:\Program Files (x86)\PICO-8\pico8.exe", "-width", "512", "-height", "512", "-run", r"C:\Users\user\AppData\Roaming\pico-8\carts\ipc\b.p8"], stdin=sp.PIPE, stdout=sp.PIPE) # synchronization data = p8A.stdout.read(1) data = p8B.stdout.read(1) p8A.stdin.write(b" ") p8A.stdin.flush() p8B.stdin.write(b" ") p8B.stdin.flush() # main loop while True: data = p8A.stdout.read(8) if not data: break p8B.stdin.write(data) p8B.stdin.flush() p8B.stdin.close()
(you'll need to manually set the file paths for your machine)
The main loop simply passes data from A to B, 8 bytes at a time. (NOTE: you need to use .flush() every time you send data to PICO-8 because of how IO streams work) This data is the raw button state at 0x5f4c. It is sent and received every frame. I inserted some code at the top of _update. Here's version A:
function _update() serial(0x805,0x5f4c,8) ...
And here's version B:
function _update() serial(0x804,0x5f4c,8) ...
There are a few more things to do.
First, the two instances of PICO-8 usually take different amounts of time to load, so we need to synchronize them. It turns out, the data is actually passed pretty much instantly, all we need is to make sure the game starts at the same time. To do that, both A and B send a random byte to signal that they're ready, then the script replies with a byte to signal them to start. This is the synchronization part of the script. I inserted this at the top of both A and B:
print("sync...") serial(0x805,0x4300,1) flip() serial(0x804,0x4300,1)
(0x4300 is the start of general purpose section of the RAM, PICO-8 doesn't use it)
(NOTE: for the script to receive data from PICO-8, PICO-8 needs to flush its stdout. It does it once every frame. Since the code above is outside the game loop, it calls flip() manually to advance a frame and force a flush)
Second, we need to disable sounds and music for the cart B. To avoid having to learn where and what Jelpi does with them, I just replaced sfx() and music() with empty placeholder functions:
Finally, we shift the camera for the cart B. I searched for "cam" and found where the camera position is calculated, then inserted this:
And that's it! I'm not sure I would trust this enough to make a game relying on it entirely - it's not very robust and it's tricky to set up (this could be improved probably), but it works.
If you just want to try it out, here's the complete source code for cart A and cart B, however, it is based on the old version of Jelpi that I had left from v0.1.12c - forgot to update it. That code is outdated now anyway, just follow the steps above to make your own (try another game!).
UPD: it seems that btnp() actually uses some memory that is not reflected in PICO-8 RAM (or something else is wrong with my approach), so it never returns true in cart B. Needs a workaround. The fact that the demo above works is kind of a miracle - it's possible in part because old Jelpi never used btnp() but did the same thing manually (even the new version in v0.2.0 keeps that old code and only uses btnp() once, for level transitions).
Prototype of a chalkboard simulator/math scratchpad. (I might add special characters for that later)
This cart defines type = 2 for some reason. Tostr doesn't like that.