Log In  

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

sfx=function()end
music=function()end

Finally, we shift the camera for the cart B. I searched for "cam" and found where the camera position is calculated, then inserted this:

    cam_x+=128

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

P#79071 2020-07-08 22:31 ( Edited 2020-07-09 18:29)

:: shy

This is awesome. There's so much potential here.

P#79073 2020-07-08 22:50
2

Well, I guess we can make Nintendo DS demakes now 8D

Jokes aside, the idea of using multiple windows to increase the screen size is very cool. Personally I would probably use it for local multiplayer instead, while the standard game would still run on a single screen (racing games for instance fit perfectly here).

If someone makes a more user-friendly version of this I'll gladly jump on board!

P#79107 2020-07-10 01:07

omg you absolute madman hahahaha

P#79128 2020-07-10 19:03

Head to head arcade cabinet anyone?

P#79146 2020-07-11 04:28
:: Mot
1

This is bonkers, and also incredibly cool.

P#79152 2020-07-11 06:44
2

OMG, Darius and Ninja Warriors three-screen-madness demakes incoming...

P#79158 2020-07-11 09:51 ( Edited 2020-07-11 09:53)
:: zoom

Thank you for sharing this. One question please...
Does this form of IPC only work in PICO-8 IDE?
Is it possible to do this on exported cartridges?
Thanks again.

P#87624 2021-02-14 09:37

unfortunately no - limited to pico8 standalone

P#87629 2021-02-14 13:24

[Please log in to post a comment]

Follow Lexaloffle:        
Generated 2021-09-22 07:58:49 | 0.011s | Q:23