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)
- LMB to draw, RMB to erase
- Shift+LMB to draw harder, Shift+RMB to erase more
- Arrows to move cursor
- Tab to move cursor to the mouse position (note: tab is also detected as shift, I don't think there's much I can do about that)
- Keyboard to write
- Enter, Space, Backspace work but slightly unconventionally
- Shift+Up/Down to move cursor half a line up or down - this makes subscripts and superscripts possible
- Mouse wheel to scroll the board (it's infinite downward, but performance is not great yet)